Source code for felupe.view._scene

# -*- coding: utf-8 -*-
"""
This file is part of FElupe.

FElupe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

FElupe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with FElupe.  If not, see <http://www.gnu.org/licenses/>.
"""

import numpy as np


[docs] class Scene: r"""Base class for plotting a static scene. Attributes ---------- mesh : pyvista.UnstructuredGrid A generalized Dataset with the mesh as well as point- and cell-data. This is not an instance of :class:`felupe.Mesh`. Examples -------- .. pyvista-plot:: :force_static: >>> import numpy as np >>> import felupe as fem >>> >>> scene = fem.view.Scene() >>> scene.mesh = fem.Cube(n=3).as_unstructured_grid() >>> scene.mesh.point_data["Displacement"] = np.arange(81).reshape(27, 3) / 300 >>> scene.mesh.set_active_scalars(None) >>> >>> scene.plot("Displacement", component=None).show() See Also -------- felupe.ViewMesh : Provide Visualization methods for a mesh with optional given dicts of point- and cell-data items. felupe.ViewField : Provide Visualization methods for a field container. felupe.ViewSolid : Provide Visualization methods for a field container or a solid body. """ def plot( self, name=None, component=0, label=None, factor=1.0, show_edges=True, show_undeformed=True, cmap="turbo", view="default", theme="default", scalar_bar_args=None, scalar_bar_vertical=False, add_axes=True, off_screen=False, plotter=None, notebook=False, extract_surface=False, nonlinear_subdivision=1, smooth_shading=True, split_sharp_edges=True, edge_color="black", line_width=1.0, **kwargs, ): """Plot scalars, selected by name and component. Parameters ---------- name: str or None, optional Name of array of scalars to plot (default is None). component : int, optional Component of vector-valued scalars to plot (default is 0). label : str or None, optional A custom label which is shown in the scalar bar. If no label is given, it is created by the name including the component. For vector-valued scalars, the component number is replaced by ``"X"``, etc. For 3d-tensors in full or reduced vector storage (Voigt-Notation) the component number is replaced by ``"XY"``, etc. If ``"Principal Values of"`` is in the name, the component number is replaced by ``"\n (Max. Principal)"``, assuming that the principal values are sorted in descending order. factor : float, optional Factor for the scaling of the warped (deformed) mesh (default is 1.0). show_edges : bool, optional Show the edges of the cells (default is True). show_undeformed : bool, optional Show the undeformed model (default is True). cmap : str, optional The color map (default is "turbo"). view : str or None, optional The camera position, e.g. "xy" or "iso" (default is "default"). If not specified, this is None for 3d-meshes and "xy" for 2d-meshes. theme : str or None, optional The theme used for plotting, e.g. "default" or "document" (default is None). scalar_bar_vertical : bool, optional A flag to show the interactive scalar bar in vertical orientation on the right side (default is True). add_axes : bool, optional Add the axes, i.e. the coordinate system to the scene (default is True). off_screen : bool, optional Initialize the plotter off-screen and don't open a window on plotting. For screenshots, it is necessary to set ``off_screen=True`` (default is False). plotter : pyvista.Plotter or None, optional Use a given Plotter instead of creating a new instance (default is None). notebook : bool, optional When True, the resulting plot is placed inline a jupyter notebook. Assumes a jupyter console is active. Automatically enables off_screen (default is False). extract_surface : bool, optional Extract the surface mesh. Required to hide internal edges of quadratic cells (default is False). If True and ``show_edges=True``, the feature edges of a separated mesh are plotted. nonlinear_subdivision : int, optional Number of subdivisions to generate a smooth surface based on the mid-edge points (default is 1, no subdivision). If greater than 1, the surface of the mesh is extracted. smooth_shading : bool, optional A flag to enable smooth shading (default is True). Only considered if number of subdivisions is greater than 1. split_sharp_edges : bool, optional A flag to split sharp edges (default is True). Use this flag in combination with smooth shading. Only considered if number of subdivisions is greater than 1. edge_color : str, optional The color of the edge lines (default is "black"). line_width : float, optional The line-width of the edge lines (default is 1.0). Returns ------- plotter : pyvista.Plotter A Plotter object with methods ``plot()``, ``screenshot()``, etc. See Also -------- pyvista.Plotter : Plotting object to display vtk meshes or numpy arrays. """ import pyvista as pv if theme is not None: pv.set_plot_theme(theme) if plotter is None: plotter_kwargs = dict() if off_screen: plotter_kwargs["off_screen"] = off_screen if notebook: plotter_kwargs["notebook"] = notebook plotter = pv.Plotter(**plotter_kwargs) if scalar_bar_args is None: scalar_bar_args = {} if name is not None: if component is not None: if name in self.mesh.point_data.keys(): data = self.mesh.point_data[name] else: data = self.mesh.cell_data[name] if name is not None and label is None: data_label = name if component is not None: if name in self.mesh.point_data.keys(): data = self.mesh.point_data[name] else: data = self.mesh.cell_data[name] dim = 1 if len(data.shape) == 2: dim = data.shape[1] component_labels_dict = { 1: [""], 2: ["X", "Y"], 3: ["X", "Y", "Z"], 6: ["XX", "YY", "ZZ", "XY", "YZ", "XZ"], 9: [ "XX", "XY", "XZ", "YX", "YY", "YZ", "ZX", "ZY", "ZZ", ], } if "Principal Values of " in data_label: data[:] = np.flip(np.sort(data, axis=-1), axis=-1) component_labels_dict[2] = [ "(Max. Principal)", "(Min. Principal)", ] component_labels_dict[3] = [ "(Max. Principal)", "(Int. Principal)", "(Min. Principal)", ] data_label = data_label[20:] if "Equivalent of " in data_label: component_labels_dict[1] = [""] component_labels = np.arange(dim) if dim in component_labels_dict.keys(): component_labels = component_labels_dict[dim] component_label = component_labels[component] else: component_label = "Magnitude" label = f"{data_label} {component_label}" if show_undeformed: plotter.add_mesh( self.mesh, show_edges=False, opacity=0.2, line_width=line_width ) mesh = self.mesh if "Displacement" in self.mesh.point_data.keys(): mesh = mesh.warp_by_vector("Displacement", factor=factor) surface = mesh show_edges_surface = show_edges kwargs_with_line_width = {**kwargs} if mesh.number_of_cells > 0: if extract_surface or nonlinear_subdivision > 1: surface = surface.extract_surface( nonlinear_subdivision=nonlinear_subdivision ) show_edges_surface = False else: kwargs_with_line_width["line_width"] = line_width # disable surface-related arguments if the mesh contains no cells if mesh.number_of_cells == 0 or nonlinear_subdivision == 1: smooth_shading = None split_sharp_edges = None # don't show edges for the base (surface) mesh to hide internal edges of # quadratic / Lagrange cell-types plotter.add_mesh( mesh=surface, scalars=name, component=component, show_edges=show_edges_surface, cmap=cmap, scalar_bar_args={ "title": label, "interactive": True, "vertical": scalar_bar_vertical, **scalar_bar_args, }, smooth_shading=smooth_shading, split_sharp_edges=split_sharp_edges, **kwargs_with_line_width, ) # extract the feature edges (without cell-internal edges) if ( mesh.number_of_cells > 0 and show_edges and (extract_surface or nonlinear_subdivision > 1) ): edges = ( mesh.separate_cells() .extract_surface(nonlinear_subdivision=nonlinear_subdivision) .extract_feature_edges() ) actor = plotter.add_mesh(edges, color=edge_color, line_width=line_width) actor.mapper.SetResolveCoincidentTopologyToPolygonOffset() if view == "default": if np.allclose(self.mesh.points[:, 2], 0): view = "xy" plotter.enable_parallel_projection() else: view = None plotter.camera.elevation = -15 plotter.camera.azimuth = -100 plotter.camera_position = view # pv.set_plot_theme(theme) if add_axes: plotter.add_axes() return plotter