From 8adef1a99499bf6aea4f46ac02e55fd72b444638 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Tue, 17 Jun 2025 16:29:32 +0200 Subject: [PATCH 01/16] Fix typehint for Nodes.scoping and Elements.scoping --- src/ansys/dpf/core/elements.py | 6 +++++- src/ansys/dpf/core/nodes.py | 11 +++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/ansys/dpf/core/elements.py b/src/ansys/dpf/core/elements.py index 70c9c478d62..4c942dcda00 100644 --- a/src/ansys/dpf/core/elements.py +++ b/src/ansys/dpf/core/elements.py @@ -25,6 +25,7 @@ from __future__ import annotations from enum import Enum +from typing import TYPE_CHECKING import numpy as np @@ -34,6 +35,9 @@ from ansys.dpf.core.element_descriptor import ElementDescriptor from ansys.dpf.gate import integral_types +if TYPE_CHECKING: + from ansys.dpf.core.scoping import Scoping + class Element: """ @@ -492,7 +496,7 @@ def __get_element(self, elementindex=None, elementid=None): return Element(self._mesh, elementid, elementindex, nodesOut) @property - def scoping(self) -> scoping.Scoping: + def scoping(self) -> Scoping: """ Scoping of the elements. diff --git a/src/ansys/dpf/core/nodes.py b/src/ansys/dpf/core/nodes.py index 668922148ea..303b494cfbe 100644 --- a/src/ansys/dpf/core/nodes.py +++ b/src/ansys/dpf/core/nodes.py @@ -22,11 +22,18 @@ """Nodes.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + import numpy as np from ansys.dpf.core.check_version import version_requires from ansys.dpf.core.common import locations, nodal_properties +if TYPE_CHECKING: + from ansys.dpf.core.scoping import Scoping + class Node: """ @@ -194,13 +201,13 @@ def __get_node(self, nodeindex=None, nodeid=None): return Node(self._mesh, nodeid, nodeindex, node_coordinates) @property - def scoping(self): + def scoping(self) -> Scoping: """ Scoping of the nodes. Returns ------- - scoping : Scoping + scoping: Scoping of the nodes. Examples From f5118decf8a038cdba331178da881d02c7731dcb Mon Sep 17 00:00:00 2001 From: PProfizi Date: Tue, 17 Jun 2025 16:40:11 +0200 Subject: [PATCH 02/16] Fix typehint in plotter.py --- src/ansys/dpf/core/plotter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ansys/dpf/core/plotter.py b/src/ansys/dpf/core/plotter.py index 2978046899f..8c2cca83212 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -48,6 +48,7 @@ if TYPE_CHECKING: # pragma: no cover from ansys.dpf.core import Operator, Result + from ansys.dpf.core.field import Field from ansys.dpf.core.fields_container import FieldsContainer from ansys.dpf.core.meshed_region import MeshedRegion From 92d75087bb9164013df88dd92c1364a6d92cac95 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Tue, 17 Jun 2025 19:28:02 +0200 Subject: [PATCH 03/16] Add DpfPlotter.add_scoping and scoping.plot --- src/ansys/dpf/core/plotter.py | 81 +++++++++++++++++++++++++++++++++++ src/ansys/dpf/core/scoping.py | 42 ++++++++++++++++++ 2 files changed, 123 insertions(+) diff --git a/src/ansys/dpf/core/plotter.py b/src/ansys/dpf/core/plotter.py index 8c2cca83212..ae9bf6966ea 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -36,6 +36,7 @@ import tempfile from typing import TYPE_CHECKING, List, Union import warnings +from xml.etree.ElementInclude import include import numpy as np @@ -234,6 +235,37 @@ def get_label_at_grid_point(index): ) return label_actors + def add_scoping( + self, + scoping: dpf.core.Scoping, + mesh: dpf.core.MeshedRegion, + show_mesh: bool = False, + **kwargs, + ): + # Add the mesh to the scene with low opacity + if show_mesh: + self._plotter.add_mesh(mesh=mesh.grid, opacity=0.3) + + scoping_mesh = None + + # If the scoping is nodal, use the add_points_label method + if scoping.location == locations.nodal: + node_indexes = np.where(np.isin(mesh.nodes.scoping.ids, scoping.ids))[0] + # grid_points = [mesh.grid.points[node_index] for node_index in node_indexes] + scoping_mesh = mesh.grid.extract_points(ind=node_indexes, include_cells=False) + # If the scoping is elemental, extract their edges and use active scalars to color them + if scoping.location == locations.elemental: + element_indexes = np.where(np.isin(mesh.elements.scoping.ids, scoping.ids))[0] + scoping_mesh = mesh.grid.extract_cells(ind=element_indexes) + + # If the scoping is faces, extract their edges and use active scalars to color them + if scoping.location == locations.faces: + raise NotImplementedError("Cannot plot a face scoping.") + + # Filter kwargs + kwargs_in = _sort_supported_kwargs(bound_method=self._plotter.add_mesh, **kwargs) + self._plotter.add_mesh(mesh=scoping_mesh, **kwargs_in) + def add_field( self, field, @@ -689,6 +721,55 @@ def add_field( **kwargs, ) + def add_scoping( + self, + scoping: dpf.core.Scoping, + mesh: dpf.core.MeshedRegion, + show_mesh: bool = False, + **kwargs, + ): + """Add a scoping to the plotter. + + A mesh is required to translate the scoping into entities to plot. + Tou can plot the mesh along with the scoping entities using ``show_mesh``. + + Parameters + ---------- + scoping: + Scoping with a mesh-based location and IDs of entities to plot. + mesh: + ``MeshedRegion`` to plot the field on. + show_mesh: + Whether to show the mesh along with the scoping entities. + **kwargs : optional + Additional keyword arguments for the plotter. More information + are available at :func:`pyvista.plot`. + + Examples + -------- + >>> from ansys.dpf import core as dpf + >>> from ansys.dpf.core import examples + >>> model = dpf.Model(examples.download_cfx_mixing_elbow()) + >>> mesh = model.metadata.meshed_region + >>> node_scoping = dpf.Scoping( + ... location=dpf.locations.nodal, + ... ids=mesh.nodes.scoping.ids[0:100] + ...) + >>> element_scoping = dpf.Scoping( + ... location=dpf.locations.elemental, + ... ids=mesh.elements.scoping.ids[0:100] + ...) + >>> from ansys.dpf.core.plotter import DpfPlotter + >>> plt = DpfPlotter() + >>> plt.add_scoping(node_scoping, mesh, show_mesh=True, color="red") + >>> plt.add_scoping(element_scoping, mesh, color="green") + >>> plt.show_figure() + + """ + self._internal_plotter.add_scoping( + scoping=scoping, mesh=mesh, show_mesh=show_mesh, **kwargs + ) + def show_figure(self, **kwargs): """Plot the figure built by the plotter object. diff --git a/src/ansys/dpf/core/scoping.py b/src/ansys/dpf/core/scoping.py index 4a2457e1b91..597d42175ef 100644 --- a/src/ansys/dpf/core/scoping.py +++ b/src/ansys/dpf/core/scoping.py @@ -491,6 +491,48 @@ def as_local_scoping(self): """ # noqa: E501 return _LocalScoping(self) + def plot(self, mesh, show_mesh: bool = False, **kwargs): + """Plot the entities of the mesh corresponding to the scoping. + + Parameters + ---------- + mesh: + Mesh to use to translate the scoping into mesh entities. + show_mesh: + Whether to also show the mesh with low opacity. + **kwargs : optional + Additional keyword arguments for the plotter. More information + are available at :func:`pyvista.plot`. + + Returns + ------- + (cpos, image): + Returns what the pyvista.show() method returns based on arguments. + + Examples + -------- + >>> from ansys.dpf import core as dpf + >>> from ansys.dpf.core import examples + >>> model = dpf.Model(examples.download_cfx_mixing_elbow()) + >>> mesh = model.metadata.meshed_region + >>> node_scoping = dpf.Scoping( + ... location=dpf.locations.nodal, + ... ids=mesh.nodes.scoping.ids[0:100] + ...) + >>> node_scoping.plot(mesh=mesh, color="red") + >>> element_scoping = dpf.Scoping( + ... location=dpf.locations.elemental, + ... ids=mesh.elements.scoping.ids[0:100] + ...) + >>> element_scoping.plot(mesh=mesh, color="green") + + """ + from ansys.dpf.core.plotter import DpfPlotter + + plt = DpfPlotter(**kwargs) + plt.add_scoping(scoping=self, mesh=mesh, show_mesh=show_mesh, **kwargs) + return plt.show_figure(**kwargs) + class _LocalScoping(Scoping): """Caches the internal data of the scoping so that it can be modified locally. From 9117dc3e6d1734d72ff885fa57592a8ada9486de Mon Sep 17 00:00:00 2001 From: PProfizi Date: Tue, 17 Jun 2025 19:46:36 +0200 Subject: [PATCH 04/16] Add ScopingsContainer.plot --- src/ansys/dpf/core/scopings_container.py | 73 ++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/ansys/dpf/core/scopings_container.py b/src/ansys/dpf/core/scopings_container.py index c076b9fdc64..99180664426 100644 --- a/src/ansys/dpf/core/scopings_container.py +++ b/src/ansys/dpf/core/scopings_container.py @@ -28,9 +28,16 @@ Contains classes associated to the DPF ScopingsContainer """ +from __future__ import annotations + +from typing import TYPE_CHECKING + from ansys.dpf.core import scoping from ansys.dpf.core.collection_base import CollectionBase +if TYPE_CHECKING: + from ansys.dpf.core import MeshedRegion, MeshesContainer + class ScopingsContainer(CollectionBase[scoping.Scoping]): """A class used to represent a ScopingsContainer which contains scopings split on a given space. @@ -125,3 +132,69 @@ def add_scoping(self, label_space, scoping): DPF scoping to add. """ return super()._add_entry(label_space, scoping) + + def plot( + self, + mesh: MeshedRegion | MeshesContainer, + show_mesh: bool = False, + colors: list[str] = None, + **kwargs, + ): + """Plot the entities of the mesh or meshes corresponding to the scopings. + + Parameters + ---------- + mesh: + Mesh or meshes to use to translate the scopings into mesh entities. + Associates each scoping to a mesh using labels if ``mesh`` is a collection of meshes. + show_mesh: + Whether to also show the mesh with low opacity. + colors: + List of colors to use for the scoping entities. + **kwargs : optional + Additional keyword arguments for the plotter. More information + are available at :func:`pyvista.plot`. + + Returns + ------- + (cpos, image): + Returns what the pyvista.show() method returns based on arguments. + + Examples + -------- + >>> from ansys.dpf import core as dpf + >>> from ansys.dpf.core import examples + >>> model = dpf.Model(examples.download_cfx_mixing_elbow()) + >>> mesh = model.metadata.meshed_region + >>> node_scoping_1 = dpf.Scoping( + ... location=dpf.locations.nodal, + ... ids=mesh.nodes.scoping.ids[0:100] + ...) + >>> node_scoping_2 = dpf.Scoping( + ... location=dpf.locations.nodal, + ... ids=mesh.nodes.scoping.ids[300:400] + ...) + >>> node_sc = dpf.ScopingsContainer() + >>> node_sc.add_label(label="scoping", default_value=1) + >>> node_sc.add_scoping(label_space={"scoping": 1}, scoping=node_scoping_1) + >>> node_sc.add_scoping(label_space={"scoping": 2}, scoping=node_scoping_2) + >>> node_sc.plot(mesh=mesh, show_mesh=True) + + """ + from itertools import cycle + + from ansys.dpf.core.plotter import DpfPlotter + + colors_cycle = cycle( + colors if colors else ["red", "blue", "green", "orange", "black", "yellow"] + ) + plt = DpfPlotter(**kwargs) + for i, scoping_i in enumerate(self): + plt.add_scoping( + scoping=scoping_i, + mesh=mesh, + color=next(colors_cycle), + show_mesh=show_mesh if i == 0 else False, + **kwargs, + ) + return plt.show_figure(**kwargs) From 41927bc2c5ec9447bfa65c9fec69d48fe9a27907 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Wed, 18 Jun 2025 09:05:02 +0200 Subject: [PATCH 05/16] Fix QA --- src/ansys/dpf/core/plotter.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ansys/dpf/core/plotter.py b/src/ansys/dpf/core/plotter.py index ae9bf6966ea..8a892f1d1e4 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -30,13 +30,11 @@ from __future__ import annotations -import os from pathlib import Path import sys import tempfile from typing import TYPE_CHECKING, List, Union import warnings -from xml.etree.ElementInclude import include import numpy as np From 8c775cc431f52a672375a59d9ee626019e181cf7 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Wed, 18 Jun 2025 09:08:20 +0200 Subject: [PATCH 06/16] Fix coverage --- src/ansys/dpf/core/elements.py | 2 +- src/ansys/dpf/core/nodes.py | 2 +- src/ansys/dpf/core/scopings_container.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ansys/dpf/core/elements.py b/src/ansys/dpf/core/elements.py index 4c942dcda00..2cff1b09988 100644 --- a/src/ansys/dpf/core/elements.py +++ b/src/ansys/dpf/core/elements.py @@ -35,7 +35,7 @@ from ansys.dpf.core.element_descriptor import ElementDescriptor from ansys.dpf.gate import integral_types -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ansys.dpf.core.scoping import Scoping diff --git a/src/ansys/dpf/core/nodes.py b/src/ansys/dpf/core/nodes.py index 303b494cfbe..11748960e0e 100644 --- a/src/ansys/dpf/core/nodes.py +++ b/src/ansys/dpf/core/nodes.py @@ -31,7 +31,7 @@ from ansys.dpf.core.check_version import version_requires from ansys.dpf.core.common import locations, nodal_properties -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ansys.dpf.core.scoping import Scoping diff --git a/src/ansys/dpf/core/scopings_container.py b/src/ansys/dpf/core/scopings_container.py index 99180664426..196447a1fe4 100644 --- a/src/ansys/dpf/core/scopings_container.py +++ b/src/ansys/dpf/core/scopings_container.py @@ -35,7 +35,7 @@ from ansys.dpf.core import scoping from ansys.dpf.core.collection_base import CollectionBase -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ansys.dpf.core import MeshedRegion, MeshesContainer From e5bbe129be9cb954d9c2c6cbad4ba1837d562952 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Wed, 18 Jun 2025 09:56:07 +0200 Subject: [PATCH 07/16] Add tests --- tests/test_plotter.py | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/test_plotter.py b/tests/test_plotter.py index ac3176134e6..3053510ad89 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -809,3 +809,47 @@ def test_plot_polyhedron(): # Plot the MeshedRegion mesh.plot() + + +@pytest.mark.skipif(not HAS_PYVISTA, reason="This test requires pyvista") +def test_plotter_add_scoping(fluent_mixing_elbow_steady_state): + mesh: core.MeshedRegion = core.operators.mesh.mesh_provider( + data_sources=fluent_mixing_elbow_steady_state() + ).eval() + node_scoping = core.Scoping(location=core.locations.nodal, ids=mesh.nodes.scoping.ids[0:100]) + element_scoping = core.Scoping( + location=core.locations.elemental, ids=mesh.elements.scoping.ids[0:100] + ) + plt = DpfPlotter() + plt.add_scoping(node_scoping, mesh, show_mesh=True, color="red") + plt.add_scoping(element_scoping, mesh, color="green") + plt.show_figure() + + +@pytest.mark.skipif(not HAS_PYVISTA, reason="This test requires pyvista") +def test_scoping_plot(fluent_mixing_elbow_steady_state): + mesh: core.MeshedRegion = core.operators.mesh.mesh_provider( + data_sources=fluent_mixing_elbow_steady_state() + ).eval() + node_scoping = core.Scoping(location=core.locations.nodal, ids=mesh.nodes.scoping.ids[0:100]) + node_scoping.plot(mesh=mesh, color="red") + element_scoping = core.Scoping( + location=core.locations.elemental, ids=mesh.elements.scoping.ids[0:100] + ) + element_scoping.plot(mesh=mesh, color="green") + + +@pytest.mark.skipif(not HAS_PYVISTA, reason="This test requires pyvista") +def test_scopingscontainer_plot(fluent_mixing_elbow_steady_state): + mesh: core.MeshedRegion = core.operators.mesh.mesh_provider( + data_sources=fluent_mixing_elbow_steady_state() + ).eval() + node_scoping_1 = core.Scoping(location=core.locations.nodal, ids=mesh.nodes.scoping.ids[0:100]) + node_scoping_2 = core.Scoping( + location=core.locations.nodal, ids=mesh.nodes.scoping.ids[300:400] + ) + node_sc = core.ScopingsContainer() + node_sc.add_label(label="scoping", default_value=1) + node_sc.add_scoping(label_space={"scoping": 1}, scoping=node_scoping_1) + node_sc.add_scoping(label_space={"scoping": 2}, scoping=node_scoping_2) + node_sc.plot(mesh=mesh, show_mesh=True) From b707bff65da74727f4c620e4ae82b5452d63c63d Mon Sep 17 00:00:00 2001 From: PProfizi Date: Wed, 18 Jun 2025 10:00:53 +0200 Subject: [PATCH 08/16] Add tests --- tests/test_plotter.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_plotter.py b/tests/test_plotter.py index 3053510ad89..328daabe700 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -825,6 +825,10 @@ def test_plotter_add_scoping(fluent_mixing_elbow_steady_state): plt.add_scoping(element_scoping, mesh, color="green") plt.show_figure() + face_scoping = core.Scoping(location=core.locations.faces, ids=mesh.faces.scoping.ids[0:100]) + with pytest.raises(NotImplementedError): + plt.add_scoping(face_scoping, mesh) + @pytest.mark.skipif(not HAS_PYVISTA, reason="This test requires pyvista") def test_scoping_plot(fluent_mixing_elbow_steady_state): @@ -840,7 +844,7 @@ def test_scoping_plot(fluent_mixing_elbow_steady_state): @pytest.mark.skipif(not HAS_PYVISTA, reason="This test requires pyvista") -def test_scopingscontainer_plot(fluent_mixing_elbow_steady_state): +def test_scopings_container_plot(fluent_mixing_elbow_steady_state): mesh: core.MeshedRegion = core.operators.mesh.mesh_provider( data_sources=fluent_mixing_elbow_steady_state() ).eval() From 87b5038e48b861020e7d13e92810379ce79ad19a Mon Sep 17 00:00:00 2001 From: PProfizi Date: Wed, 18 Jun 2025 10:02:22 +0200 Subject: [PATCH 09/16] Add example --- examples/06-plotting/08-plot_scopings.py | 80 ++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 examples/06-plotting/08-plot_scopings.py diff --git a/examples/06-plotting/08-plot_scopings.py b/examples/06-plotting/08-plot_scopings.py new file mode 100644 index 00000000000..258a2b9ac20 --- /dev/null +++ b/examples/06-plotting/08-plot_scopings.py @@ -0,0 +1,80 @@ +# Copyright (C) 2020 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _plotting_scopings: + +Review of available plotting commands +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This example show how to plot scopings with a mesh location. + +""" + +from ansys.dpf import core as dpf +from ansys.dpf.core import examples + +# Plot the bare mesh of a model +model = dpf.Model(examples.download_cfx_mixing_elbow()) + +mesh = model.metadata.meshed_region + +node_scoping_1 = dpf.Scoping(location=dpf.locations.nodal, ids=mesh.nodes.scoping.ids[0:100]) +node_scoping_2 = dpf.Scoping(location=dpf.locations.nodal, ids=mesh.nodes.scoping.ids[300:400]) + +node_sc = dpf.ScopingsContainer() +node_sc.add_label(label="scoping", default_value=1) +node_sc.add_scoping(label_space={"scoping": 1}, scoping=node_scoping_1) +node_sc.add_scoping(label_space={"scoping": 2}, scoping=node_scoping_2) + +# node_sc.plot(mesh=mesh, show_mesh=True) +# exit() +# node_scoping.plot(mesh=mesh, color="red") + +element_scoping_1 = dpf.Scoping( + location=dpf.locations.elemental, ids=mesh.elements.scoping.ids[0:100] +) +element_scoping_2 = dpf.Scoping( + location=dpf.locations.elemental, ids=mesh.elements.scoping.ids[300:400] +) +element_sc = dpf.ScopingsContainer() +element_sc.add_label(label="scoping", default_value=1) +element_sc.add_scoping(label_space={"scoping": 1}, scoping=element_scoping_1) +element_sc.add_scoping(label_space={"scoping": 2}, scoping=element_scoping_2) + +element_sc.plot(mesh=mesh, show_mesh=True) +# element_scoping.plot(mesh=mesh, color="green") + + +# from ansys.dpf.core.plotter import DpfPlotter +# +# plt = DpfPlotter() +# plt.add_scoping(node_scoping, mesh, show_mesh=True, color="red") +# plt.add_scoping(element_scoping, mesh, color="green") +# plt.show_figure() +# +# faces_scoping = dpf.Scoping( +# location=dpf.locations.faces, +# ids=mesh.faces.scoping.ids[0:100] +# ) +# +# faces_scoping.plot(mesh=mesh, color="orange") From 87c7117cdbc6486bf90e527e22a9ded5450f2757 Mon Sep 17 00:00:00 2001 From: Paul Profizi <100710998+PProfizi@users.noreply.github.com> Date: Wed, 18 Jun 2025 16:34:18 +0200 Subject: [PATCH 10/16] Fix retro --- tests/test_plotter.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_plotter.py b/tests/test_plotter.py index 328daabe700..28a99e18bc3 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -812,6 +812,10 @@ def test_plot_polyhedron(): @pytest.mark.skipif(not HAS_PYVISTA, reason="This test requires pyvista") +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_5_0, + reason="Polyhedrons are supported starting server version 5.0", +) def test_plotter_add_scoping(fluent_mixing_elbow_steady_state): mesh: core.MeshedRegion = core.operators.mesh.mesh_provider( data_sources=fluent_mixing_elbow_steady_state() @@ -831,6 +835,10 @@ def test_plotter_add_scoping(fluent_mixing_elbow_steady_state): @pytest.mark.skipif(not HAS_PYVISTA, reason="This test requires pyvista") +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_5_0, + reason="Polyhedrons are supported starting server version 5.0", +) def test_scoping_plot(fluent_mixing_elbow_steady_state): mesh: core.MeshedRegion = core.operators.mesh.mesh_provider( data_sources=fluent_mixing_elbow_steady_state() @@ -844,6 +852,10 @@ def test_scoping_plot(fluent_mixing_elbow_steady_state): @pytest.mark.skipif(not HAS_PYVISTA, reason="This test requires pyvista") +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_5_0, + reason="Polyhedrons are supported starting server version 5.0", +) def test_scopings_container_plot(fluent_mixing_elbow_steady_state): mesh: core.MeshedRegion = core.operators.mesh.mesh_provider( data_sources=fluent_mixing_elbow_steady_state() From 4a225fe44b6f3be39277775f91f21115df0dae4c Mon Sep 17 00:00:00 2001 From: Paul Profizi <100710998+PProfizi@users.noreply.github.com> Date: Wed, 18 Jun 2025 17:27:40 +0200 Subject: [PATCH 11/16] Fix retro --- tests/test_plotter.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_plotter.py b/tests/test_plotter.py index 28a99e18bc3..5dc3d604ae6 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -813,8 +813,8 @@ def test_plot_polyhedron(): @pytest.mark.skipif(not HAS_PYVISTA, reason="This test requires pyvista") @pytest.mark.skipif( - not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_5_0, - reason="Polyhedrons are supported starting server version 5.0", + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, + reason="cff::cas::meshes_provider requires DPF 24R1", ) def test_plotter_add_scoping(fluent_mixing_elbow_steady_state): mesh: core.MeshedRegion = core.operators.mesh.mesh_provider( @@ -836,8 +836,8 @@ def test_plotter_add_scoping(fluent_mixing_elbow_steady_state): @pytest.mark.skipif(not HAS_PYVISTA, reason="This test requires pyvista") @pytest.mark.skipif( - not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_5_0, - reason="Polyhedrons are supported starting server version 5.0", + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, + reason="cff::cas::meshes_provider requires DPF 24R1", ) def test_scoping_plot(fluent_mixing_elbow_steady_state): mesh: core.MeshedRegion = core.operators.mesh.mesh_provider( @@ -853,8 +853,8 @@ def test_scoping_plot(fluent_mixing_elbow_steady_state): @pytest.mark.skipif(not HAS_PYVISTA, reason="This test requires pyvista") @pytest.mark.skipif( - not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_5_0, - reason="Polyhedrons are supported starting server version 5.0", + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, + reason="cff::cas::meshes_provider requires DPF 24R1", ) def test_scopings_container_plot(fluent_mixing_elbow_steady_state): mesh: core.MeshedRegion = core.operators.mesh.mesh_provider( From 509ce77f2c340394d56a65ff3b6ae597c89f5e64 Mon Sep 17 00:00:00 2001 From: Paul Profizi <100710998+PProfizi@users.noreply.github.com> Date: Wed, 18 Jun 2025 18:15:46 +0200 Subject: [PATCH 12/16] Update test_plotter.py --- tests/test_plotter.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_plotter.py b/tests/test_plotter.py index 5dc3d604ae6..f4b59c9c3aa 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -28,7 +28,11 @@ from ansys.dpf import core from ansys.dpf.core import Model, Operator, element_types, errors as dpf_errors, misc from ansys.dpf.core.plotter import plot_chart -from conftest import SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_5_0, running_docker +from conftest import ( + SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_5_0, + SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, + running_docker, +) if misc.module_exists("pyvista"): HAS_PYVISTA = True From e0b9f429dbea8f2b2302d9da8a931af2e03cfbd1 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Thu, 19 Jun 2025 10:56:28 +0200 Subject: [PATCH 13/16] Fix typehint for MeshesContainer.get_mesh --- src/ansys/dpf/core/meshes_container.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ansys/dpf/core/meshes_container.py b/src/ansys/dpf/core/meshes_container.py index 6b722bff1a6..98a583ac27e 100644 --- a/src/ansys/dpf/core/meshes_container.py +++ b/src/ansys/dpf/core/meshes_container.py @@ -27,6 +27,8 @@ Contains classes associated with the DPF MeshesContainer. """ +from __future__ import annotations + from ansys.dpf.core import errors as dpf_errors, meshed_region from ansys.dpf.core.collection_base import CollectionBase from ansys.dpf.core.plotter import DpfPlotter @@ -159,14 +161,14 @@ def get_meshes(self, label_space): """ return super()._get_entries(label_space) - def get_mesh(self, label_space_or_index): + def get_mesh(self, label_space_or_index: int | dict[str, int]): """Retrieve the mesh at a requested index or label space. Raises an exception if the request returns more than one mesh. Parameters ---------- - label_space_or_index : dict[str,int] , int + label_space_or_index: Scoping of the requested mesh, such as ``{"time": 1, "complex": 0}`` or the index of the mesh. From e725a5d1bd215a052bd6e488b6fee313bad88438 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Thu, 19 Jun 2025 10:57:33 +0200 Subject: [PATCH 14/16] Implement behavior for ScopingContainer.plot(mesh: MeshesContainer) --- src/ansys/dpf/core/scopings_container.py | 22 +++++++++++++++++++--- tests/test_plotter.py | 17 +++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/ansys/dpf/core/scopings_container.py b/src/ansys/dpf/core/scopings_container.py index 196447a1fe4..29a9f139f01 100644 --- a/src/ansys/dpf/core/scopings_container.py +++ b/src/ansys/dpf/core/scopings_container.py @@ -30,8 +30,10 @@ from __future__ import annotations +from argparse import ArgumentError from typing import TYPE_CHECKING +import ansys.dpf.core as dpf from ansys.dpf.core import scoping from ansys.dpf.core.collection_base import CollectionBase @@ -146,7 +148,7 @@ def plot( ---------- mesh: Mesh or meshes to use to translate the scopings into mesh entities. - Associates each scoping to a mesh using labels if ``mesh`` is a collection of meshes. + Associates each scoping to a mesh using labels if ``mesh`` is a MeshesContainer. show_mesh: Whether to also show the mesh with low opacity. colors: @@ -190,11 +192,25 @@ def plot( ) plt = DpfPlotter(**kwargs) for i, scoping_i in enumerate(self): + if isinstance(mesh, dpf.MeshedRegion): + show_mesh_i = show_mesh if i == 0 else False + mesh_i = mesh + elif isinstance(mesh, dpf.MeshesContainer): + show_mesh_i = True + mesh_i = mesh.get_mesh(label_space_or_index=self.get_label_space(index=i)) + if mesh_i is None: + raise ValueError( + f"ScopingsContainer.plot: could not associate a mesh to the scoping for label '{self.get_label_space(index=i)}'." + ) + else: + raise ValueError( + f"ScopingsContainer.plot: type '{type(mesh)}' is not a valid type for argument 'mesh'." + ) plt.add_scoping( scoping=scoping_i, - mesh=mesh, + mesh=mesh_i, color=next(colors_cycle), - show_mesh=show_mesh if i == 0 else False, + show_mesh=show_mesh_i, **kwargs, ) return plt.show_figure(**kwargs) diff --git a/tests/test_plotter.py b/tests/test_plotter.py index f4b59c9c3aa..70599d73945 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -873,3 +873,20 @@ def test_scopings_container_plot(fluent_mixing_elbow_steady_state): node_sc.add_scoping(label_space={"scoping": 1}, scoping=node_scoping_1) node_sc.add_scoping(label_space={"scoping": 2}, scoping=node_scoping_2) node_sc.plot(mesh=mesh, show_mesh=True) + + meshes: core.MeshesContainer = core.operators.mesh.meshes_provider( + data_sources=fluent_mixing_elbow_steady_state() + ).eval() + + with pytest.raises(ValueError, match="could not associate a mesh to the scoping for label"): + node_sc.plot(mesh=meshes) + + label_space = {"time": 1, "zone": 4} + node_scoping_3 = core.Scoping( + location=core.locations.nodal, ids=meshes.get_mesh(label_space).nodes.scoping.ids[0:100] + ) + node_sc_2 = core.ScopingsContainer() + node_sc_2.add_label(label="time", default_value=1) + node_sc_2.add_label(label="zone") + node_sc_2.add_scoping(label_space=label_space, scoping=node_scoping_3) + node_sc_2.plot(mesh=meshes) From 5162a132fc85aa01c0536a71b0a6710c0324e366 Mon Sep 17 00:00:00 2001 From: Paul Profizi <100710998+PProfizi@users.noreply.github.com> Date: Thu, 19 Jun 2025 12:08:34 +0200 Subject: [PATCH 15/16] Delete examples/06-plotting/08-plot_scopings.py --- examples/06-plotting/08-plot_scopings.py | 80 ------------------------ 1 file changed, 80 deletions(-) delete mode 100644 examples/06-plotting/08-plot_scopings.py diff --git a/examples/06-plotting/08-plot_scopings.py b/examples/06-plotting/08-plot_scopings.py deleted file mode 100644 index 258a2b9ac20..00000000000 --- a/examples/06-plotting/08-plot_scopings.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (C) 2020 - 2025 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _plotting_scopings: - -Review of available plotting commands -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This example show how to plot scopings with a mesh location. - -""" - -from ansys.dpf import core as dpf -from ansys.dpf.core import examples - -# Plot the bare mesh of a model -model = dpf.Model(examples.download_cfx_mixing_elbow()) - -mesh = model.metadata.meshed_region - -node_scoping_1 = dpf.Scoping(location=dpf.locations.nodal, ids=mesh.nodes.scoping.ids[0:100]) -node_scoping_2 = dpf.Scoping(location=dpf.locations.nodal, ids=mesh.nodes.scoping.ids[300:400]) - -node_sc = dpf.ScopingsContainer() -node_sc.add_label(label="scoping", default_value=1) -node_sc.add_scoping(label_space={"scoping": 1}, scoping=node_scoping_1) -node_sc.add_scoping(label_space={"scoping": 2}, scoping=node_scoping_2) - -# node_sc.plot(mesh=mesh, show_mesh=True) -# exit() -# node_scoping.plot(mesh=mesh, color="red") - -element_scoping_1 = dpf.Scoping( - location=dpf.locations.elemental, ids=mesh.elements.scoping.ids[0:100] -) -element_scoping_2 = dpf.Scoping( - location=dpf.locations.elemental, ids=mesh.elements.scoping.ids[300:400] -) -element_sc = dpf.ScopingsContainer() -element_sc.add_label(label="scoping", default_value=1) -element_sc.add_scoping(label_space={"scoping": 1}, scoping=element_scoping_1) -element_sc.add_scoping(label_space={"scoping": 2}, scoping=element_scoping_2) - -element_sc.plot(mesh=mesh, show_mesh=True) -# element_scoping.plot(mesh=mesh, color="green") - - -# from ansys.dpf.core.plotter import DpfPlotter -# -# plt = DpfPlotter() -# plt.add_scoping(node_scoping, mesh, show_mesh=True, color="red") -# plt.add_scoping(element_scoping, mesh, color="green") -# plt.show_figure() -# -# faces_scoping = dpf.Scoping( -# location=dpf.locations.faces, -# ids=mesh.faces.scoping.ids[0:100] -# ) -# -# faces_scoping.plot(mesh=mesh, color="orange") From 82ce2d4f5e590952bb5c54d966db1987c42ce97d Mon Sep 17 00:00:00 2001 From: PProfizi Date: Thu, 19 Jun 2025 13:40:52 +0200 Subject: [PATCH 16/16] Fix QA --- src/ansys/dpf/core/scopings_container.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ansys/dpf/core/scopings_container.py b/src/ansys/dpf/core/scopings_container.py index 29a9f139f01..1ce834b6ada 100644 --- a/src/ansys/dpf/core/scopings_container.py +++ b/src/ansys/dpf/core/scopings_container.py @@ -30,7 +30,6 @@ from __future__ import annotations -from argparse import ArgumentError from typing import TYPE_CHECKING import ansys.dpf.core as dpf