+
Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 1 addition & 2 deletions examples/06-plotting/00-basic_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,8 @@
# - The "off_screen" keyword only works when "notebook=False" to prevent the GUI from appearing.


# Plot a field on its supporting mesh (field location must be Elemental or Nodal)
# Plot a field on its supporting mesh
stress = model.results.stress()
stress.inputs.requested_location.connect(dpf.locations.nodal)
fc = stress.outputs.fields_container()
field = fc[0]
field.plot(notebook=False, shell_layers=None, show_axes=True, title="Field", text="Field plot")
Expand Down
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 @@

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 @@

"""
if external_scope.location in ["Nodal", "NodalElemental"]:
raise ValueError('Input scope location must be "Nodal"')
raise ValueError('Input scope location must be "Elemental"')

Check warning on line 692 in src/ansys/dpf/core/elements.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/elements.py#L692

Added line #L692 was not covered by tests
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
47 changes: 47 additions & 0 deletions src/ansys/dpf/core/meshed_region.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
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
from ansys.dpf.core.check_version import meets_version, server_meet_version, version_requires
Expand Down Expand Up @@ -713,3 +715,48 @@
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: locations) -> int:
"""Return the data length for a given mesh location.

Accepted mesh locations are nodal, elemental, faces, and elemental_nodal.

Parameters
----------
location:
The mesh location to compute data length for.
Can be nodal, elemental, faces, or elemental_nodal.

