Source code for opstool.vis.pyvista.vis_unstru_resp

from functools import partial
from typing import Optional, Union

import numpy as np
import pyvista as pv

from ...post import loadODB
from .plot_resp_base import PlotResponseBase
from .plot_utils import PLOT_ARGS, _get_unstru_cells, _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"

    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

    def _make_unstru_info(self, ele_tags, step):
        pos = self._get_node_da(step)
        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, 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()
        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 _make_title(self, scalars, step, time):
        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()
        info = {
            "title": self.ele_type.capitalize(),
            "resp_type": resp_type,
            "dof": self.component.capitalize(),
            "min": np.min(scalars),
            "max": np.max(scalars),
            "step": step,
            "time": time,
        }
        lines = [
            f"* {info['title']} Responses",
            f"* {info['resp_type']}",
            f"* {info['dof']} (DOF)",
            f"{info['min']:.3E} (min)",
            f"{info['max']:.3E} (max)",
            f"{info['step']} (step)",
            f"{info['time']:.3f} (time)",
        ]
        if self.unit_symbol:
            info["unit"] = self.unit_symbol
            lines.insert(3, f"{info['unit']} (unit)")
        if self.fiber_point and "Sec" not in resp_type and self.ele_type.lower() == "shell":
            info["fiber_point"] = self.fiber_point
            lines.insert(3, f"* {info['fiber_point']} (Fiber)")

        max_len = max(len(line) for line in lines)
        padded_lines = [line.rjust(max_len) for line in lines]
        text = "\n".join(padded_lines)
        return text + "\n"

    def _get_mesh_data(self, step, ele_tags, defo_scale):
        pos_defo = np.array(self._get_defo_coord_da(step, defo_scale))
        tags, pos, cells, cell_types = self._make_unstru_info(ele_tags, step)
        scalars = self.resp_step[step].to_numpy()
        return pos_defo, cells, cell_types, scalars

    def _create_mesh(
        self,
        plotter,
        value,
        ele_tags=None,
        plot_all_mesh=True,
        clim=None,
        style="surface",
        cpos="iso",
        defo_scale=1.0,
        show_outline=False,
        show_bc: bool = True,
        bc_scale: float = 1.0,
        show_mp_constraint: bool = False,
    ):
        step = round(value)
        node_no_deform_coords = np.array(self._get_node_da(step))
        pos, cells, cell_types, scalars = self._get_mesh_data(step, ele_tags, defo_scale)
        #  ---------------------------------
        plotter.clear_actors()  # !!!!!!
        if plot_all_mesh:
            self._plot_all_mesh(plotter, color="gray", step=step)
        resp_plot = _plot_unstru_cmap(
            plotter,
            pos=pos,
            cells=cells,
            cell_types=cell_types,
            scalars=scalars,
            cmap=self.pargs.cmap,
            clim=clim,
            show_scalar_bar=False,
            show_edges=self.pargs.show_mesh_edges,
            edge_color=self.pargs.mesh_edge_color,
            edge_width=self.pargs.mesh_edge_width,
            opacity=self.pargs.mesh_opacity,
            style=style,
            pos_origin=node_no_deform_coords,
        )

        title = self._make_title(scalars, step, self.time[step])
        scalar_bar = plotter.add_scalar_bar(title=title, **self.pargs.scalar_bar_kargs)
        if scalar_bar:
            # scalar_bar.SetTitle(title)
            title_prop = scalar_bar.GetTitleTextProperty()
            # title_prop.SetJustificationToRight()
            title_prop.BoldOn()

        if show_outline:
            self._plot_outline(plotter)
        bc_grid, mp_grid = None, None
        if show_bc:
            bc_grid = self._plot_bc(plotter, step, defo_scale=defo_scale, bc_scale=bc_scale)
        if show_mp_constraint:
            mp_grid = self._plot_mp_constraint(plotter, step, defo_scale=defo_scale)

        self._update_plotter(plotter, cpos)
        return resp_plot, scalar_bar, bc_grid, mp_grid

    def _update_mesh(
        self,
        step,
        ele_tags,
        resp_plot,
        scalar_bar,
        bc_grid=None,
        mp_grid=None,
        defo_scale=1.0,
        bc_scale: float = 1.0,
    ):
        step = round(step)
        pos, cells, cell_types, scalars = self._get_mesh_data(step, ele_tags, defo_scale)

        if resp_plot:
            resp_plot.points = pos
            resp_plot["scalars"] = scalars

        if scalar_bar:
            title = self._make_title(scalars, step, self.time[step])
            scalar_bar.SetTitle(title)

        if mp_grid:
            self._plot_mp_constraint_update(mp_grid, step, defo_scale=defo_scale)
        if bc_grid:
            self._plot_bc_update(bc_grid, step, defo_scale=defo_scale, bc_scale=bc_scale)

    def plot_slide(
        self,
        plotter,
        ele_tags=None,
        style="surface",
        plot_model=True,
        cpos="iso",
        show_defo=True,
        defo_scale: float = 1.0,
        show_bc: bool = True,
        bc_scale: float = 1.0,
        show_mp_constraint: bool = True,
        show_outline=False,
    ):
        _, clim = self._get_resp_peak()
        alpha_ = defo_scale if show_defo else 0.0
        if self.ModelUpdate:
            func = partial(
                self._create_mesh,
                plotter,
                ele_tags=ele_tags,
                clim=clim,
                plot_all_mesh=plot_model,
                style=style,
                cpos=cpos,
                defo_scale=alpha_,
                show_outline=show_outline,
                show_bc=show_bc,
                bc_scale=bc_scale,
                show_mp_constraint=show_mp_constraint,
            )
        else:
            resp_plot, scalar_bar, bc_grid, mp_grid = self._create_mesh(
                plotter,
                self.num_steps - 1,
                ele_tags=ele_tags,
                clim=clim,
                plot_all_mesh=plot_model,
                style=style,
                cpos=cpos,
                defo_scale=alpha_,
                show_outline=show_outline,
                show_bc=show_bc,
                bc_scale=bc_scale,
                show_mp_constraint=show_mp_constraint,
            )
            func = partial(
                self._update_mesh,
                ele_tags=ele_tags,
                resp_plot=resp_plot,
                scalar_bar=scalar_bar,
                bc_grid=bc_grid,
                mp_grid=mp_grid,
                bc_scale=bc_scale,
                defo_scale=alpha_,
            )
        plotter.add_slider_widget(func, [0, self.num_steps - 1], value=self.num_steps - 1, **self.slider_widget_args)

    def plot_peak_step(
        self,
        plotter,
        step="absMax",
        ele_tags=None,
        style="surface",
        plot_model=True,
        cpos="iso",
        defo_scale=1.0,
        show_defo=True,
        show_bc: bool = True,
        bc_scale: float = 1.0,
        show_mp_constraint: bool = True,
        show_outline=False,
    ):
        step, clim = self._get_resp_peak(idx=step)
        alpha_ = defo_scale if show_defo else 0.0
        self._create_mesh(
            plotter=plotter,
            value=step,
            ele_tags=ele_tags,
            clim=clim,
            plot_all_mesh=plot_model,
            style=style,
            cpos=cpos,
            defo_scale=alpha_,
            show_outline=show_outline,
            show_bc=show_bc,
            bc_scale=bc_scale,
            show_mp_constraint=show_mp_constraint,
        )

    def plot_anim(
        self,
        plotter,
        ele_tags=None,
        framerate: Optional[int] = None,
        savefig: str = "ShellRespAnimation.gif",
        style="surface",
        plot_model=True,
        cpos="iso",
        defo_scale=1.0,
        show_defo=True,
        show_bc: bool = True,
        bc_scale: float = 1.0,
        show_mp_constraint: bool = True,
        show_outline=False,
    ):
        if framerate is None:
            framerate = np.ceil(self.num_steps / 11)
        if savefig.endswith(".gif"):
            plotter.open_gif(savefig, fps=framerate)
        else:
            plotter.open_movie(savefig, framerate=framerate)
        _, clim = self._get_resp_peak()
        alpha_ = defo_scale if show_defo else 0.0
        # plotter.write_frame()  # write initial data

        if self.ModelUpdate:
            for step in range(self.num_steps):
                self._create_mesh(
                    plotter,
                    step,
                    ele_tags=ele_tags,
                    clim=clim,
                    plot_all_mesh=plot_model,
                    style=style,
                    cpos=cpos,
                    defo_scale=alpha_,
                    show_outline=show_outline,
                    show_bc=show_bc,
                    bc_scale=bc_scale,
                    show_mp_constraint=show_mp_constraint,
                )
                plotter.write_frame()
        else:
            resp_plot, scalar_bar, bc_grid, mp_grid = self._create_mesh(
                plotter,
                0,
                ele_tags=ele_tags,
                clim=clim,
                plot_all_mesh=plot_model,
                style=style,
                cpos=cpos,
                defo_scale=alpha_,
                show_outline=show_outline,
                show_bc=show_bc,
                bc_scale=bc_scale,
                show_mp_constraint=show_mp_constraint,
            )
            plotter.write_frame()
            for step in range(1, self.num_steps):
                self._update_mesh(
                    step=step,
                    ele_tags=ele_tags,
                    resp_plot=resp_plot,
                    scalar_bar=scalar_bar,
                    bc_grid=bc_grid,
                    mp_grid=mp_grid,
                    bc_scale=bc_scale,
                    defo_scale=alpha_,
                )
                plotter.write_frame()


