Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
dee441f
Start work on allowing plots of ElementalNodal
PProfizi Sep 19, 2022
a7731e8
WIP
PProfizi Sep 21, 2022
1645c73
WIP
PProfizi Sep 21, 2022
4c8e627
WIP
PProfizi Sep 26, 2022
f3c3657
Works on simplebar with one element type, even for a rescoped partial…
PProfizi Sep 26, 2022
c783090
Add a test with multiple element types
PProfizi Sep 26, 2022
9d7e7e5
WIP
PProfizi Sep 27, 2022
0d76749
Add extend_to_mid_nodes operation and fix elementalnodal size computa…
PProfizi Sep 27, 2022
43cf93d
Finalize test_field_elemental_nodal_plot_multiple_solid_types
PProfizi Sep 27, 2022
db86faf
Raise an error when plotting shells on ElementalNodal location.
PProfizi Sep 27, 2022
477a2c1
WIP
PProfizi Oct 26, 2022
f232fc3
Delete examples/05-plotting/00-basic_plotting.py
PProfizi Jul 4, 2025
70cdab2
Update src/ansys/dpf/core/plotter.py
PProfizi Jul 4, 2025
6bbc519
Clean tests
PProfizi Jul 4, 2025
9150ac5
Fix plot_contour for elemental_nodal on non_linear meshes
PProfizi Jul 4, 2025
b77245a
Clean comments
PProfizi Jul 4, 2025
704b1f8
Support plot of elemental_nodal for shells in plot_contour
PProfizi Jul 4, 2025
e820901
Fix and test DpfPlotter.add_field for elemental_nodal
PProfizi Jul 4, 2025
9bc7d9f
Update the plotting example
PProfizi Jul 4, 2025
bb6c0a3
Fix plot of elemental_nodal field for mix of solids and shells
PProfizi Jul 4, 2025
324edc7
Fix plot of mix of shells and solids in plot_contour for elemental_nodal
PProfizi Jul 4, 2025
d4cba24
Merge branch 'master' into feat/plot_ElementalNodal
PProfizi Jul 7, 2025
7b177b6
Fix Style Check
PProfizi Jul 7, 2025
027cff1
Fix issue with consecutive calls to mesh.plot() with a field/fields_c…
PProfizi Jul 8, 2025
1a00f5e
Fix potential issue with consecutive calls to DpfPlotter.add_mesh() w…
PProfizi Jul 8, 2025
9129b3a
Handle location overall in MeshedRegion.location_data_len
PProfizi Jul 8, 2025
360f540
Handle location overall in MeshedRegion.location_data_len
PProfizi Jul 8, 2025
93c822e
Fix Code Quality
PProfizi Jul 8, 2025
4dae7f3
Fix retro
PProfizi Jul 9, 2025
0c5aa13
Fix retro
PProfizi Jul 9, 2025
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
4 changes: 2 additions & 2 deletions src/ansys/dpf/core/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ def map_scoping(self, external_scope):

Examples
--------
Return the indices that map a field to an elements collection.
Return the indices that map a field to an Elements collection.

>>> import ansys.dpf.core as dpf
>>> from ansys.dpf.core import examples
Expand All @@ -689,7 +689,7 @@ def map_scoping(self, external_scope):

"""
if external_scope.location in ["Nodal", "NodalElemental"]:
raise ValueError('Input scope location must be "Nodal"')
raise ValueError('Input scope location must be "Elemental"')
arr = np.array(list(map(self.mapping_id_to_index.get, external_scope.ids)))
mask = arr != None
ind = arr[mask].astype(np.int32)
Expand Down
33 changes: 33 additions & 0 deletions src/ansys/dpf/core/meshed_region.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

import traceback
import warnings
import numpy as np

from ansys.dpf.core import field, property_field, scoping, server as server_module
from ansys.dpf.core.cache import class_handling_cache
Expand Down Expand Up @@ -713,3 +714,35 @@ def is_empty(self) -> bool:
no_elements = self.elements.n_elements == 0
no_nodes = self.nodes.n_nodes == 0
return no_nodes and no_faces and no_elements

def location_data_len(self, location):
"""

Parameters
----------
location

