Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions doc/changelog.d/3784.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fix: improve element and node selection handling in post-processing
12 changes: 5 additions & 7 deletions src/ansys/mapdl/core/mesh/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,17 +191,15 @@
grid.cell_data["ansys_elem_type_num"] = mesh.etype

# store node angles
if angles is not None:
if angles.shape[1] == 3:
grid.point_data["angles"] = angles
if angles is not None and angles.shape[1] == 3:
grid.point_data["angles"] = angles

Check warning on line 195 in src/ansys/mapdl/core/mesh/mesh.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/mapdl/core/mesh/mesh.py#L195

Added line #L195 was not covered by tests

if not null_unallowed:
grid = grid.extract_cells(grid.celltypes != 0)

if force_linear:
# only run if the grid has points or cells
if grid.n_points:
grid = grid.linear_copy()
# only run if the grid has points or cells
if force_linear and grid.n_points:
grid = grid.linear_copy()

# map over element types
# Add tracker for original node numbering
Expand Down
198 changes: 107 additions & 91 deletions src/ansys/mapdl/core/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,29 +633,40 @@
"exist within the result file."
)

mask = self.selected_nodes
all_scalars = np.empty(mask.size)
all_scalars[mask] = scalars

# we can directly the node numbers as the array of selected
# nodes will be a mask sized to the highest node index - 1
surf = self._mapdl.mesh._surf
node_id = surf["ansys_node_num"].astype(np.int32) - 1
all_scalars = all_scalars[node_id]

meshes = [
{
"mesh": surf.copy(deep=False), # deep=False for ipyvtk-simple
"scalar_bar_args": {"title": kwargs.pop("stitle", "")},
"scalars": all_scalars,
}
]
with self._mapdl.save_selection:
mask = self.selected_nodes
nodes_ids = self._mapdl.get_array("NODE", item1="NLIST")
nodes_loc = self._mapdl.mesh.nodes

self._mapdl.esln("S", 0) # selecting elements associated with those nodes
self._mapdl.nsle(
"A", "all"
) # selecting nodes associated with those elements to avoid segfault

all_scalars = np.empty(mask.size)
all_scalars[mask] = scalars

# we can directly the node numbers as the array of selected
# nodes will be a mask sized to the highest node index - 1
surf = self._mapdl.mesh._surf # problem here
node_id = surf["ansys_node_num"].astype(np.int32) - 1
all_scalars = all_scalars[node_id]

meshes = [
{
"mesh": surf.copy(deep=False), # deep=False for ipyvtk-simple
"scalar_bar_args": {"title": kwargs.pop("stitle", "")},
"scalars": all_scalars,
}
]

labels = []
if show_node_numbering:
labels = [{"points": nodes_loc, "labels": nodes_ids}]

pl = MapdlPlotter()
pl.plot(meshes, [], labels, mapdl=self, **kwargs)

labels = []
if show_node_numbering:
labels = [{"points": surf.points, "labels": surf["ansys_node_num"]}]
pl = MapdlPlotter()
pl.plot(meshes, [], labels, mapdl=self, **kwargs)
return pl.show(**kwargs)

@requires_package("ansys.tools.visualization_interface")
Expand All @@ -668,78 +679,83 @@
"exist within the result file."
)

surf = self._mapdl.mesh._surf

# as ``disp`` returns the result for all nodes/elems, we need all node/elem numbers
# and to index to the output node numbers
if hasattr(self._mapdl.mesh, "enum_all"):
enum = self._mapdl.mesh.enum_all
else:
enum = self._all_enum

#######################################################################
# Bool operations
# ===============
# I'm going to explain this clearly because it can be confusing for the
# future developers (me).
# This explanation is based in scalars (`element_values`) NOT having the
# full elements (selected and not selected) size.
#
# First, it's possible that there are duplicated element numbers,
# in the surf object returned by Pyvista.
# Therefore we need to get the unique values and a reverse index, to
# later convert the MAPDL values to Pyvista values.
uni, ridx = np.unique(surf["ansys_elem_num"], return_inverse=True)
# which means that, uni is the id of mapdl elements in the polydata
# object. These elements does not need to be in order, and there can be
# duplicated!
# Hence:
# uni[ridx] = surf["ansys_elem_num"]
#
# Let's notice that:
# * enum[self.selected_elements] is mapdl selected elements ids in MAPDL notation.
#
# Theoretical approach
# --------------------
# The theoretical approach will be using an intermediate array of the
# size of the MAPDL total number of elements (we do not care about selected).
#
values = np.zeros(enum.shape)
#
# Then assign the MAPDL values for the selected element (scalars)
#
values[self.selected_elements] = scalars
#
# Because values are in order, but with python notation, then we can do:
#
surf_values = values[
uni - 1
] # -1 to set MAPDL element indexing to python indexing
#
# Then to account for the original Pyvista object:
#
surf_values = surf_values[ridx]
#
#######################################################################

meshes = [
{
"mesh": surf.copy(deep=False), # deep=False for ipyvtk-simple
"scalar_bar_args": {"title": kwargs.pop("stitle", "")},
"scalars": surf_values,
}
]