Returns
-------
data_size:
If location is nodal, return the number of nodes.
If location is elemental, return the number of elements.
If location is faces, return the number of faces.
If location is elemental nodal, return the sum of the number of nodes per element.
"""
if location == locations.nodal:
return len(self.nodes)
elif location == locations.elemental:
return len(self.elements)
elif location == locations.faces:
return len(self.faces)

Check warning on line 743 in src/ansys/dpf/core/meshed_region.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/meshed_region.py#L743

Added line #L743 was not covered by tests
elif location == locations.elemental_nodal:
return np.sum(self.get_elemental_nodal_size_list())
elif location == locations.overall:
return len(self.elements)

Check warning on line 747 in src/ansys/dpf/core/meshed_region.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/meshed_region.py#L746-L747

Added lines #L746 - L747 were not covered by tests
else:
raise TypeError(f"Location {location} is not recognized.")

Check warning on line 749 in src/ansys/dpf/core/meshed_region.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/meshed_region.py#L749

Added line #L749 was not covered by tests

def get_elemental_nodal_size_list(self) -> np.ndarray:
"""Return the array of number of nodes per element in the mesh."""
# 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]
150 changes: 119 additions & 31 deletions src/ansys/dpf/core/plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
grid = meshed_region._as_vtk(
meshed_region.nodes.coordinates_field, as_linear=as_linear
)
meshed_region._full_grid = grid

Check warning on line 150 in src/ansys/dpf/core/plotter.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/plotter.py#L150

Added line #L150 was not covered by tests
meshed_region.as_linear = as_linear
else:
grid = meshed_region.grid
Expand Down Expand Up @@ -317,8 +318,14 @@
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(

Check warning on line 326 in src/ansys/dpf/core/plotter.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/plotter.py#L326

Added line #L326 was not covered by tests
"Only elemental, elemental nodal, nodal, faces, or overall location are supported for plotting."
)

# Treat multilayered shells
if not isinstance(shell_layer, eshell_layers):
Expand All @@ -333,13 +340,27 @@
)
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])]
)
mask = mask_2
ind = ind_2
overall_data[ind] = field.data[mask]
else:
overall_data[:] = field.data[0]
Expand All @@ -348,12 +369,22 @@
# Have to remove any active scalar field from the pre-existing grid object,
# otherwise we get two scalar bars when calling several plot_contour on the same mesh
# but not for the same field. The PyVista UnstructuredGrid keeps memory of it.
if not deform_by:
grid = meshed_region.grid
else:
if location == locations.elemental_nodal:
as_linear = False
if deform_by:
grid = meshed_region._as_vtk(
meshed_region.deform_by(deform_by, scale_factor), as_linear
meshed_region.deform_by(deform_by, scale_factor), as_linear=as_linear
)
else:
if as_linear != meshed_region.as_linear:
grid = meshed_region._as_vtk(
meshed_region.nodes.coordinates_field, as_linear=as_linear
)
meshed_region.as_linear = as_linear
else:
grid = meshed_region.grid

Check warning on line 385 in src/ansys/dpf/core/plotter.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/plotter.py#L385

Added line #L385 was not covered by tests
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 @@ -976,15 +1007,14 @@
import warnings

warnings.simplefilter("ignore")

if isinstance(field_or_fields_container, (dpf.core.Field, dpf.core.FieldsContainer)):
fields_container = None
if isinstance(field_or_fields_container, dpf.core.Field):
fields_container = dpf.core.FieldsContainer(
server=field_or_fields_container._server
)
fields_container.add_label(DefinitionLabels.time)
fields_container.add_field({DefinitionLabels.time: 1}, field_or_fields_container)
fields_container.add_label("id")
fields_container.add_field({"id": 1}, field_or_fields_container)
elif isinstance(field_or_fields_container, dpf.core.FieldsContainer):
fields_container = field_or_fields_container
else:
Expand Down Expand Up @@ -1022,43 +1052,96 @@
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)
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(

Check warning on line 1071 in src/ansys/dpf/core/plotter.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/plotter.py#L1071

Added line #L1071 was not covered by tests
"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")
for field in fields_container:
shell_layer_check = field.shell_layers
if shell_layer_check in [
eshell_layers.topbottom,
eshell_layers.topbottommid,
]:
changeOp.inputs.fields_container.connect(fields_container)
sl = eshell_layers.top
if shell_layers is not None:
if not isinstance(shell_layers, eshell_layers):
raise TypeError(
"shell_layer attribute must be a core.shell_layers instance."
)
sl = shell_layers
changeOp.inputs.e_shell_layer.connect(sl.value) # top layers taken
fields_container = changeOp.get_output(0, core.types.fields_container)
break
changeOp = core.operators.utility.change_shell_layers()
if location == locations.elemental_nodal:
# change_shell_layers does not support elemental_nodal when given a fields_container
new_fields_container = dpf.core.FieldsContainer()
for l in fields_container.labels:
new_fields_container.add_label(l)
for i, field in enumerate(fields_container):
label_space_i = fields_container.get_label_space(i)
shell_layer_check = field.shell_layers
if shell_layer_check in [
eshell_layers.topbottom,
eshell_layers.topbottommid,
]:
changeOp.inputs.fields_container.connect(field)
changeOp.inputs.merge.connect(True)
sl = eshell_layers.top
if shell_layers is not None:
if not isinstance(shell_layers, eshell_layers):
raise TypeError(

Check warning on line 1094 in src/ansys/dpf/core/plotter.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/plotter.py#L1093-L1094

Added lines #L1093 - L1094 were not covered by tests
"shell_layer attribute must be a core.shell_layers instance."
)
sl = shell_layers

Check warning on line 1097 in src/ansys/dpf/core/plotter.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/plotter.py#L1097

Added line #L1097 was not covered by tests
changeOp.inputs.e_shell_layer.connect(sl.value) # top layers taken
field = changeOp.get_output(0, core.types.field)
new_fields_container.add_field(label_space=label_space_i, field=field)
fields_container = new_fields_container
else:
for field in fields_container:
shell_layer_check = field.shell_layers
if shell_layer_check in [
eshell_layers.topbottom,
eshell_layers.topbottommid,
]:
changeOp.inputs.fields_container.connect(fields_container)
sl = eshell_layers.top
if shell_layers is not None:
if not isinstance(shell_layers, eshell_layers):
raise TypeError(
"shell_layer attribute must be a core.shell_layers instance."
)
sl = shell_layers
changeOp.inputs.e_shell_layer.connect(sl.value) # top layers taken
fields_container = changeOp.get_output(0, core.types.fields_container)
break

# 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)

Check warning on line 1125 in src/ansys/dpf/core/plotter.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/plotter.py#L1125

Added line #L1125 was not covered by tests

# 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)
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)
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])]
)
mask = mask_2
ind = ind_2
overall_data[ind] = field.data[mask]

# create the plotter and add the meshes
Expand Down Expand Up @@ -1087,15 +1170,20 @@
bound_method=self._internal_plotter._plotter.add_mesh, **kwargs
)
as_linear = True
if location == locations.elemental_nodal:
as_linear = False
if deform_by:
grid = mesh._as_vtk(mesh.deform_by(deform_by, scale_factor), as_linear=as_linear)
self._internal_plotter.add_scale_factor_legend(scale_factor, **kwargs)
else:
if as_linear != mesh.as_linear:
grid = mesh._as_vtk(mesh.nodes.coordinates_field, as_linear=as_linear)
mesh._full_grid = grid
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
Loading
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载