Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: avoid changing entities ids after plotting #3421

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
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/3421.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fix: avoid changing entities ids after plotting
10 changes: 5 additions & 5 deletions doc/source/user_guide/components.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Managing components
*******************

MAPDL components can be retrieved and set using
:attr:`Mapdl.components <ansys.mapdl.core.Mapdl.components>`.
:attr:`Mapdl.components <ansys.mapdl.core.component.ComponentManager>`.


There are several ways to create a component in MAPDL.
Expand Down Expand Up @@ -42,7 +42,7 @@ Set a component without specifying the type, by default it is ``NODE``:
warnings.warn(

You can change the default type by changing
:attr:`Mapdl.components.default_entity <ansys.mapdl.core.Mapdl.components.default_entity>`
:attr:`Mapdl.components.default_entity <ansys.mapdl.core.component.ComponentManager.default_entity>`

.. code:: pycon

Expand Down Expand Up @@ -78,10 +78,10 @@ Selecting a component and retrieving it:
Component object
================

The `Component object <ansys.mapdl.core.component.Component>` is the object returned by
The :class:`Component object <ansys.mapdl.core.component.Component>` is the object returned by
:attr:`Mapdl.components <ansys.mapdl.core.Mapdl.components>` when you query it with a component name.
This object has two main attributes: `type <Component.type>` and `items <Component.items>`.
The former returns the component type (`"ELEM"`, `"NODE"`, `"KP"`, etc) and the later returns
This object has two main attributes: :attr:`type <Component.type>` and :attr:`items <Component.items>`.
The former returns the component type (``"ELEM"``, ``"NODE"``, ``"KP"``, etc) and the later returns
a tuple with the index of the entities which belong to that component.

.. code:: pycon
Expand Down
8 changes: 5 additions & 3 deletions src/ansys/mapdl/core/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ class ComponentManager:
-----

**Components need to be selected** using
:attr:`Mapdl.cmsel() <ansys.mapdl.core.Mapdl.cmsel>` before
being listed in :attr:`Mapdl.components <ansys.mapdl.core.Mapdl.components>`
:attr:`Mapdl.cmsel() <ansys.mapdl.core.Mapdl.cmsel>` before being listed in
:class:`Mapdl.components <ansys.mapdl.core.component.ComponentManager>`

Examples
--------
Expand Down Expand Up @@ -507,7 +507,9 @@ def items(self):
"""
return self._comp.items()

def select(self, names: Union[str, list[str], tuple[str]], mute=False) -> None:
def select(
self, names: Union[str, list[str], tuple[str]], mute: bool = False
) -> None:
"""Select Select components given their names

Select components given their names.
Expand Down
76 changes: 35 additions & 41 deletions src/ansys/mapdl/core/mapdl_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@
self._default_file_type_for_plots = file_type_for_plots
self._version = None # cached version
self._mute = False
self._save_selection_obj = None

if _HAS_PYVISTA:
if use_vtk is not None: # pragma: no cover
Expand Down Expand Up @@ -954,7 +955,9 @@
when exit returns to that selection.

"""
return self._save_selection(self)
if self._save_selection_obj is None:
self._save_selection_obj = self._save_selection(self)
return self._save_selection_obj

@property
def solution(self) -> "Solution":
Expand Down Expand Up @@ -1390,44 +1393,40 @@

def __init__(self, parent):
self._parent = weakref.ref(parent)
self.selection_sets = []
self.selection_sets_comps = []
self.selection = []

def __enter__(self):
self._parent()._log.debug("Entering saving selection context")

selection_set_name = random_string(10)
self.selection_sets.append(selection_set_name)
self.selection_sets_comps.append(self._parent().components.names)

mapdl = self._parent()

prev_ier = mapdl.ignore_errors
mapdl.ignore_errors = True
for entity in ["kp", "lines", "area", "volu", "node", "elem"]:
mapdl.cm(f"_{selection_set_name}_{entity}_", f"{entity}", mute=True)
mapdl.ignore_errors = prev_ier
# Storing components
selection = {
"cmsel": mapdl.components.names,
# "components_type": mapdl.components.types,
"nsel": mapdl.mesh.nnum,
"esel": mapdl.mesh.enum,
"ksel": mapdl.geometry.knum,
"lsel": mapdl.geometry.lnum,
"asel": mapdl.geometry.anum,
"vsel": mapdl.geometry.vnum,
}

self.selection.append(selection)

def __exit__(self, *args):
self._parent()._log.debug("Exiting saving selection context")
last_selection_name = self.selection_sets.pop()
last_selection_cmps = self.selection_sets_comps.pop()

selection = self.selection.pop()
mapdl = self._parent()

# probably this is redundant
prev_ier = mapdl.ignore_errors
mapdl.ignore_errors = True
for entity in ["kp", "lines", "area", "volu", "node", "elem"]:
cmp_name = f"_{last_selection_name}_{entity}_"
mapdl.cmsel("s", cmp_name, f"{entity}", mute=True)
mapdl.cmdele(cmp_name)
cmps = selection.pop("cmsel")

mapdl.ignore_errors = prev_ier
if cmps:
mapdl.components.select(cmps)

# mute to avoid getting issues when the component wasn't created in
# first place because there was no entities.
self._parent().components.select(last_selection_cmps, mute=True)
for select_cmd, ids in selection.items():
if ids.size > 0:
func = getattr(mapdl, select_cmd)
func(vmin=ids)

class _chain_commands:
"""Store MAPDL commands and send one chained command."""
Expand All @@ -1440,7 +1439,7 @@
self._parent()._store_commands = True

def __exit__(self, *args):
self._parent()._log.debug("Entering chained command mode")
self._parent()._log.debug("Exiting chained command mode")

Check warning on line 1442 in src/ansys/mapdl/core/mapdl_core.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/mapdl/core/mapdl_core.py#L1442

Added line #L1442 was not covered by tests
self._parent()._chain_stored()
self._parent()._store_commands = False

Expand Down Expand Up @@ -2700,22 +2699,17 @@
self, entity, selection_function, type_, item, comp, vmin, kabs
):
"""Select entities using CM, and the supplied selection function."""
self.cm(f"__temp_{entity}s__", f"{entity}") # Saving previous selection

# Getting new selection
for id_, each_ in enumerate(vmin):
selection_function(
self, "S" if id_ == 0 else "A", item, comp, each_, "", "", kabs
)

self.cm(f"__temp_{entity}s_1__", f"{entity}")

self.cmsel("S", f"__temp_{entity}s__")
self.cmsel(type_, f"__temp_{entity}s_1__")
if type_ == "S" or not type_:
type__ = "S" if id_ == 0 else "A"
# R is an issue, because first iteration will clean up the rest.
elif type_ == "R":
raise NotImplementedError("Mode R is not supported.")

Check warning on line 2708 in src/ansys/mapdl/core/mapdl_core.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/mapdl/core/mapdl_core.py#L2707-L2708

Added lines #L2707 - L2708 were not covered by tests
else:
type__ = type_

Check warning on line 2710 in src/ansys/mapdl/core/mapdl_core.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/mapdl/core/mapdl_core.py#L2710

Added line #L2710 was not covered by tests

# Cleaning
self.cmdele(f"__temp_{entity}s__")
self.cmdele(f"__temp_{entity}s_1__")
selection_function(self, type__, item, comp, each_, "", "", kabs)

def _raise_errors(self, text):
# to make sure the following error messages are caught even if a breakline is in between.
Expand Down
9 changes: 5 additions & 4 deletions src/ansys/mapdl/core/mapdl_extended.py
Original file line number Diff line number Diff line change
Expand Up @@ -733,10 +733,11 @@
pl.plot([], [], [], **kwargs)
return pl.show(**kwargs)

if quality > 10:
quality = 10
if quality < 1:
quality = 1
if quality < 1 or quality > 10:
raise ValueError(

Check warning on line 737 in src/ansys/mapdl/core/mapdl_extended.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/mapdl/core/mapdl_extended.py#L736-L737

Added lines #L736 - L737 were not covered by tests
f"The argument 'quality' can only be a integer between 1 and 10 (included both)."
)

surfs = self.geometry.get_areas(return_as_list=True, quality=quality)
meshes = []
labels = []
Expand Down
19 changes: 10 additions & 9 deletions src/ansys/mapdl/core/mapdl_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,24 +650,25 @@

# reselect from existing selection to mimic APDL behavior
if amin or amax:
if amax is None:
amax = amin

if amin is None: # amax is non-zero
amin = 1

if ninc is None:
ninc = ""
amax = amax or amin
amin = amin or 1
ninc = ninc or ""

Check warning on line 655 in src/ansys/mapdl/core/mapdl_geometry.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/mapdl/core/mapdl_geometry.py#L653-L655

Added lines #L653 - L655 were not covered by tests

self._mapdl.asel("R", "AREA", vmin=amin, vmax=amax, vinc=ninc)

## Duplication
# duplicate areas to avoid affecting existing areas
# Getting the maximum area ID
a_num = int(self._mapdl.get(entity="AREA", item1="NUM", it1num="MAXD"))
# Setting the new areas ID starting number
self._mapdl.numstr("AREA", a_num, mute=True)
# Generating new areas
self._mapdl.agen(2, "ALL", noelem=1, mute=True)
a_max = int(self._mapdl.get(entity="AREA", item1="NUM", it1num="MAXD"))

# Getting the new maximum area ID
a_max = int(self._mapdl.get(entity="AREA", item1="NUM", it1num="MAXD"))

Check warning on line 669 in src/ansys/mapdl/core/mapdl_geometry.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/mapdl/core/mapdl_geometry.py#L669

Added line #L669 was not covered by tests
self._mapdl.asel("S", "AREA", vmin=a_num + 1, vmax=a_max, mute=True)

# necessary to reset element/area meshing association
self._mapdl.aatt(mute=True)

Expand Down
2 changes: 1 addition & 1 deletion src/ansys/mapdl/core/mesh/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
"""
if not mesh._has_nodes or not mesh._has_elements:
# warnings.warn('Missing nodes or elements. Unable to parse to vtk')
return
return pv.UnstructuredGrid()

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L115 was not covered by tests

etype_map = ETYPE_MAP
if allowable_types is not None:
Expand Down
128 changes: 127 additions & 1 deletion tests/test_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,43 @@ def block_example_coupled(mapdl):
mapdl.n(3, 2, 0, 0)


def check_geometry(mapdl, function):
prev_knum = mapdl.geometry.knum
prev_lnum = mapdl.geometry.lnum
prev_anum = mapdl.geometry.anum
prev_kps = mapdl.geometry.get_keypoints(
return_as_array=True, return_ids_in_array=True
)
prev_lines = mapdl.geometry.get_lines(return_as_list=True)
prev_areas = mapdl.geometry.get_areas(return_as_list=True)

out = function()

new_knum = mapdl.geometry.knum
new_lnum = mapdl.geometry.lnum
new_anum = mapdl.geometry.anum
new_kps = mapdl.geometry.get_keypoints(
return_as_array=True, return_ids_in_array=True
)
new_lines = mapdl.geometry.get_lines(return_as_list=True)
new_areas = mapdl.geometry.get_areas(return_as_list=True)

assert np.allclose(prev_knum, new_knum)
assert np.allclose(prev_lnum, new_lnum)
assert np.allclose(prev_anum, new_anum)
assert len(prev_kps) == len(new_kps)
assert len(prev_lines) == len(new_lines)
assert len(prev_areas) == len(new_areas)
assert all([each in new_kps for each in prev_kps])
assert all([each in new_lines for each in prev_lines])
assert all([each in new_areas for each in prev_areas])
assert all([each in prev_kps for each in new_kps])
assert all([each in prev_lines for each in new_lines])
assert all([each in prev_areas for each in new_areas])

return out


def test_plot_empty_mesh(mapdl, cleared):
with pytest.warns(UserWarning):
mapdl.nplot(vtk=True)
Expand Down Expand Up @@ -218,7 +255,7 @@ def test_aplot(cleared, mapdl, vtk):
mapdl.aplot(show_area_numbering=True)
mapdl.aplot(vtk=vtk, color_areas=vtk, show_lines=True, show_line_numbering=True)

mapdl.aplot(quality=100)
mapdl.aplot(quality=10)
mapdl.aplot(quality=-1)


Expand Down Expand Up @@ -1126,3 +1163,92 @@ def test_lplot_line(mapdl, cleared):
mapdl.lplot(
show_line_numbering=False, show_keypoint_numbering=True, color_lines=True
)


@pytest.mark.parametrize(
"func,entity",
[("vplot", "VOLU"), ("aplot", "AREA"), ("lplot", "LINE"), ("kplot", "KP")],
)
@pytest.mark.parametrize("partial", [True, False])
def test_xplot_not_changing_geo_selection(mapdl, cleared, func, entity, partial):
mapdl.prep7()
mapdl.block(0, 1, 0, 1, 0, 1)
mapdl.block(1, 2, 1, 2, 1, 2)
mapdl.block(2, 3, 2, 3, 2, 3)

mapdl.geometry._select_items(1, entity, "S")
mapdl.cm("selection1", entity)
mapdl.cmsel("u", "selection1")

mapdl.geometry._select_items(2, entity, "S")
mapdl.cm("selection2", entity)

if not partial:
mapdl.allsel()
mapdl.cmsel("all")

fn = getattr(mapdl, func)
check_geometry(mapdl, fn)


def test_xplot_not_changing_geo_selection2(mapdl, cleared):
mapdl.prep7()
mapdl.rectng(0, 1, 0, 1)
mapdl.cm("area1", "area")
mapdl.cmsel("u", "area1")
mapdl.rectng(2, 4, -1, 1)
mapdl.cm("area2", "area")
mapdl.allsel()
mapdl.cmsel("all")

check_geometry(mapdl, mapdl.aplot)


@pytest.mark.parametrize(
"plot_func,entity,gen_func,arg1,arg2",
[
("vplot", "VOLU", "block", (0, 1, 0, 1, 0, 1), (1, 2, 1, 2, 1, 2)),
("aplot", "AREA", "rectng", (0, 1, 0, 1), (1, 2, 1, 2)),
("lplot", "LINE", "slashline", (1, 1, 1), (1, -1, 1)),
("kplot", "KP", "k", ("", 0, 0, 0), ("", 1, 1, 1)),
],
)
def test_xplot_not_changing_geo_selection_components(
mapdl, cleared, plot_func, entity, gen_func, arg1, arg2
):
mapdl.prep7()
gen_func = getattr(mapdl, gen_func)

if entity == "LINE":
l0 = mapdl.k("", (0, 0, 0, 0))
l1 = mapdl.k("", *arg1)
mapdl.l(l0, l1)
else:
gen_func(*arg1)

mapdl.cm("select1", entity)
mapdl.cmsel("u", "select1")

if entity == "LINE":
l0 = mapdl.k("", (0, 0, 0, 0))
l1 = mapdl.k("", *arg2)
mapdl.l(l0, l1)
else:
gen_func(*arg2)

mapdl.cm("select2", entity)

mapdl.allsel()
mapdl.cmsel("all")

plot_func = getattr(mapdl, plot_func)
check_geometry(mapdl, plot_func)


@pytest.mark.parametrize("quality", [101, -2, 0, "as"])
def test_aplot_quality_fail(mapdl, block, quality):
with pytest.raises(
ValueError,
match="The argument 'quality' can only be a integer between 1 and 10 (included both)",
):
mapdl.aplot(quality=quality)
Loading