Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/ansys/dpf/core/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from __future__ import annotations

from enum import Enum
from typing import TYPE_CHECKING

import numpy as np

Expand All @@ -34,6 +35,9 @@
from ansys.dpf.core.element_descriptor import ElementDescriptor
from ansys.dpf.gate import integral_types

if TYPE_CHECKING: # pragma: no cover
from ansys.dpf.core.scoping import Scoping


class Element:
"""
Expand Down Expand Up @@ -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.

Expand Down
11 changes: 9 additions & 2 deletions src/ansys/dpf/core/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: # pragma: no cover
from ansys.dpf.core.scoping import Scoping


class Node:
"""
Expand Down Expand Up @@ -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
Expand Down
82 changes: 81 additions & 1 deletion src/ansys/dpf/core/plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@

from __future__ import annotations

import os
from pathlib import Path
import sys
import tempfile
Expand All @@ -48,6 +47,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

Expand Down Expand Up @@ -233,6 +233,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,
Expand Down Expand Up @@ -688,6 +719,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.

Expand Down
42 changes: 42 additions & 0 deletions src/ansys/dpf/core/scoping.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
73 changes: 73 additions & 0 deletions src/ansys/dpf/core/scopings_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: # pragma: no cover
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.
Expand Down Expand Up @@ -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)
60 changes: 60 additions & 0 deletions tests/test_plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,3 +809,63 @@ def test_plot_polyhedron():

# Plot the MeshedRegion
mesh.plot()


@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()
).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()

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")
@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()
).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")
@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()
).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)
Loading