diff --git a/conftest.py b/conftest.py index a42d00cbb9..2bec6ec46c 100644 --- a/conftest.py +++ b/conftest.py @@ -21,8 +21,8 @@ # enable off_screen plotting to avoid test interruption -core.settings.disable_off_screen_rendering() -core.settings.bypass_pv_opengl_osmesa_crash() +# core.settings.disable_off_screen_rendering() +# core.settings.bypass_pv_opengl_osmesa_crash() class DPFDocTestRunner(DocTestRunner): diff --git a/src/ansys/dpf/core/meshed_region.py b/src/ansys/dpf/core/meshed_region.py index 8ca58a644f..0af5f46f08 100644 --- a/src/ansys/dpf/core/meshed_region.py +++ b/src/ansys/dpf/core/meshed_region.py @@ -2,12 +2,19 @@ MeshedRegion ============ """ +from __future__ import annotations import traceback import warnings +from typing import Union, List, TYPE_CHECKING +import ansys.dpf.core import ansys.dpf.core.errors -from ansys.dpf.core import scoping, field, property_field +from ansys.dpf.core import scoping, field, fields_container +from ansys.dpf.core.property_field import PropertyField +if TYPE_CHECKING: + from ansys.dpf.core.results import Result + from ansys.dpf.core.dpf_operator import Operator from ansys.dpf.core.check_version import server_meet_version, version_requires from ansys.dpf.core.common import ( locations, @@ -291,28 +298,29 @@ def available_property_fields(self): ) return available_property_fields - def property_field(self, property_name): + def property_field(self, property_name: str) -> Union[field.Field, PropertyField]: """ Property field getter. It can be coordinates (field), element types (property field)... Returns ------- - field_or_property_field : core.Field or core.PropertyField + field_or_property_field: + Field or PropertyField. """ return self.field_of_properties(property_name) @version_requires("3.0") - def set_property_field(self, property_name, value): + def set_property_field(self, property_name: str, value: Union[field.Field, PropertyField]): """ Property field setter. It can be coordinates (field), element types (property field)... Parameters ---------- - property_name : str + property_name: property name of the field to set - value : PropertyField or Field + value: """ if property_name is nodal_properties.coordinates: self.set_coordinates_field(value) @@ -321,24 +329,24 @@ def set_property_field(self, property_name, value): @update_grid @version_requires("3.0") - def set_coordinates_field(self, coordinates_field): + def set_coordinates_field(self, coordinates_field: field.Field): """ Coordinates field setter. Parameters ---------- - coordinates_field : PropertyField or Field + coordinates_field: """ self._api.meshed_region_set_coordinates_field(self, coordinates_field) @property - def available_named_selections(self): + def available_named_selections(self) -> List[str]: """ List of available named selections. Returns ------- - named_selections : list str + named_selections: """ return self._get_available_named_selections() @@ -356,18 +364,19 @@ def _get_available_named_selections(self): named_selections.append(self._api.meshed_region_get_named_selection_name(self, index)) return named_selections - def named_selection(self, named_selection): + def named_selection(self, named_selection: str) -> scoping.Scoping: """ Scoping containing the list of nodes or elements in the named selection. Parameters ---------- - named_selection : str + named_selection: Name of the named selection. Returns ------- - named_selection : Scoping + named_selection: + Scoping equivalent to the named selection. """ if server_meet_version("2.1", self._server): out = self._api.meshed_region_get_named_selection_scoping(self, named_selection) @@ -388,15 +397,16 @@ def named_selection(self, named_selection): ) @version_requires("3.0") - def set_named_selection_scoping(self, named_selection_name, scoping): + def set_named_selection_scoping(self, named_selection_name: str, scoping: scoping.Scoping): """ Named selection scoping setter. Parameters ---------- - named_selection_name : str - named selection name - scoping : Scoping + named_selection_name: + Name of the named selection. + scoping: + Scoping to associate to the named selection. """ return self._api.meshed_region_set_named_selection_scoping( self, named_selection_name, scoping @@ -529,27 +539,29 @@ def grid(self): def plot( self, - field_or_fields_container=None, + field_or_fields_container: Union[ + field.Field, PropertyField, fields_container.FieldsContainer, None + ] = None, shell_layers=None, - deform_by=None, - scale_factor=1.0, + deform_by: Union[field.Field, Result, Operator, None] = None, + scale_factor: float = 1.0, **kwargs, ): """ - Plot the field or fields container on the mesh. + Plot the mesh, bare or with data. Parameters ---------- - field_or_fields_container : dpf.core.Field or dpf.core.FieldsContainer - Field or fields container to plot. The default is ``None``. - shell_layers : core.shell_layers, optional + field_or_fields_container: + Field, PropertyField, or FieldsContainer to plot on the mesh. + shell_layers: core.shell_layers, optional Enum used to set the shell layers if the model to plot contains shell elements. - deform_by : Field, Result, Operator, optional + deform_by: Field, Result, Operator, optional Used to deform the plotted mesh. Must output a 3D vector field. Defaults to None. - scale_factor : float, optional - Scaling factor to apply when warping the mesh. Defaults to 1.0. - **kwargs : optional + scale_factor: + Scaling factor to apply when warping the mesh. + **kwargs: optional Additional keyword arguments for the plotter. For additional keyword arguments, see ``help(pyvista.plot)``. @@ -565,7 +577,7 @@ def plot( >>> model.metadata.meshed_region.plot(field) """ - if field_or_fields_container is not None: + if isinstance(field_or_fields_container, (field.Field, fields_container.FieldsContainer)): pl = Plotter(self, **kwargs) return pl.plot_contour( field_or_fields_container, @@ -585,6 +597,8 @@ def plot( show_axes=kwargs.pop("show_axes", True), **kwargs, ) + if isinstance(field_or_fields_container, PropertyField): + pl.add_field(field=field_or_fields_container, meshed_region=self, **kwargs) kwargs.pop("notebook", None) return pl.show_figure(**kwargs) @@ -670,11 +684,11 @@ def field_of_properties(self, property_name): else: field_out = self._api.meshed_region_get_property_field(self, property_name) if isinstance(field_out, int): - res = property_field.PropertyField(server=self._server, property_field=field_out) + res = PropertyField(server=self._server, property_field=field_out) return res else: if field_out.datatype == "int": - return property_field.PropertyField( + return PropertyField( server=self._server, property_field=field_out ) else: diff --git a/src/ansys/dpf/core/plotter.py b/src/ansys/dpf/core/plotter.py index a321588b1f..2e00d2211e 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -216,11 +216,28 @@ def add_field( **kwargs, ): # Get the field name - name = field.name.split("_")[0] - unit = field.unit - kwargs.setdefault("stitle", f"{name} ({unit})") - - kwargs = self._set_scalar_bar_title(kwargs) + if isinstance(field, dpf.core.Field): + name = field.name.split("_")[0] + categories = False + unit = field.unit + kwargs.setdefault("stitle", f"{name} ({unit})") + kwargs = self._set_scalar_bar_title(kwargs) + elif isinstance(field, dpf.core.PropertyField): + try: + name = field.name + except dpf_errors.DpfVersionNotSupported: + name = "" + categories = True + kwargs.setdefault("scalar_bar_args", { + "title": name, + "n_labels": 0, + }) + # from itertools import cycle + # cycler = cycle(['Reds', 'Greens', 'Blues', 'Greys', 'Oranges', 'Purples']) + kwargs.setdefault("cmap", "tab20") + values = set(field.data) + kwargs.setdefault("clim", [min(values)-0.5, max(values)+0.5]) + kwargs.setdefault("annotations", dict([(v, str(int(v))) for v in values])) kwargs.setdefault("show_edges", True) kwargs.setdefault("nan_color", "grey") @@ -277,7 +294,7 @@ def add_field( meshed_region.deform_by(deform_by, scale_factor), as_linear ) grid.set_active_scalars(None) - self._plotter.add_mesh(grid, scalars=overall_data, **kwargs_in) + self._plotter.add_mesh(grid, scalars=overall_data, categories=categories, **kwargs_in) # If deformed geometry, print the scale_factor if deform_by and scale_factor_legend is not False: diff --git a/src/ansys/dpf/core/property_field.py b/src/ansys/dpf/core/property_field.py index 3059f43316..a87a84843e 100644 --- a/src/ansys/dpf/core/property_field.py +++ b/src/ansys/dpf/core/property_field.py @@ -2,8 +2,15 @@ PropertyField ============= """ +from __future__ import annotations import numpy as np +from typing import Union, TYPE_CHECKING +if TYPE_CHECKING: + from ansys.dpf.core.results import Result + from ansys.dpf.core.field import Field + from ansys.dpf.core.dpf_operator import Operator + from ansys.dpf.core.meshed_region import MeshedRegion from ansys.dpf.core.check_version import version_requires from ansys.dpf.core.common import natures, locations, _get_size_of_list from ansys.dpf.core import scoping, dimensionality @@ -17,6 +24,7 @@ dpf_array, dpf_vector, ) +from ansys.dpf.core.plotter import DpfPlotter class PropertyField(_FieldBase): @@ -342,6 +350,51 @@ def name(self, value): self._field_definition, name=value ) + def plot( + self, + meshed_region: MeshedRegion, + deform_by: Union[Field, Result, Operator, None] = None, + scale_factor: float = 1.0, + **kwargs, + ): + """Plot the PropertyField on a MeshedRegion. + + Parameters + ---------- + meshed_region: + The mesh to plot the property field onto. + deform_by: Field, Result, Operator, optional + Used to deform the plotted mesh. Must output a 3D vector field. + Defaults to None. + scale_factor: + Scaling factor to apply when warping the mesh. + **kwargs: optional + Additional keyword arguments for the plotter. For additional keyword + arguments, see ``help(pyvista.plot)``. + + Examples + -------- + Plot the displacement field from an example file. + + >>> import ansys.dpf.core as dpf + >>> from ansys.dpf.core import examples + >>> model = dpf.Model(examples.find_multishells_rst()) + >>> mesh = model.metadata.meshed_region + >>> pf = mesh.property_field(property_name="mat") + >>> pf.plot(meshed_region=mesh) + """ + pl = DpfPlotter(**kwargs) + pl.add_field( + field=self, + meshed_region=meshed_region, + deform_by=deform_by, + scale_factor=scale_factor, + show_axes=kwargs.pop("show_axes", True), + **kwargs, + ) + kwargs.pop("notebook", None) + return pl.show_figure(**kwargs) + class _LocalPropertyField(_LocalFieldBase, PropertyField): """Caches the internal data of a field so that it can be modified locally. diff --git a/tests/conftest.py b/tests/conftest.py index bd63851974..a6af43ca07 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,10 +19,10 @@ ACCEPTABLE_FAILURE_RATE = 0 -core.settings.disable_off_screen_rendering() -os.environ["PYVISTA_OFF_SCREEN"] = "true" -core.settings.bypass_pv_opengl_osmesa_crash() -os.environ["MPLBACKEND"] = "Agg" +# core.settings.disable_off_screen_rendering() +# os.environ["PYVISTA_OFF_SCREEN"] = "true" +# core.settings.bypass_pv_opengl_osmesa_crash() +# os.environ["MPLBACKEND"] = "Agg" # currently running dpf on docker. Used for testing on CI DPF_SERVER_TYPE = os.environ.get("DPF_SERVER_TYPE", None) running_docker = ansys.dpf.core.server_types.RUNNING_DOCKER.use_docker diff --git a/tests/test_plotter.py b/tests/test_plotter.py index c50e7f0a1e..49083222f0 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -65,6 +65,22 @@ def test_mesh_field_plot(multishells): mesh.plot(f) +@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista") +def test_mesh_property_field_plot(multishells): + model = core.Model(multishells) + mesh = model.metadata.meshed_region + pf = mesh.property_field(property_name="mat") + mesh.plot(pf) + + +@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista") +def test_property_field_plot(multishells): + model = core.Model(multishells) + mesh = model.metadata.meshed_region + pf = mesh.property_field(property_name="mat") + pf.plot(meshed_region=mesh) + + @pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista") def test_plotter_on_mesh(allkindofcomplexity): model = Model(allkindofcomplexity)