From 3058ac6f47736cbfb9e8d134eeac75d9223f763b Mon Sep 17 00:00:00 2001 From: PProfizi Date: Tue, 18 Feb 2025 16:27:20 +0100 Subject: [PATCH 01/12] Add EmptyMeshPlottingError --- src/ansys/dpf/core/errors.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ansys/dpf/core/errors.py b/src/ansys/dpf/core/errors.py index fb7332c57c0..63a0ff66a88 100644 --- a/src/ansys/dpf/core/errors.py +++ b/src/ansys/dpf/core/errors.py @@ -41,6 +41,12 @@ ``fields_container[index]``. """ +_EMPTY_MESH_PLOTTING_MSG = """" +The mesh support is empty. +Either provide one to the plot function called, or use MeshedRegion.plot +and provide the current data as parameter. +""" + class DpfValueError(ValueError): """Error raised when a specific DPF error value must be defined.""" @@ -80,6 +86,13 @@ def __init__(self, msg=_FIELD_CONTAINER_PLOTTING_MSG): ValueError.__init__(self, msg) +class EmptyMeshPlottingError(ValueError): + """Error raised when attempting to plot data with no mesh.""" + + def __init__(self, msg=_EMPTY_MESH_PLOTTING_MSG): + ValueError.__init__(self, msg) + + class InvalidANSYSVersionError(RuntimeError): """Error raised when the Ansys version is invalid.""" From 15687170cb050513febac0968af89b164dd99109 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Tue, 18 Feb 2025 16:35:56 +0100 Subject: [PATCH 02/12] Raise EmptyMeshPlottingError in Plotter.plot_contour when mesh is empty --- 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 b7a7558bcbe..4588c6f8b61 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -892,6 +892,8 @@ def plot_contour( mesh = meshed_region else: mesh = self._mesh + if mesh.elements.n_elements == 0 and mesh.nodes.n_nodes == 0 and mesh.faces.n_faces == 0: + raise dpf_errors.EmptyMeshPlottingError # get mesh scoping location = None From 441dacb33cc0eb40629274dd6a2de09f82981bf7 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Tue, 18 Feb 2025 16:36:58 +0100 Subject: [PATCH 03/12] Improve docstring of Plotter.plot_contour --- src/ansys/dpf/core/plotter.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/ansys/dpf/core/plotter.py b/src/ansys/dpf/core/plotter.py index 4588c6f8b61..9309389a0be 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -47,6 +47,8 @@ from ansys.dpf.core.nodes import Node, Nodes if TYPE_CHECKING: # pragma: no cover + from ansys.dpf.core import Operator, Result + from ansys.dpf.core.fields_container import FieldsContainer from ansys.dpf.core.meshed_region import MeshedRegion @@ -831,31 +833,30 @@ def plot_chart(fields_container, off_screen=False, screenshot=None): def plot_contour( self, - field_or_fields_container, - shell_layers=None, - meshed_region=None, - deform_by=None, - scale_factor=1.0, + field_or_fields_container: Union[Field, FieldsContainer], + shell_layers: eshell_layers = None, + meshed_region: MeshedRegion = None, + deform_by: Union[Field, Result, Operator] = None, + scale_factor: float = 1.0, **kwargs, ): """Plot the contour result on its mesh support. You cannot plot a fields container containing results at several - time steps. + time steps. Use :func:`FieldsContainer.animate` instead. Parameters ---------- - field_or_fields_container : dpf.core.Field or dpf.core.FieldsContainer + field_or_fields_container: Field or field container that contains the result to plot. - shell_layers : core.shell_layers, optional + shell_layers: Enum used to set the shell layers if the model to plot - contains shell elements. - deform_by : Field, Result, Operator, optional + contains shell elements. Defaults to the top layer. + deform_by: 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: Additional keyword arguments for the plotter. For more information, see ``help(pyvista.plot)``. """ From c65c1fee11d5fa6182c86dd4d22604f4e300c522 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Tue, 18 Feb 2025 16:40:51 +0100 Subject: [PATCH 04/12] Add meshed_region parameter to field.plot() --- src/ansys/dpf/core/field.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ansys/dpf/core/field.py b/src/ansys/dpf/core/field.py index f98b34fe8dc..516e746115a 100644 --- a/src/ansys/dpf/core/field.py +++ b/src/ansys/dpf/core/field.py @@ -500,7 +500,9 @@ def to_nodal(self): op.inputs.connect(self) return op.outputs.field() - def plot(self, shell_layers=None, deform_by=None, scale_factor=1.0, **kwargs): + def plot( + self, shell_layers=None, deform_by=None, scale_factor=1.0, meshed_region=None, **kwargs + ): """Plot the field or fields container on the mesh support if it exists. Warning @@ -536,7 +538,9 @@ def plot(self, shell_layers=None, deform_by=None, scale_factor=1.0, **kwargs): """ from ansys.dpf.core.plotter import Plotter - pl = Plotter(self.meshed_region, **kwargs) + if meshed_region is None: + meshed_region = self.meshed_region + pl = Plotter(meshed_region, **kwargs) return pl.plot_contour( self, shell_layers, From ef56179813d4d56a1dc895c1ab27c981cf09f85f Mon Sep 17 00:00:00 2001 From: PProfizi Date: Tue, 18 Feb 2025 16:45:11 +0100 Subject: [PATCH 05/12] Update docstring of field.plot() --- src/ansys/dpf/core/field.py | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/ansys/dpf/core/field.py b/src/ansys/dpf/core/field.py index 516e746115a..ee121a48fbf 100644 --- a/src/ansys/dpf/core/field.py +++ b/src/ansys/dpf/core/field.py @@ -22,11 +22,21 @@ """Field.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + import numpy as np from ansys import dpf from ansys.dpf.core import dimensionality, errors, meshed_region, scoping, time_freq_support -from ansys.dpf.core.common import _get_size_of_list, locations, natures, types +from ansys.dpf.core.common import ( + _get_size_of_list, + locations, + natures, + shell_layers as eshell_layers, + types, +) from ansys.dpf.core.field_base import _FieldBase, _LocalFieldBase from ansys.dpf.core.field_definition import FieldDefinition from ansys.dpf.gate import ( @@ -37,6 +47,11 @@ field_grpcapi, ) +if TYPE_CHECKING: + from ansys.dpf.core.dpf_operator import Operator + from ansys.dpf.core.meshed_region import MeshedRegion + from ansys.dpf.core.results import Result + class Field(_FieldBase): """Represents the main simulation data container. @@ -501,7 +516,12 @@ def to_nodal(self): return op.outputs.field() def plot( - self, shell_layers=None, deform_by=None, scale_factor=1.0, meshed_region=None, **kwargs + self, + shell_layers: eshell_layers = None, + deform_by: Union[Field, Result, Operator] = None, + scale_factor: float = 1.0, + meshed_region: MeshedRegion = None, + **kwargs, ): """Plot the field or fields container on the mesh support if it exists. @@ -524,15 +544,14 @@ def plot( Parameters ---------- - shell_layers : shell_layers, optional + shell_layers: Enum used to set the shell layers if the model to plot - contains shell elements. The default is ``None``. - deform_by : Field, Result, Operator, optional + contains shell elements. Defaults to the top layer. + deform_by: 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: Additional keyword arguments for the plotter. For additional keyword arguments, see ``help(pyvista.plot)``. """ From 8ccf16c20b2d575a4baaf3c6dbd0c8adfda97578 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Tue, 18 Feb 2025 16:50:58 +0100 Subject: [PATCH 06/12] Add test --- tests/test_plotter.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_plotter.py b/tests/test_plotter.py index ce39cc31731..da48f04ac9a 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -252,6 +252,16 @@ def test_field_shell_plot_scoping_elemental(multishells): f.plot(shell_layers=core.shell_layers.top) +@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista") +def test_field_plot_raise_empty_mesh(simple_bar): + ds = core.DataSources(simple_bar) + stream_prov = core.operators.metadata.streams_provider(data_sources=ds) + result_op = core.operators.result.displacement(streams_container=stream_prov) + field = result_op.outputs.fields_container()[0] + with pytest.raises(dpf_errors.EmptyMeshPlottingError): + field.plot() + + @pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista") def test_throw_shell_layers(multishells): model = core.Model(multishells) From 3ffa89cf84311df8c048fc6a9ba45cd68edabde9 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Tue, 18 Feb 2025 17:37:57 +0100 Subject: [PATCH 07/12] Update docstrings --- src/ansys/dpf/core/field.py | 2 ++ src/ansys/dpf/core/plotter.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/ansys/dpf/core/field.py b/src/ansys/dpf/core/field.py index ee121a48fbf..6373bd007d1 100644 --- a/src/ansys/dpf/core/field.py +++ b/src/ansys/dpf/core/field.py @@ -551,6 +551,8 @@ def plot( Used to deform the plotted mesh. Must output a 3D vector field. scale_factor: Scaling factor to apply when warping the mesh. + meshed_region: + Mesh to plot the field on. **kwargs: Additional keyword arguments for the plotter. For additional keyword arguments, see ``help(pyvista.plot)``. diff --git a/src/ansys/dpf/core/plotter.py b/src/ansys/dpf/core/plotter.py index 9309389a0be..dbf2d08faea 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -852,6 +852,8 @@ def plot_contour( shell_layers: Enum used to set the shell layers if the model to plot contains shell elements. Defaults to the top layer. + meshed_region: + Mesh to plot the data on. deform_by: Used to deform the plotted mesh. Must output a 3D vector field. scale_factor: From 76693f1e7743893167e6288d84a4a3ab52ee79a4 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Wed, 19 Feb 2025 09:36:34 +0100 Subject: [PATCH 08/12] Catch server error on support type unavailable on Field.meshed_region getter --- src/ansys/dpf/core/field.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/ansys/dpf/core/field.py b/src/ansys/dpf/core/field.py index 6373bd007d1..85219dcce26 100644 --- a/src/ansys/dpf/core/field.py +++ b/src/ansys/dpf/core/field.py @@ -46,6 +46,7 @@ field_capi, field_grpcapi, ) +from ansys.dpf.gate.errors import DPFServerException if TYPE_CHECKING: from ansys.dpf.core.dpf_operator import Operator @@ -716,7 +717,7 @@ def field_definition(self): def field_definition(self, value): return self._set_field_definition(value) - def _get_meshed_region(self): + def _get_meshed_region(self) -> MeshedRegion: """Retrieve the meshed region. Returns @@ -724,8 +725,13 @@ def _get_meshed_region(self): :class:`ansys.dpf.core.meshed_region.MeshedRegion` """ + try: + support = self._api.csfield_get_support_as_meshed_region(self) + except DPFServerException as e: + if "the field doesn't have this support type" in e.msg: + support = None return meshed_region.MeshedRegion( - mesh=self._api.csfield_get_support_as_meshed_region(self), + mesh=support, server=self._server, ) @@ -761,7 +767,7 @@ def time_freq_support(self, value): self._api.csfield_set_support(self, value) @property - def meshed_region(self): + def meshed_region(self) -> MeshedRegion: """Meshed region of the field. Return @@ -772,8 +778,8 @@ def meshed_region(self): return self._get_meshed_region() @meshed_region.setter - def meshed_region(self, value): - self._set_support(value, "MESHED_REGION") + def meshed_region(self, value: MeshedRegion): + self._set_support(support=value, support_type="MESHED_REGION") def __add__(self, field_b): """Add two fields. From ea1912c7cdaa041678ddca4b959360e9bae374ef Mon Sep 17 00:00:00 2001 From: PProfizi Date: Wed, 19 Feb 2025 10:20:56 +0100 Subject: [PATCH 09/12] Fix error conditional --- src/ansys/dpf/core/field.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ansys/dpf/core/field.py b/src/ansys/dpf/core/field.py index 85219dcce26..cf264cd9047 100644 --- a/src/ansys/dpf/core/field.py +++ b/src/ansys/dpf/core/field.py @@ -728,8 +728,10 @@ def _get_meshed_region(self) -> MeshedRegion: try: support = self._api.csfield_get_support_as_meshed_region(self) except DPFServerException as e: - if "the field doesn't have this support type" in e.msg: + if "the field doesn't have this support type" in str(e): support = None + else: + raise e return meshed_region.MeshedRegion( mesh=support, server=self._server, From 3fd97ad41ef05d37b93aa3d7591e6aa73d5bdf79 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Wed, 19 Feb 2025 10:40:40 +0100 Subject: [PATCH 10/12] Add MeshedRegion.is_empty() query handling retro for faces --- src/ansys/dpf/core/meshed_region.py | 14 +++++++++++++- src/ansys/dpf/core/plotter.py | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/ansys/dpf/core/meshed_region.py b/src/ansys/dpf/core/meshed_region.py index f17bdf64673..6818645f736 100644 --- a/src/ansys/dpf/core/meshed_region.py +++ b/src/ansys/dpf/core/meshed_region.py @@ -35,7 +35,7 @@ from ansys.dpf.core import field, property_field, scoping, server as server_module from ansys.dpf.core.cache import class_handling_cache -from ansys.dpf.core.check_version import server_meet_version, version_requires +from ansys.dpf.core.check_version import meets_version, server_meet_version, version_requires from ansys.dpf.core.common import ( locations, nodal_properties, @@ -701,3 +701,15 @@ def field_of_properties(self, property_name): # Not sure we go through here since the only datatype not int is coordinates, # which is already dealt with previously. return field.Field(server=self._server, field=field_out) + + def is_empty(self) -> bool: + """Whether the mesh is empty. + + A mesh is considered empty when it has zero element, zero face, and zero node. + """ + no_faces = True + if meets_version(self._server, "7.0"): + no_faces = self.faces.n_faces == 0 + no_elements = self.elements.n_elements == 0 + no_nodes = self.nodes.n_nodes == 0 + return no_nodes and no_faces and no_elements diff --git a/src/ansys/dpf/core/plotter.py b/src/ansys/dpf/core/plotter.py index dbf2d08faea..c881228ad60 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -895,7 +895,7 @@ def plot_contour( mesh = meshed_region else: mesh = self._mesh - if mesh.elements.n_elements == 0 and mesh.nodes.n_nodes == 0 and mesh.faces.n_faces == 0: + if mesh.is_empty(): raise dpf_errors.EmptyMeshPlottingError # get mesh scoping From 4b3ce57a447ff06900b59ddccc8216a7f741e82b Mon Sep 17 00:00:00 2001 From: PProfizi Date: Wed, 19 Feb 2025 11:25:25 +0100 Subject: [PATCH 11/12] Add MeshedRegion.is_empty() query handling retro for faces --- src/ansys/dpf/core/meshed_region.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/dpf/core/meshed_region.py b/src/ansys/dpf/core/meshed_region.py index 6818645f736..193798edc69 100644 --- a/src/ansys/dpf/core/meshed_region.py +++ b/src/ansys/dpf/core/meshed_region.py @@ -708,7 +708,7 @@ def is_empty(self) -> bool: A mesh is considered empty when it has zero element, zero face, and zero node. """ no_faces = True - if meets_version(self._server, "7.0"): + if meets_version(self._server.version, "7.0"): no_faces = self.faces.n_faces == 0 no_elements = self.elements.n_elements == 0 no_nodes = self.nodes.n_nodes == 0 From 3c89eae7f800c13604e5e5035ee29fe2385a9f49 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Wed, 19 Feb 2025 12:20:16 +0100 Subject: [PATCH 12/12] Ignore type check imports for coverage --- src/ansys/dpf/core/field.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/dpf/core/field.py b/src/ansys/dpf/core/field.py index cf264cd9047..3af42e6ef32 100644 --- a/src/ansys/dpf/core/field.py +++ b/src/ansys/dpf/core/field.py @@ -48,7 +48,7 @@ ) from ansys.dpf.gate.errors import DPFServerException -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: nocover from ansys.dpf.core.dpf_operator import Operator from ansys.dpf.core.meshed_region import MeshedRegion from ansys.dpf.core.results import Result