Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 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
6 changes: 4 additions & 2 deletions src/ansys/dpf/core/meshes_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

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
89 changes: 89 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,18 @@
Contains classes associated to the DPF ScopingsContainer
"""

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

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 +134,83 @@
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 MeshesContainer.
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):
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(

Check warning on line 206 in src/ansys/dpf/core/scopings_container.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/scopings_container.py#L206

Added line #L206 was not covered by tests
f"ScopingsContainer.plot: type '{type(mesh)}' is not a valid type for argument 'mesh'."
)
plt.add_scoping(
scoping=scoping_i,
mesh=mesh_i,
color=next(colors_cycle),
show_mesh=show_mesh_i,
**kwargs,
)
return plt.show_figure(**kwargs)
Loading
Loading