From 1d4aa8d3d7a2c8ee97a9af5350ce4b656a8306ec Mon Sep 17 00:00:00 2001 From: gmalinve Date: Wed, 24 Jun 2026 15:57:02 +0200 Subject: [PATCH 01/23] Add create named selection and get obejcts from named selections --- src/ansys/aedt/core/modeler/cad/primitives.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/ansys/aedt/core/modeler/cad/primitives.py b/src/ansys/aedt/core/modeler/cad/primitives.py index 6c4e9f1d6543..b8a5d3bbefe7 100644 --- a/src/ansys/aedt/core/modeler/cad/primitives.py +++ b/src/ansys/aedt/core/modeler/cad/primitives.py @@ -51,6 +51,7 @@ from ansys.aedt.core.generic.numbers_utils import decompose_variable_value from ansys.aedt.core.generic.numbers_utils import is_number from ansys.aedt.core.generic.quaternion import Quaternion +from ansys.aedt.core.internal.errors import AEDTRuntimeError from ansys.aedt.core.internal.errors import GrpcApiError from ansys.aedt.core.modeler.cad.components_3d import UserDefinedComponent from ansys.aedt.core.modeler.cad.elements_3d import EdgePrimitive @@ -1236,6 +1237,68 @@ def get_objects_by_material(self, material: str | None = None) -> list: ] return obj_lst + @pyaedt_function_handler() + def create_named_selection(self, name: str, objects: list[str] | list[int]) -> str: + """Create a new named selection given objects or face ids. + + Parameters + ---------- + name : str + Name of the named selection. + objects : list of str or int + List of object names or face ids to include in the named selection. + + Returns + ------- + str + Name of the created named selection. + + References + ---------- + >>> oEditor.CreateNamedSelection + """ + is_str_list = all(isinstance(obj, str) for obj in objects) + is_id_list = all(isinstance(obj, int) for obj in objects) + + if is_str_list: + selection_type = "Object" + selection = ", ".join(objects) + elif is_id_list: + selection_type = "Face" + selection = objects + + params = ["NAME:NamedSelectionParameters", "Type:=", selection_type, "Selection:=", selection] + attr = ["NAME:Attributes", "Name:=", name, "UDM ID:=", -1] + self.oeditor.CreateNamedSelection(params, attr) + return name + + @pyaedt_function_handler + def get_named_selection_objects(self, name: str) -> list: + """Objects in named selection. + + Parameters + ---------- + name : str + Name of the named selection. + + Returns + ------- + list + List of objects contained in the named selection. + + Reference + --------- + >>> oEditor.GetEntityIDsContainedByNamedSelection + """ + try: + self.oeditor.GetNamedSelectionIDByName(name) + except Exception: + raise AEDTRuntimeError("Named selection does not exist.") + + entity_ids = self.oeditor.GetEntityIDsContainedByNamedSelection(name) + selected_objects = [self.objects[int(entity_id)] for entity_id in entity_ids] + return selected_objects + @pyaedt_function_handler() def _get_coordinates_data(self): # pragma: no cover coord = [] From e70435e647075f06b2b08cbed86eff027440cefa Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Wed, 24 Jun 2026 14:00:54 +0000 Subject: [PATCH 02/23] chore: adding changelog file 7832.added.md [dependabot-skip] --- doc/changelog.d/7832.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/7832.added.md diff --git a/doc/changelog.d/7832.added.md b/doc/changelog.d/7832.added.md new file mode 100644 index 000000000000..d808eb3867a5 --- /dev/null +++ b/doc/changelog.d/7832.added.md @@ -0,0 +1 @@ +Add create named selection and get obejcts from named selections From ab685511f4a25be2a246c7d0f0606c5acdec948a Mon Sep 17 00:00:00 2001 From: gmalinve Date: Wed, 24 Jun 2026 16:33:29 +0200 Subject: [PATCH 03/23] add tests to create and get objs in named selection --- tests/system/general/test_primitives_3d.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/system/general/test_primitives_3d.py b/tests/system/general/test_primitives_3d.py index 0a419db7f14c..e1105f3aaede 100644 --- a/tests/system/general/test_primitives_3d.py +++ b/tests/system/general/test_primitives_3d.py @@ -2563,3 +2563,24 @@ def test_delete_all_points(aedt_app) -> None: assert result assert [] == aedt_app.modeler.oeditor.GetPoints() + + +def test_create_named_selections(aedt_app) -> None: + aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + + result = aedt_app.modeler.create_named_selection(name="test", objects=aedt_app.modeler.object_names) + + assert result == "test" + + +def test_get_named_selection_objects(aedt_app) -> None: + box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + + aedt_app.modeler.create_named_selection(name="test", objects=aedt_app.modeler.object_names) + objs = aedt_app.modeler.get_named_selection_objects(name="test") + + assert isinstance(objs, list) + assert box1 in objs + assert box2 in objs From d15e490760b2b47abdc208717b74a7429ee714a0 Mon Sep 17 00:00:00 2001 From: gmalinve Date: Fri, 3 Jul 2026 11:47:41 +0200 Subject: [PATCH 04/23] add new NamedSelection --- src/ansys/aedt/core/modeler/cad/modeler.py | 185 ++++++++++++++++++ src/ansys/aedt/core/modeler/cad/primitives.py | 68 ++++--- tests/system/general/test_3d_modeler.py | 8 +- 3 files changed, 232 insertions(+), 29 deletions(-) diff --git a/src/ansys/aedt/core/modeler/cad/modeler.py b/src/ansys/aedt/core/modeler/cad/modeler.py index 5b8cb6f20bdd..9c6cba6cc558 100644 --- a/src/ansys/aedt/core/modeler/cad/modeler.py +++ b/src/ansys/aedt/core/modeler/cad/modeler.py @@ -32,6 +32,8 @@ from __future__ import annotations +import warnings + from ansys.aedt.core.base import PyAedtBase from ansys.aedt.core.generic.data_handlers import _dict2arg from ansys.aedt.core.generic.file_utils import generate_unique_name @@ -40,6 +42,7 @@ from ansys.aedt.core.generic.general_methods import settings from ansys.aedt.core.generic.numbers_utils import _units_assignment from ansys.aedt.core.generic.quaternion import Quaternion +from ansys.aedt.core.internal.errors import AEDTRuntimeError from ansys.aedt.core.modeler.cad.elements_3d import EdgePrimitive from ansys.aedt.core.modeler.cad.elements_3d import FacePrimitive from ansys.aedt.core.modeler.cad.elements_3d import VertexPrimitive @@ -1733,6 +1736,12 @@ class Lists(PropsManager, PyAedtBase): """ def __init__(self, modeler, props=None, name: str | None = None) -> None: + # Deprecated: Lists will be replaced by NamedSelections in a future release. + warnings.warn( + "`Lists` is deprecated and will be removed in a future release. Use `NamedSelections` instead.", + DeprecationWarning, + stacklevel=2, + ) self.auto_update = True self._modeler = modeler self.name = name @@ -1891,6 +1900,182 @@ def _list_verification(self, object_list, list_type): return object_list_new +class NamedSelections(PropsManager, PyAedtBase): + """Manages Named Selections (replacement for `Lists`). + + Keeps create and delete behavior and provides an ``update`` method that wraps + the native ``oEditor.EditNamedSelection`` call. + """ + + def __init__(self, modeler, props=None, name: str | None = None) -> None: + self.auto_update = True + self._modeler = modeler + self.name = name + # Reuse ListsProps for the simple properties structure + self.props = ListsProps(self, props) + + @pyaedt_function_handler() + def create( + self, assignment: list | str | None = None, name: str | None = None, entity_type: str = "Object" + ) -> bool: + """Create a named selection. + + Parameters + ---------- + assignment : list or str, optional + List of object names or face ids or a comma-separated string. + name : str, optional + Name of the named selection. If not provided a unique name is generated. + entity_type : str optional + Type of the selection: ``"Object"`` or ``"Face"``. Default is ``"Object"``. + """ + if not name: + name = generate_unique_name(entity_type + "NamedSelection") + + user_list_names = [sel for sel in self._modeler.user_lists if sel.name == name] + if name in user_list_names: + raise ValueError(f"Named selection with name '{name}' already exists.") + + # Normalize selection + if isinstance(assignment, (list, tuple)): + if all(isinstance(x, int) for x in assignment): + selection = assignment + sel_type = "Face" + else: + selection = ", ".join([str(x) for x in assignment]) + sel_type = entity_type + elif isinstance(assignment, str): + selection = assignment + sel_type = entity_type + else: + selection = "" + sel_type = entity_type + + params = ["NAME:NamedSelectionParameters", "Type:=", sel_type, "Selection:=", selection] + attr = ["NAME:Attributes", "Name:=", name, "UDM ID:=", -1] + try: + self._modeler.oeditor.CreateNamedSelection(params, attr) + except Exception: + raise AEDTRuntimeError("Failed to create named selection") + + props = {} + props["Selection"] = selection + props["Type"] = sel_type + self.props = ListsProps(self, props) + # Keep compatibility with existing storage + try: + self._modeler.user_lists.append(self) + except Exception: + pass + self.name = name + return True + + @pyaedt_function_handler() + def delete(self) -> bool: + """Delete the named selection.""" + self._modeler.oeditor.Delete(["NAME:Selections", "Selections:=", self.name]) + try: + self._modeler.user_lists.remove(self) + except Exception: + pass + return True + + @pyaedt_function_handler() + def rename(self, name: str) -> bool: + """Rename the named selection. + + Parameters + ---------- + name : str + New name for the named selection. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + """ + argument = [ + "NAME:AllTabs", + [ + "NAME:Geometry3DListTab", + ["NAME:PropServers", self.name], + ["NAME:ChangedProps", ["NAME:Name", "Value:=", name]], + ], + ] + self._modeler.oeditor.ChangeProperty(argument) + self.name = name + return True + + @pyaedt_function_handler() + def update(self, selection: list | str | None = None, entity_type: str = "Object", mode: str = "Reassign") -> bool: + """Update an existing named selection using the native EditNamedSelection wrapper. + + This method mirrors the signature and semantics of :class:`Lists.update` for + backward compatibility. If ``selection`` is ``None``, the stored properties + in ``self.props`` are used. + + Parameters + ---------- + selection : str or list, optional + Comma-separated string of object names or a list of names/ids. If + ``None``, uses the selection stored in ``self.props["Selection"]``. + entity_type : str, optional + Type of selection, e.g. ``"Object"`` or ``"Face"``. Default ``"Object"``. + mode : str, optional + Edit mode for the named selection. Default ``"Reassign"``. + """ + # If no selection provided, use stored properties + if selection is None: + selection = self.props.get("Selection", "") + entity_type = self.props.get("Type", entity_type) + + # If caller provided a list, normalize using Lists._list_verification to keep + # behavior consistent with Lists.update + if isinstance(selection, (list, tuple)): + try: + object_list_new = Lists._list_verification(self, selection, entity_type) + except Exception: + # Fallback to simple string conversion if verification fails + selection = ", ".join([str(s) for s in selection]) + else: + if entity_type == "Object": + selection = ", ".join(object_list_new) + else: + # For faces, keep a list of ints as EditNamedSelection accepts it + selection = object_list_new + + # If selection is a string, leave it as-is + + argument1 = ["NAME:Selections", "Selections:=", self.name] + argument2 = [ + "NAME:NamedSelectionParameters", + "Type:=", + entity_type, + "Selection:=", + selection, + "Mode:=", + mode, + ] + try: + self._modeler.oeditor.EditNamedSelection(argument1, argument2) + except Exception: + raise ValueError("Failed to update named selection") + + # Update stored props to reflect the new selection/type when provided + try: + # self.props calls __setitem__ and internally calls update() + # auto_update needs to be disabled + previous_auto_update = self.auto_update + self.auto_update = False + self.props["Selection"] = selection + self.props["Type"] = entity_type + self.auto_update = previous_auto_update + except Exception: + pass + + return True + + class Modeler(PyAedtBase): """Provides the `Modeler` application class that other `Modeler` classes inherit. diff --git a/src/ansys/aedt/core/modeler/cad/primitives.py b/src/ansys/aedt/core/modeler/cad/primitives.py index b8a5d3bbefe7..8b27037ff332 100644 --- a/src/ansys/aedt/core/modeler/cad/primitives.py +++ b/src/ansys/aedt/core/modeler/cad/primitives.py @@ -64,6 +64,7 @@ from ansys.aedt.core.modeler.cad.modeler import FaceCoordinateSystem from ansys.aedt.core.modeler.cad.modeler import Lists from ansys.aedt.core.modeler.cad.modeler import Modeler +from ansys.aedt.core.modeler.cad.modeler import NamedSelections from ansys.aedt.core.modeler.cad.modeler import ObjectCoordinateSystem from ansys.aedt.core.modeler.cad.object_3d import Object3d from ansys.aedt.core.modeler.cad.object_3d import PolylineSegment @@ -4420,13 +4421,13 @@ def edit_region_dimensions(self, values: list) -> bool: return True @pyaedt_function_handler() - def create_face_list(self, assignment: list, name: str = None) -> Lists | bool: + def create_face_list(self, assignment: list, name: str = None) -> Lists | NamedSelections | bool: """Create a list of faces given a list of face ID or a list of objects. Parameters ---------- assignment : list - List of face ID or list of objects + List of face ID or list of FacePrimitive objects. name : str, optional Name of the new list. @@ -4445,28 +4446,35 @@ def create_face_list(self, assignment: list, name: str = None) -> Lists | bool: if i.name == name: self.logger.warning("A List with the specified name already exists!") return i + # Check whether all elements of list are either int or FacePrimitve objects + all_int = all(isinstance(item, int) for item in assignment) + all_face_primitive = all(isinstance(item, FacePrimitive) for item in assignment) + + if not (all_int or all_face_primitive): + raise AEDTRuntimeError("`assignment` must contain only integers (face IDs) or only FacePrimitive objects.") assignment = self.convert_to_selections(assignment, True) - user_list = Lists(self) + # Prefer NamedSelections and only fall back to legacy Lists if create() fails. + user_list = NamedSelections(self) list_type = "Face" - if user_list: - result = user_list.create(assignment=assignment, name=name, entity_type=list_type) - if result: - return user_list - else: # pragma: no cover - self._app.logger.error("Wrong object definition. Review object list and type") - return False - else: # pragma: no cover - self._app.logger.error("User list object could not be created") - return False + result = user_list.create(assignment=assignment, name=name, entity_type=list_type) + if result: + return user_list + # If NamedSelections.create failed, try legacy Lists implementation + user_list = Lists(self) + result = user_list.create(assignment=assignment, name=name, entity_type=list_type) + if result: + return user_list + # If both failed raise an exception + raise AEDTRuntimeError("Failed to create faces list") @pyaedt_function_handler() - def create_object_list(self, assignment: list, name: str | None = None) -> Lists | bool: + def create_object_list(self, assignment: list, name: str | None = None) -> Lists | NamedSelections | bool: """Create an object list given a list of object names. Parameters ---------- assignment : list - List of object names. + List of object names or Object3D. name : str, optional Name of the new object list. @@ -4484,19 +4492,27 @@ def create_object_list(self, assignment: list, name: str | None = None) -> Lists if i.name == name: self.logger.warning("A List with the specified name already exists!") return i + # Check whether all elements of list are either string or Object3D objects + all_str = all(isinstance(item, str) for item in assignment) + all_object_3d = all(isinstance(item, Object3d) for item in assignment) + + if not (all_str or all_object_3d): + raise AEDTRuntimeError("`assignment` must contain only strings (object names) or only Object3d objects.") + assignment = self.convert_to_selections(assignment, True) - user_list = Lists(self) + # Prefer NamedSelections and only fall back to legacy Lists if create() fails. + user_list = NamedSelections(self) list_type = "Object" - if user_list: - result = user_list.create(assignment=assignment, name=name, entity_type=list_type) - if result: - return user_list - else: # pragma: no cover - self._app.logger.error("Wrong object definition. Review object list and type") - return False - else: # pragma: no cover - self._app.logger.error("User list object could not be created") - return False + result = user_list.create(assignment=assignment, name=name, entity_type=list_type) + if result: + return user_list + # If NamedSelections.create failed, try legacy Lists implementation + user_list = Lists(self) + result = user_list.create(assignment=assignment, name=name, entity_type=list_type) + if result: + return user_list + # If both failed raise exception + raise AEDTRuntimeError("Failed to create objects list") @pyaedt_function_handler() def generate_object_history(self, assignment: str | int, non_model: bool = False) -> bool: diff --git a/tests/system/general/test_3d_modeler.py b/tests/system/general/test_3d_modeler.py index aca3411f8f7c..73c46a56765f 100644 --- a/tests/system/general/test_3d_modeler.py +++ b/tests/system/general/test_3d_modeler.py @@ -34,6 +34,7 @@ from ansys.aedt.core.generic.math_utils import MathUtils from ansys.aedt.core.generic.numbers_utils import decompose_variable_value from ansys.aedt.core.generic.quaternion import Quaternion +from ansys.aedt.core.internal.errors import AEDTRuntimeError from ansys.aedt.core.modeler.cad.elements_3d import FacePrimitive from ansys.aedt.core.modeler.cad.elements_3d import VertexPrimitive from ansys.aedt.core.modeler.cad.modeler import FaceCoordinateSystem @@ -342,8 +343,8 @@ def test_create_face_list(coaxial) -> None: assert fl2.update() assert coaxial.modeler.create_face_list(fl, "my_face_list") == fl2 assert coaxial.modeler.create_face_list(fl) - assert coaxial.modeler.create_face_list([str(fl[0])]) - assert not coaxial.modeler.create_face_list(["outer2"]) + with pytest.raises(AEDTRuntimeError): + coaxial.modeler.create_face_list(["outer2"]) def test_create_object_list(coaxial) -> None: @@ -362,7 +363,8 @@ def test_create_object_list(coaxial) -> None: assert coaxial.modeler.user_lists[-1].rename("new_list") assert coaxial.modeler.user_lists[-1].delete() assert coaxial.modeler.create_object_list(["Core", "outer"]) - assert not coaxial.modeler.create_object_list(["Core2", "Core3"]) + with pytest.raises(AEDTRuntimeError): + coaxial.modeler.create_object_list(["Core2", "Core3"]) def test_create_outer_face_list(aedt_app) -> None: From 942c08bb80191eee11a7768c45779b66e58db119 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Fri, 3 Jul 2026 09:54:37 +0000 Subject: [PATCH 05/23] chore: adding changelog file 7832.added.md [dependabot-skip] --- doc/changelog.d/7832.added.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.d/7832.added.md b/doc/changelog.d/7832.added.md index d808eb3867a5..fce3b1c3782d 100644 --- a/doc/changelog.d/7832.added.md +++ b/doc/changelog.d/7832.added.md @@ -1 +1 @@ -Add create named selection and get obejcts from named selections +Add new class for named selections From 37f068fd628e01f255203edfd0bb77330ed8ad68 Mon Sep 17 00:00:00 2001 From: gmalinve Date: Fri, 3 Jul 2026 15:14:22 +0200 Subject: [PATCH 06/23] update tests --- src/ansys/aedt/core/modeler/cad/modeler.py | 25 +++++++++++++++++++--- tests/system/general/test_3d_modeler.py | 3 +++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/ansys/aedt/core/modeler/cad/modeler.py b/src/ansys/aedt/core/modeler/cad/modeler.py index c6db412c4fe6..f4eda4fdfe55 100644 --- a/src/ansys/aedt/core/modeler/cad/modeler.py +++ b/src/ansys/aedt/core/modeler/cad/modeler.py @@ -2109,6 +2109,10 @@ def create( Name of the named selection. If not provided a unique name is generated. entity_type : str optional Type of the selection: ``"Object"`` or ``"Face"``. Default is ``"Object"``. + + References + ---------- + >>> oEditor.CreateNamedSelection """ if not name: name = generate_unique_name(entity_type + "NamedSelection") @@ -2153,7 +2157,12 @@ def create( @pyaedt_function_handler() def delete(self) -> bool: - """Delete the named selection.""" + """Delete the named selection. + + References + ---------- + >>> oEditor.Delete + """ self._modeler.oeditor.Delete(["NAME:Selections", "Selections:=", self.name]) try: self._modeler.user_lists.remove(self) @@ -2174,6 +2183,10 @@ def rename(self, name: str) -> bool: ------- bool ``True`` when successful, ``False`` when failed. + + References + ---------- + >>> oEditor.ChangeProperty """ argument = [ "NAME:AllTabs", @@ -2189,7 +2202,7 @@ def rename(self, name: str) -> bool: @pyaedt_function_handler() def update(self, selection: list | str | None = None, entity_type: str = "Object", mode: str = "Reassign") -> bool: - """Update an existing named selection using the native EditNamedSelection wrapper. + """Update an existing named selection. This method mirrors the signature and semantics of :class:`Lists.update` for backward compatibility. If ``selection`` is ``None``, the stored properties @@ -2203,7 +2216,13 @@ def update(self, selection: list | str | None = None, entity_type: str = "Object entity_type : str, optional Type of selection, e.g. ``"Object"`` or ``"Face"``. Default ``"Object"``. mode : str, optional - Edit mode for the named selection. Default ``"Reassign"``. + Edit mode for the named selection. + Options are: ``"Reassign"``, ``"Add"`,``"Remove"``. + The default value is ``"Reassign"``. + + References + ---------- + >>> oEditor.EditNamedSelection """ # If no selection provided, use stored properties if selection is None: diff --git a/tests/system/general/test_3d_modeler.py b/tests/system/general/test_3d_modeler.py index 73c46a56765f..b4578ed4906a 100644 --- a/tests/system/general/test_3d_modeler.py +++ b/tests/system/general/test_3d_modeler.py @@ -352,6 +352,9 @@ def test_create_object_list(coaxial) -> None: fl1 = coaxial.modeler.create_object_list([o2.name], "my_object_list") assert fl1 assert fl1.update() + assert fl1.update(selection=[coaxial.modeler["inner"]], entity_type="Object", mode="Add") + assert fl1.update(selection=[coaxial.modeler["inner"]], entity_type="Object", mode="Remove") + assert fl1.update(selection=[coaxial.modeler["inner"]], entity_type="Object", mode="Reassign") assert coaxial.modeler.create_object_list([o2.name], "my_object_list") == fl1 assert coaxial.modeler.create_object_list(["Core", "outer"]) coaxial.modeler.user_lists[-1].props["List"] = ["outer", "Core", "inner"] From 056c1a7c1c49290f035fe14bedd963dd7dca0f76 Mon Sep 17 00:00:00 2001 From: gmalinve Date: Fri, 3 Jul 2026 15:53:43 +0200 Subject: [PATCH 07/23] Add example docstrings --- src/ansys/aedt/core/modeler/cad/modeler.py | 48 +++++++++++++++++++ src/ansys/aedt/core/modeler/cad/primitives.py | 27 +++++++---- 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/src/ansys/aedt/core/modeler/cad/modeler.py b/src/ansys/aedt/core/modeler/cad/modeler.py index f4eda4fdfe55..c026f2543a94 100644 --- a/src/ansys/aedt/core/modeler/cad/modeler.py +++ b/src/ansys/aedt/core/modeler/cad/modeler.py @@ -2110,6 +2110,17 @@ def create( entity_type : str optional Type of the selection: ``"Object"`` or ``"Face"``. Default is ``"Object"``. + Examples + -------- + >>> from ansys.aedt.core.modeler.cad.modeler import NamedSelections + >>> m3d = Maxwell3d(version="2026.1") + >>> box1 = m3d.modeler.create_box([1, 1, 1], [5, 2, 5], name="box1") + Create a new instance of NamedSelections + >>> lst1 = NamedSelections(m3d.modeler) + Create a named selection by specifying assignment and entity type + >>> lst1.create(assignment=["box1"], name="my_list", entity_type="Object") + >>> m3d.release_desktop(False, False) + References ---------- >>> oEditor.CreateNamedSelection @@ -2159,6 +2170,16 @@ def create( def delete(self) -> bool: """Delete the named selection. + Examples + -------- + >>> from ansys.aedt.core import Maxwell3d + >>> m3d = Maxwell3d(version="2026.1") + >>> box1 = m3d.modeler.create_box([1, 1, 1], [5, 2, 5], name="box1") + Create Objects list and delete it afterward + >>> lst = m3d.modeler.create_object_list(assignment=["box1"], name="my_list") + >>> lst.delete() + >>> m3d.release_desktop(False, False) + References ---------- >>> oEditor.Delete @@ -2179,6 +2200,16 @@ def rename(self, name: str) -> bool: name : str New name for the named selection. + Examples + -------- + >>> from ansys.aedt.core import Maxwell3d + >>> m3d = Maxwell3d(version="2026.1") + >>> box1 = m3d.modeler.create_box([1, 1, 1], [5, 2, 5], name="box1") + Create Objects list and rename it afterward + >>> lst = m3d.modeler.create_object_list(assignment=["box1"], name="my_list") + >>> lst.rename(name="new_list") + >>> m3d.release_desktop(False, False) + Returns ------- bool @@ -2220,6 +2251,23 @@ def update(self, selection: list | str | None = None, entity_type: str = "Object Options are: ``"Reassign"``, ``"Add"`,``"Remove"``. The default value is ``"Reassign"``. + Examples + -------- + >>> from ansys.aedt.core import Maxwell3d + >>> m3d = Maxwell3d(version="2026.1") + >>> box1 = m3d.modeler.create_box([1, 1, 1], [5, 2, 5], name="box1") + >>> box2 = m3d.modeler.create_box([2, 2, 2], [5, 2, 5], name="box2") + >>> box3 = m3d.modeler.create_box([3, 3, 3], [5, 2, 5], name="box3") + Create objects list + >>> lst = m3d.modeler.create_object_list(assignment=["box1"], name="my_list") + Add box2 in the NamedSelection "my_list" + >>> lst.update(selection=[m3d.modeler["box2"]], entity_type="Object", mode="Add") + Remove box2 in the NamedSelection "my_list" + >>> lst.update(selection=[m3d.modeler["box2"]], entity_type="Object", mode="Remove") + Reassign box2 in the NamedSelection "my_list" + >>> lst.update(selection=[m3d.modeler["box2"]], entity_type="Object", mode="Reassign") + >>> m3d.release_desktop(False, False) + References ---------- >>> oEditor.EditNamedSelection diff --git a/src/ansys/aedt/core/modeler/cad/primitives.py b/src/ansys/aedt/core/modeler/cad/primitives.py index 747e9e9a0f96..82f816057fb0 100644 --- a/src/ansys/aedt/core/modeler/cad/primitives.py +++ b/src/ansys/aedt/core/modeler/cad/primitives.py @@ -5130,18 +5130,23 @@ def create_face_list(self, assignment: list, name: str = None) -> Lists | NamedS Returns ------- - :class:`ansys.aedt.core.modeler.Modeler.Lists` + :class:`ansys.aedt.core.modeler.Modeler.Lists` or + :class:`ansys.aedt.core.modeler.Modeler.NamedSelections` List object when successful, ``False`` when failed. References ---------- >>> oEditor.CreateEntityList + >>> oEditor.CreateNamedSelection Examples -------- - >>> from ansys.aedt.core.modeler.cad.primitives import GeometryModeler - >>> obj = GeometryModeler() - >>> obj.create_face_list(assignment="Box1") + >>> from ansys.aedt.core import Maxwell3d + >>> m3d = Maxwell3d(version="2026.1") + >>> box1 = m3d.modeler.create_box([1, 1, 1], [5, 2, 5], name="box1") + >>> faces = m3d.modeler.get_object_faces(box1.name) + >>> fl = m3d.modeler.create_face_list(faces, "my_face_list") + >>> m3d.release_desktop(False, False) """ if name: @@ -5183,19 +5188,23 @@ def create_object_list(self, assignment: list, name: str | None = None) -> Lists Returns ------- - :class:`ansys.aedt.core.modeler.Modeler.Lists` + :class:`ansys.aedt.core.modeler.Modeler.Lists` or + :class:`ansys.aedt.core.modeler.Modeler.NamedSelections` List object when successful, ``False`` when failed. References ---------- >>> oEditor.CreateEntityList + >>> oEditor.CreateNamedSelection Examples -------- - >>> from ansys.aedt.core.modeler.cad.primitives import GeometryModeler - >>> obj = GeometryModeler() - >>> obj.create_object_list(assignment="Box1") - + >>> from ansys.aedt.core import Maxwell3d + >>> m3d = Maxwell3d(version="2026.1") + >>> box1 = m3d.modeler.create_box([1, 1, 1], [5, 2, 5], name="box1") + >>> box2 = m3d.modeler.create_box([2, 2, 2], [5, 2, 5], name="box2") + >>> lst = m3d.modeler.create_object_list(assignment=["box1", "box2"], name="my_list") + >>> m3d.release_desktop(False, False) """ if name: for i in self.user_lists: From e7b159764e5283a8da51d5962e6fd96bcc799757 Mon Sep 17 00:00:00 2001 From: gmalinve Date: Fri, 3 Jul 2026 15:56:13 +0200 Subject: [PATCH 08/23] Add example docstrings --- src/ansys/aedt/core/modeler/cad/modeler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ansys/aedt/core/modeler/cad/modeler.py b/src/ansys/aedt/core/modeler/cad/modeler.py index c026f2543a94..a73699969e8a 100644 --- a/src/ansys/aedt/core/modeler/cad/modeler.py +++ b/src/ansys/aedt/core/modeler/cad/modeler.py @@ -1893,9 +1893,9 @@ class Lists(PropsManager, PyAedtBase): """ def __init__(self, modeler, props=None, name: str | None = None) -> None: - # Deprecated: Lists will be replaced by NamedSelections in a future release. + # Deprecated: Lists will be replaced by NamedSelections in future releases. warnings.warn( - "`Lists` is deprecated and will be removed in a future release. Use `NamedSelections` instead.", + "`Lists` is deprecated and will be removed in future releases. Use `NamedSelections` instead.", DeprecationWarning, stacklevel=2, ) From 1ce50de5bd5c4a38f71b90979bb1af627787ffef Mon Sep 17 00:00:00 2001 From: gmalinve Date: Mon, 6 Jul 2026 15:28:04 +0200 Subject: [PATCH 09/23] fix icepak tests --- src/ansys/aedt/core/icepak.py | 2 +- src/ansys/aedt/core/modeler/cad/primitives.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ansys/aedt/core/icepak.py b/src/ansys/aedt/core/icepak.py index 113723bf4e96..c4ac02390851 100644 --- a/src/ansys/aedt/core/icepak.py +++ b/src/ansys/aedt/core/icepak.py @@ -355,9 +355,9 @@ def assign_openings(self, air_faces: list | FacePrimitive) -> BoundaryObject: """ boundary_name = generate_unique_name("Opening") - self.modeler.create_face_list(air_faces, "boundary_faces" + boundary_name) props = {} air_faces = self.modeler.convert_to_selections(air_faces, True) + self.modeler.create_face_list(air_faces, "boundary_faces" + boundary_name) props["Faces"] = air_faces props["Temperature"] = "AmbientTemp" diff --git a/src/ansys/aedt/core/modeler/cad/primitives.py b/src/ansys/aedt/core/modeler/cad/primitives.py index 82f816057fb0..367364723661 100644 --- a/src/ansys/aedt/core/modeler/cad/primitives.py +++ b/src/ansys/aedt/core/modeler/cad/primitives.py @@ -5155,12 +5155,12 @@ def create_face_list(self, assignment: list, name: str = None) -> Lists | NamedS self.logger.warning("A List with the specified name already exists!") return i # Check whether all elements of list are either int or FacePrimitve objects + assignment = self.convert_to_selections(assignment, True) all_int = all(isinstance(item, int) for item in assignment) all_face_primitive = all(isinstance(item, FacePrimitive) for item in assignment) if not (all_int or all_face_primitive): raise AEDTRuntimeError("`assignment` must contain only integers (face IDs) or only FacePrimitive objects.") - assignment = self.convert_to_selections(assignment, True) # Prefer NamedSelections and only fall back to legacy Lists if create() fails. user_list = NamedSelections(self) list_type = "Face" From b2052d7adf65ee22935ed74f824491f09b6d362f Mon Sep 17 00:00:00 2001 From: gmalinve Date: Tue, 7 Jul 2026 10:35:59 +0200 Subject: [PATCH 10/23] remove try/except --- src/ansys/aedt/core/modeler/cad/modeler.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ansys/aedt/core/modeler/cad/modeler.py b/src/ansys/aedt/core/modeler/cad/modeler.py index a73699969e8a..53940bbe2976 100644 --- a/src/ansys/aedt/core/modeler/cad/modeler.py +++ b/src/ansys/aedt/core/modeler/cad/modeler.py @@ -2159,10 +2159,7 @@ def create( props["Type"] = sel_type self.props = ListsProps(self, props) # Keep compatibility with existing storage - try: - self._modeler.user_lists.append(self) - except Exception: - pass + self._modeler.user_lists.append(self) self.name = name return True From 02ffc724f73ffc6939d60a2d2bbd1cf6ca8cb1db Mon Sep 17 00:00:00 2001 From: gmalinve Date: Tue, 7 Jul 2026 11:01:42 +0200 Subject: [PATCH 11/23] remove try/except --- src/ansys/aedt/core/modeler/cad/modeler.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/ansys/aedt/core/modeler/cad/modeler.py b/src/ansys/aedt/core/modeler/cad/modeler.py index 53940bbe2976..d7911905919d 100644 --- a/src/ansys/aedt/core/modeler/cad/modeler.py +++ b/src/ansys/aedt/core/modeler/cad/modeler.py @@ -2182,10 +2182,7 @@ def delete(self) -> bool: >>> oEditor.Delete """ self._modeler.oeditor.Delete(["NAME:Selections", "Selections:=", self.name]) - try: - self._modeler.user_lists.remove(self) - except Exception: - pass + self._modeler.user_lists.remove(self) return True @pyaedt_function_handler() @@ -2307,16 +2304,13 @@ def update(self, selection: list | str | None = None, entity_type: str = "Object raise ValueError("Failed to update named selection") # Update stored props to reflect the new selection/type when provided - try: - # self.props calls __setitem__ and internally calls update() - # auto_update needs to be disabled - previous_auto_update = self.auto_update - self.auto_update = False - self.props["Selection"] = selection - self.props["Type"] = entity_type - self.auto_update = previous_auto_update - except Exception: - pass + # self.props calls __setitem__ and internally calls update() + # auto_update needs to be disabled + previous_auto_update = self.auto_update + self.auto_update = False + self.props["Selection"] = selection + self.props["Type"] = entity_type + self.auto_update = previous_auto_update return True From 9128a299fa1e050db13c1f824ac79915e6817a1a Mon Sep 17 00:00:00 2001 From: gmalinve Date: Wed, 8 Jul 2026 14:20:26 +0200 Subject: [PATCH 12/23] add AEDT version check --- src/ansys/aedt/core/modeler/cad/modeler.py | 6 +-- src/ansys/aedt/core/modeler/cad/primitives.py | 40 +++++++++---------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/ansys/aedt/core/modeler/cad/modeler.py b/src/ansys/aedt/core/modeler/cad/modeler.py index d7911905919d..920a19dcb4d3 100644 --- a/src/ansys/aedt/core/modeler/cad/modeler.py +++ b/src/ansys/aedt/core/modeler/cad/modeler.py @@ -1889,13 +1889,12 @@ class Lists(PropsManager, PyAedtBase): -------- >>> from ansys.aedt.core.modeler.cad.modeler import Lists >>> obj = Lists() - """ def __init__(self, modeler, props=None, name: str | None = None) -> None: - # Deprecated: Lists will be replaced by NamedSelections in future releases. + # Deprecated: Lists is replaced by NamedSelections from AEDT 2026.1. warnings.warn( - "`Lists` is deprecated and will be removed in future releases. Use `NamedSelections` instead.", + "`Lists` is deprecated from AEDT 2026.1. Use `NamedSelections` instead.", DeprecationWarning, stacklevel=2, ) @@ -1918,7 +1917,6 @@ def update(self) -> bool: >>> from ansys.aedt.core.modeler.cad.modeler import Lists >>> obj = Lists() >>> obj.update() - """ # self._change_property(self.name, ["NAME:ChangedProps", ["NAME:Reference CS", "Value:=", self.ref_cs]]) object_list_new = self._list_verification(self.props["List"], self.props["Type"]) diff --git a/src/ansys/aedt/core/modeler/cad/primitives.py b/src/ansys/aedt/core/modeler/cad/primitives.py index 367364723661..8c0a1ed184c5 100644 --- a/src/ansys/aedt/core/modeler/cad/primitives.py +++ b/src/ansys/aedt/core/modeler/cad/primitives.py @@ -5147,7 +5147,6 @@ def create_face_list(self, assignment: list, name: str = None) -> Lists | NamedS >>> faces = m3d.modeler.get_object_faces(box1.name) >>> fl = m3d.modeler.create_face_list(faces, "my_face_list") >>> m3d.release_desktop(False, False) - """ if name: for i in self.user_lists: @@ -5161,17 +5160,17 @@ def create_face_list(self, assignment: list, name: str = None) -> Lists | NamedS if not (all_int or all_face_primitive): raise AEDTRuntimeError("`assignment` must contain only integers (face IDs) or only FacePrimitive objects.") - # Prefer NamedSelections and only fall back to legacy Lists if create() fails. - user_list = NamedSelections(self) list_type = "Face" - result = user_list.create(assignment=assignment, name=name, entity_type=list_type) - if result: - return user_list - # If NamedSelections.create failed, try legacy Lists implementation - user_list = Lists(self) - result = user_list.create(assignment=assignment, name=name, entity_type=list_type) - if result: - return user_list + if self._app._aedt_version >= "2026.1": + user_list = NamedSelections(self) + result = user_list.create(assignment=assignment, name=name, entity_type=list_type) + if result: + return user_list + else: + user_list = Lists(self) + result = user_list.create(assignment=assignment, name=name, entity_type=list_type) + if result: + return user_list # If both failed raise an exception raise AEDTRuntimeError("Failed to create faces list") @@ -5220,16 +5219,17 @@ def create_object_list(self, assignment: list, name: str | None = None) -> Lists assignment = self.convert_to_selections(assignment, True) # Prefer NamedSelections and only fall back to legacy Lists if create() fails. - user_list = NamedSelections(self) list_type = "Object" - result = user_list.create(assignment=assignment, name=name, entity_type=list_type) - if result: - return user_list - # If NamedSelections.create failed, try legacy Lists implementation - user_list = Lists(self) - result = user_list.create(assignment=assignment, name=name, entity_type=list_type) - if result: - return user_list + if self._app._aedt_version >= "2026.1": + user_list = NamedSelections(self) + result = user_list.create(assignment=assignment, name=name, entity_type=list_type) + if result: + return user_list + else: + user_list = Lists(self) + result = user_list.create(assignment=assignment, name=name, entity_type=list_type) + if result: + return user_list # If both failed raise exception raise AEDTRuntimeError("Failed to create objects list") From ecf882400ae298f08566755e1ff9d58cc45399eb Mon Sep 17 00:00:00 2001 From: gmalinve Date: Wed, 8 Jul 2026 14:22:31 +0200 Subject: [PATCH 13/23] pragma no cover --- src/ansys/aedt/core/modeler/cad/primitives.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ansys/aedt/core/modeler/cad/primitives.py b/src/ansys/aedt/core/modeler/cad/primitives.py index 8c0a1ed184c5..14f8b1ee2d43 100644 --- a/src/ansys/aedt/core/modeler/cad/primitives.py +++ b/src/ansys/aedt/core/modeler/cad/primitives.py @@ -5166,7 +5166,7 @@ def create_face_list(self, assignment: list, name: str = None) -> Lists | NamedS result = user_list.create(assignment=assignment, name=name, entity_type=list_type) if result: return user_list - else: + else: # pragma: no cover user_list = Lists(self) result = user_list.create(assignment=assignment, name=name, entity_type=list_type) if result: @@ -5225,7 +5225,7 @@ def create_object_list(self, assignment: list, name: str | None = None) -> Lists result = user_list.create(assignment=assignment, name=name, entity_type=list_type) if result: return user_list - else: + else: # pragma: no cover user_list = Lists(self) result = user_list.create(assignment=assignment, name=name, entity_type=list_type) if result: From 1d649a6fc821e8ff4a40d965afdeb65498962845 Mon Sep 17 00:00:00 2001 From: gmalinve Date: Wed, 22 Jul 2026 10:12:07 +0200 Subject: [PATCH 14/23] add warning messages to List methods and update new named selections methods --- src/ansys/aedt/core/modeler/cad/primitives.py | 143 +++++++++--------- tests/system/general/test_3d_modeler.py | 11 +- tests/system/general/test_primitives_3d.py | 11 +- 3 files changed, 80 insertions(+), 85 deletions(-) diff --git a/src/ansys/aedt/core/modeler/cad/primitives.py b/src/ansys/aedt/core/modeler/cad/primitives.py index 14f8b1ee2d43..aa421b6ecf7e 100644 --- a/src/ansys/aedt/core/modeler/cad/primitives.py +++ b/src/ansys/aedt/core/modeler/cad/primitives.py @@ -34,6 +34,7 @@ import secrets import string import time +import warnings import ansys.aedt.core from ansys.aedt.core.application.variables import Variable @@ -51,6 +52,7 @@ from ansys.aedt.core.generic.numbers_utils import decompose_variable_value from ansys.aedt.core.generic.numbers_utils import is_number from ansys.aedt.core.generic.quaternion import Quaternion +from ansys.aedt.core.internal.checks import min_aedt_version from ansys.aedt.core.internal.errors import AEDTRuntimeError from ansys.aedt.core.internal.errors import GrpcApiError from ansys.aedt.core.modeler.cad.components_3d import UserDefinedComponent @@ -1564,44 +1566,55 @@ def get_objects_by_material(self, material: str | None = None) -> list: return obj_lst @pyaedt_function_handler() - def create_named_selection(self, name: str, objects: list[str] | list[int]) -> str: + @min_aedt_version("2026.1") + def create_named_selection(self, name: str, assignment: list[str] | list[int]) -> NamedSelections | None: """Create a new named selection given objects or face ids. + Method valid from AEDT 2026.1. + Parameters ---------- name : str Name of the named selection. - objects : list of str or int + assignment : list of str or int List of object names or face ids to include in the named selection. Returns ------- - str - Name of the created named selection. + :class:`ansys.aedt.core.modeler.Modeler.NamedSelections` or bool References ---------- >>> oEditor.CreateNamedSelection """ - is_str_list = all(isinstance(obj, str) for obj in objects) - is_id_list = all(isinstance(obj, int) for obj in objects) + assignment = list(self.convert_to_selections(assignment, True)) + + if not assignment: + raise AEDTRuntimeError("`assignment` cannot be empty.") + + all_str_list = all(isinstance(obj, str) for obj in assignment) + all_id_list = all(isinstance(obj, int) and not isinstance(obj, bool) for obj in assignment) - if is_str_list: + if all_str_list: selection_type = "Object" - selection = ", ".join(objects) - elif is_id_list: + elif all_id_list: selection_type = "Face" - selection = objects + else: + raise AEDTRuntimeError("`assignment` must contain only integers (face IDs) or only object names.") - params = ["NAME:NamedSelectionParameters", "Type:=", selection_type, "Selection:=", selection] - attr = ["NAME:Attributes", "Name:=", name, "UDM ID:=", -1] - self.oeditor.CreateNamedSelection(params, attr) - return name + user_list = NamedSelections(self) + result = user_list.create(assignment=assignment, name=name, entity_type=selection_type) + if result: + return user_list + return False @pyaedt_function_handler + @min_aedt_version("2026.1") def get_named_selection_objects(self, name: str) -> list: """Objects in named selection. + Method valid from AEDT 2026.1. + Parameters ---------- name : str @@ -5117,121 +5130,107 @@ def edit_region_dimensions(self, values: list) -> bool: return True @pyaedt_function_handler() - def create_face_list(self, assignment: list, name: str = None) -> Lists | NamedSelections | bool: + def create_face_list(self, assignment: list, name: str = None) -> Lists | bool: """Create a list of faces given a list of face ID or a list of objects. + .. warning:: + + This method will be deprecated in future releases. + It is maintained for backward compatibility. + Use `create_named_selection` for AEDT releases >= 2026.1. + Parameters ---------- assignment : list - List of face ID or list of FacePrimitive objects. + List of face ID or list of objects name : str, optional Name of the new list. Returns ------- - :class:`ansys.aedt.core.modeler.Modeler.Lists` or - :class:`ansys.aedt.core.modeler.Modeler.NamedSelections` + :class:`ansys.aedt.core.modeler.Modeler.Lists` List object when successful, ``False`` when failed. References ---------- >>> oEditor.CreateEntityList - >>> oEditor.CreateNamedSelection - - Examples - -------- - >>> from ansys.aedt.core import Maxwell3d - >>> m3d = Maxwell3d(version="2026.1") - >>> box1 = m3d.modeler.create_box([1, 1, 1], [5, 2, 5], name="box1") - >>> faces = m3d.modeler.get_object_faces(box1.name) - >>> fl = m3d.modeler.create_face_list(faces, "my_face_list") - >>> m3d.release_desktop(False, False) """ + warnings.warn( + "`create_face_list` will soon be deprecated and will be removed in future releases." + "For AEDT version >= 2026.1 use `create_named_selection` instead.", + FutureWarning, + stacklevel=2, + ) if name: for i in self.user_lists: if i.name == name: self.logger.warning("A List with the specified name already exists!") return i - # Check whether all elements of list are either int or FacePrimitve objects assignment = self.convert_to_selections(assignment, True) - all_int = all(isinstance(item, int) for item in assignment) - all_face_primitive = all(isinstance(item, FacePrimitive) for item in assignment) - - if not (all_int or all_face_primitive): - raise AEDTRuntimeError("`assignment` must contain only integers (face IDs) or only FacePrimitive objects.") + user_list = Lists(self) list_type = "Face" - if self._app._aedt_version >= "2026.1": - user_list = NamedSelections(self) + if user_list: result = user_list.create(assignment=assignment, name=name, entity_type=list_type) if result: return user_list + else: # pragma: no cover + self._app.logger.error("Wrong object definition. Review object list and type") + return False else: # pragma: no cover - user_list = Lists(self) - result = user_list.create(assignment=assignment, name=name, entity_type=list_type) - if result: - return user_list - # If both failed raise an exception - raise AEDTRuntimeError("Failed to create faces list") + self._app.logger.error("User list object could not be created") + return False @pyaedt_function_handler() - def create_object_list(self, assignment: list, name: str | None = None) -> Lists | NamedSelections | bool: + def create_object_list(self, assignment: list, name: str | None = None) -> Lists | bool: """Create an object list given a list of object names. + .. warning:: + + This method will be deprecated in future releases. + It is maintained for backward compatibility. + Use `create_named_selection` for AEDT releases >= 2026.1. + Parameters ---------- assignment : list - List of object names or Object3D. + List of object names. name : str, optional Name of the new object list. Returns ------- - :class:`ansys.aedt.core.modeler.Modeler.Lists` or - :class:`ansys.aedt.core.modeler.Modeler.NamedSelections` + :class:`ansys.aedt.core.modeler.Modeler.Lists` List object when successful, ``False`` when failed. References ---------- >>> oEditor.CreateEntityList - >>> oEditor.CreateNamedSelection - - Examples - -------- - >>> from ansys.aedt.core import Maxwell3d - >>> m3d = Maxwell3d(version="2026.1") - >>> box1 = m3d.modeler.create_box([1, 1, 1], [5, 2, 5], name="box1") - >>> box2 = m3d.modeler.create_box([2, 2, 2], [5, 2, 5], name="box2") - >>> lst = m3d.modeler.create_object_list(assignment=["box1", "box2"], name="my_list") - >>> m3d.release_desktop(False, False) """ + warnings.warn( + "`create_object_list` will soon be deprecated and will be removed in future releases." + "For AEDT version >= 2026.1 use `create_named_selection` instead.", + FutureWarning, + stacklevel=2, + ) if name: for i in self.user_lists: if i.name == name: self.logger.warning("A List with the specified name already exists!") return i - # Check whether all elements of list are either string or Object3D objects - all_str = all(isinstance(item, str) for item in assignment) - all_object_3d = all(isinstance(item, Object3d) for item in assignment) - - if not (all_str or all_object_3d): - raise AEDTRuntimeError("`assignment` must contain only strings (object names) or only Object3d objects.") - assignment = self.convert_to_selections(assignment, True) - # Prefer NamedSelections and only fall back to legacy Lists if create() fails. + user_list = Lists(self) list_type = "Object" - if self._app._aedt_version >= "2026.1": - user_list = NamedSelections(self) + if user_list: result = user_list.create(assignment=assignment, name=name, entity_type=list_type) if result: return user_list + else: # pragma: no cover + self._app.logger.error("Wrong object definition. Review object list and type") + return False else: # pragma: no cover - user_list = Lists(self) - result = user_list.create(assignment=assignment, name=name, entity_type=list_type) - if result: - return user_list - # If both failed raise exception - raise AEDTRuntimeError("Failed to create objects list") + self._app.logger.error("User list object could not be created") + return False @pyaedt_function_handler() def generate_object_history(self, assignment: str | int, non_model: bool = False) -> bool: diff --git a/tests/system/general/test_3d_modeler.py b/tests/system/general/test_3d_modeler.py index 44fe4c3bc7e6..f75c3bde54cd 100644 --- a/tests/system/general/test_3d_modeler.py +++ b/tests/system/general/test_3d_modeler.py @@ -34,7 +34,6 @@ from ansys.aedt.core.generic.math_utils import MathUtils from ansys.aedt.core.generic.numbers_utils import decompose_variable_value from ansys.aedt.core.generic.quaternion import Quaternion -from ansys.aedt.core.internal.errors import AEDTRuntimeError from ansys.aedt.core.modeler.cad.elements_3d import FacePrimitive from ansys.aedt.core.modeler.cad.elements_3d import VertexPrimitive from ansys.aedt.core.modeler.cad.modeler import FaceCoordinateSystem @@ -343,8 +342,8 @@ def test_create_face_list(coaxial) -> None: assert fl2.update() assert coaxial.modeler.create_face_list(fl, "my_face_list") == fl2 assert coaxial.modeler.create_face_list(fl) - with pytest.raises(AEDTRuntimeError): - coaxial.modeler.create_face_list(["outer2"]) + assert coaxial.modeler.create_face_list([str(fl[0])]) + assert not coaxial.modeler.create_face_list(["outer2"]) def test_create_object_list(coaxial) -> None: @@ -352,9 +351,6 @@ def test_create_object_list(coaxial) -> None: fl1 = coaxial.modeler.create_object_list([o2.name], "my_object_list") assert fl1 assert fl1.update() - assert fl1.update(selection=[coaxial.modeler["inner"]], entity_type="Object", mode="Add") - assert fl1.update(selection=[coaxial.modeler["inner"]], entity_type="Object", mode="Remove") - assert fl1.update(selection=[coaxial.modeler["inner"]], entity_type="Object", mode="Reassign") assert coaxial.modeler.create_object_list([o2.name], "my_object_list") == fl1 assert coaxial.modeler.create_object_list(["Core", "outer"]) coaxial.modeler.user_lists[-1].props["List"] = ["outer", "Core", "inner"] @@ -366,8 +362,7 @@ def test_create_object_list(coaxial) -> None: assert coaxial.modeler.user_lists[-1].rename("new_list") assert coaxial.modeler.user_lists[-1].delete() assert coaxial.modeler.create_object_list(["Core", "outer"]) - with pytest.raises(AEDTRuntimeError): - coaxial.modeler.create_object_list(["Core2", "Core3"]) + assert not coaxial.modeler.create_object_list(["Core2", "Core3"]) def test_create_outer_face_list(aedt_app) -> None: diff --git a/tests/system/general/test_primitives_3d.py b/tests/system/general/test_primitives_3d.py index e1105f3aaede..f53fb2a76d60 100644 --- a/tests/system/general/test_primitives_3d.py +++ b/tests/system/general/test_primitives_3d.py @@ -2566,19 +2566,20 @@ def test_delete_all_points(aedt_app) -> None: def test_create_named_selections(aedt_app) -> None: - aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") - aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") - result = aedt_app.modeler.create_named_selection(name="test", objects=aedt_app.modeler.object_names) + result = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) - assert result == "test" + assert result.name == "test" + assert result.props["Selection"] == ", ".join([box1.name, box2.name]) def test_get_named_selection_objects(aedt_app) -> None: box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") - aedt_app.modeler.create_named_selection(name="test", objects=aedt_app.modeler.object_names) + aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) objs = aedt_app.modeler.get_named_selection_objects(name="test") assert isinstance(objs, list) From 40a2b4ddc4492f4795641e86002f7130e929a509 Mon Sep 17 00:00:00 2001 From: gmalinve Date: Wed, 22 Jul 2026 14:49:41 +0200 Subject: [PATCH 15/23] add tests for named selection methods --- src/ansys/aedt/core/modeler/cad/modeler.py | 72 +++++++++++-------- src/ansys/aedt/core/modeler/cad/primitives.py | 8 +++ tests/system/general/test_primitives_3d.py | 44 +++++++++++- 3 files changed, 93 insertions(+), 31 deletions(-) diff --git a/src/ansys/aedt/core/modeler/cad/modeler.py b/src/ansys/aedt/core/modeler/cad/modeler.py index 920a19dcb4d3..0f1ffc3bcdf8 100644 --- a/src/ansys/aedt/core/modeler/cad/modeler.py +++ b/src/ansys/aedt/core/modeler/cad/modeler.py @@ -2080,11 +2080,7 @@ def _list_verification(self, object_list, list_type): class NamedSelections(PropsManager, PyAedtBase): - """Manages Named Selections (replacement for `Lists`). - - Keeps create and delete behavior and provides an ``update`` method that wraps - the native ``oEditor.EditNamedSelection`` call. - """ + """Manages Named Selections (replacement for `Lists`).""" def __init__(self, modeler, props=None, name: str | None = None) -> None: self.auto_update = True @@ -2168,12 +2164,12 @@ def delete(self) -> bool: Examples -------- >>> from ansys.aedt.core import Maxwell3d - >>> m3d = Maxwell3d(version="2026.1") - >>> box1 = m3d.modeler.create_box([1, 1, 1], [5, 2, 5], name="box1") - Create Objects list and delete it afterward - >>> lst = m3d.modeler.create_object_list(assignment=["box1"], name="my_list") - >>> lst.delete() - >>> m3d.release_desktop(False, False) + >>> aedt_app = Maxwell3d(version="2026.1") + >>> box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + >>> box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + >>> sel = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) + >>> sel.delete() + >>> aedt_app.release_desktop() References ---------- @@ -2195,12 +2191,12 @@ def rename(self, name: str) -> bool: Examples -------- >>> from ansys.aedt.core import Maxwell3d - >>> m3d = Maxwell3d(version="2026.1") - >>> box1 = m3d.modeler.create_box([1, 1, 1], [5, 2, 5], name="box1") - Create Objects list and rename it afterward - >>> lst = m3d.modeler.create_object_list(assignment=["box1"], name="my_list") - >>> lst.rename(name="new_list") - >>> m3d.release_desktop(False, False) + >>> aedt_app = Maxwell3d(version="2026.1") + >>> box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + >>> box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + >>> sel = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) + >>> sel.rename(name="new_test") + >>> aedt_app.release_desktop() Returns ------- @@ -2246,19 +2242,18 @@ def update(self, selection: list | str | None = None, entity_type: str = "Object Examples -------- >>> from ansys.aedt.core import Maxwell3d - >>> m3d = Maxwell3d(version="2026.1") - >>> box1 = m3d.modeler.create_box([1, 1, 1], [5, 2, 5], name="box1") - >>> box2 = m3d.modeler.create_box([2, 2, 2], [5, 2, 5], name="box2") - >>> box3 = m3d.modeler.create_box([3, 3, 3], [5, 2, 5], name="box3") - Create objects list - >>> lst = m3d.modeler.create_object_list(assignment=["box1"], name="my_list") - Add box2 in the NamedSelection "my_list" - >>> lst.update(selection=[m3d.modeler["box2"]], entity_type="Object", mode="Add") - Remove box2 in the NamedSelection "my_list" - >>> lst.update(selection=[m3d.modeler["box2"]], entity_type="Object", mode="Remove") - Reassign box2 in the NamedSelection "my_list" - >>> lst.update(selection=[m3d.modeler["box2"]], entity_type="Object", mode="Reassign") - >>> m3d.release_desktop(False, False) + >>> aedt_app = Maxwell3d(version="2026.1") + >>> box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + >>> box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + >>> sel = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) + >>> box3 = aedt_app.modeler.create_box([20, 20, 20], [1, 2, 3], name="box3") + # Reassign named selection elements + >>> sel.update(selection=[box1.name, box3.name]) + # Add new element to name selection + >>> sel.update(selection=[box2.name], mode="Add") + # Remove element from named selection + >>> sel.update(selection=[box1.name], mode="Remove") + >>> aedt_app.release_desktop() References ---------- @@ -2273,6 +2268,23 @@ def update(self, selection: list | str | None = None, entity_type: str = "Object # behavior consistent with Lists.update if isinstance(selection, (list, tuple)): try: + if mode in ("Add", "Remove"): + current_selection = [ + s.strip() for s in str(self.props.get("Selection", "")).split(",") if s.strip() + ] + + if mode == "Add": + items_to_add = [] + for item in selection: + item_str = str(item) + if item_str not in current_selection and item_str not in items_to_add: + items_to_add.append(item_str) + selection = current_selection + items_to_add + + elif mode == "Remove": + items_to_remove = [str(item) for item in selection] + selection = [item for item in current_selection if item not in items_to_remove] + object_list_new = Lists._list_verification(self, selection, entity_type) except Exception: # Fallback to simple string conversion if verification fails diff --git a/src/ansys/aedt/core/modeler/cad/primitives.py b/src/ansys/aedt/core/modeler/cad/primitives.py index aa421b6ecf7e..888979eefcc1 100644 --- a/src/ansys/aedt/core/modeler/cad/primitives.py +++ b/src/ansys/aedt/core/modeler/cad/primitives.py @@ -1583,6 +1583,14 @@ def create_named_selection(self, name: str, assignment: list[str] | list[int]) - ------- :class:`ansys.aedt.core.modeler.Modeler.NamedSelections` or bool + Examples + -------- + >>> from ansys.aedt.core import Maxwell3d + >>> aedt_app = Maxwell3d(version="2026.1") + >>> box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + >>> box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + >>> sel = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) + References ---------- >>> oEditor.CreateNamedSelection diff --git a/tests/system/general/test_primitives_3d.py b/tests/system/general/test_primitives_3d.py index f53fb2a76d60..9093a67395e0 100644 --- a/tests/system/general/test_primitives_3d.py +++ b/tests/system/general/test_primitives_3d.py @@ -35,6 +35,7 @@ from ansys.aedt.core.hfss import Hfss from ansys.aedt.core.internal.errors import AEDTRuntimeError from ansys.aedt.core.modeler.cad.components_3d import UserDefinedComponent +from ansys.aedt.core.modeler.cad.modeler import NamedSelections from ansys.aedt.core.modeler.cad.object_3d import Object3d from ansys.aedt.core.modeler.cad.polylines import Polyline from ansys.aedt.core.modeler.cad.primitives import PolylineSegment @@ -2568,11 +2569,52 @@ def test_delete_all_points(aedt_app) -> None: def test_create_named_selections(aedt_app) -> None: box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + result = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) + assert result.name == "test" + assert result.props["Selection"] == ", ".join([box1.name, box2.name]) + +def test_delete_named_selections(aedt_app) -> None: + aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") result = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) + assert len(aedt_app.modeler.user_lists) == 1 + assert isinstance(aedt_app.modeler.user_lists[0], NamedSelections) + assert result.delete() + assert len(aedt_app.modeler.user_lists) == 0 + +def test_rename_named_selections(aedt_app) -> None: + aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + result = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) assert result.name == "test" - assert result.props["Selection"] == ", ".join([box1.name, box2.name]) + result.rename("new_name") + assert result.name == "new_name" + + +def test_update_named_selections(aedt_app) -> None: + box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + result = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) + selection_list = [item.strip() for item in result.props["Selection"].split(",") if item.strip()] + assert box1.name in selection_list + assert box2.name in selection_list + box3 = aedt_app.modeler.create_box([20, 20, 20], [1, 2, 3], name="box3") + result.update(selection=[box1.name, box3.name]) + selection_list = [item.strip() for item in result.props["Selection"].split(",") if item.strip()] + assert box1.name in selection_list + assert box3.name in selection_list + result.update(selection=[box2.name], mode="Add") + selection_list = [item.strip() for item in result.props["Selection"].split(",") if item.strip()] + assert box1.name in selection_list + assert box2.name in selection_list + assert box3.name in selection_list + result.update(selection=[box1.name], mode="Remove") + selection_list = [item.strip() for item in result.props["Selection"].split(",") if item.strip()] + assert box3.name in selection_list + assert box2.name in selection_list + assert box1.name not in selection_list def test_get_named_selection_objects(aedt_app) -> None: From 08e9c35593c1d33205d0f0e13a7cfa1036aa41f9 Mon Sep 17 00:00:00 2001 From: gmalinve Date: Wed, 22 Jul 2026 14:50:46 +0200 Subject: [PATCH 16/23] update docstring --- src/ansys/aedt/core/modeler/cad/modeler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/aedt/core/modeler/cad/modeler.py b/src/ansys/aedt/core/modeler/cad/modeler.py index 0f1ffc3bcdf8..fbba910a2205 100644 --- a/src/ansys/aedt/core/modeler/cad/modeler.py +++ b/src/ansys/aedt/core/modeler/cad/modeler.py @@ -2080,7 +2080,7 @@ def _list_verification(self, object_list, list_type): class NamedSelections(PropsManager, PyAedtBase): - """Manages Named Selections (replacement for `Lists`).""" + """Manages Named Selections (replacement for `Lists`) from AEDT version >= 2026.1.""" def __init__(self, modeler, props=None, name: str | None = None) -> None: self.auto_update = True From 463bdde9a1bf0e38938ac801c7fc07b9a4688cb0 Mon Sep 17 00:00:00 2001 From: gmalinve Date: Wed, 22 Jul 2026 16:22:57 +0200 Subject: [PATCH 17/23] update docstrings and logic --- src/ansys/aedt/core/modeler/cad/modeler.py | 50 +++++++++++-------- src/ansys/aedt/core/modeler/cad/primitives.py | 5 -- tests/system/general/test_primitives_3d.py | 2 + 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/ansys/aedt/core/modeler/cad/modeler.py b/src/ansys/aedt/core/modeler/cad/modeler.py index fbba910a2205..39dec54c3e8c 100644 --- a/src/ansys/aedt/core/modeler/cad/modeler.py +++ b/src/ansys/aedt/core/modeler/cad/modeler.py @@ -2115,6 +2115,16 @@ def create( >>> lst1.create(assignment=["box1"], name="my_list", entity_type="Object") >>> m3d.release_desktop(False, False) + Returns + ------- + bool + ``True`` when successful. + + Raises + ------ + AEDTRuntimeError + If the operation fails. + References ---------- >>> oEditor.CreateNamedSelection @@ -2146,16 +2156,14 @@ def create( try: self._modeler.oeditor.CreateNamedSelection(params, attr) except Exception: - raise AEDTRuntimeError("Failed to create named selection") - - props = {} - props["Selection"] = selection - props["Type"] = sel_type - self.props = ListsProps(self, props) - # Keep compatibility with existing storage - self._modeler.user_lists.append(self) - self.name = name - return True + raise AEDTRuntimeError("Failed to create the named selection.") + else: + props = {"Selection": selection, "Type": sel_type} + self.props = ListsProps(self, props) + # Keep compatibility with existing storage + self._modeler.user_lists.append(self) + self.name = name + return True @pyaedt_function_handler() def delete(self) -> bool: @@ -2171,6 +2179,11 @@ def delete(self) -> bool: >>> sel.delete() >>> aedt_app.release_desktop() + Returns + ------- + bool + ``True`` when successful. + References ---------- >>> oEditor.Delete @@ -2201,7 +2214,7 @@ def rename(self, name: str) -> bool: Returns ------- bool - ``True`` when successful, ``False`` when failed. + ``True`` when successful. References ---------- @@ -2255,6 +2268,11 @@ def update(self, selection: list | str | None = None, entity_type: str = "Object >>> sel.update(selection=[box1.name], mode="Remove") >>> aedt_app.release_desktop() + Returns + ------- + bool + ``True`` when successful. + References ---------- >>> oEditor.EditNamedSelection @@ -2308,20 +2326,12 @@ def update(self, selection: list | str | None = None, entity_type: str = "Object "Mode:=", mode, ] - try: - self._modeler.oeditor.EditNamedSelection(argument1, argument2) - except Exception: - raise ValueError("Failed to update named selection") - - # Update stored props to reflect the new selection/type when provided - # self.props calls __setitem__ and internally calls update() - # auto_update needs to be disabled + self._modeler.oeditor.EditNamedSelection(argument1, argument2) previous_auto_update = self.auto_update self.auto_update = False self.props["Selection"] = selection self.props["Type"] = entity_type self.auto_update = previous_auto_update - return True diff --git a/src/ansys/aedt/core/modeler/cad/primitives.py b/src/ansys/aedt/core/modeler/cad/primitives.py index 888979eefcc1..ca1ce4686d15 100644 --- a/src/ansys/aedt/core/modeler/cad/primitives.py +++ b/src/ansys/aedt/core/modeler/cad/primitives.py @@ -1597,9 +1597,6 @@ def create_named_selection(self, name: str, assignment: list[str] | list[int]) - """ assignment = list(self.convert_to_selections(assignment, True)) - if not assignment: - raise AEDTRuntimeError("`assignment` cannot be empty.") - all_str_list = all(isinstance(obj, str) for obj in assignment) all_id_list = all(isinstance(obj, int) and not isinstance(obj, bool) for obj in assignment) @@ -1607,8 +1604,6 @@ def create_named_selection(self, name: str, assignment: list[str] | list[int]) - selection_type = "Object" elif all_id_list: selection_type = "Face" - else: - raise AEDTRuntimeError("`assignment` must contain only integers (face IDs) or only object names.") user_list = NamedSelections(self) result = user_list.create(assignment=assignment, name=name, entity_type=selection_type) diff --git a/tests/system/general/test_primitives_3d.py b/tests/system/general/test_primitives_3d.py index 9093a67395e0..62711f7bbf92 100644 --- a/tests/system/general/test_primitives_3d.py +++ b/tests/system/general/test_primitives_3d.py @@ -2572,6 +2572,8 @@ def test_create_named_selections(aedt_app) -> None: result = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) assert result.name == "test" assert result.props["Selection"] == ", ".join([box1.name, box2.name]) + with pytest.raises(AEDTRuntimeError): + aedt_app.modeler.create_named_selection(name="test", assignment=["invalid"]) def test_delete_named_selections(aedt_app) -> None: From 2d3d2f86673fbb043f450f1b515aeff829929e85 Mon Sep 17 00:00:00 2001 From: gmalinve Date: Thu, 23 Jul 2026 14:48:42 +0200 Subject: [PATCH 18/23] updates in delete and rename --- src/ansys/aedt/core/modeler/cad/modeler.py | 57 +++++++- tests/system/general/test_primitives_3d.py | 3 + tests/unit/test_named_selections.py | 150 +++++++++++++++++++++ 3 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 tests/unit/test_named_selections.py diff --git a/src/ansys/aedt/core/modeler/cad/modeler.py b/src/ansys/aedt/core/modeler/cad/modeler.py index 39dec54c3e8c..f195139210b6 100644 --- a/src/ansys/aedt/core/modeler/cad/modeler.py +++ b/src/ansys/aedt/core/modeler/cad/modeler.py @@ -2089,6 +2089,37 @@ def __init__(self, modeler, props=None, name: str | None = None) -> None: # Reuse ListsProps for the simple properties structure self.props = ListsProps(self, props) + def _objects_verification(self, object_list, list_type): + object_list = self._modeler.convert_to_selections(object_list, True) + object_list_new = [] + if list_type == "Object": + obj_names = [i for i in self._modeler.object_names] + check = [item for item in object_list if item in obj_names] + if check: + object_list_new = check + else: + return [] + + elif list_type == "Face": + object_list_new = [] + for element in object_list: + if isinstance(element, str): + if element.isnumeric(): + object_list_new.append(int(element)) + else: + if element in self._modeler.object_names: + obj_id = self._modeler.objects[element].id + for sel in self._modeler.object_list: + if sel.id == obj_id: + for f in sel.faces: + object_list_new.append(f.id) + break + else: + return [] + else: + object_list_new.append(int(element)) + return object_list_new + @pyaedt_function_handler() def create( self, assignment: list | str | None = None, name: str | None = None, entity_type: str = "Object" @@ -2184,13 +2215,22 @@ def delete(self) -> bool: bool ``True`` when successful. + Raises + ------ + AEDTRuntimeError + If the operation fails. + References ---------- >>> oEditor.Delete """ self._modeler.oeditor.Delete(["NAME:Selections", "Selections:=", self.name]) self._modeler.user_lists.remove(self) - return True + user_list_names = [sel.name for sel in self._modeler.user_lists] + if self.name not in user_list_names: + return True + else: + raise AEDTRuntimeError("Failed to delete the named selection.") @pyaedt_function_handler() def rename(self, name: str) -> bool: @@ -2216,6 +2256,11 @@ def rename(self, name: str) -> bool: bool ``True`` when successful. + Raises + ------ + AEDTRuntimeError + If the operation fails. + References ---------- >>> oEditor.ChangeProperty @@ -2229,8 +2274,12 @@ def rename(self, name: str) -> bool: ], ] self._modeler.oeditor.ChangeProperty(argument) - self.name = name - return True + user_list_names = [sel.name for sel in self._modeler.user_lists] + if name in user_list_names: + self.name = name + return True + else: + raise AEDTRuntimeError("Failed to rename the named selection.") @pyaedt_function_handler() def update(self, selection: list | str | None = None, entity_type: str = "Object", mode: str = "Reassign") -> bool: @@ -2303,7 +2352,7 @@ def update(self, selection: list | str | None = None, entity_type: str = "Object items_to_remove = [str(item) for item in selection] selection = [item for item in current_selection if item not in items_to_remove] - object_list_new = Lists._list_verification(self, selection, entity_type) + object_list_new = self._objects_verification(self, selection, entity_type) except Exception: # Fallback to simple string conversion if verification fails selection = ", ".join([str(s) for s in selection]) diff --git a/tests/system/general/test_primitives_3d.py b/tests/system/general/test_primitives_3d.py index 62711f7bbf92..7ecd9abe5913 100644 --- a/tests/system/general/test_primitives_3d.py +++ b/tests/system/general/test_primitives_3d.py @@ -2629,3 +2629,6 @@ def test_get_named_selection_objects(aedt_app) -> None: assert isinstance(objs, list) assert box1 in objs assert box2 in objs + + with pytest.raises(AEDTRuntimeError): + aedt_app.modeler.get_named_selection_objects(name="invalid") diff --git a/tests/unit/test_named_selections.py b/tests/unit/test_named_selections.py new file mode 100644 index 000000000000..c5ee3ec13766 --- /dev/null +++ b/tests/unit/test_named_selections.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from unittest.mock import MagicMock +from unittest.mock import patch + +import pytest + +from ansys.aedt.core.internal.errors import AEDTRuntimeError +from ansys.aedt.core.modeler.cad.modeler import NamedSelections + +# NOTE: we avoid defining test-local helper classes; tests use MagicMock to +# simulate a user_lists object when needed. + + +@pytest.fixture +def named_selection_setup(request): + """Fixture used to provide either a MagicMock NamedSelections or a real instance. + + By default (no indirect param) it yields a MagicMock(spec=NamedSelections). + To get a real instance, parametrize the fixture with value "real": + @pytest.mark.parametrize("named_selection_setup", ["real"], indirect=True) + def test_x(named_selection_setup): + # named_selection_setup is a real NamedSelections instance + """ + mode = getattr(request, "param", "mock") + if mode == "mock": + with patch("ansys.aedt.core.modeler.cad.modeler.NamedSelections.__init__", lambda x: None): + mock_instance = MagicMock(spec=NamedSelections) + yield mock_instance + elif mode == "real": + # Create a fake modeler to attach to the real NamedSelections + fake_modeler = MagicMock() + fake_modeler.oeditor = MagicMock() + fake_modeler.user_lists = [] + ns = NamedSelections(fake_modeler, props={}, name="ns_real") + yield ns + else: + raise ValueError(f"Unknown fixture mode: {mode}") + + +@pytest.mark.parametrize("named_selection_setup", ["real"], indirect=True) +def test_create_and_delete_runs_real_method(named_selection_setup) -> None: + """Create a real NamedSelections via the fixture and exercise the real delete() method. + + This verifies the delete implementation calls the editor and removes the instance + from modeler.user_lists. + """ + ns = named_selection_setup + + # Ensure the real instance has a modeler and is present in user_lists + ns._modeler.user_lists.append(ns) + ns._modeler.oeditor.Delete = MagicMock(return_value=None) + + assert ns.delete() + + # Verify the editor Delete was called with the expected arguments + ns._modeler.oeditor.Delete.assert_called_once_with( + [ + "NAME:Selections", + "Selections:=", + ns.name, + ] + ) + + # Verify the named selection was removed from the modeler's user_lists + assert ns not in ns._modeler.user_lists + + +@pytest.mark.parametrize("named_selection_setup", ["real"], indirect=True) +def test_delete_raises_when_user_lists_remove_fails(named_selection_setup) -> None: + """Exercise delete() so that the internal removal from modeler.user_lists fails + and the method raises AEDTRuntimeError("Failed to delete the named selection."). + """ + ns = named_selection_setup + + # Replace the modeler's user_lists with a MagicMock that iterates over [ns] + # but whose remove() does nothing, to simulate a failed removal. + mock_lists = MagicMock() + mock_lists.__iter__.return_value = iter([ns]) + mock_lists.remove = MagicMock(return_value=None) + ns._modeler.user_lists = mock_lists + + # Make editor.Delete succeed + ns._modeler.oeditor.Delete = MagicMock(return_value=None) + + # Now calling delete() should reach the final check and raise AEDTRuntimeError + with pytest.raises(AEDTRuntimeError, match="Failed to delete the named selection."): + ns.delete() + + +class TestNamedSelectionsRename: + @pytest.mark.parametrize("named_selection_setup", ["real"], indirect=True) + def test_rename_success(self, named_selection_setup) -> None: + """Rename succeeds when the new name is not already present. + + Simplified: mock ChangeProperty, ensure rename updates local name and + ChangeProperty is called. + """ + ns = named_selection_setup + new_name = "renamed_ns" + + # Mock ChangeProperty to do nothing + ns._modeler.oeditor.ChangeProperty = MagicMock(return_value=None) + + # Simulate AEDT having the new named selection present after the rename + new_ns = MagicMock() + new_ns.name = new_name + ns._modeler.user_lists = [new_ns] + + # Call rename and verify it returns True and updates ns.name + assert ns.rename(new_name) + assert ns.name == new_name + + @pytest.mark.parametrize("named_selection_setup", ["real"], indirect=True) + def test_rename_failure_raises(self, named_selection_setup) -> None: + """Rename raises AEDTRuntimeError when the new name is not present in modeler.user_lists. + + Renaming to an existing name should be considered a failure. + """ + ns = named_selection_setup + # Mock ChangeProperty to do nothing + ns._modeler.oeditor.ChangeProperty = MagicMock(return_value=None) + # Simulate that ChangeProperty did not update modeler.user_lists: keep only ns + ns._modeler.user_lists = [ns] + + # Now rename should raise because the new name is not found in user_lists + with pytest.raises(AEDTRuntimeError, match="Failed to rename the named selection."): + ns.rename(ns.name) From 2a9b2cf4160f4d388114a35f4e4f0606ffce3209 Mon Sep 17 00:00:00 2001 From: gmalinve Date: Thu, 23 Jul 2026 14:51:40 +0200 Subject: [PATCH 19/23] update test --- tests/unit/test_named_selections.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/unit/test_named_selections.py b/tests/unit/test_named_selections.py index c5ee3ec13766..3038b6ee9bb0 100644 --- a/tests/unit/test_named_selections.py +++ b/tests/unit/test_named_selections.py @@ -135,10 +135,7 @@ def test_rename_success(self, named_selection_setup) -> None: @pytest.mark.parametrize("named_selection_setup", ["real"], indirect=True) def test_rename_failure_raises(self, named_selection_setup) -> None: - """Rename raises AEDTRuntimeError when the new name is not present in modeler.user_lists. - - Renaming to an existing name should be considered a failure. - """ + """Rename raises AEDTRuntimeError when the new name is not present in modeler.user_lists.""" ns = named_selection_setup # Mock ChangeProperty to do nothing ns._modeler.oeditor.ChangeProperty = MagicMock(return_value=None) @@ -147,4 +144,4 @@ def test_rename_failure_raises(self, named_selection_setup) -> None: # Now rename should raise because the new name is not found in user_lists with pytest.raises(AEDTRuntimeError, match="Failed to rename the named selection."): - ns.rename(ns.name) + ns.rename("invalid") From 0f37cd82f79e03c9cfbe237acfbf8bcae00debef Mon Sep 17 00:00:00 2001 From: gmalinve Date: Fri, 24 Jul 2026 17:18:02 +0200 Subject: [PATCH 20/23] update implementation --- src/ansys/aedt/core/modeler/cad/modeler.py | 145 ++++++++++-------- src/ansys/aedt/core/modeler/cad/primitives.py | 5 +- tests/system/general/test_primitives_3d.py | 57 +++---- tests/unit/test_named_selections.py | 70 ++++----- 4 files changed, 149 insertions(+), 128 deletions(-) diff --git a/src/ansys/aedt/core/modeler/cad/modeler.py b/src/ansys/aedt/core/modeler/cad/modeler.py index f195139210b6..672023723e56 100644 --- a/src/ansys/aedt/core/modeler/cad/modeler.py +++ b/src/ansys/aedt/core/modeler/cad/modeler.py @@ -2089,37 +2089,6 @@ def __init__(self, modeler, props=None, name: str | None = None) -> None: # Reuse ListsProps for the simple properties structure self.props = ListsProps(self, props) - def _objects_verification(self, object_list, list_type): - object_list = self._modeler.convert_to_selections(object_list, True) - object_list_new = [] - if list_type == "Object": - obj_names = [i for i in self._modeler.object_names] - check = [item for item in object_list if item in obj_names] - if check: - object_list_new = check - else: - return [] - - elif list_type == "Face": - object_list_new = [] - for element in object_list: - if isinstance(element, str): - if element.isnumeric(): - object_list_new.append(int(element)) - else: - if element in self._modeler.object_names: - obj_id = self._modeler.objects[element].id - for sel in self._modeler.object_list: - if sel.id == obj_id: - for f in sel.faces: - object_list_new.append(f.id) - break - else: - return [] - else: - object_list_new.append(int(element)) - return object_list_new - @pyaedt_function_handler() def create( self, assignment: list | str | None = None, name: str | None = None, entity_type: str = "Object" @@ -2189,7 +2158,7 @@ def create( except Exception: raise AEDTRuntimeError("Failed to create the named selection.") else: - props = {"Selection": selection, "Type": sel_type} + props = {"List": assignment, "Type": sel_type, "ID": self._modeler.oeditor.GetNamedSelectionIDByName(name)} self.props = ListsProps(self, props) # Keep compatibility with existing storage self._modeler.user_lists.append(self) @@ -2274,6 +2243,11 @@ def rename(self, name: str) -> bool: ], ] self._modeler.oeditor.ChangeProperty(argument) + old_name = self.name + self.name = name + for sel in self._modeler.user_lists: + if sel is self or sel.name == old_name: + sel.name = name user_list_names = [sel.name for sel in self._modeler.user_lists] if name in user_list_names: self.name = name @@ -2291,9 +2265,9 @@ def update(self, selection: list | str | None = None, entity_type: str = "Object Parameters ---------- - selection : str or list, optional - Comma-separated string of object names or a list of names/ids. If - ``None``, uses the selection stored in ``self.props["Selection"]``. + selection : list, optional + List of object names or list of FacePrimitive Ids. + If ``None``, uses the selection stored in ``self.props["Selection"]``. entity_type : str, optional Type of selection, e.g. ``"Object"`` or ``"Face"``. Default ``"Object"``. mode : str, optional @@ -2333,37 +2307,39 @@ def update(self, selection: list | str | None = None, entity_type: str = "Object # If caller provided a list, normalize using Lists._list_verification to keep # behavior consistent with Lists.update - if isinstance(selection, (list, tuple)): - try: - if mode in ("Add", "Remove"): - current_selection = [ - s.strip() for s in str(self.props.get("Selection", "")).split(",") if s.strip() - ] - - if mode == "Add": - items_to_add = [] - for item in selection: - item_str = str(item) - if item_str not in current_selection and item_str not in items_to_add: - items_to_add.append(item_str) - selection = current_selection + items_to_add - - elif mode == "Remove": - items_to_remove = [str(item) for item in selection] - selection = [item for item in current_selection if item not in items_to_remove] - - object_list_new = self._objects_verification(self, selection, entity_type) - except Exception: - # Fallback to simple string conversion if verification fails - selection = ", ".join([str(s) for s in selection]) + object_list_new = [] + try: + if mode in ("Add", "Remove"): + current_selection = self.props["List"] + + if mode == "Add": + items_to_add = [] + for item in selection: + item_str = str(item) + if item_str not in current_selection and item_str not in items_to_add: + items_to_add.append(item_str) + selection = current_selection + items_to_add + object_list_new = self._objects_verification(selection, entity_type) + + elif mode == "Remove": + selection = [str(item) for item in selection] + object_list_new = [item for item in current_selection if str(item) not in selection] + # update object_list_new + # selection = [item for item in current_selection if item not in items_to_remove] + + except Exception: + # Fallback to simple string conversion if verification fails + selection = ", ".join([str(s) for s in selection]) + else: + if entity_type == "Object": + selection = ", ".join(selection) else: - if entity_type == "Object": - selection = ", ".join(object_list_new) - else: - # For faces, keep a list of ints as EditNamedSelection accepts it - selection = object_list_new + # For faces, keep a list of ints as EditNamedSelection accepts it + selection = object_list_new - # If selection is a string, leave it as-is + # if object_list_new is empty, it means that the provided objects do not exist + # if not object_list_new: + # raise AEDTRuntimeError("Failed to update the named selection.") argument1 = ["NAME:Selections", "Selections:=", self.name] argument2 = [ @@ -2376,13 +2352,52 @@ def update(self, selection: list | str | None = None, entity_type: str = "Object mode, ] self._modeler.oeditor.EditNamedSelection(argument1, argument2) + + # self._modeler.get_named_selection_objects(self.name): + # Verify the selection was updated correctly + expected_objects = [item.name for item in self._modeler.get_named_selection_objects(self.name)] + # if object_list_new is empty it means that user has provided invalid or non-existing objects. + if not object_list_new or not object_list_new == expected_objects: + raise AEDTRuntimeError("Failed to update the named selection.") previous_auto_update = self.auto_update self.auto_update = False - self.props["Selection"] = selection + self.props["List"] = object_list_new self.props["Type"] = entity_type self.auto_update = previous_auto_update return True + def _objects_verification(self, object_list, list_type): + object_list = self._modeler.convert_to_selections(object_list, True) + object_list_new = [] + if list_type == "Object": + obj_names = [i for i in self._modeler.object_names] + # if user passes a non-existing object or an invalid object this check filters it out + check = [item for item in object_list if item in obj_names] + if check: + object_list_new = check + else: + return [] + + elif list_type == "Face": + object_list_new = [] + for element in object_list: + if isinstance(element, str): + if element.isnumeric(): + object_list_new.append(int(element)) + else: + if element in self._modeler.object_names: + obj_id = self._modeler.objects[element].id + for sel in self._modeler.object_list: + if sel.id == obj_id: + for f in sel.faces: + object_list_new.append(f.id) + break + else: + return [] + else: + object_list_new.append(int(element)) + return object_list_new + class Modeler(PyAedtBase): """Provides the `Modeler` application class that other `Modeler` classes inherit. diff --git a/src/ansys/aedt/core/modeler/cad/primitives.py b/src/ansys/aedt/core/modeler/cad/primitives.py index ca1ce4686d15..d2a0c3e1be0a 100644 --- a/src/ansys/aedt/core/modeler/cad/primitives.py +++ b/src/ansys/aedt/core/modeler/cad/primitives.py @@ -1792,7 +1792,10 @@ def _get_lists_data(self): # pragma: no cover props["List"] = name_list else: props["List"] = data["GeometryEntityListParameters"]["EntityList"] - design_lists.append(Lists(self, props, name)) + if self._app._aedt_version >= "2026.1": + design_lists.append(NamedSelections(self, props, name)) + else: + design_lists.append(Lists(self, props, name)) except Exception: self.logger.info("Lists were not retrieved from AEDT file") return design_lists diff --git a/tests/system/general/test_primitives_3d.py b/tests/system/general/test_primitives_3d.py index 7ecd9abe5913..fcba27558542 100644 --- a/tests/system/general/test_primitives_3d.py +++ b/tests/system/general/test_primitives_3d.py @@ -2569,9 +2569,9 @@ def test_delete_all_points(aedt_app) -> None: def test_create_named_selections(aedt_app) -> None: box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") - result = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) - assert result.name == "test" - assert result.props["Selection"] == ", ".join([box1.name, box2.name]) + ns = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) + assert ns.name == "test" + assert ns.props["Selection"] == ", ".join([box1.name, box2.name]) with pytest.raises(AEDTRuntimeError): aedt_app.modeler.create_named_selection(name="test", assignment=["invalid"]) @@ -2579,44 +2579,47 @@ def test_create_named_selections(aedt_app) -> None: def test_delete_named_selections(aedt_app) -> None: aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") - result = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) + ns = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) assert len(aedt_app.modeler.user_lists) == 1 assert isinstance(aedt_app.modeler.user_lists[0], NamedSelections) - assert result.delete() + assert ns.delete() assert len(aedt_app.modeler.user_lists) == 0 def test_rename_named_selections(aedt_app) -> None: aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") - result = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) - assert result.name == "test" - result.rename("new_name") - assert result.name == "new_name" + ns = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) + assert ns.name == "test" + ns.rename("new_name") + assert ns.name == "new_name" def test_update_named_selections(aedt_app) -> None: box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") - result = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) - selection_list = [item.strip() for item in result.props["Selection"].split(",") if item.strip()] - assert box1.name in selection_list - assert box2.name in selection_list + ns = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) + assert aedt_app.modeler[box1.name] in aedt_app.modeler.get_named_selection_objects(ns.name) + assert aedt_app.modeler[box2.name] in aedt_app.modeler.get_named_selection_objects(ns.name) + with pytest.raises(AEDTRuntimeError): + ns.update(selection=["invalid"]) box3 = aedt_app.modeler.create_box([20, 20, 20], [1, 2, 3], name="box3") - result.update(selection=[box1.name, box3.name]) - selection_list = [item.strip() for item in result.props["Selection"].split(",") if item.strip()] - assert box1.name in selection_list - assert box3.name in selection_list - result.update(selection=[box2.name], mode="Add") - selection_list = [item.strip() for item in result.props["Selection"].split(",") if item.strip()] - assert box1.name in selection_list - assert box2.name in selection_list - assert box3.name in selection_list - result.update(selection=[box1.name], mode="Remove") - selection_list = [item.strip() for item in result.props["Selection"].split(",") if item.strip()] - assert box3.name in selection_list - assert box2.name in selection_list - assert box1.name not in selection_list + ns.update(selection=["invalid", box3.name]) + assert aedt_app.modeler[box3.name] in aedt_app.modeler.get_named_selection_objects(ns.name) + assert len(aedt_app.modeler.get_named_selection_objects(ns.name)) == 1 + ns.update(selection=[box1.name, box3.name]) + assert aedt_app.modeler[box1.name] in aedt_app.modeler.get_named_selection_objects(ns.name) + assert aedt_app.modeler[box3.name] in aedt_app.modeler.get_named_selection_objects(ns.name) + assert len(aedt_app.modeler.get_named_selection_objects(ns.name)) == 2 + ns.update(selection=[box2.name], mode="Add") + assert aedt_app.modeler[box1.name] in aedt_app.modeler.get_named_selection_objects(ns.name) + assert aedt_app.modeler[box2.name] in aedt_app.modeler.get_named_selection_objects(ns.name) + assert aedt_app.modeler[box3.name] in aedt_app.modeler.get_named_selection_objects(ns.name) + assert len(aedt_app.modeler.get_named_selection_objects(ns.name)) == 3 + ns.update(selection=[box1.name], mode="Remove") + assert aedt_app.modeler[box2.name] in aedt_app.modeler.get_named_selection_objects(ns.name) + assert aedt_app.modeler[box3.name] in aedt_app.modeler.get_named_selection_objects(ns.name) + assert len(aedt_app.modeler.get_named_selection_objects(ns.name)) == 2 def test_get_named_selection_objects(aedt_app) -> None: diff --git a/tests/unit/test_named_selections.py b/tests/unit/test_named_selections.py index 3038b6ee9bb0..98bf7c4b64f2 100644 --- a/tests/unit/test_named_selections.py +++ b/tests/unit/test_named_selections.py @@ -110,38 +110,38 @@ def test_delete_raises_when_user_lists_remove_fails(named_selection_setup) -> No ns.delete() -class TestNamedSelectionsRename: - @pytest.mark.parametrize("named_selection_setup", ["real"], indirect=True) - def test_rename_success(self, named_selection_setup) -> None: - """Rename succeeds when the new name is not already present. - - Simplified: mock ChangeProperty, ensure rename updates local name and - ChangeProperty is called. - """ - ns = named_selection_setup - new_name = "renamed_ns" - - # Mock ChangeProperty to do nothing - ns._modeler.oeditor.ChangeProperty = MagicMock(return_value=None) - - # Simulate AEDT having the new named selection present after the rename - new_ns = MagicMock() - new_ns.name = new_name - ns._modeler.user_lists = [new_ns] - - # Call rename and verify it returns True and updates ns.name - assert ns.rename(new_name) - assert ns.name == new_name - - @pytest.mark.parametrize("named_selection_setup", ["real"], indirect=True) - def test_rename_failure_raises(self, named_selection_setup) -> None: - """Rename raises AEDTRuntimeError when the new name is not present in modeler.user_lists.""" - ns = named_selection_setup - # Mock ChangeProperty to do nothing - ns._modeler.oeditor.ChangeProperty = MagicMock(return_value=None) - # Simulate that ChangeProperty did not update modeler.user_lists: keep only ns - ns._modeler.user_lists = [ns] - - # Now rename should raise because the new name is not found in user_lists - with pytest.raises(AEDTRuntimeError, match="Failed to rename the named selection."): - ns.rename("invalid") +@pytest.mark.parametrize("named_selection_setup", ["real"], indirect=True) +def test_rename_success(self, named_selection_setup) -> None: + """Rename succeeds when the new name is not already present. + + Simplified: mock ChangeProperty, ensure rename updates local name and + ChangeProperty is called. + """ + ns = named_selection_setup + new_name = "renamed_ns" + + # Mock ChangeProperty to do nothing + ns._modeler.oeditor.ChangeProperty = MagicMock(return_value=None) + + # Simulate AEDT having the new named selection present after the rename + new_ns = MagicMock() + new_ns.name = new_name + ns._modeler.user_lists = [new_ns] + + # Call rename and verify it returns True and updates ns.name + assert ns.rename(new_name) + assert ns.name == new_name + + +@pytest.mark.parametrize("named_selection_setup", ["real"], indirect=True) +def test_rename_failure_raises(self, named_selection_setup) -> None: + """Rename raises AEDTRuntimeError when the new name is not present in modeler.user_lists.""" + ns = named_selection_setup + # Mock ChangeProperty to do nothing + ns._modeler.oeditor.ChangeProperty = MagicMock(return_value=None) + # Simulate that ChangeProperty did not update modeler.user_lists: keep only ns + ns._modeler.user_lists = [ns] + + # Now rename should raise because the new name is not found in user_lists + with pytest.raises(AEDTRuntimeError, match="Failed to rename the named selection."): + ns.rename("invalid") From 85e57969765c626b370341e8b3f7896805afc350 Mon Sep 17 00:00:00 2001 From: gmalinve Date: Mon, 27 Jul 2026 12:31:13 +0200 Subject: [PATCH 21/23] updates for faces selection --- src/ansys/aedt/core/modeler/cad/modeler.py | 116 +++++++++--------- src/ansys/aedt/core/modeler/cad/primitives.py | 11 +- tests/system/general/test_primitives_3d.py | 107 +++++++++++++++- 3 files changed, 167 insertions(+), 67 deletions(-) diff --git a/src/ansys/aedt/core/modeler/cad/modeler.py b/src/ansys/aedt/core/modeler/cad/modeler.py index 672023723e56..7ea94af726b5 100644 --- a/src/ansys/aedt/core/modeler/cad/modeler.py +++ b/src/ansys/aedt/core/modeler/cad/modeler.py @@ -2256,7 +2256,7 @@ def rename(self, name: str) -> bool: raise AEDTRuntimeError("Failed to rename the named selection.") @pyaedt_function_handler() - def update(self, selection: list | str | None = None, entity_type: str = "Object", mode: str = "Reassign") -> bool: + def update(self, selection: list | None = None, entity_type: str = "Object", mode: str = "Reassign") -> bool: """Update an existing named selection. This method mirrors the signature and semantics of :class:`Lists.update` for @@ -2277,20 +2277,38 @@ def update(self, selection: list | str | None = None, entity_type: str = "Object Examples -------- + Update Named Selection that contains objects >>> from ansys.aedt.core import Maxwell3d >>> aedt_app = Maxwell3d(version="2026.1") >>> box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") >>> box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") >>> sel = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) >>> box3 = aedt_app.modeler.create_box([20, 20, 20], [1, 2, 3], name="box3") - # Reassign named selection elements + Reassign named selection elements >>> sel.update(selection=[box1.name, box3.name]) - # Add new element to name selection + Add new element to name selection >>> sel.update(selection=[box2.name], mode="Add") - # Remove element from named selection + Remove element from named selection >>> sel.update(selection=[box1.name], mode="Remove") >>> aedt_app.release_desktop() + Update Named Selection that contains faces + >>> from ansys.aedt.core import Maxwell3d + >>> aedt_app = Maxwell3d(version="2026.1") + >>> box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + >>> box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + >>> ns = aedt_app.modeler.create_named_selection(name="test", assignment=[box1.faces[0], box2.faces[0]]) + Add two new faces in Named Selection + >>> ns.update( + ... selection=[ + ... aedt_app.modeler["box2"].faces[1].id, + ... aedt_app.modeler["box2"].faces[3].id, + ... ], + ... entity_type="Face", + ... mode="Add", + ... ) + >>> aedt_app.release_desktop() + Returns ------- bool @@ -2300,46 +2318,21 @@ def update(self, selection: list | str | None = None, entity_type: str = "Object ---------- >>> oEditor.EditNamedSelection """ - # If no selection provided, use stored properties + if mode not in ["Add", "Remove", "Reassign"]: + raise AEDTRuntimeError(f"Invalid mode `{mode}`. Allowed values are `Add`, `Remove`, `Reassign`.") + + # If no selection provided raise an exception if selection is None: - selection = self.props.get("Selection", "") - entity_type = self.props.get("Type", entity_type) + raise AEDTRuntimeError("No selection provided to update.") - # If caller provided a list, normalize using Lists._list_verification to keep - # behavior consistent with Lists.update - object_list_new = [] - try: - if mode in ("Add", "Remove"): - current_selection = self.props["List"] - - if mode == "Add": - items_to_add = [] - for item in selection: - item_str = str(item) - if item_str not in current_selection and item_str not in items_to_add: - items_to_add.append(item_str) - selection = current_selection + items_to_add - object_list_new = self._objects_verification(selection, entity_type) - - elif mode == "Remove": - selection = [str(item) for item in selection] - object_list_new = [item for item in current_selection if str(item) not in selection] - # update object_list_new - # selection = [item for item in current_selection if item not in items_to_remove] + current_selection = self.props["List"] - except Exception: - # Fallback to simple string conversion if verification fails - selection = ", ".join([str(s) for s in selection]) + object_list_new = self._objects_verification(selection, entity_type) + if entity_type == "Object": + selection = ", ".join(object_list_new) else: - if entity_type == "Object": - selection = ", ".join(selection) - else: - # For faces, keep a list of ints as EditNamedSelection accepts it - selection = object_list_new - - # if object_list_new is empty, it means that the provided objects do not exist - # if not object_list_new: - # raise AEDTRuntimeError("Failed to update the named selection.") + # For faces, keep a list of ints as EditNamedSelection accepts it + selection = object_list_new argument1 = ["NAME:Selections", "Selections:=", self.name] argument2 = [ @@ -2352,16 +2345,27 @@ def update(self, selection: list | str | None = None, entity_type: str = "Object mode, ] self._modeler.oeditor.EditNamedSelection(argument1, argument2) + # When updating a Named Selection the project must be saved + self._modeler._app.save_project() - # self._modeler.get_named_selection_objects(self.name): # Verify the selection was updated correctly - expected_objects = [item.name for item in self._modeler.get_named_selection_objects(self.name)] + named_selection_objects = self._modeler.get_named_selection_objects(self.name) or [] + if entity_type == "Object": + expected_objects = [obj.name for obj in named_selection_objects] + else: + expected_objects = named_selection_objects + if mode == "Add": + expected_after = current_selection + object_list_new + elif mode == "Remove": + expected_after = [item for item in current_selection if item not in object_list_new] + else: # Reassign + expected_after = object_list_new # if object_list_new is empty it means that user has provided invalid or non-existing objects. - if not object_list_new or not object_list_new == expected_objects: + if not object_list_new or set(expected_after) != set(expected_objects): raise AEDTRuntimeError("Failed to update the named selection.") previous_auto_update = self.auto_update self.auto_update = False - self.props["List"] = object_list_new + self.props["List"] = expected_after self.props["Type"] = entity_type self.auto_update = previous_auto_update return True @@ -2380,22 +2384,14 @@ def _objects_verification(self, object_list, list_type): elif list_type == "Face": object_list_new = [] - for element in object_list: - if isinstance(element, str): - if element.isnumeric(): - object_list_new.append(int(element)) - else: - if element in self._modeler.object_names: - obj_id = self._modeler.objects[element].id - for sel in self._modeler.object_list: - if sel.id == obj_id: - for f in sel.faces: - object_list_new.append(f.id) - break - else: - return [] - else: - object_list_new.append(int(element)) + faces = [] + for obj in self._modeler.object_list: + faces.extend(f.id for f in obj.faces) + for face_id in object_list: + if face_id in faces: + object_list_new.append(int(face_id)) + if not object_list_new: + return [] return object_list_new diff --git a/src/ansys/aedt/core/modeler/cad/primitives.py b/src/ansys/aedt/core/modeler/cad/primitives.py index d2a0c3e1be0a..bda6288bb795 100644 --- a/src/ansys/aedt/core/modeler/cad/primitives.py +++ b/src/ansys/aedt/core/modeler/cad/primitives.py @@ -1633,13 +1633,18 @@ def get_named_selection_objects(self, name: str) -> list: >>> oEditor.GetEntityIDsContainedByNamedSelection """ try: - self.oeditor.GetNamedSelectionIDByName(name) + ns_id = self.oeditor.GetNamedSelectionIDByName(name) except Exception: raise AEDTRuntimeError("Named selection does not exist.") entity_ids = self.oeditor.GetEntityIDsContainedByNamedSelection(name) - selected_objects = [self.objects[int(entity_id)] for entity_id in entity_ids] - return selected_objects + + if (ns := next((n for n in self.user_lists if n.props.get("ID") == ns_id), None)) and ns.props.get( + "Type" + ) == "Object": + return [self.objects[int(entity_id)] for entity_id in entity_ids] + + return [int(entity_id) for entity_id in entity_ids] @pyaedt_function_handler() def _get_coordinates_data(self): # pragma: no cover diff --git a/tests/system/general/test_primitives_3d.py b/tests/system/general/test_primitives_3d.py index fcba27558542..1a5cf9ca0a25 100644 --- a/tests/system/general/test_primitives_3d.py +++ b/tests/system/general/test_primitives_3d.py @@ -2566,7 +2566,7 @@ def test_delete_all_points(aedt_app) -> None: assert [] == aedt_app.modeler.oeditor.GetPoints() -def test_create_named_selections(aedt_app) -> None: +def test_create_named_selections_objects(aedt_app) -> None: box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") ns = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) @@ -2576,7 +2576,7 @@ def test_create_named_selections(aedt_app) -> None: aedt_app.modeler.create_named_selection(name="test", assignment=["invalid"]) -def test_delete_named_selections(aedt_app) -> None: +def test_delete_named_selections_objects(aedt_app) -> None: aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") ns = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) @@ -2586,7 +2586,7 @@ def test_delete_named_selections(aedt_app) -> None: assert len(aedt_app.modeler.user_lists) == 0 -def test_rename_named_selections(aedt_app) -> None: +def test_rename_named_selections_objects(aedt_app) -> None: aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") ns = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) @@ -2595,33 +2595,117 @@ def test_rename_named_selections(aedt_app) -> None: assert ns.name == "new_name" -def test_update_named_selections(aedt_app) -> None: +def test_update_named_selections_objects(aedt_app) -> None: box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") ns = aedt_app.modeler.create_named_selection(name="test", assignment=aedt_app.modeler.object_names) assert aedt_app.modeler[box1.name] in aedt_app.modeler.get_named_selection_objects(ns.name) assert aedt_app.modeler[box2.name] in aedt_app.modeler.get_named_selection_objects(ns.name) + with pytest.raises(AEDTRuntimeError): + ns.update(selection=[]) with pytest.raises(AEDTRuntimeError): ns.update(selection=["invalid"]) box3 = aedt_app.modeler.create_box([20, 20, 20], [1, 2, 3], name="box3") ns.update(selection=["invalid", box3.name]) + aedt_app.save_project() assert aedt_app.modeler[box3.name] in aedt_app.modeler.get_named_selection_objects(ns.name) assert len(aedt_app.modeler.get_named_selection_objects(ns.name)) == 1 ns.update(selection=[box1.name, box3.name]) + aedt_app.save_project() assert aedt_app.modeler[box1.name] in aedt_app.modeler.get_named_selection_objects(ns.name) assert aedt_app.modeler[box3.name] in aedt_app.modeler.get_named_selection_objects(ns.name) assert len(aedt_app.modeler.get_named_selection_objects(ns.name)) == 2 ns.update(selection=[box2.name], mode="Add") + aedt_app.save_project() assert aedt_app.modeler[box1.name] in aedt_app.modeler.get_named_selection_objects(ns.name) assert aedt_app.modeler[box2.name] in aedt_app.modeler.get_named_selection_objects(ns.name) assert aedt_app.modeler[box3.name] in aedt_app.modeler.get_named_selection_objects(ns.name) assert len(aedt_app.modeler.get_named_selection_objects(ns.name)) == 3 ns.update(selection=[box1.name], mode="Remove") + aedt_app.save_project() assert aedt_app.modeler[box2.name] in aedt_app.modeler.get_named_selection_objects(ns.name) assert aedt_app.modeler[box3.name] in aedt_app.modeler.get_named_selection_objects(ns.name) assert len(aedt_app.modeler.get_named_selection_objects(ns.name)) == 2 +def test_create_named_selections_faces(aedt_app) -> None: + box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + ns = aedt_app.modeler.create_named_selection( + name="test", + assignment=[ + box1.faces[0], + box2.faces[0], + ], + ) + assert ns.props["Type"] == "Face" + assert len(ns.props["List"]) == 2 + with pytest.raises(AEDTRuntimeError): + aedt_app.modeler.create_named_selection(name="test", assignment=["invalid"]) + + +def test_delete_named_selections_faces(aedt_app) -> None: + box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + ns = aedt_app.modeler.create_named_selection( + name="test", + assignment=[ + box1.faces[0], + box2.faces[0], + ], + ) + assert len(aedt_app.modeler.user_lists) == 1 + assert isinstance(aedt_app.modeler.user_lists[0], NamedSelections) + assert ns.delete() + assert len(aedt_app.modeler.user_lists) == 0 + + +def test_rename_named_selections_faces(aedt_app) -> None: + box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + ns = aedt_app.modeler.create_named_selection( + name="test", + assignment=[ + box1.faces[0], + box2.faces[0], + ], + ) + assert ns.name == "test" + ns.rename("new_name") + assert ns.name == "new_name" + + +def test_update_named_selections_faces(aedt_app) -> None: + box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + ns = aedt_app.modeler.create_named_selection( + name="test", + assignment=[ + box1.faces[0], + box2.faces[0], + ], + ) + assert box1.faces[0].id in aedt_app.modeler.get_named_selection_objects(ns.name) + assert box2.faces[0].id in aedt_app.modeler.get_named_selection_objects(ns.name) + with pytest.raises(AEDTRuntimeError): + ns.update(selection=["invalid"], entity_type="Face", mode="Add") + ns.update( + selection=[aedt_app.modeler["box2"].faces[1].id, aedt_app.modeler["box2"].faces[3].id], + entity_type="Face", + mode="Add", + ) + assert box2.faces[1].id in aedt_app.modeler.get_named_selection_objects(ns.name) + assert box2.faces[3].id in aedt_app.modeler.get_named_selection_objects(ns.name) + ns.update(selection=[aedt_app.modeler["box1"].faces[1].id, "invalid"], entity_type="Face", mode="Add") + assert len(aedt_app.modeler.get_named_selection_objects(ns.name)) == 5 + assert box2.faces[1].id in aedt_app.modeler.get_named_selection_objects(ns.name) + assert box2.faces[3].id in aedt_app.modeler.get_named_selection_objects(ns.name) + assert box1.faces[1].id in aedt_app.modeler.get_named_selection_objects(ns.name) + ns.update(selection=[box2.faces[1].id], entity_type="Face", mode="Reassign") + assert len(aedt_app.modeler.get_named_selection_objects(ns.name)) == 1 + assert box2.faces[1].id in aedt_app.modeler.get_named_selection_objects(ns.name) + + def test_get_named_selection_objects(aedt_app) -> None: box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") @@ -2635,3 +2719,18 @@ def test_get_named_selection_objects(aedt_app) -> None: with pytest.raises(AEDTRuntimeError): aedt_app.modeler.get_named_selection_objects(name="invalid") + + +def test_get_named_selection_faces(aedt_app) -> None: + box1 = aedt_app.modeler.create_box([0, 0, 0], [1, 2, 3], name="box1") + box2 = aedt_app.modeler.create_box([10, 10, 10], [1, 2, 3], name="box2") + + ns = aedt_app.modeler.create_named_selection( + name="test", + assignment=[ + box1.faces[0], + box2.faces[0], + ], + ) + assert len(aedt_app.modeler.get_named_selection_objects(ns.name)) == 2 + assert isinstance(all(aedt_app.modeler.get_named_selection_objects(ns.name)), int) From 8b90d6a57e91e6ae2cdc7ce384325e8bda2b44c6 Mon Sep 17 00:00:00 2001 From: gmalinve Date: Mon, 27 Jul 2026 12:31:36 +0200 Subject: [PATCH 22/23] updates for faces selection --- src/ansys/aedt/core/modeler/cad/modeler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/aedt/core/modeler/cad/modeler.py b/src/ansys/aedt/core/modeler/cad/modeler.py index 7ea94af726b5..019bbf812aa9 100644 --- a/src/ansys/aedt/core/modeler/cad/modeler.py +++ b/src/ansys/aedt/core/modeler/cad/modeler.py @@ -2322,7 +2322,7 @@ def update(self, selection: list | None = None, entity_type: str = "Object", mod raise AEDTRuntimeError(f"Invalid mode `{mode}`. Allowed values are `Add`, `Remove`, `Reassign`.") # If no selection provided raise an exception - if selection is None: + if not selection: raise AEDTRuntimeError("No selection provided to update.") current_selection = self.props["List"] From 42c7d33c62d3ba5b4998b037b60b327a308519f8 Mon Sep 17 00:00:00 2001 From: gmalinve Date: Mon, 27 Jul 2026 14:29:50 +0200 Subject: [PATCH 23/23] fix UT --- tests/unit/test_named_selections.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_named_selections.py b/tests/unit/test_named_selections.py index 98bf7c4b64f2..6454ae53acbf 100644 --- a/tests/unit/test_named_selections.py +++ b/tests/unit/test_named_selections.py @@ -111,7 +111,7 @@ def test_delete_raises_when_user_lists_remove_fails(named_selection_setup) -> No @pytest.mark.parametrize("named_selection_setup", ["real"], indirect=True) -def test_rename_success(self, named_selection_setup) -> None: +def test_rename_success(named_selection_setup) -> None: """Rename succeeds when the new name is not already present. Simplified: mock ChangeProperty, ensure rename updates local name and @@ -134,13 +134,17 @@ def test_rename_success(self, named_selection_setup) -> None: @pytest.mark.parametrize("named_selection_setup", ["real"], indirect=True) -def test_rename_failure_raises(self, named_selection_setup) -> None: +def test_rename_failure_raises(named_selection_setup) -> None: """Rename raises AEDTRuntimeError when the new name is not present in modeler.user_lists.""" ns = named_selection_setup # Mock ChangeProperty to do nothing ns._modeler.oeditor.ChangeProperty = MagicMock(return_value=None) - # Simulate that ChangeProperty did not update modeler.user_lists: keep only ns - ns._modeler.user_lists = [ns] + ns.name = "old_name" + # Simulate that ChangeProperty did not update modeler.user_lists: + # keep a list that does not contain this instance nor the old name. + other_ns = MagicMock() + other_ns.name = "unrelated_name" + ns._modeler.user_lists = [other_ns] # Now rename should raise because the new name is not found in user_lists with pytest.raises(AEDTRuntimeError, match="Failed to rename the named selection."):