Returns
-------
data_size : int
"""
if location == locations.nodal:
return len(self.nodes)
elif location == locations.elemental:
return len(self.elements)
elif location == locations.elemental_nodal:
return np.sum(self.get_elemental_nodal_size_list())
else:
raise TypeError(f"Location {location} is not recognized.")

def get_elemental_nodal_size_list(self):
# Get the field of element types
element_types_field = self.elements.element_types_field
# get the number of nodes for each possible element type
size_map = dict([(e_type.value, element_types.descriptor(e_type).n_nodes)
for e_type in element_types])
keys = list(size_map.keys())
sort_idx = np.argsort(keys)
idx = np.searchsorted(keys, element_types_field.data, sorter=sort_idx)
return np.asarray(list(size_map.values()))[sort_idx][idx]

63 changes: 56 additions & 7 deletions src/ansys/dpf/core/plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,16 @@ def add_field(
show_min = False
elif location == locations.overall:
mesh_location = meshed_region.elements
elif location == locations.elemental_nodal:
mesh_location = meshed_region.elements
# If ElementalNodal, first extend results to mid-nodes
field = dpf.core.operators.averaging.extend_to_mid_nodes(
field=field
).eval()
else:
raise ValueError("Only elemental, nodal or faces location are supported for plotting.")
raise ValueError(
"Only elemental, elemental nodal, nodal or faces location are supported for plotting."
)

# Treat multilayered shells
if not isinstance(shell_layer, eshell_layers):
Expand All @@ -333,13 +341,25 @@ def add_field(
)
field = change_shell_layer_op.get_output(0, core.types.field)

location_data_len = meshed_region.location_data_len(location)
component_count = field.component_count
if component_count > 1:
overall_data = np.full((len(mesh_location), component_count), np.nan)
overall_data = np.full((location_data_len, component_count), np.nan)
else:
overall_data = np.full(len(mesh_location), np.nan)
overall_data = np.full(location_data_len, np.nan)
if location != locations.overall:
ind, mask = mesh_location.map_scoping(field.scoping)

# Rework ind and mask to take into account n_nodes per element if ElementalNodal
if location == locations.elemental_nodal:
n_nodes_list = meshed_region.get_elemental_nodal_size_list().astype(np.int32)
first_index = np.insert(np.cumsum(n_nodes_list)[:-1], 0, 0).astype(np.int32)
mask_2 = np.asarray([mask_i for i, mask_i in enumerate(mask)
for _ in range(n_nodes_list[ind[i]])])
ind_2 = np.asarray([first_index[ind_i]+j for ind_i in ind
for j in range(n_nodes_list[ind_i])]) # OK
mask = mask_2
ind = ind_2
overall_data[ind] = field.data[mask]
else:
overall_data[:] = field.data[0]
Expand All @@ -354,6 +374,8 @@ def add_field(
grid = meshed_region._as_vtk(
meshed_region.deform_by(deform_by, scale_factor), as_linear
)
if location == locations.elemental_nodal:
grid = grid.shrink(1.0)
grid.set_active_scalars(None)
self._plotter.add_mesh(grid, scalars=overall_data, **kwargs_in)

Expand Down Expand Up @@ -1022,14 +1044,23 @@ def plot_contour(
unit = field.unit
break

# If ElementalNodal, first extend results to mid-nodes
if location == locations.elemental_nodal:
fields_container = dpf.core.operators.averaging.extend_to_mid_nodes_fc(
fields_container=fields_container
).eval()

location_data_len = mesh.location_data_len(location) # 3751 nodes # 3000 elements # 24000 elemental nodal # field.data 72000
if location == locations.nodal:
mesh_location = mesh.nodes
elif location == locations.elemental:
mesh_location = mesh.elements
elif location == locations.faces:
mesh_location = mesh.faces
elif location == locations.elemental_nodal:
mesh_location = mesh.elements
else:
raise ValueError("Only elemental, nodal or faces location are supported for plotting.")
raise ValueError("Only elemental, elemental nodal, nodal or faces location are supported for plotting.")

# pre-loop: check if shell layers for each field, if yes, set the shell layers
changeOp = core.Operator("change_shellLayers")
Expand All @@ -1039,6 +1070,8 @@ def plot_contour(
eshell_layers.topbottom,
eshell_layers.topbottommid,
]:
if location == locations.elemental_nodal:
raise TypeError("Trying to plot ElementalNodal values for shells.")
changeOp.inputs.fields_container.connect(fields_container)
sl = eshell_layers.top
if shell_layers is not None:
Expand All @@ -1053,13 +1086,27 @@ def plot_contour(

# Merge field data into a single array
if component_count > 1:
overall_data = np.full((len(mesh_location), component_count), np.nan)
overall_data = np.full((location_data_len, component_count), np.nan)
else:
overall_data = np.full(len(mesh_location), np.nan)
overall_data = np.full(location_data_len, np.nan)

# field._data_pointer gives the first index of each entity data
# (should be of size nb_elements)

for field in fields_container:
ind, mask = mesh_location.map_scoping(field.scoping)
overall_data[ind] = field.data[mask]
if location == locations.elemental_nodal:
# Rework ind and mask to take into account n_nodes per element
# entity_index_map = field._data_pointer
n_nodes_list = mesh.get_elemental_nodal_size_list().astype(np.int32) # OK
first_index = np.insert(np.cumsum(n_nodes_list)[:-1], 0, 0).astype(np.int32)
mask_2 = np.asarray([mask_i for i, mask_i in enumerate(mask)
for _ in range(n_nodes_list[ind[i]])])
ind_2 = np.asarray([first_index[ind_i]+j for ind_i in ind
for j in range(n_nodes_list[ind_i])]) # OK
mask = mask_2
ind = ind_2
overall_data[ind] = field.data[mask] # (24000,3)[:3000] = (24000,3)[:3000]

# create the plotter and add the meshes

Expand Down Expand Up @@ -1096,6 +1143,8 @@ def plot_contour(
mesh.as_linear = as_linear
else:
grid = mesh.grid
if location == locations.elemental_nodal:
grid = grid.shrink(1.0)
grid.clear_data()
self._internal_plotter._plotter.add_mesh(grid, scalars=overall_data, **kwargs_in)

Expand Down
51 changes: 49 additions & 2 deletions tests/test_plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def test_plotter_on_fields_container_elemental(allkindofcomplexity):
avg_op.inputs.fields_container.connect(stress.outputs.fields_container)
fc = avg_op.outputs.fields_container()
pl = Plotter(model.metadata.meshed_region)
cpos = pl.plot_contour(fc)
_ = pl.plot_contour(fc)


@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista")
Expand All @@ -141,7 +141,7 @@ def test_plotter_on_fields_container_nodal(allkindofcomplexity):
avg_op.inputs.fields_container.connect(stress.outputs.fields_container)
fc = avg_op.outputs.fields_container()
pl = Plotter(model.metadata.meshed_region)
cpos = pl.plot_contour(fc)
_ = pl.plot_contour(fc)


@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista")
Expand Down Expand Up @@ -199,6 +199,53 @@ def test_field_nodal_plot(allkindofcomplexity):
remove_picture(picture)


@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista")
def test_field_elemental_nodal_plot_simple(simple_bar):
model = Model(simple_bar)
stress = model.results.element_nodal_forces()
fc = stress.outputs.fields_container()
f = fc[0]
print(f.data.shape)
f.plot()
picture = 'test_plotter1.png'
remove_picture(picture)
f.plot(off_screen=True, screenshot=picture)
assert os.path.exists(os.path.join(os.getcwd(), picture))
remove_picture(picture)


@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista")
def test_field_elemental_nodal_plot_scoped(simple_bar):
model = Model(simple_bar)
mesh_scoping = dpf.core.mesh_scoping_factory.elemental_scoping(element_ids=list(range(1501,
3001)))
stress = model.results.element_nodal_forces.on_mesh_scoping(mesh_scoping)
fc = stress.eval()
f = fc[0]
print(f.data.shape)
f.plot()
picture = 'test_plotter2.png'
remove_picture(picture)
f.plot(off_screen=True, screenshot=picture)
assert os.path.exists(os.path.join(os.getcwd(), picture))
remove_picture(picture)


@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista")
def test_field_elemental_nodal_plot_multiple_solid_types():
from ansys.dpf.core import examples
model = dpf.core.Model(examples.download_hemisphere())
stress = model.results.stress()
fc = stress.outputs.fields_container()
f = fc[0]
f.plot()
picture = 'test_plotter3.png'
remove_picture(picture)
f.plot(off_screen=True, screenshot=picture)
assert os.path.exists(os.path.join(os.getcwd(), picture))
remove_picture(picture)


@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista")
def test_field_solid_plot(allkindofcomplexity):
model = Model(allkindofcomplexity)
Expand Down
Loading