[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", style: str = "surface", unit_symbol: Optional[str] = None, unit_factor: Optional[float] = None, 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_outline: bool = False, cpos: str = "iso", show_model: bool = True, ) -> pv.Plotter: """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. unit_factor: float, default: None 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 the mesh style of surfaces and solids. One of the following: style='surface', style='wireframe', style='points', style='points_gaussian'. Defaults to 'surface'. Note that 'wireframe' only shows a wireframe of the outer geometry. show_defo: bool, default: True 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: True Whether to display boundary supports. 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. show_outline: bool, default: False Whether to display the outline of the model. cpos: str, default: iso Model display perspective, optional: "iso", "xy", "yx", "xz", "zx", "yz", "zy". If 3d, defaults to "iso". If 2d, defaults to "xy". show_model: bool, default: True Whether to plot the all model or not. Returns ------- Plotting object of PyVista to display vtk meshes or numpy arrays. See `pyvista.Plotter <https://docs.pyvista.org/api/plotting/_autosummary/pyvista.plotter>`_. You can use `Plotter.show <https://docs.pyvista.org/api/plotting/_autosummary/pyvista.plotter.show#pyvista.Plotter.show>`_. to display the plotting window. You can also use `Plotter.export_html <https://docs.pyvista.org/api/plotting/_autosummary/pyvista.plotter.export_html#pyvista.Plotter.export_html>`_. to export this plotter as an interactive scene to an HTML file. """ ele_type, resp_type, resp_dof, fiber_pts = _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 plotter = pv.Plotter( notebook=PLOT_ARGS.notebook, line_smoothing=PLOT_ARGS.line_smoothing, polygon_smoothing=PLOT_ARGS.polygon_smoothing, off_screen=PLOT_ARGS.off_screen, ) 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_pts ) if slides: plotbase.plot_slide( plotter, ele_tags=ele_tags, style=style, cpos=cpos, plot_model=show_model, defo_scale=defo_scale, show_defo=show_defo, show_bc=show_bc, bc_scale=bc_scale, show_mp_constraint=show_mp_constraint, show_outline=show_outline, ) else: plotbase.plot_peak_step( plotter, ele_tags=ele_tags, step=step, style=style, cpos=cpos, plot_model=show_model, defo_scale=defo_scale, show_defo=show_defo, show_bc=show_bc, bc_scale=bc_scale, show_mp_constraint=show_mp_constraint, show_outline=show_outline, ) if PLOT_ARGS.anti_aliasing: plotter.enable_anti_aliasing(PLOT_ARGS.anti_aliasing) return plotbase._update_plotter(plotter, cpos)
[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", savefig: Optional[str] = None, off_screen: bool = True, style: str = "surface", unit_symbol: Optional[str] = None, unit_factor: Optional[float] = None, 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_outline: bool = False, cpos: str = "iso", show_model: bool = True, ) -> pv.Plotter: """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. ele_type: str, default: "Shell" Element type, optional, one of ["Shell", "Plane", "Solid"]. framerate: int, default: None Framerate for the display, i.e., the number of frames per second. savefig: str, default: None Path to save the animation. The suffix can be ``.gif`` or ``.mp4``. off_screen: bool, default: True Off-screen rendering, i.e., not showing the rendering window. If False, the rendering window will be displayed. 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. unit_factor: float, default: None 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 the mesh style of surfaces and solids. One of the following: style='surface', style='wireframe', style='points', style='points_gaussian'. Defaults to 'surface'. Note that 'wireframe' only shows a wireframe of the outer geometry. show_defo: bool, default: True Whether to display the deformed shape. defo_scale: float, default: 1.0 Scales the size of the deformation presentation. 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: True Whether to display boundary supports. 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. show_outline: bool, default: False Whether to display the outline of the model. cpos: str, default: iso Model display perspective, optional: "iso", "xy", "yx", "xz", "zx", "yz", "zy". If 3d, defaults to "iso". If 2d, defaults to "xy". show_model: bool, default: True Whether to plot the all model or not. Returns ------- Plotting object of PyVista to display vtk meshes or numpy arrays. See `pyvista.Plotter <https://docs.pyvista.org/api/plotting/_autosummary/pyvista.plotter>`_. You can use `Plotter.show <https://docs.pyvista.org/api/plotting/_autosummary/pyvista.plotter.show#pyvista.Plotter.show>`_. to display the plotting window. You can also use `Plotter.export_html <https://docs.pyvista.org/api/plotting/_autosummary/pyvista.plotter.export_html#pyvista.Plotter.export_html>`_. to export this plotter as an interactive scene to an HTML file. """ ele_type, resp_type, resp_dof, fiber_point = _check_input(ele_type, resp_type, resp_dof, fiber_pts=shell_fiber_loc) if savefig is None: savefig = f"{ele_type.capitalize()}RespAnimation.gif" 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 plotter = pv.Plotter( notebook=PLOT_ARGS.notebook, line_smoothing=PLOT_ARGS.line_smoothing, polygon_smoothing=PLOT_ARGS.polygon_smoothing, off_screen=off_screen, ) 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( plotter, ele_tags=ele_tags, framerate=framerate, savefig=savefig, style=style, cpos=cpos, plot_model=show_model, defo_scale=defo_scale, show_defo=show_defo, show_bc=show_bc, bc_scale=bc_scale, show_mp_constraint=show_mp_constraint, show_outline=show_outline, ) if PLOT_ARGS.anti_aliasing: plotter.enable_anti_aliasing(PLOT_ARGS.anti_aliasing) print(f"Animation has been saved as {savefig}!") return plotbase._update_plotter(plotter, cpos)
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" # fiber_pts check 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