From e0df5bff9a2d8967cb11756a6128baf76287d7d6 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 22 Apr 2024 16:32:55 +0200 Subject: [PATCH 1/8] Enable plotting of PropertyField onto a MeshedRegion Signed-off-by: paul.profizi --- src/ansys/dpf/core/meshed_region.py | 76 +++++++++++++++++------------ src/ansys/dpf/core/plotter.py | 19 +++++--- 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/src/ansys/dpf/core/meshed_region.py b/src/ansys/dpf/core/meshed_region.py index 8ca58a644f..8bb1769cc0 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.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..5edb3f91c2 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -216,11 +216,18 @@ 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): + name = field.name + categories = True + kwargs.setdefault("stitle", f"{name}") + kwargs = self._set_scalar_bar_title(kwargs) + kwargs["scalar_bar_args"]["fmt"] = "%.0f" kwargs.setdefault("show_edges", True) kwargs.setdefault("nan_color", "grey") @@ -277,7 +284,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: From 41598798c5b64d929951df4c3c9b8a9dff3cd34b Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 22 Apr 2024 17:01:27 +0200 Subject: [PATCH 2/8] Add a test Signed-off-by: paul.profizi --- tests/test_plotter.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_plotter.py b/tests/test_plotter.py index c50e7f0a1e..a696416e7b 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -65,6 +65,14 @@ 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_plotter_on_mesh(allkindofcomplexity): model = Model(allkindofcomplexity) From dc74a33fe0262f112c37d880659e76ffe1515159 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 22 Apr 2024 17:02:02 +0200 Subject: [PATCH 3/8] Update PyVista parameters Signed-off-by: paul.profizi --- src/ansys/dpf/core/plotter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ansys/dpf/core/plotter.py b/src/ansys/dpf/core/plotter.py index 5edb3f91c2..ed55e713eb 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -227,7 +227,9 @@ def add_field( categories = True kwargs.setdefault("stitle", f"{name}") kwargs = self._set_scalar_bar_title(kwargs) + kwargs["scalar_bar_args"]["n_labels"] = len(set(field.data)) kwargs["scalar_bar_args"]["fmt"] = "%.0f" + kwargs["cmap"] = "brg" kwargs.setdefault("show_edges", True) kwargs.setdefault("nan_color", "grey") From 540cc5b47af15acdb08375c9d729795daa07d2ed Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 22 Apr 2024 17:58:58 +0200 Subject: [PATCH 4/8] PropertyField.name is not available below 8.1 Signed-off-by: paul.profizi --- src/ansys/dpf/core/plotter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ansys/dpf/core/plotter.py b/src/ansys/dpf/core/plotter.py index ed55e713eb..dc023fa669 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -223,7 +223,10 @@ def add_field( kwargs.setdefault("stitle", f"{name} ({unit})") kwargs = self._set_scalar_bar_title(kwargs) elif isinstance(field, dpf.core.PropertyField): - name = field.name + try: + name = field.name + except dpf_errors.DpfVersionNotSupported: + name = "" categories = True kwargs.setdefault("stitle", f"{name}") kwargs = self._set_scalar_bar_title(kwargs) From dd6f88784f1a4f7970f10e774545fd1853da1508 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 23 Apr 2024 16:04:55 +0200 Subject: [PATCH 5/8] Improve scalar bar Signed-off-by: paul.profizi --- src/ansys/dpf/core/meshed_region.py | 2 +- src/ansys/dpf/core/plotter.py | 13 ++++++++----- tests/test_plotter.py | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/ansys/dpf/core/meshed_region.py b/src/ansys/dpf/core/meshed_region.py index 8bb1769cc0..0af5f46f08 100644 --- a/src/ansys/dpf/core/meshed_region.py +++ b/src/ansys/dpf/core/meshed_region.py @@ -598,7 +598,7 @@ def plot( **kwargs, ) if isinstance(field_or_fields_container, PropertyField): - pl.add_field(field=field_or_fields_container, meshed_region=self) + pl.add_field(field=field_or_fields_container, meshed_region=self, **kwargs) kwargs.pop("notebook", None) return pl.show_figure(**kwargs) diff --git a/src/ansys/dpf/core/plotter.py b/src/ansys/dpf/core/plotter.py index dc023fa669..b8854df760 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -228,11 +228,14 @@ def add_field( except dpf_errors.DpfVersionNotSupported: name = "" categories = True - kwargs.setdefault("stitle", f"{name}") - kwargs = self._set_scalar_bar_title(kwargs) - kwargs["scalar_bar_args"]["n_labels"] = len(set(field.data)) - kwargs["scalar_bar_args"]["fmt"] = "%.0f" - kwargs["cmap"] = "brg" + kwargs.setdefault("scalar_bar_args", { + "title": name, + "n_labels": 0, + }) + 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") diff --git a/tests/test_plotter.py b/tests/test_plotter.py index a696416e7b..61daf00442 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -70,6 +70,7 @@ def test_mesh_property_field_plot(multishells): model = core.Model(multishells) mesh = model.metadata.meshed_region pf = mesh.property_field(property_name="mat") + pf.name = "mat_id" mesh.plot(pf) From 64c9f82a9f2ab1c10c54d3ba1df49134ff62d631 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 14 May 2024 17:29:40 +0200 Subject: [PATCH 6/8] Fix retro Signed-off-by: paul.profizi --- tests/test_plotter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_plotter.py b/tests/test_plotter.py index 61daf00442..a696416e7b 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -70,7 +70,6 @@ def test_mesh_property_field_plot(multishells): model = core.Model(multishells) mesh = model.metadata.meshed_region pf = mesh.property_field(property_name="mat") - pf.name = "mat_id" mesh.plot(pf) From 474f48c5feb603d6a6ebf3be1e0c0790afb32367 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 14 May 2024 17:45:20 +0200 Subject: [PATCH 7/8] Add PropertyField.plot Signed-off-by: paul.profizi --- src/ansys/dpf/core/property_field.py | 53 ++++++++++++++++++++++++++++ tests/test_plotter.py | 8 +++++ 2 files changed, 61 insertions(+) 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/test_plotter.py b/tests/test_plotter.py index a696416e7b..49083222f0 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -73,6 +73,14 @@ def test_mesh_property_field_plot(multishells): 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) From c8eb2b31247979b78cc8f0a781411289d1e17e51 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 22 Jul 2024 10:00:25 +0200 Subject: [PATCH 8/8] WIP Signed-off-by: paul.profizi --- conftest.py | 4 ++-- src/ansys/dpf/core/plotter.py | 2 ++ tests/conftest.py | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) 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/plotter.py b/src/ansys/dpf/core/plotter.py index b8854df760..2e00d2211e 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -232,6 +232,8 @@ def add_field( "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]) 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