Skip to content

Commit 640780c

Browse files
authored
Raise an explicit error on plot_contour with empty mesh (#2096)
* Add EmptyMeshPlottingError * Raise EmptyMeshPlottingError in Plotter.plot_contour when mesh is empty * Improve docstring of Plotter.plot_contour * Add meshed_region parameter to field.plot() * Update docstring of field.plot() * Add test * Update docstrings * Catch server error on support type unavailable on Field.meshed_region getter * Fix error conditional * Add MeshedRegion.is_empty() query handling retro for faces * Add MeshedRegion.is_empty() query handling retro for faces * Ignore type check imports for coverage
1 parent e2ed560 commit 640780c

File tree

5 files changed

+103
-30
lines changed

5 files changed

+103
-30
lines changed

src/ansys/dpf/core/errors.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@
4141
``fields_container[index]``.
4242
"""
4343

44+
_EMPTY_MESH_PLOTTING_MSG = """"
45+
The mesh support is empty.
46+
Either provide one to the plot function called, or use MeshedRegion.plot
47+
and provide the current data as parameter.
48+
"""
49+
4450

4551
class DpfValueError(ValueError):
4652
"""Error raised when a specific DPF error value must be defined."""
@@ -80,6 +86,13 @@ def __init__(self, msg=_FIELD_CONTAINER_PLOTTING_MSG):
8086
ValueError.__init__(self, msg)
8187

8288

89+
class EmptyMeshPlottingError(ValueError):
90+
"""Error raised when attempting to plot data with no mesh."""
91+
92+
def __init__(self, msg=_EMPTY_MESH_PLOTTING_MSG):
93+
ValueError.__init__(self, msg)
94+
95+
8396
class InvalidANSYSVersionError(RuntimeError):
8497
"""Error raised when the Ansys version is invalid."""
8598

src/ansys/dpf/core/field.py

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,21 @@
2222

2323
"""Field."""
2424

25+
from __future__ import annotations
26+
27+
from typing import TYPE_CHECKING
28+
2529
import numpy as np
2630

2731
from ansys import dpf
2832
from ansys.dpf.core import dimensionality, errors, meshed_region, scoping, time_freq_support
29-
from ansys.dpf.core.common import _get_size_of_list, locations, natures, types
33+
from ansys.dpf.core.common import (
34+
_get_size_of_list,
35+
locations,
36+
natures,
37+
shell_layers as eshell_layers,
38+
types,
39+
)
3040
from ansys.dpf.core.field_base import _FieldBase, _LocalFieldBase
3141
from ansys.dpf.core.field_definition import FieldDefinition
3242
from ansys.dpf.gate import (
@@ -36,6 +46,12 @@
3646
field_capi,
3747
field_grpcapi,
3848
)
49+
from ansys.dpf.gate.errors import DPFServerException
50+
51+
if TYPE_CHECKING: # pragma: nocover
52+
from ansys.dpf.core.dpf_operator import Operator
53+
from ansys.dpf.core.meshed_region import MeshedRegion
54+
from ansys.dpf.core.results import Result
3955

4056

4157
class Field(_FieldBase):
@@ -500,7 +516,14 @@ def to_nodal(self):
500516
op.inputs.connect(self)
501517
return op.outputs.field()
502518

503-
def plot(self, shell_layers=None, deform_by=None, scale_factor=1.0, **kwargs):
519+
def plot(
520+
self,
521+
shell_layers: eshell_layers = None,
522+
deform_by: Union[Field, Result, Operator] = None,
523+
scale_factor: float = 1.0,
524+
meshed_region: MeshedRegion = None,
525+
**kwargs,
526+
):
504527
"""Plot the field or fields container on the mesh support if it exists.
505528
506529
Warning
@@ -522,21 +545,24 @@ def plot(self, shell_layers=None, deform_by=None, scale_factor=1.0, **kwargs):
522545
523546
Parameters
524547
----------
525-
shell_layers : shell_layers, optional
548+
shell_layers:
526549
Enum used to set the shell layers if the model to plot
527-
contains shell elements. The default is ``None``.
528-
deform_by : Field, Result, Operator, optional
550+
contains shell elements. Defaults to the top layer.
551+
deform_by:
529552
Used to deform the plotted mesh. Must output a 3D vector field.
530-
Defaults to None.
531-
scale_factor : float, optional
532-
Scaling factor to apply when warping the mesh. Defaults to 1.0.
533-
**kwargs : optional
553+
scale_factor:
554+
Scaling factor to apply when warping the mesh.
555+
meshed_region:
556+
Mesh to plot the field on.
557+
**kwargs:
534558
Additional keyword arguments for the plotter. For additional keyword
535559
arguments, see ``help(pyvista.plot)``.
536560
"""
537561
from ansys.dpf.core.plotter import Plotter
538562

539-
pl = Plotter(self.meshed_region, **kwargs)
563+
if meshed_region is None:
564+
meshed_region = self.meshed_region
565+
pl = Plotter(meshed_region, **kwargs)
540566
return pl.plot_contour(
541567
self,
542568
shell_layers,
@@ -691,16 +717,23 @@ def field_definition(self):
691717
def field_definition(self, value):
692718
return self._set_field_definition(value)
693719

694-
def _get_meshed_region(self):
720+
def _get_meshed_region(self) -> MeshedRegion:
695721
"""Retrieve the meshed region.
696722
697723
Returns
698724
-------
699725
:class:`ansys.dpf.core.meshed_region.MeshedRegion`
700726
701727
"""
728+
try:
729+
support = self._api.csfield_get_support_as_meshed_region(self)
730+
except DPFServerException as e:
731+
if "the field doesn't have this support type" in str(e):
732+
support = None
733+
else:
734+
raise e
702735
return meshed_region.MeshedRegion(
703-
mesh=self._api.csfield_get_support_as_meshed_region(self),
736+
mesh=support,
704737
server=self._server,
705738
)
706739

@@ -736,7 +769,7 @@ def time_freq_support(self, value):
736769
self._api.csfield_set_support(self, value)
737770

738771
@property
739-
def meshed_region(self):
772+
def meshed_region(self) -> MeshedRegion:
740773
"""Meshed region of the field.
741774
742775
Return
@@ -747,8 +780,8 @@ def meshed_region(self):
747780
return self._get_meshed_region()
748781

749782
@meshed_region.setter
750-
def meshed_region(self, value):
751-
self._set_support(value, "MESHED_REGION")
783+
def meshed_region(self, value: MeshedRegion):
784+
self._set_support(support=value, support_type="MESHED_REGION")
752785

753786
def __add__(self, field_b):
754787
"""Add two fields.

src/ansys/dpf/core/meshed_region.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
from ansys.dpf.core import field, property_field, scoping, server as server_module
3737
from ansys.dpf.core.cache import class_handling_cache
38-
from ansys.dpf.core.check_version import server_meet_version, version_requires
38+
from ansys.dpf.core.check_version import meets_version, server_meet_version, version_requires
3939
from ansys.dpf.core.common import (
4040
locations,
4141
nodal_properties,
@@ -701,3 +701,15 @@ def field_of_properties(self, property_name):
701701
# Not sure we go through here since the only datatype not int is coordinates,
702702
# which is already dealt with previously.
703703
return field.Field(server=self._server, field=field_out)
704+
705+
def is_empty(self) -> bool:
706+
"""Whether the mesh is empty.
707+
708+
A mesh is considered empty when it has zero element, zero face, and zero node.
709+
"""
710+
no_faces = True
711+
if meets_version(self._server.version, "7.0"):
712+
no_faces = self.faces.n_faces == 0
713+
no_elements = self.elements.n_elements == 0
714+
no_nodes = self.nodes.n_nodes == 0
715+
return no_nodes and no_faces and no_elements

src/ansys/dpf/core/plotter.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
from ansys.dpf.core.nodes import Node, Nodes
4848

4949
if TYPE_CHECKING: # pragma: no cover
50+
from ansys.dpf.core import Operator, Result
51+
from ansys.dpf.core.fields_container import FieldsContainer
5052
from ansys.dpf.core.meshed_region import MeshedRegion
5153

5254

@@ -851,31 +853,32 @@ def plot_chart(fields_container, off_screen=False, screenshot=None):
851853

852854
def plot_contour(
853855
self,
854-
field_or_fields_container,
855-
shell_layers=None,
856-
meshed_region=None,
857-
deform_by=None,
858-
scale_factor=1.0,
856+
field_or_fields_container: Union[Field, FieldsContainer],
857+
shell_layers: eshell_layers = None,
858+
meshed_region: MeshedRegion = None,
859+
deform_by: Union[Field, Result, Operator] = None,
860+
scale_factor: float = 1.0,
859861
**kwargs,
860862
):
861863
"""Plot the contour result on its mesh support.
862864
863865
You cannot plot a fields container containing results at several
864-
time steps.
866+
time steps. Use :func:`FieldsContainer.animate` instead.
865867
866868
Parameters
867869
----------
868-
field_or_fields_container : dpf.core.Field or dpf.core.FieldsContainer
870+
field_or_fields_container:
869871
Field or field container that contains the result to plot.
870-
shell_layers : core.shell_layers, optional
872+
shell_layers:
871873
Enum used to set the shell layers if the model to plot
872-
contains shell elements.
873-
deform_by : Field, Result, Operator, optional
874+
contains shell elements. Defaults to the top layer.
875+
meshed_region:
876+
Mesh to plot the data on.
877+
deform_by:
874878
Used to deform the plotted mesh. Must output a 3D vector field.
875-
Defaults to None.
876-
scale_factor : float, optional
877-
Scaling factor to apply when warping the mesh. Defaults to 1.0.
878-
**kwargs : optional
879+
scale_factor:
880+
Scaling factor to apply when warping the mesh.
881+
**kwargs:
879882
Additional keyword arguments for the plotter. For more information,
880883
see ``help(pyvista.plot)``.
881884
"""
@@ -912,6 +915,8 @@ def plot_contour(
912915
mesh = meshed_region
913916
else:
914917
mesh = self._mesh
918+
if mesh.is_empty():
919+
raise dpf_errors.EmptyMeshPlottingError
915920

916921
# get mesh scoping
917922
location = None

tests/test_plotter.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,16 @@ def test_field_shell_plot_scoping_elemental(multishells):
252252
f.plot(shell_layers=core.shell_layers.top)
253253

254254

255+
@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista")
256+
def test_field_plot_raise_empty_mesh(simple_bar):
257+
ds = core.DataSources(simple_bar)
258+
stream_prov = core.operators.metadata.streams_provider(data_sources=ds)
259+
result_op = core.operators.result.displacement(streams_container=stream_prov)
260+
field = result_op.outputs.fields_container()[0]
261+
with pytest.raises(dpf_errors.EmptyMeshPlottingError):
262+
field.plot()
263+
264+
255265
@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista")
256266
def test_plotter_plot_contour_throw_shell_layers(multishells):
257267
model = core.Model(multishells)

0 commit comments

Comments
 (0)