from typing import Optional, Union
import numpy as np
import plotly.graph_objs as go
from ...post import loadODB
from .plot_resp_base import PlotResponseBase
from .plot_utils import (
_get_unstru_cells,
_plot_points_cmap,
_plot_unstru_cmap,
)
class PlotUnstruResponse(PlotResponseBase):
def __init__(self, model_info_steps, resp_step, model_update, node_resp_steps=None):
super().__init__(model_info_steps, resp_step, model_update, nodal_resp_steps=node_resp_steps)
self.ele_type = "Shell"
self.fiber_point = None # fiber point for shell fiber response
def _get_unstru_da(self, step):
if self.ele_type.lower() == "shell":
return self._get_model_da("ShellData", step)
elif self.ele_type.lower() == "plane":
return self._get_model_da("PlaneData", step)
elif self.ele_type.lower() in ["brick", "solid"]:
return self._get_model_da("BrickData", step)
else:
raise ValueError(f"Invalid element type {self.ele_type}! Valid options are: Shell, Plane, Brick.") # noqa: TRY003
def _set_comp_resp_type(self, ele_type, resp_type, component, fiber_point=None):
self.ele_type = ele_type
self.resp_type = resp_type
self.component = component
self.fiber_point = fiber_point
title = f"<b>{self.PKG_NAME}:: {self.ele_type.capitalize()} Responses 3D Viewer</b><br><br>"
self.title = {"text": title, "font": {"size": self.pargs.title_font_size}}
def _make_unstru_info(self, ele_tags, step, defo_scale):
pos_defo = self._get_defo_coord_da(step, defo_scale)
# pos = self._get_node_da(step).to_numpy()
unstru_data = self._get_unstru_da(step)
if ele_tags is None:
tags, cell_types, cells = _get_unstru_cells(unstru_data)
else:
tags = np.atleast_1d(ele_tags)
cells = unstru_data.sel(eleTags=tags)
tags, cell_types, cells = _get_unstru_cells(cells)
return tags, pos_defo, cells, cell_types
def refactor_resp_step(self, ele_tags, ele_type, resp_type: str, component: str, fiber_point: Optional[int] = None):
self._set_comp_resp_type(ele_type, resp_type, component, fiber_point=fiber_point)
resps = []
for i in range(self.num_steps):
da = self._get_resp_da(i, self.resp_type, self.component)
if self.ModelUpdate or ele_tags is not None:
tags, pos, _, _ = self._make_unstru_info(ele_tags, i)
if "eleTags" in da.dims:
da = da.sel(eleTags=tags)
else:
pos = self._get_node_da(i)
resps.append(self._process_scalar_from_da(da, pos, fiber_point))
self.resp_step = resps
def _process_scalar_from_da(self, da, pos, fiber_point):
def _reset_fiber_point(fiber_point, da):
if fiber_point == "top":
fiber_point = da.coords["fiberPoints"].values[-1]
elif fiber_point == "bottom":
fiber_point = da.coords["fiberPoints"].values[0]
elif fiber_point == "middle":
fiber_point = da.coords["fiberPoints"].values[len(da.coords["fiberPoints"]) // 2]
return fiber_point
if "nodeTags" in da.dims:
scalars = pos.sel(coords="x").copy() * 0
if "fiberPoints" in da.dims:
fiber_point = _reset_fiber_point(fiber_point, da)
da = da.sel(fiberPoints=fiber_point)
scalars.loc[{"nodeTags": da.coords["nodeTags"]}] = da
return scalars
if "fiberPoints" in da.dims and "GaussPoints" in da.dims:
fiber_point = _reset_fiber_point(fiber_point, da)
da = da.sel(fiberPoints=fiber_point)
return da.sel(fiberPoints=fiber_point).mean(dim="GaussPoints", skipna=True)
if "GaussPoints" in da.dims:
return da.mean(dim="GaussPoints", skipna=True)
return da # fallback: return raw
def _get_resp_peak(self, idx="absMax"):
if isinstance(idx, str):
if idx.lower() == "absmax":
resp = [np.max(np.abs(data)) for data in self.resp_step]
step = np.argmax(resp)
elif idx.lower() == "max":
resp = [np.max(data) for data in self.resp_step]
step = np.argmax(resp)
elif idx.lower() == "absmin":
resp = [np.min(np.abs(data)) for data in self.resp_step]
step = np.argmin(resp)
elif idx.lower() == "min":
resp = [np.min(data) for data in self.resp_step]
step = np.argmin(resp)
else:
raise ValueError("Invalid argument, one of [absMax, absMin, Max, Min]") # noqa: TRY003
else:
step = int(idx)
cmin, cmax = self._get_resp_clim()
self.clim = (cmin, cmax)
return step, (cmin, cmax)
def _get_resp_clim(self):
maxv = [np.max(data) for data in self.resp_step]
minv = [np.min(data) for data in self.resp_step]
cmin, cmax = np.min(minv), np.max(maxv)
return cmin, cmax
def _create_mesh(
self,
plotter,
value,
ele_tags=None,
plot_all_mesh=True,
clim=None,
coloraxis="coloraxis",
style="surface",
show_values=False,
defo_scale: float = 1.0,
show_bc: bool = True,
bc_scale: float = 1.0,
show_mp_constraint: bool = True,
):
step = round(value)
tags, pos, cells, cell_types = self._make_unstru_info(ele_tags, step, defo_scale=defo_scale)
pos = np.array(pos)
resps = self.resp_step[step].to_numpy()
scalars = resps
# ---------------------------------
if plot_all_mesh:
self._plot_all_mesh(plotter=plotter, step=step)
if show_bc:
self._plot_bc(plotter=plotter, step=step, defo_scale=defo_scale, bc_scale=bc_scale)
if show_mp_constraint:
self._plot_mp_constraint(plotter, step=step, defo_scale=defo_scale)
# ---------------------------------------------------------------
scalars_by_element = len(scalars) != len(pos)
(
face_points,
face_line_points,
face_mid_points,
veci,
vecj,
veck,
face_scalars,
face_line_scalars,
) = self._get_plotly_unstru_data(pos, cell_types, cells, scalars, scalars_by_element=scalars_by_element)
_plot_unstru_cmap(
plotter,
face_points,
veci=veci,
vecj=vecj,
veck=veck,
scalars=face_scalars,
clim=clim,
coloraxis=coloraxis,
style=style,
line_width=self.pargs.line_width,
opacity=self.pargs.mesh_opacity,
show_edges=self.pargs.show_mesh_edges,
edge_color=self.pargs.mesh_edge_color,
edge_width=self.pargs.mesh_edge_width,
edge_points=face_line_points,
edge_scalars=face_line_scalars,
)
_plot_points_cmap(
plotter,
face_points,
scalars=face_scalars,
clim=clim,
coloraxis=coloraxis,
name=self.resp_type,
size=self.pargs.point_size,
show_hover=show_values,
)
def _make_txt(self, step, add_title=False):
resp = self.resp_step[step].to_numpy()
maxv, minv = np.max(resp), np.min(resp)
t_ = self.time[step]
if self.resp_type.lower() in ["stressmeasures", "stressmeasuresatnodes"]:
resp_type = "Stress Measures"
elif self.resp_type.lower() in ["strainmeasures", "strainmeasuresatnodes"]:
resp_type = "Strain Measures"
elif self.resp_type.lower() in ["sectionforces", "sectionforcesatnodes"]:
resp_type = "Section Forces"
elif self.resp_type.lower() in ["sectiondeformations", "sectiondeformationsatnodes"]:
resp_type = "Section Deformations"
elif self.resp_type.lower() in ["stresses", "stressesatnodes"]:
resp_type = "Stresses"
elif self.resp_type.lower() in ["strains", "strainsatnodes"]:
resp_type = "Strains"
else:
resp_type = self.resp_type.capitalize()
title = f"<b>{self._set_txt_props(resp_type)} *</b><br>"
comp = self.component if isinstance(self.component, str) else " ".join(self.component)
title += f"<b>(DOF) {self._set_txt_props(comp)}</b><br>"
if self.unit_symbol:
unit_txt = self._set_txt_props(self.unit_symbol)
title += f"<b>(unit) {unit_txt}</b><br>"
if self.fiber_point and "Sec" not in resp_type and self.ele_type.lower() == "shell":
fiber_point = self._set_txt_props(self.fiber_point)
title += f"<b>* (Fiber) {fiber_point}</b><br>"
maxv = self._set_txt_props(f"{maxv:.3E}")
minv = self._set_txt_props(f"{minv:.3E}")
title += f"<b>Max.:</b> {maxv}<br><b>Min.:</b> {minv}"
step_txt = self._set_txt_props(f"{step}")
title += f"<br><b>step:</b> {step_txt}; "
t_txt = self._set_txt_props(f"{t_:.3f}")
title += f"<b>time</b>: {t_txt}<br> <br>"
if add_title:
title = self.title["text"] + title
txt = {
"font": {"size": self.pargs.font_size},
"text": title,
}
return txt
def plot_slide(
self,
ele_tags=None,
style="surface",
show_values=False,
plot_all_mesh=False,
show_defo=True,
defo_scale: float = 1.0,
show_bc: bool = True,
bc_scale: float = 1.0,
show_mp_constraint: bool = True,
):
_, clim = self._get_resp_peak()
alpha_ = defo_scale if show_defo else 0.0
ndatas = []
ndata_cum = 0
for i in range(self.num_steps):
plotter = []
self._create_mesh(
plotter,
i,
ele_tags=ele_tags,
clim=clim,
coloraxis=f"coloraxis{i + 1}",
show_values=show_values,
plot_all_mesh=plot_all_mesh,
style=style,
defo_scale=alpha_,
show_bc=show_bc,
bc_scale=bc_scale,
show_mp_constraint=show_mp_constraint,
)
self.FIGURE.add_traces(plotter)
ndatas.append(len(self.FIGURE.data) - ndata_cum)
ndata_cum = len(self.FIGURE.data)
self._update_slider_layout(ndatas=ndatas, clim=clim)
def plot_peak_step(
self,
ele_tags=None,
step="absMax",
style="surface",
show_values=False,
plot_all_mesh=False,
show_defo=True,
defo_scale: float = 1.0,
show_bc: bool = True,
bc_scale: float = 1.0,
show_mp_constraint: bool = True,
):
max_step, clim = self._get_resp_peak(idx=step)
alpha_ = defo_scale if show_defo else 0.0
plotter = []
self._create_mesh(
plotter=plotter,
value=max_step,
ele_tags=ele_tags,
clim=clim,
coloraxis="coloraxis",
show_values=show_values,
plot_all_mesh=plot_all_mesh,
style=style,
defo_scale=alpha_,
show_bc=show_bc,
bc_scale=bc_scale,
show_mp_constraint=show_mp_constraint,
)
self.FIGURE.add_traces(plotter)
txt = self._make_txt(max_step)
self.FIGURE.update_layout(
coloraxis={
"colorscale": self.pargs.cmap,
"cmin": clim[0],
"cmax": clim[1],
"colorbar": {"tickfont": {"size": self.pargs.font_size - 2}, "title": txt},
},
)
def plot_anim(
self,
ele_tags=None,
framerate: Optional[int] = None,
style="surface",
show_values=False,
plot_all_mesh=False,
show_defo=True,
defo_scale: float = 1.0,
show_bc: bool = True,
bc_scale: float = 1.0,
show_mp_constraint: bool = True,
):
if framerate is None:
framerate = np.ceil(self.num_steps / 11)
nb_frames = self.num_steps
duration = 1000 / framerate
# ---------------------------------------------
_, clim = self._get_resp_peak()
alpha_ = defo_scale if show_defo else 0.0
# -----------------------------------------------------------------------------
# start plot
frames = []
for i in range(nb_frames):
plotter = []
self._create_mesh(
plotter=plotter,
value=i,
ele_tags=ele_tags,
clim=clim,
coloraxis="coloraxis",
show_values=show_values,
plot_all_mesh=plot_all_mesh,
style=style,
defo_scale=alpha_,
show_bc=show_bc,
bc_scale=bc_scale,
show_mp_constraint=show_mp_constraint,
)
frames.append(go.Frame(data=plotter, name="step:" + str(i)))
# Add data to be displayed before animation starts
plotter0 = []
self._create_mesh(
plotter0,
0,
ele_tags=ele_tags,
clim=clim,
coloraxis="coloraxis",
show_values=show_values,
style=style,
plot_all_mesh=plot_all_mesh,
defo_scale=alpha_,
show_bc=show_bc,
bc_scale=bc_scale,
show_mp_constraint=show_mp_constraint,
)
self.FIGURE = go.Figure(frames=frames, data=plotter0)
# self.title = self._make_txt(0, add_title=True)
self._update_antimate_layout(duration=duration, cbar_title=self.component)
[docs]
def plot_unstruct_responses(
odb_tag: Union[int, str] = 1,
ele_type: str = "Shell",
ele_tags: Optional[Union[int, list]] = None,
slides: bool = False,
step: Union[int, str] = "absMax",
resp_type: str = "sectionForces",
resp_dof: str = "MXX",
shell_fiber_loc: Optional[Union[str, int]] = "top",
unit_symbol: Optional[str] = None,
unit_factor: Optional[float] = None,
style: str = "surface",
show_outline: bool = False,
show_values: bool = False,
show_defo: bool = False,
defo_scale: Union[float, int, bool] = 1.0,
show_bc: bool = False,
bc_scale: float = 1.0,
show_mp_constraint: bool = False,
show_model: bool = False,
) -> go.Figure:
"""Visualizing unstructured element (Shell, Plane, Brick) Response.
.. Note::
The responses at all Gaussian points are averaged.
Parameters
----------
odb_tag: Union[int, str], default: 1
Tag of output databases (ODB) to be visualized.
ele_tags: Union[int, list], default: None
The tags of elements to be visualized.
If None, all elements are selected.
slides: bool, default: False
Display the response for each step in the form of a slideshow.
Otherwise, show the step with the following ``step`` parameter.
step: Union[int, str], default: "absMax"
If slides = False, this parameter will be used as the step to plot.
If str, Optional: [absMax, absMin, Max, Min].
If int, this step will be demonstrated (counting from 0).
ele_type: str, default: "Shell"
Element type, optional, one of ["Shell", "Plane", "Solid"].
resp_type: str, default: None
Response type, which dependents on the element type `ele_type`.
#. For ``Shell`` elements, one of ["sectionForces", "sectionDeformations", "sectionForcesAtNodes", "sectionDeformationsAtNodes", "Stresses", "Strains", "StressesAtNodes", "StrainsAtNodes"].
If it endswith `AtNodes`, responses at nodes will be displayed,
else responses at Gaussian integration points will be averaged for each element (per unit length).
If None, defaults to "sectionForces".
#. For ``Plane`` elements, one of ["stresses", "strains", "stressesAtNodes", "strainsAtNodes"].
If it endswith `AtNodes`, responses at nodes will be displayed,
else responses at Gaussian integration points will be averaged for each element.
If None, defaults to "stresses".
#. For ``Brick`` or ``Solid`` elements, one of ["stresses", "strains", "stressesAtNodes", "strainsAtNodes"].
If it endswith `AtNodes`, responses at nodes will be displayed,
else responses at Gaussian integration points will be averaged for each element.
If None, defaults to "stresses".
resp_dof: str, default: None
Dof to be visualized, which dependents on the element type `ele_type`.
.. Note::
The `resp_dof` here is consistent with stress-strain (force-deformation),
and whether it is stress or strain depends on the parameter `resp_type`.
#. For ``Shell`` elements, If resp_type is the section responses, one of ["FXX", "FYY", "FXY", "MXX", "MYY", "MXY", "VXZ", "VYZ"]. If resp_type is the stress or strain, one of ["sigma11", "sigma22", "sigma12", "sigma23", "sigma13"].
If None, defaults to "MXX".
#. For ``Plane`` elements, one of ["sigma11", "sigma22", "sigma12", "p1", "p2", "sigma_vm", "tau_max"].
* "sigma11, sigma22, sigma12": Normal stress and shear stress (strain) in the x-y plane.
* "p1, p2": Principal stresses (strains).
* "sigma_vm": Von Mises stress.
* "tau_max": Maximum shear stress (strains).
* If None, defaults to "sigma_vm".
#. For ``Brick`` or ``Solid`` elements, one of ["sigma11", "sigma22", "sigma33", "sigma12", "sigma23", "sigma13", "p1", "p2", "p3", "sigma_vm", "tau_max", "sigma_oct", "tau_oct"]
* "sigma11, sigma22, sigma33": Normal stress (strain) along x, y, z.
* "sigma12, sigma23, sigma13": Shear stress (strain).
* "p1, p2, p3": Principal stresses (strains).
* "sigma_vm": Von Mises stress.
* "tau_max": Maximum shear stress (strains).
* "sigma_oct": Octahedral normal stress (strains).
* "tau_oct": Octahedral shear stress (strains).
* If None, defaults to "sigma_vm".
shell_fiber_loc: Optional[Union[str, int]], default: "top", added in v1.0.16
The location of the fiber point for shell elements.
If str, one of ["top", "bottom", "middle"].
If int, the index of the fiber point to be visualized, from 1 (bottom) to N (top).
The fiber point is the fiber layer in the shell section.
Note that this parameter is only valid for stresses and strains in shell elements.
unit_symbol: str, default: None
Unit symbol to be displayed in the plot.
This feature is added since v1.0.15.
unit_factor: float, default: None
This feature is added since v1.0.15.
The multiplier used to convert units.
For example, if you want to visualize stress and the current data unit is kPa, you can set ``unit_symbol="kPa" and unit_factor=1.0``.
If you want to visualize in MPa, you can set ``unit_symbol="MPa" and unit_factor=0.001``.
style: str, default: surface
Visualization mesh style of surfaces and solids.
One of the following: style='surface' or style='wireframe'
Defaults to 'surface'. Note that 'wireframe' only shows a wireframe of the outer geometry.
show_values: bool, default: False
Whether to display the response value by hover.
Set to False can improve the performance of the visualization.
show_outline: bool, default: False
Whether to display the outline of the model.
show_defo: bool, default: False
Whether to display the deformed shape.
defo_scale: float, default: 1.0
Scales the size of the deformation presentation when show_defo is True.
If set to False, the deformed shape will not be scaled.
If set to a float, it will scale the deformed shape by that factor based on the default scale (i.e., 1/20 of the maximum model dimensions).
show_bc: bool, default: False
Whether to display boundary supports.
Set to False can improve the performance of the visualization.
bc_scale: float, default: 1.0
Scale the size of boundary support display.
show_mp_constraint: bool, default: False
Whether to show multipoint (MP) constraint.
Set to False can improve the performance of the visualization.
show_model: bool, default: False
Whether to plot the all model or not.
Set to False can improve the performance of the visualization.
Returns
-------
fig: `plotly.graph_objects.Figure <https://plotly.com/python-api-reference/generated/plotly.graph_objects.Figure.html>`_
You can use `fig.show()` to display,
You can also use `fig.write_html("path/to/file.html")` to save as an HTML file, see
`Interactive HTML Export in Python <https://plotly.com/python/interactive-html-export/>`_
"""
ele_type, resp_type, resp_dof, fiber_point = _check_input(ele_type, resp_type, resp_dof, fiber_pts=shell_fiber_loc)
model_info_steps, model_update, resp_step = loadODB(odb_tag, resp_type=ele_type)
if show_defo:
_, _, node_resp_steps = loadODB(odb_tag, resp_type="Nodal", verbose=False)
else:
node_resp_steps = None
plotbase = PlotUnstruResponse(model_info_steps, resp_step, model_update, node_resp_steps=node_resp_steps)
plotbase.set_unit(symbol=unit_symbol, factor=unit_factor)
plotbase.refactor_resp_step(
ele_tags=ele_tags, ele_type=ele_type, resp_type=resp_type, component=resp_dof, fiber_point=fiber_point
)
if slides:
plotbase.plot_slide(
ele_tags=ele_tags,
style=style,
show_values=show_values,
show_defo=show_defo,
defo_scale=defo_scale,
plot_all_mesh=show_model,
show_bc=show_bc,
bc_scale=bc_scale,
show_mp_constraint=show_mp_constraint,
)
else:
plotbase.plot_peak_step(
ele_tags=ele_tags,
step=step,
style=style,
show_values=show_values,
show_defo=show_defo,
defo_scale=defo_scale,
plot_all_mesh=show_model,
show_bc=show_bc,
bc_scale=bc_scale,
show_mp_constraint=show_mp_constraint,
)
return plotbase.update_fig(show_outline)
[docs]
def plot_unstruct_responses_animation(
odb_tag: Union[int, str] = 1,
ele_tags: Optional[Union[int, list]] = None,
framerate: Optional[int] = None,
ele_type: str = "Shell",
resp_type: Optional[str] = None,
resp_dof: Optional[str] = None,
shell_fiber_loc: Optional[Union[str, int]] = "top",
unit_symbol: Optional[str] = None,
unit_factor: Optional[float] = None,
style: str = "surface",
show_outline: bool = False,
show_values: bool = False,
show_defo: bool = True,
defo_scale: Union[float, int, bool] = 1.0,
show_bc: bool = True,
bc_scale: float = 1.0,
show_mp_constraint: bool = False,
show_model: bool = True,
) -> go.Figure:
"""Unstructured element (Shell, Plane, Brick) response animation.
.. Note::
The responses at all Gaussian points are averaged.
Parameters
----------
odb_tag: Union[int, str], default: 1
Tag of output databases (ODB) to be visualized.
ele_tags: Union[int, list], default: None
The tags of truss elements to be visualized. If None, all truss elements are selected.
framerate: int, default: None
Framerate for the display, i.e., the number of frames per second.
ele_type: str, default: "Shell"
Element type, optional, one of ["Shell", "Plane", "Solid"].
resp_type: str, default: None
Response type, which dependents on the element type `ele_type`.
#. For ``Shell`` elements, one of ["sectionForces", "sectionDeformations", "sectionForcesAtNodes", "sectionDeformationsAtNodes", "Stresses", "Strains", "StressesAtNodes", "StrainsAtNodes"].
If it endswith `AtNodes`, responses at nodes will be displayed,
else responses at Gaussian integration points will be averaged for each element (per unit length).
If None, defaults to "sectionForces".
#. For ``Plane`` elements, one of ["stresses", "strains", "stressesAtNodes", "strainsAtNodes"].
If it endswith `AtNodes`, responses at nodes will be displayed,
else responses at Gaussian integration points will be averaged for each element.
If None, defaults to "stresses".
#. For ``Brick`` or ``Solid`` elements, one of ["stresses", "strains", "stressesAtNodes", "strainsAtNodes"].
If it endswith `AtNodes`, responses at nodes will be displayed,
else responses at Gaussian integration points will be averaged for each element.
If None, defaults to "stresses".
resp_dof: str, default: None
Dof to be visualized, which dependents on the element type `ele_type`.
.. Note::
The `resp_dof` here is consistent with stress-strain (force-deformation),
and whether it is stress or strain depends on the parameter `resp_type`.
#. For ``Shell`` elements, If resp_type is the section responses, one of ["FXX", "FYY", "FXY", "MXX", "MYY", "MXY", "VXZ", "VYZ"]. If resp_type is the stress or strain, one of ["sigma11", "sigma22", "sigma12", "sigma23", "sigma13"].
If None, defaults to "MXX".
#. For ``Plane`` elements, one of ["sigma11", "sigma22", "sigma12", "p1", "p2", "sigma_vm", "tau_max"].
* "sigma11, sigma22, sigma12": Normal stress and shear stress (strain) in the x-y plane.
* "p1, p2": Principal stresses (strains).
* "sigma_vm": Von Mises stress.
* "tau_max": Maximum shear stress (strains).
* If None, defaults to "sigma_vm".
#. For ``Brick`` or ``Solid`` elements, one of ["sigma11", "sigma22", "sigma33", "sigma12", "sigma23", "sigma13", "p1", "p2", "p3", "sigma_vm", "tau_max", "sigma_oct", "tau_oct"]
* "sigma11, sigma22, sigma33": Normal stress (strain) along x, y, z.
* "sigma12, sigma23, sigma13": Shear stress (strain).
* "p1, p2, p3": Principal stresses (strains).
* "sigma_vm": Von Mises stress.
* "tau_max": Maximum shear stress (strains).
* "sigma_oct": Octahedral normal stress (strains).
* "tau_oct": Octahedral shear stress (strains).
* If None, defaults to "sigma_vm".
shell_fiber_loc: Optional[Union[str, int]], default: "top", added in v1.0.16
The location of the fiber point for shell elements.
If str, one of ["top", "bottom", "middle"].
If int, the index of the fiber point to be visualized, from 1 (bottom) to N (top).
The fiber point is the fiber layer in the shell section.
Note that this parameter is only valid for stresses and strains in shell elements.
unit_symbol: str, default: None
Unit symbol to be displayed in the plot.
This feature is added since v1.0.15.
unit_factor: float, default: None
This feature is added since v1.0.15.
The multiplier used to convert units.
For example, if you want to visualize stress and the current data unit is kPa, you can set ``unit_symbol="kPa" and unit_factor=1.0``.
If you want to visualize in MPa, you can set ``unit_symbol="MPa" and unit_factor=0.001``.
style: str, default: surface
Visualization mesh style of surfaces and solids.
One of the following: style='surface' or style='wireframe'
Defaults to 'surface'. Note that 'wireframe' only shows a wireframe of the outer geometry.
show_values: bool, default: False
Whether to display the response value by hover.
Set to False can improve the performance of the visualization.
show_outline: bool, default: False
Whether to display the outline of the model.
show_defo: bool, default: False
Whether to display the deformed shape.
defo_scale: float, default: 1.0
Scales the size of the deformation presentation when show_defo is True.
If set to False, the deformed shape will not be scaled.
If set to a float, it will scale the deformed shape by that factor based on the default scale (i.e., 1/20 of the maximum model dimensions).
show_bc: bool, default: False
Whether to display boundary supports.
Set to False can improve the performance of the visualization.
bc_scale: float, default: 1.0
Scale the size of boundary support display.
show_mp_constraint: bool, default: False
Whether to show multipoint (MP) constraint.
Set to False can improve the performance of the visualization.
show_model: bool, default: False
Whether to plot the all model or not.
Set to False can improve the performance of the visualization.
Returns
-------
fig: `plotly.graph_objects.Figure <https://plotly.com/python-api-reference/generated/plotly.graph_objects.Figure.html>`_
You can use `fig.show()` to display,
You can also use `fig.write_html("path/to/file.html")` to save as an HTML file, see
`Interactive HTML Export in Python <https://plotly.com/python/interactive-html-export/>`_
"""
ele_type, resp_type, resp_dof, fiber_point = _check_input(ele_type, resp_type, resp_dof, fiber_pts=shell_fiber_loc)
model_info_steps, model_update, resp_step = loadODB(odb_tag, resp_type=ele_type)
if show_defo:
_, _, node_resp_steps = loadODB(odb_tag, resp_type="Nodal", verbose=False)
else:
node_resp_steps = None
plotbase = PlotUnstruResponse(model_info_steps, resp_step, model_update, node_resp_steps=node_resp_steps)
plotbase.set_unit(symbol=unit_symbol, factor=unit_factor)
plotbase.refactor_resp_step(
ele_tags=ele_tags, ele_type=ele_type, resp_type=resp_type, component=resp_dof, fiber_point=fiber_point
)
plotbase.plot_anim(
ele_tags=ele_tags,
framerate=framerate,
style=style,
show_values=show_values,
show_defo=show_defo,
defo_scale=defo_scale,
plot_all_mesh=show_model,
show_bc=show_bc,
bc_scale=bc_scale,
show_mp_constraint=show_mp_constraint,
)
return plotbase.update_fig(show_outline)
def _check_input(ele_type, resp_type, resp_dof, fiber_pts=None):
if ele_type.lower() == "shell":
ele_type = "Shell"
resp_type, resp_dof, fiber_pts = _check_input_shell(resp_type, resp_dof, fiber_pts)
elif ele_type.lower() == "plane":
ele_type = "Plane"
resp_type, resp_dof = _check_input_plane(resp_type, resp_dof)
elif ele_type.lower() in ["brick", "solid"]:
ele_type = "Brick"
resp_type, resp_dof = _check_input_solid(resp_type, resp_dof)
else:
raise ValueError(f"Not supported element type {ele_type}! Valid options are: Shell, Plane, Brick.") # noqa: TRY003
return ele_type, resp_type, resp_dof, fiber_pts
def _check_input_shell(resp_type, resp_dof, fiber_pts=None):
if resp_type is None:
resp_type = "sectionForces"
resp_type_lower = resp_type.lower()
valid_resp_map = {
"sectionforces": "sectionForces",
"sectiondeformations": "sectionDeformations",
"sectionforcesatnodes": "sectionForcesAtNodes",
"sectiondeformationsatnodes": "sectionDeformationsAtNodes",
"stresses": "Stresses",
"strains": "Strains",
"stressesatnodes": "StressesAtNodes",
"strainsatnodes": "StrainsAtNodes",
}
if resp_type_lower not in valid_resp_map:
raise ValueError(
f"Not supported GP response type {resp_type}! Valid options are: "
+ ", ".join(valid_resp_map.values())
+ "."
)
resp_type = valid_resp_map[resp_type_lower]
if "section" in resp_type.lower():
valid_dofs = {"fxx", "fyy", "fxy", "mxx", "myy", "mxy", "vxz", "vyz"}
if resp_dof is None:
resp_dof = "MXX"
else:
valid_dofs = {"sigma11", "sigma22", "sigma12", "sigma23", "sigma13"}
if resp_dof is None:
resp_dof = "sigma11"
if fiber_pts is None:
fiber_pts = "top"
elif isinstance(fiber_pts, str):
fiber_pts = fiber_pts.lower()
if fiber_pts not in {"top", "bottom", "middle"}:
raise ValueError(f"Not supported fiber points {fiber_pts}! Valid options are: top, bottom, middle.") # noqa: TRY003
else:
fiber_pts = int(fiber_pts)
if resp_dof.lower() not in valid_dofs:
raise ValueError( # noqa: TRY003
f"Not supported component {resp_dof}! Valid options are: {', '.join(d.upper() for d in valid_dofs)}."
)
return resp_type, resp_dof, fiber_pts
def _check_input_plane(resp_type, resp_dof):
if resp_type is None:
resp_type = "Stresses"
resp_type_lower = resp_type.lower()
type_map = {
"stresses": "Stresses",
"stress": "Stresses",
"stressesatnodes": "StressesAtNodes",
"stressatnodes": "StressesAtNodes",
"strains": "Strains",
"strain": "Strains",
"strainsatnodes": "StrainsAtNodes",
"strainatnodes": "StrainsAtNodes",
}
if resp_type_lower not in type_map:
raise ValueError( # noqa: TRY003
f"Not supported response type {resp_type}! "
"Valid options are: Stresses, StressesAtNodes, Strains, StrainsAtNodes"
)
is_stress = "stress" in resp_type_lower
is_node = "nodes" in resp_type_lower
if resp_dof is None:
resp_dof = "sigma_vm"
resp_dof_lower = resp_dof.lower()
tensor_dofs = {"sigma11", "sigma22", "sigma12"}
measure_dofs = {"p1", "p2", "sigma_vm", "tau_max"}
if resp_dof_lower in measure_dofs:
resp_type = ("StressMeasures" if is_stress else "StrainMeasures") + ("AtNodes" if is_node else "")
elif resp_dof_lower in tensor_dofs:
resp_type = ("Stresses" if is_stress else "Strains") + ("AtNodes" if is_node else "")
if not is_stress:
resp_dof = resp_dof_lower.replace("sigma", "eps")
else:
raise ValueError( # noqa: TRY003
f"Not supported component {resp_dof}! "
"Valid options are: sigma11, sigma22, sigma12, p1, p2, sigma_vm, tau_max."
)
return resp_type, resp_dof
def _check_input_solid(resp_type, resp_dof):
if resp_type is None:
resp_type = "Stresses"
resp_type_lower = resp_type.lower()
type_map = {
"stresses": "Stresses",
"stress": "Stresses",
"stressesatnodes": "StressesAtNodes",
"stressatnodes": "StressesAtNodes",
"strains": "Strains",
"strain": "Strains",
"strainsatnodes": "StrainsAtNodes",
"strainatnodes": "StrainsAtNodes",
}
if resp_type_lower not in type_map:
raise ValueError( # noqa: TRY003
f"Not supported response type {resp_type}! "
"Valid options are: Stresses, StressesAtNodes, Strains, StrainsAtNodes"
)
is_stress = "stress" in resp_type_lower
is_node = "nodes" in resp_type_lower
if resp_dof is None:
resp_dof = "sigma_vm"
resp_dof_lower = resp_dof.lower()
tensor_dofs = {"sigma11", "sigma22", "sigma33", "sigma12", "sigma23", "sigma13"}
measure_dofs = {"p1", "p2", "p3", "sigma_vm", "tau_max", "sigma_oct", "tau_oct"}
if resp_dof_lower in measure_dofs:
resp_type = ("StressMeasures" if is_stress else "StrainMeasures") + ("AtNodes" if is_node else "")
elif resp_dof_lower in tensor_dofs:
resp_type = ("Stresses" if is_stress else "Strains") + ("AtNodes" if is_node else "")
if not is_stress:
resp_dof = resp_dof_lower.replace("sigma", "eps")
else:
raise ValueError( # noqa: TRY003
f"Not supported component {resp_dof}! "
"Valid options are: sigma11, sigma22, sigma33, sigma12, sigma23, sigma13, "
"p1, p2, p3, sigma_vm, tau_max, sigma_oct, tau_oct."
)
return resp_type, resp_dof