labels = []
if show_elem_numbering:
labels = [
with self._mapdl.save_selection:
# Select nodes to avoid segfault
self._mapdl.nsle("s", "all")

# Getting mesh
surf = self._mapdl.mesh._surf

# as ``disp`` returns the result for all nodes/elems, we need all node/elem numbers
# and to index to the output node numbers
if hasattr(self._mapdl.mesh, "enum_all"):
enum = self._mapdl.mesh.enum_all
else:
enum = self._all_enum

Check warning on line 694 in src/ansys/mapdl/core/post.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/mapdl/core/post.py#L694

Added line #L694 was not covered by tests

#######################################################################
# Bool operations
# ===============
# I'm going to explain this clearly because it can be confusing for the
# future developers (me).
# This explanation is based in scalars (`element_values`) NOT having the
# full elements (selected and not selected) size.
#
# First, it's possible that there are duplicated element numbers,
# in the surf object returned by Pyvista.
# Therefore we need to get the unique values and a reverse index, to
# later convert the MAPDL values to Pyvista values.
uni, ridx = np.unique(surf["ansys_elem_num"], return_inverse=True)
# which means that, uni is the id of mapdl elements in the polydata
# object. These elements does not need to be in order, and there can be
# duplicated!
# Hence:
# uni[ridx] = surf["ansys_elem_num"]
#
# Let's notice that:
# * enum[self.selected_elements] is mapdl selected elements ids in MAPDL notation.
#
# Theoretical approach
# --------------------
# The theoretical approach will be using an intermediate array of the
# size of the MAPDL total number of elements (we do not care about selected).
#
values = np.zeros(enum.shape)
#
# Then assign the MAPDL values for the selected element (scalars)
#
values[self.selected_elements] = scalars
#
# Because values are in order, but with python notation, then we can do:
#
surf_values = values[
uni - 1
] # -1 to set MAPDL element indexing to python indexing
#
# Then to account for the original Pyvista object:
#
surf_values = surf_values[ridx]
#
#######################################################################

meshes = [
{
"points": surf.cell_centers().points,
"labels": surf["ansys_elem_num"],
"mesh": surf.copy(deep=False), # deep=False for ipyvtk-simple
"scalar_bar_args": {"title": kwargs.pop("stitle", "")},
"scalars": surf_values,
}
]
pl = MapdlPlotter()
pl.plot(meshes, [], labels, mapdl=self, **kwargs)

labels = []
if show_elem_numbering:
labels = [

Check warning on line 751 in src/ansys/mapdl/core/post.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/mapdl/core/post.py#L751

Added line #L751 was not covered by tests
{
"points": surf.cell_centers().points,
"labels": surf["ansys_elem_num"],
}
]
pl = MapdlPlotter()
pl.plot(meshes, [], labels, mapdl=self, **kwargs)
return pl.show(**kwargs)

@property
Expand Down
47 changes: 41 additions & 6 deletions tests/test_post.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,19 +215,54 @@ def test_disp_plot(mapdl, resume, comp):
@staticmethod
@requires("ansys-tools-visualization_interface")
def test_disp_plot_subselection(mapdl, resume):
mapdl.nsel("S", "NODE", vmin=500, vmax=503, mute=True)
mapdl.esel("S", "ELEM", vmin=500, vmax=510, mute=True)

pl = mapdl.post_processing.plot_nodal_displacement(
"X", smooth_shading=True, show_node_numbering=True, return_plotter=True
"X",
smooth_shading=True,
show_node_numbering=True,
return_plotter=True,
)
poly = pl.meshes[0]
elem_ids = np.unique(poly.cell_data["ansys_elem_num"])

assert mapdl.mesh.n_elem == len(elem_ids)
assert np.allclose(mapdl.mesh.enum, elem_ids)
assert pl.show() is None

mapdl.allsel()
@staticmethod
@requires("ansys-tools-visualization_interface")
def test_uncomplete_element_plotting(mapdl, resume):
enums = mapdl.esel("S", "ELEM", vmin=500, vmax=510)
mapdl.nsel("s", "node", vmin=50, vmax=60)

pl = mapdl.post_processing.plot_element_displacement(
"X",
smooth_shading=True,
show_node_numbering=True,
return_plotter=True,
)

mesh = pl.meshes[0]
elem_ids = np.unique(mesh.cell_data["ansys_elem_num"])

# assert no state change
assert mapdl.mesh.n_elem == len(enums)

assert np.allclose(elem_ids, enums)

@staticmethod
@requires("ansys-tools-visualization_interface")
def test_uncomplete_nodal_plotting(mapdl, resume):
nnums = mapdl.nsel("S", "node", vmin=500, vmax=510)

pl = mapdl.post_processing.plot_nodal_displacement(
"X",
smooth_shading=True,
show_node_numbering=True,
return_plotter=True,
)

# assert no state change
assert mapdl.mesh.n_node == len(nnums)
assert np.allclose(mapdl.mesh.nnum, nnums)

@staticmethod
def test_nodal_eqv_stress(mapdl, resume):
Expand Down