From bb2d14b80e2b86f712667c7d63812460fe249aef Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Tue, 4 Nov 2025 09:04:18 -0500 Subject: [PATCH 01/14] wip - vertex has error --- .../_grpc/_services/v0/named_selection.py | 1 + src/ansys/geometry/core/designer/beam.py | 20 ++++ src/ansys/geometry/core/designer/body.py | 27 +++++ src/ansys/geometry/core/designer/edge.py | 21 ++++ src/ansys/geometry/core/designer/face.py | 21 ++++ src/ansys/geometry/core/designer/selection.py | 4 + src/ansys/geometry/core/designer/vertex.py | 35 ++++++ tests/integration/test_design.py | 100 ++++++++++++++++++ tests/integration/test_edges.py | 23 ++++ 9 files changed, 252 insertions(+) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py b/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py index 761eaf7430..208471eaf6 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py @@ -57,6 +57,7 @@ def get_named_selection(self, **kwargs): # noqa: D102 response = self.stub.Get(request) # Return the response - formatted as a dictionary + print('grpc response:', [vertex.id.id for vertex in response.vertices]) return { "id": response.id, "name": response.name, diff --git a/src/ansys/geometry/core/designer/beam.py b/src/ansys/geometry/core/designer/beam.py index 5d13f4c2ba..ed177022e7 100644 --- a/src/ansys/geometry/core/designer/beam.py +++ b/src/ansys/geometry/core/designer/beam.py @@ -30,6 +30,7 @@ from ansys.geometry.core.math.frame import Frame from ansys.geometry.core.math.point import Point3D from ansys.geometry.core.math.vector import UnitVector3D +from ansys.geometry.core.misc.auxiliary import get_design_from_component from ansys.geometry.core.misc.checks import check_type from ansys.geometry.core.misc.measurements import Distance from ansys.geometry.core.shapes.curves.trimmed_curve import TrimmedCurve @@ -37,6 +38,7 @@ if TYPE_CHECKING: # pragma: no cover from ansys.geometry.core.designer.component import Component + from ansys.geometry.core.designer.selection import NamedSelection class BeamType(Enum): @@ -422,6 +424,24 @@ def is_alive(self) -> bool: """Flag indicating whether the beam is still alive on the server.""" return self._is_alive + def get_named_selections(self) -> list["NamedSelection"]: + """Get the named selections that include this beam. + + Returns + ------- + list["NamedSelection"] + List of named selections that include this beam. + """ + design = get_design_from_component(self) + named_selections = design.named_selections + + included_ns = [] + for ns in named_selections: + if self in ns.beams: + included_ns.append(ns) + + return included_ns + def __repr__(self) -> str: """Represent the beam as a string.""" lines = [f"ansys.geometry.core.designer.Beam {hex(id(self))}"] diff --git a/src/ansys/geometry/core/designer/body.py b/src/ansys/geometry/core/designer/body.py index 4475d803f7..e6c7683aee 100644 --- a/src/ansys/geometry/core/designer/body.py +++ b/src/ansys/geometry/core/designer/body.py @@ -67,6 +67,7 @@ from pyvista import MultiBlock, PolyData from ansys.geometry.core.designer.component import Component + from ansys.geometry.core.designer.selection import NamedSelection # TODO: Temporary fix for boolean operations # This is a temporary fix for the boolean operations issue. The issue is that the @@ -571,6 +572,17 @@ def copy(self, parent: "Component", name: str = None) -> "Body": Copy of the body. """ return + + @abstractmethod + def get_named_selections(self) -> list["NamedSelection"]: + """Get the named selections associated with the body. + + Returns + ------- + list[NamedSelection] + List of named selections associated with the body. + """ + return @abstractmethod def get_raw_tessellation( @@ -1052,6 +1064,7 @@ def _get_vertices_from_id(self, body: Union["Body", "MasterBody"]) -> list[Verte Vertex( vertex_resp.get("id"), vertex_resp.get("position"), + body, ) for vertex_resp in response.get("vertices") ] @@ -1278,6 +1291,16 @@ def copy(self, parent: "Component", name: str = None) -> "Body": # noqa: D102 raise NotImplementedError( "Copy method is not implemented on the MasterBody. Call this method on a body instead." ) + + def get_named_selections(self, body: "Body") -> list["NamedSelection"]: # noqa: D102 + named_selections = get_design_from_body(body).named_selections + + included_ns = [] + for ns in named_selections: + if body.id in [body.id for body in ns.bodies]: + included_ns.append(ns) + + return included_ns @min_backend_version(26, 1, 0) def get_raw_tessellation( # noqa: D102 @@ -1879,6 +1902,10 @@ def copy(self, parent: "Component", name: str = None) -> "Body": # noqa: D102 parent._clear_cached_bodies() body_id = f"{parent.id}/{tb.id}" if parent.parent_component else tb.id return Body(body_id, response.get("name"), parent, tb) + + @ensure_design_is_active + def get_named_selections(self) -> list["NamedSelection"]: # noqa: D102 + return self._template.get_named_selections(self) @ensure_design_is_active def get_raw_tessellation( # noqa: D102 diff --git a/src/ansys/geometry/core/designer/edge.py b/src/ansys/geometry/core/designer/edge.py index 11f8136c6b..b7b468a029 100644 --- a/src/ansys/geometry/core/designer/edge.py +++ b/src/ansys/geometry/core/designer/edge.py @@ -24,6 +24,7 @@ from enum import Enum, unique from typing import TYPE_CHECKING +from ansys.geometry.core.misc.auxiliary import get_design_from_body from pint import Quantity from ansys.geometry.core.connection.client import GrpcClient @@ -37,6 +38,7 @@ if TYPE_CHECKING: # pragma: no cover from ansys.geometry.core.designer.body import Body from ansys.geometry.core.designer.face import Face + from ansys.geometry.core.designer.selection import NamedSelection from ansys.geometry.core.designer.vertex import Vertex @@ -185,6 +187,7 @@ def vertices(self) -> list["Vertex"]: Vertex( vertex_resp.get("id"), vertex_resp.get("position"), + self.body, ) for vertex_resp in response.get("vertices") ] @@ -229,3 +232,21 @@ def bounding_box(self) -> BoundingBox: return BoundingBox( response.get("min_corner"), response.get("max_corner"), response.get("center") ) + + @ensure_design_is_active + def get_named_selections(self) -> list["NamedSelection"]: + """Get named selections associated with the edge. + + Returns + ------- + list[NamedSelection] + List of named selections that include the edge. + """ + named_selections = get_design_from_body(self.body).named_selections + + included_ns = [] + for ns in named_selections: + if self in ns.edges: + included_ns.append(ns) + + return included_ns diff --git a/src/ansys/geometry/core/designer/face.py b/src/ansys/geometry/core/designer/face.py index 71d30097bf..5bf8d07a9f 100644 --- a/src/ansys/geometry/core/designer/face.py +++ b/src/ansys/geometry/core/designer/face.py @@ -39,6 +39,7 @@ DEFAULT_COLOR, convert_color_to_hex, convert_opacity_to_hex, + get_design_from_body, ) from ansys.geometry.core.misc.checks import ( ensure_design_is_active, @@ -58,6 +59,7 @@ import pyvista as pv from ansys.geometry.core.designer.body import Body + from ansys.geometry.core.designer.selection import NamedSelection @unique @@ -267,6 +269,7 @@ def vertices(self) -> list[Vertex]: Vertex( vertex_resp.get("id"), vertex_resp.get("position"), + self.body, ) for vertex_resp in response.get("vertices") ] @@ -529,6 +532,24 @@ def setup_offset_relationship( ) return result.get("success") + + @ensure_design_is_active + def get_named_selections(self) -> list["NamedSelection"]: + """Get named selections associated with the edge. + + Returns + ------- + list[NamedSelection] + List of named selections that include the edge. + """ + named_selections = get_design_from_body(self.body).named_selections + + included_ns = [] + for ns in named_selections: + if self in ns.faces: + included_ns.append(ns) + + return included_ns @graphics_required def tessellate(self, tess_options: TessellationOptions | None = None) -> "pv.PolyData": diff --git a/src/ansys/geometry/core/designer/selection.py b/src/ansys/geometry/core/designer/selection.py index f326c961d8..593fdc2726 100644 --- a/src/ansys/geometry/core/designer/selection.py +++ b/src/ansys/geometry/core/designer/selection.py @@ -72,6 +72,8 @@ class NamedSelection: All design points to include in the named selection. components: list[Component], default: None All components to include in the named selection. + vertices: list[Vertex], default: None + All vertices to include in the named selection. """ def __init__( @@ -264,6 +266,8 @@ def __verify_ns(self) -> None: "vertices": response.get("vertices"), } + print('verifying NS:', ids["vertices"]) + for key in ids: if ids[key] != self._ids_cached[key]: # Clear the cache for that specific entity diff --git a/src/ansys/geometry/core/designer/vertex.py b/src/ansys/geometry/core/designer/vertex.py index d8c1481a9f..b49a24429d 100644 --- a/src/ansys/geometry/core/designer/vertex.py +++ b/src/ansys/geometry/core/designer/vertex.py @@ -21,11 +21,18 @@ # SOFTWARE. """Module for managing a vertex.""" +from typing import TYPE_CHECKING + import numpy as np +from ansys.geometry.core.misc.auxiliary import get_design_from_body from ansys.geometry.core.math.point import Point3D from ansys.geometry.core.typing import RealSequence +if TYPE_CHECKING: # pragma: no cover + from ansys.geometry.core.designer.body import Body + from ansys.geometry.core.designer.selection import NamedSelection + class Vertex(Point3D): """Represents a single vertex of a body within the design assembly. @@ -44,6 +51,7 @@ def __new__( cls, id: str, position: np.ndarray | RealSequence, + body: "Body", ): """Initialize ``Vertex`` class.""" # Only pass position and unit to Point3D.__new__ @@ -54,9 +62,11 @@ def __init__( self, id: str, position: np.ndarray | RealSequence, + body: "Body", ): """Initialize the Vertex with a unique identifier.""" self._id = id + self._body = body super().__init__(position) # Make immutable @@ -66,10 +76,35 @@ def __init__( def id(self) -> str: """Get the unique identifier of the vertex.""" return self._id + + @property + def body(self) -> "Body": + """Get the body this vertex belongs to.""" + return self._body + + def get_named_selections(self) -> list["NamedSelection"]: + """Get all named selections that include this vertex. + + Returns + ------- + list["NamedSelection"] + List of named selections that include this vertex. + """ + design = get_design_from_body(self.body) + named_selections = design.named_selections + + included_ns = [] + for ns in named_selections: + print(ns.vertices) + if self in ns.vertices: + included_ns.append(ns) + + return included_ns def __repr__(self) -> str: """Return a string representation of the vertex.""" lines = [f"ansys.geometry.core.designer.Vertex {hex(id(self))}"] lines.append(f" Id : {self.id}") lines.append(f" Position : {self.position}") + lines.append(f" Body Id : {self.body.id}") return "\n".join(lines) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 11acbbca5b..16fca59487 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -4089,3 +4089,103 @@ def test_combine_merge(modeler: Modeler): design._update_design_inplace() assert len(design.bodies) == 1 assert box1.volume.m == pytest.approx(Quantity(2.5, UNITS.m**3).m, rel=1e-6, abs=1e-8) + + +def test_faces_get_named_selections(modeler: Modeler): + # Test getting named selections associated with faces + design = modeler.create_design("faces_named_selections") + box = design.extrude_sketch("box", Sketch().box(Point2D([0, 0]), 1, 1), 1) + + # create named selection from faces + face_ns1 = [box.faces[0], box.faces[1]] + face_ns2 = [box.faces[2], box.faces[3]] + design.create_named_selection("face_ns_1", faces=face_ns1) + design.create_named_selection("face_ns_2", faces=face_ns2) + + # Check that edges return the correct named selections + for face in box.faces: + ns_list = face.get_named_selections() + if face.id in face_ns1: + assert len(ns_list) == 1 + assert any(ns.name == "face_ns_1" for ns in ns_list) + elif face.id in face_ns2: + assert len(ns_list) == 1 + assert any(ns.name == "face_ns_2" for ns in ns_list) + else: + assert len(ns_list) == 0 # No named selection for this face + + +def test_body_get_named_selections(modeler: Modeler): + # Test getting named selections associated with bodies + design = modeler.create_design("body_named_selections") + box1 = design.extrude_sketch("box1", Sketch().box(Point2D([0, 0]), 1, 1), 1) + box2 = design.extrude_sketch("box2", Sketch().box(Point2D([2, 2]), 1, 1), 1) + + # create named selection from bodies + design.create_named_selection("body_ns_1", bodies=[box1]) + design.create_named_selection("body_ns_2", bodies=[box2]) + + # Check that bodies return the correct named selections + for body in design.bodies: + ns_list = body.get_named_selections() + if body.id == box1.id: + assert len(ns_list) == 1 + assert any(ns.name == "body_ns_1" for ns in ns_list) + elif body.id == box2.id: + assert len(ns_list) == 1 + assert any(ns.name == "body_ns_2" for ns in ns_list) + else: + assert len(ns_list) == 0 # No named selection for this body + + +def test_beam_get_named_selections(modeler: Modeler): + # Test getting named selections associated with beams + design = modeler.create_design("beam_named_selections") + profile = design.add_beam_circular_profile("profile1", Distance(0.1, UNITS.m)) + beam1 = design.create_beam(Point3D([0, 0, 0]), Point3D([1, 0, 0]), profile) + beam2 = design.create_beam(Point3D([0, 1, 0]), Point3D([1, 1, 0]), profile) + + # create named selection from beams + design.create_named_selection("beam_ns_1", beams=[beam1]) + design.create_named_selection("beam_ns_2", beams=[beam2]) + + # Check that beams return the correct named selections + for beam in design.beams: + ns_list = beam.get_named_selections() + if beam.id == beam1.id: + assert len(ns_list) == 1 + assert any(ns.name == "beam_ns_1" for ns in ns_list) + elif beam.id == beam2.id: + assert len(ns_list) == 1 + assert any(ns.name == "beam_ns_2" for ns in ns_list) + else: + assert len(ns_list) == 0 # No named selection for this beam + + +def test_vertices_get_named_selections(modeler: Modeler): + # Test getting named selections associated with vertices + design = modeler.create_design("vertex_named_selections") + box = design.extrude_sketch("box", Sketch().box(Point2D([0, 0]), 1, 1), 1) + + # create named selection from vertices + vertex_ns1 = [box.vertices[0], box.vertices[1]] + vertex_ns2 = [box.vertices[2], box.vertices[3]] + print(box.vertices[3].id, box.vertices[3].position) + ns1 = design.create_named_selection("vertex_ns_1", vertices=vertex_ns1) + ns2 = design.create_named_selection("vertex_ns_2", vertices=vertex_ns2) + + print('ns1', ns1.vertices) + print('ns2', ns2.vertices) + + # Check that vertices return the correct named selections + for vertex in box.vertices: + ns_list = vertex.get_named_selections() + print('ns found:', [ns.name for ns in ns_list]) + if vertex in vertex_ns1: + assert len(ns_list) == 1 + assert any(ns.name == "vertex_ns_1" for ns in ns_list) + elif vertex in vertex_ns2: + assert len(ns_list) == 1 + assert any(ns.name == "vertex_ns_2" for ns in ns_list) + else: + assert len(ns_list) == 0 # No named selection for this vertex \ No newline at end of file diff --git a/tests/integration/test_edges.py b/tests/integration/test_edges.py index c7828dfc21..e85e37a1c7 100644 --- a/tests/integration/test_edges.py +++ b/tests/integration/test_edges.py @@ -87,3 +87,26 @@ def test_edges_get_vertices(modeler: Modeler): with pytest.raises(AttributeError): vertices[0].id = "new_id" + + +def test_edges_get_named_selections(modeler: Modeler): + # Test getting named selections associated with edges + # Create a simple design with a box + design = modeler.create_design("BoxNamedSelections") + body = design.extrude_sketch("box", Sketch().box(Point2D([0, 0]), 1, 1), 1) + + # Create named selections on some edges + edge_ns1 = [body.edges[0], body.edges[1]] + edge_ns2 = [body.edges[2], body.edges[3]] + design.create_named_selection("NS1", edges=edge_ns1) + design.create_named_selection("NS2", edges=edge_ns2) + + # Check that edges return the correct named selections + for edge in body.edges: + ns_list = edge.get_named_selections() + if edge.id in edge_ns1: + assert any(ns.name == "NS1" for ns in ns_list) + elif edge.id in edge_ns2: + assert any(ns.name == "NS2" for ns in ns_list) + else: + assert len(ns_list) == 0 # No named selection for this edge \ No newline at end of file From cba074d6cd282dcf615fe217f113d392cc707953 Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Tue, 4 Nov 2025 13:36:11 -0500 Subject: [PATCH 02/14] removing debug statements --- .../core/_grpc/_services/v0/named_selection.py | 1 - src/ansys/geometry/core/designer/selection.py | 2 -- src/ansys/geometry/core/designer/vertex.py | 1 - tests/integration/test_design.py | 14 +++++++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py b/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py index 208471eaf6..761eaf7430 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py @@ -57,7 +57,6 @@ def get_named_selection(self, **kwargs): # noqa: D102 response = self.stub.Get(request) # Return the response - formatted as a dictionary - print('grpc response:', [vertex.id.id for vertex in response.vertices]) return { "id": response.id, "name": response.name, diff --git a/src/ansys/geometry/core/designer/selection.py b/src/ansys/geometry/core/designer/selection.py index 593fdc2726..921117f7ec 100644 --- a/src/ansys/geometry/core/designer/selection.py +++ b/src/ansys/geometry/core/designer/selection.py @@ -266,8 +266,6 @@ def __verify_ns(self) -> None: "vertices": response.get("vertices"), } - print('verifying NS:', ids["vertices"]) - for key in ids: if ids[key] != self._ids_cached[key]: # Clear the cache for that specific entity diff --git a/src/ansys/geometry/core/designer/vertex.py b/src/ansys/geometry/core/designer/vertex.py index b49a24429d..26a134394d 100644 --- a/src/ansys/geometry/core/designer/vertex.py +++ b/src/ansys/geometry/core/designer/vertex.py @@ -95,7 +95,6 @@ def get_named_selections(self) -> list["NamedSelection"]: included_ns = [] for ns in named_selections: - print(ns.vertices) if self in ns.vertices: included_ns.append(ns) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 16fca59487..f710763f3a 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -4170,12 +4170,13 @@ def test_vertices_get_named_selections(modeler: Modeler): # create named selection from vertices vertex_ns1 = [box.vertices[0], box.vertices[1]] vertex_ns2 = [box.vertices[2], box.vertices[3]] - print(box.vertices[3].id, box.vertices[3].position) - ns1 = design.create_named_selection("vertex_ns_1", vertices=vertex_ns1) - ns2 = design.create_named_selection("vertex_ns_2", vertices=vertex_ns2) + vertex_ns3 = [box.vertices[4], box.vertices[5]] + vertex_ns4 = [box.vertices[4], box.vertices[5]] - print('ns1', ns1.vertices) - print('ns2', ns2.vertices) + design.create_named_selection("vertex_ns_1", vertices=vertex_ns1) + design.create_named_selection("vertex_ns_2", vertices=vertex_ns2) + design.create_named_selection("vertex_ns_3", vertices=vertex_ns3) + design.create_named_selection("vertex_ns_4", vertices=vertex_ns4) # Check that vertices return the correct named selections for vertex in box.vertices: @@ -4187,5 +4188,8 @@ def test_vertices_get_named_selections(modeler: Modeler): elif vertex in vertex_ns2: assert len(ns_list) == 1 assert any(ns.name == "vertex_ns_2" for ns in ns_list) + elif vertex in vertex_ns3: + assert len(ns_list) == 2 + assert any(ns.name == "vertex_ns_3" for ns in ns_list) else: assert len(ns_list) == 0 # No named selection for this vertex \ No newline at end of file From f5c5581b9ef2613813cb94c4f76337dc82ba64ac Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Tue, 4 Nov 2025 13:53:38 -0500 Subject: [PATCH 03/14] added design point and components get_named_selections functions --- src/ansys/geometry/core/designer/component.py | 19 +++++++ .../geometry/core/designer/designpoint.py | 22 ++++++++ tests/integration/test_design.py | 50 ++++++++++++++++++- 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 710f832986..fbe30aaee2 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -67,6 +67,8 @@ if TYPE_CHECKING: # pragma: no cover from pyvista import MultiBlock, PolyData + from ansys.geometry.core.designer.selection import NamedSelection + @unique class SharedTopologyType(Enum): @@ -1980,3 +1982,20 @@ def make_independent(self, others: list["Component"] = None) -> None: """ ids = [self.id, *[o.id for o in others or []]] self._grpc_client._services.components.make_independent(ids=ids) + + def get_named_selections(self) -> list["NamedSelection"]: + """Get the named selections of the component. + + Returns + ------- + list[NamedSelection] + List of named selections belonging to the component. + """ + named_selections = get_design_from_component(self).named_selections + + included_ns = [] + for ns in named_selections: + if self in ns.components: + included_ns.append(ns) + + return included_ns \ No newline at end of file diff --git a/src/ansys/geometry/core/designer/designpoint.py b/src/ansys/geometry/core/designer/designpoint.py index d062943bfa..78ae817a88 100644 --- a/src/ansys/geometry/core/designer/designpoint.py +++ b/src/ansys/geometry/core/designer/designpoint.py @@ -24,6 +24,7 @@ from typing import TYPE_CHECKING, Union from ansys.geometry.core.math.point import Point3D +from ansys.geometry.core.misc.auxiliary import get_design_from_component from ansys.geometry.core.misc.checks import graphics_required from ansys.geometry.core.misc.units import UNITS @@ -31,6 +32,7 @@ import pyvista as pv from ansys.geometry.core.designer.component import Component + from ansys.geometry.core.designer.selection import NamedSelection class DesignPoint: @@ -77,6 +79,26 @@ def value(self) -> Point3D: def parent_component(self) -> "Component": """Component node that the design point is under.""" return self._parent_component + + def get_named_selections(self) -> list["NamedSelection"]: + """Get named selections that contain this design point. + + Returns + ------- + list[NamedSelection] + List of named selections that contain this design point. + """ + if self.parent_component is None: + raise ValueError("Design point does not have a parent component.") + + named_selections = get_design_from_component(self.parent_component).named_selections + + included_ns = [] + for ns in named_selections: + if self.id in [dp.id for dp in ns.design_points]: + included_ns.append(ns) + + return included_ns def __repr__(self) -> str: """Represent the design points as a string.""" diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index f710763f3a..e33fdeacdd 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -4192,4 +4192,52 @@ def test_vertices_get_named_selections(modeler: Modeler): assert len(ns_list) == 2 assert any(ns.name == "vertex_ns_3" for ns in ns_list) else: - assert len(ns_list) == 0 # No named selection for this vertex \ No newline at end of file + assert len(ns_list) == 0 # No named selection for this vertex + + +def test_components_get_named_selections(modeler: Modeler): + # Test getting named selections associated with components + design = modeler.create_design("component_named_selections") + comp1 = design.add_component("Component1") + comp2 = design.add_component("Component2") + comp3 = design.add_component("Component3") + + # create named selection from components + design.create_named_selection("component_ns_1", components=[comp1]) + design.create_named_selection("component_ns_2", components=[comp2]) + + # Check that components return the correct named selections + for component in design.components: + ns_list = component.get_named_selections() + if component.id == comp1.id: + assert len(ns_list) == 1 + assert any(ns.name == "component_ns_1" for ns in ns_list) + elif component.id == comp2.id: + assert len(ns_list) == 1 + assert any(ns.name == "component_ns_2" for ns in ns_list) + else: + assert len(ns_list) == 0 # No named selection for this component + + +def test_design_point_get_named_selections(modeler: Modeler): + # Test getting named selections associated with design points + design = modeler.create_design("design_point_named_selections") + dp1 = design.add_design_point("DesignPoint1", Point3D([0, 0, 0])) + dp2 = design.add_design_point("DesignPoint2", Point3D([1, 1, 1])) + dp3 = design.add_design_point("DesignPoint3", Point3D([2, 2, 2])) + + # create named selection from design points + design.create_named_selection("design_point_ns_1", design_points=[dp1]) + design.create_named_selection("design_point_ns_2", design_points=[dp2]) + + # Check that design points return the correct named selections + for design_point in design.design_points: + ns_list = design_point.get_named_selections() + if design_point.id == dp1.id: + assert len(ns_list) == 1 + assert any(ns.name == "design_point_ns_1" for ns in ns_list) + elif design_point.id == dp2.id: + assert len(ns_list) == 1 + assert any(ns.name == "design_point_ns_2" for ns in ns_list) + else: + assert len(ns_list) == 0 # No named selection for this design point \ No newline at end of file From 40a65cfea51d7bf23e4b7d59c3b2a8266f3fece4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 19:15:13 +0000 Subject: [PATCH 04/14] chore: auto fixes from pre-commit hooks --- src/ansys/geometry/core/designer/body.py | 8 ++++---- src/ansys/geometry/core/designer/component.py | 4 ++-- src/ansys/geometry/core/designer/designpoint.py | 2 +- src/ansys/geometry/core/designer/edge.py | 6 +++--- src/ansys/geometry/core/designer/face.py | 6 +++--- src/ansys/geometry/core/designer/vertex.py | 6 +++--- tests/integration/test_design.py | 4 ++-- tests/integration/test_edges.py | 2 +- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/ansys/geometry/core/designer/body.py b/src/ansys/geometry/core/designer/body.py index e6c7683aee..bec4f0188b 100644 --- a/src/ansys/geometry/core/designer/body.py +++ b/src/ansys/geometry/core/designer/body.py @@ -572,7 +572,7 @@ def copy(self, parent: "Component", name: str = None) -> "Body": Copy of the body. """ return - + @abstractmethod def get_named_selections(self) -> list["NamedSelection"]: """Get the named selections associated with the body. @@ -1291,10 +1291,10 @@ def copy(self, parent: "Component", name: str = None) -> "Body": # noqa: D102 raise NotImplementedError( "Copy method is not implemented on the MasterBody. Call this method on a body instead." ) - + def get_named_selections(self, body: "Body") -> list["NamedSelection"]: # noqa: D102 named_selections = get_design_from_body(body).named_selections - + included_ns = [] for ns in named_selections: if body.id in [body.id for body in ns.bodies]: @@ -1902,7 +1902,7 @@ def copy(self, parent: "Component", name: str = None) -> "Body": # noqa: D102 parent._clear_cached_bodies() body_id = f"{parent.id}/{tb.id}" if parent.parent_component else tb.id return Body(body_id, response.get("name"), parent, tb) - + @ensure_design_is_active def get_named_selections(self) -> list["NamedSelection"]: # noqa: D102 return self._template.get_named_selections(self) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index fbe30aaee2..10e9328f3b 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -1992,10 +1992,10 @@ def get_named_selections(self) -> list["NamedSelection"]: List of named selections belonging to the component. """ named_selections = get_design_from_component(self).named_selections - + included_ns = [] for ns in named_selections: if self in ns.components: included_ns.append(ns) - return included_ns \ No newline at end of file + return included_ns diff --git a/src/ansys/geometry/core/designer/designpoint.py b/src/ansys/geometry/core/designer/designpoint.py index 78ae817a88..dc6bced488 100644 --- a/src/ansys/geometry/core/designer/designpoint.py +++ b/src/ansys/geometry/core/designer/designpoint.py @@ -79,7 +79,7 @@ def value(self) -> Point3D: def parent_component(self) -> "Component": """Component node that the design point is under.""" return self._parent_component - + def get_named_selections(self) -> list["NamedSelection"]: """Get named selections that contain this design point. diff --git a/src/ansys/geometry/core/designer/edge.py b/src/ansys/geometry/core/designer/edge.py index b7b468a029..d983e59115 100644 --- a/src/ansys/geometry/core/designer/edge.py +++ b/src/ansys/geometry/core/designer/edge.py @@ -24,13 +24,13 @@ from enum import Enum, unique from typing import TYPE_CHECKING -from ansys.geometry.core.misc.auxiliary import get_design_from_body from pint import Quantity from ansys.geometry.core.connection.client import GrpcClient from ansys.geometry.core.errors import GeometryRuntimeError from ansys.geometry.core.math.bbox import BoundingBox from ansys.geometry.core.math.point import Point3D +from ansys.geometry.core.misc.auxiliary import get_design_from_body from ansys.geometry.core.misc.checks import ensure_design_is_active, min_backend_version from ansys.geometry.core.shapes.curves.trimmed_curve import ReversedTrimmedCurve, TrimmedCurve from ansys.geometry.core.shapes.parameterization import Interval @@ -236,14 +236,14 @@ def bounding_box(self) -> BoundingBox: @ensure_design_is_active def get_named_selections(self) -> list["NamedSelection"]: """Get named selections associated with the edge. - + Returns ------- list[NamedSelection] List of named selections that include the edge. """ named_selections = get_design_from_body(self.body).named_selections - + included_ns = [] for ns in named_selections: if self in ns.edges: diff --git a/src/ansys/geometry/core/designer/face.py b/src/ansys/geometry/core/designer/face.py index 5bf8d07a9f..f2585b36db 100644 --- a/src/ansys/geometry/core/designer/face.py +++ b/src/ansys/geometry/core/designer/face.py @@ -532,18 +532,18 @@ def setup_offset_relationship( ) return result.get("success") - + @ensure_design_is_active def get_named_selections(self) -> list["NamedSelection"]: """Get named selections associated with the edge. - + Returns ------- list[NamedSelection] List of named selections that include the edge. """ named_selections = get_design_from_body(self.body).named_selections - + included_ns = [] for ns in named_selections: if self in ns.faces: diff --git a/src/ansys/geometry/core/designer/vertex.py b/src/ansys/geometry/core/designer/vertex.py index 26a134394d..c01ef9b1e4 100644 --- a/src/ansys/geometry/core/designer/vertex.py +++ b/src/ansys/geometry/core/designer/vertex.py @@ -25,8 +25,8 @@ import numpy as np -from ansys.geometry.core.misc.auxiliary import get_design_from_body from ansys.geometry.core.math.point import Point3D +from ansys.geometry.core.misc.auxiliary import get_design_from_body from ansys.geometry.core.typing import RealSequence if TYPE_CHECKING: # pragma: no cover @@ -76,12 +76,12 @@ def __init__( def id(self) -> str: """Get the unique identifier of the vertex.""" return self._id - + @property def body(self) -> "Body": """Get the body this vertex belongs to.""" return self._body - + def get_named_selections(self) -> list["NamedSelection"]: """Get all named selections that include this vertex. diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index e33fdeacdd..9816de8f03 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -4181,7 +4181,7 @@ def test_vertices_get_named_selections(modeler: Modeler): # Check that vertices return the correct named selections for vertex in box.vertices: ns_list = vertex.get_named_selections() - print('ns found:', [ns.name for ns in ns_list]) + print("ns found:", [ns.name for ns in ns_list]) if vertex in vertex_ns1: assert len(ns_list) == 1 assert any(ns.name == "vertex_ns_1" for ns in ns_list) @@ -4240,4 +4240,4 @@ def test_design_point_get_named_selections(modeler: Modeler): assert len(ns_list) == 1 assert any(ns.name == "design_point_ns_2" for ns in ns_list) else: - assert len(ns_list) == 0 # No named selection for this design point \ No newline at end of file + assert len(ns_list) == 0 # No named selection for this design point diff --git a/tests/integration/test_edges.py b/tests/integration/test_edges.py index e85e37a1c7..684d3fc802 100644 --- a/tests/integration/test_edges.py +++ b/tests/integration/test_edges.py @@ -109,4 +109,4 @@ def test_edges_get_named_selections(modeler: Modeler): elif edge.id in edge_ns2: assert any(ns.name == "NS2" for ns in ns_list) else: - assert len(ns_list) == 0 # No named selection for this edge \ No newline at end of file + assert len(ns_list) == 0 # No named selection for this edge From ca0e812f05871c21c846af79c6588fc2c844c660 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Tue, 4 Nov 2025 19:16:25 +0000 Subject: [PATCH 05/14] chore: adding changelog file 2356.added.md [dependabot-skip] --- doc/changelog.d/2356.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/2356.added.md diff --git a/doc/changelog.d/2356.added.md b/doc/changelog.d/2356.added.md new file mode 100644 index 0000000000..d2bd450fb8 --- /dev/null +++ b/doc/changelog.d/2356.added.md @@ -0,0 +1 @@ +Add Named Selections query to geometric entities From 2e0c925bdff83b05d99a8ce89fbdf5ad7f0a7b21 Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Tue, 4 Nov 2025 14:26:24 -0500 Subject: [PATCH 06/14] fix precommit failures --- tests/integration/test_design.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index e33fdeacdd..d95fee6a03 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -4200,7 +4200,7 @@ def test_components_get_named_selections(modeler: Modeler): design = modeler.create_design("component_named_selections") comp1 = design.add_component("Component1") comp2 = design.add_component("Component2") - comp3 = design.add_component("Component3") + design.add_component("Component3") # create named selection from components design.create_named_selection("component_ns_1", components=[comp1]) @@ -4224,7 +4224,7 @@ def test_design_point_get_named_selections(modeler: Modeler): design = modeler.create_design("design_point_named_selections") dp1 = design.add_design_point("DesignPoint1", Point3D([0, 0, 0])) dp2 = design.add_design_point("DesignPoint2", Point3D([1, 1, 1])) - dp3 = design.add_design_point("DesignPoint3", Point3D([2, 2, 2])) + design.add_design_point("DesignPoint3", Point3D([2, 2, 2])) # create named selection from design points design.create_named_selection("design_point_ns_1", design_points=[dp1]) From 038d2e746d9e7d69cebabdd2927711055bd30bef Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Tue, 11 Nov 2025 11:33:41 -0500 Subject: [PATCH 07/14] remove print statements from tests --- tests/integration/test_design.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index e8bf14d156..abae923499 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -3807,8 +3807,6 @@ def test_vertices(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory): assert design.bodies[1].vertices[0].y.magnitude == pytest.approx(-0.00288675, 1e-6, 1e-6) assert design.bodies[1].vertices[0].z.magnitude == pytest.approx(0.01, 1e-6, 1e-6) - print(design.bodies[1].vertices[0].id == "S,~sEbf61ff70-bc08-477a-8a5e-a7c7dc955f40.853__") - assert design.bodies[0].vertices == [] assert design.bodies[1].vertices[1].position == pytest.approx( Point3D([0.033, -0.0057735, 0.01]), 1e-6, 1e-6 @@ -4181,7 +4179,6 @@ def test_vertices_get_named_selections(modeler: Modeler): # Check that vertices return the correct named selections for vertex in box.vertices: ns_list = vertex.get_named_selections() - print("ns found:", [ns.name for ns in ns_list]) if vertex in vertex_ns1: assert len(ns_list) == 1 assert any(ns.name == "vertex_ns_1" for ns in ns_list) From 323b6dcc821563ba694f66dfc082a07771993fa1 Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Tue, 11 Nov 2025 11:55:19 -0500 Subject: [PATCH 08/14] fix backwards compat --- tests/_incompatible_tests.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/_incompatible_tests.yml b/tests/_incompatible_tests.yml index 514b913eb5..3751535b4a 100644 --- a/tests/_incompatible_tests.yml +++ b/tests/_incompatible_tests.yml @@ -73,6 +73,8 @@ backends: - tests/integration/test_design.py::test_named_selection_contents - tests/integration/test_design.py::test_named_selections_components - tests/integration/test_design.py::test_vertices + - tests/integration/test_geometry_commands.py::test_components_get_named_selections + - tests/integration/test_geometry_commands.py::test_vertices_get_named_selections # Bounding box center is only available from 25R2 onwards - tests/integration/test_design.py::test_get_body_bounding_box # Export to DSCO files is only available from 25R2 onwards @@ -194,6 +196,8 @@ backends: - tests/integration/test_design.py::test_named_selection_contents - tests/integration/test_design.py::test_named_selections_components - tests/integration/test_design.py::test_vertices + - tests/integration/test_geometry_commands.py::test_components_get_named_selections + - tests/integration/test_geometry_commands.py::test_vertices_get_named_selections # Bounding box center is only available from 25R2 onwards - tests/integration/test_design.py::test_get_body_bounding_box # Export to DSCO files is only available from 25R2 onwards @@ -262,6 +266,8 @@ backends: - tests/integration/test_design.py::test_named_selection_contents - tests/integration/test_design.py::test_named_selections_components - tests/integration/test_design.py::test_vertices + - tests/integration/test_geometry_commands.py::test_components_get_named_selections + - tests/integration/test_geometry_commands.py::test_vertices_get_named_selections # Potential problem in model/reading design for importing parameters - tests/integration/test_design.py::test_design_parameters # Bounding box center is only available from 25R2 onwards @@ -329,6 +335,8 @@ backends: - tests/integration/test_design.py::test_named_selections_components - tests/integration/test_design.py::test_component_make_independent - tests/integration/test_design.py::test_vertices + - tests/integration/test_geometry_commands.py::test_components_get_named_selections + - tests/integration/test_geometry_commands.py::test_vertices_get_named_selections # Insert/Import file operations caused problems prior to 26.1 - tests/integration/test_design_export.py::test_import_export_reimport_design_scdocx - tests/integration/test_design_export.py::test_import_export_reimport_design_x_t From c87f8bef47a654f72143eb3c04f53be6d94cfe53 Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Tue, 11 Nov 2025 12:14:08 -0500 Subject: [PATCH 09/14] fix backwards compat tests --- tests/_incompatible_tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/_incompatible_tests.yml b/tests/_incompatible_tests.yml index 3751535b4a..adb4dbf8e1 100644 --- a/tests/_incompatible_tests.yml +++ b/tests/_incompatible_tests.yml @@ -73,8 +73,8 @@ backends: - tests/integration/test_design.py::test_named_selection_contents - tests/integration/test_design.py::test_named_selections_components - tests/integration/test_design.py::test_vertices - - tests/integration/test_geometry_commands.py::test_components_get_named_selections - - tests/integration/test_geometry_commands.py::test_vertices_get_named_selections + - tests/integration/test_design.py::test_components_get_named_selections + - tests/integration/test_design.py::test_vertices_get_named_selections # Bounding box center is only available from 25R2 onwards - tests/integration/test_design.py::test_get_body_bounding_box # Export to DSCO files is only available from 25R2 onwards @@ -196,8 +196,8 @@ backends: - tests/integration/test_design.py::test_named_selection_contents - tests/integration/test_design.py::test_named_selections_components - tests/integration/test_design.py::test_vertices - - tests/integration/test_geometry_commands.py::test_components_get_named_selections - - tests/integration/test_geometry_commands.py::test_vertices_get_named_selections + - tests/integration/test_design.py::test_components_get_named_selections + - tests/integration/test_design.py::test_vertices_get_named_selections # Bounding box center is only available from 25R2 onwards - tests/integration/test_design.py::test_get_body_bounding_box # Export to DSCO files is only available from 25R2 onwards @@ -266,8 +266,8 @@ backends: - tests/integration/test_design.py::test_named_selection_contents - tests/integration/test_design.py::test_named_selections_components - tests/integration/test_design.py::test_vertices - - tests/integration/test_geometry_commands.py::test_components_get_named_selections - - tests/integration/test_geometry_commands.py::test_vertices_get_named_selections + - tests/integration/test_design.py::test_components_get_named_selections + - tests/integration/test_design.py::test_vertices_get_named_selections # Potential problem in model/reading design for importing parameters - tests/integration/test_design.py::test_design_parameters # Bounding box center is only available from 25R2 onwards @@ -335,8 +335,8 @@ backends: - tests/integration/test_design.py::test_named_selections_components - tests/integration/test_design.py::test_component_make_independent - tests/integration/test_design.py::test_vertices - - tests/integration/test_geometry_commands.py::test_components_get_named_selections - - tests/integration/test_geometry_commands.py::test_vertices_get_named_selections + - tests/integration/test_design.py::test_components_get_named_selections + - tests/integration/test_design.py::test_vertices_get_named_selections # Insert/Import file operations caused problems prior to 26.1 - tests/integration/test_design_export.py::test_import_export_reimport_design_scdocx - tests/integration/test_design_export.py::test_import_export_reimport_design_x_t From 00ff398d8422e0d00155c7d0c82039da001ce985 Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Wed, 12 Nov 2025 15:13:05 -0500 Subject: [PATCH 10/14] wip - resolving comments need to remove design points constructor with no parent component --- .../_grpc/_services/v0/named_selection.py | 4 +-- src/ansys/geometry/core/designer/beam.py | 5 +--- src/ansys/geometry/core/designer/component.py | 4 +-- .../geometry/core/designer/designpoint.py | 6 ++-- src/ansys/geometry/core/designer/edge.py | 5 +--- src/ansys/geometry/core/designer/face.py | 5 +--- src/ansys/geometry/core/designer/selection.py | 9 +++--- src/ansys/geometry/core/designer/vertex.py | 10 +++---- src/ansys/geometry/core/misc/auxiliary.py | 29 +++++++++++++++++++ tests/integration/test_design.py | 2 +- 10 files changed, 46 insertions(+), 33 deletions(-) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py b/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py index 761eaf7430..af137a0764 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py @@ -64,9 +64,7 @@ def get_named_selection(self, **kwargs): # noqa: D102 "faces": [face.id for face in response.faces], "edges": [edge.id for edge in response.edges], "beams": [beam.id.id for beam in response.beams], - "design_points": [ - (dp.id, from_grpc_point_to_point3d(dp.points[0])) for dp in response.design_points - ], + "design_points": [dp.id for dp in response.design_points], "components": [comp.id for comp in response.components], "vertices": [vertex.id.id for vertex in response.vertices], } diff --git a/src/ansys/geometry/core/designer/beam.py b/src/ansys/geometry/core/designer/beam.py index ed177022e7..534515f28e 100644 --- a/src/ansys/geometry/core/designer/beam.py +++ b/src/ansys/geometry/core/designer/beam.py @@ -432,11 +432,8 @@ def get_named_selections(self) -> list["NamedSelection"]: list["NamedSelection"] List of named selections that include this beam. """ - design = get_design_from_component(self) - named_selections = design.named_selections - included_ns = [] - for ns in named_selections: + for ns in get_design_from_component(self.parent_component).named_selections: if self in ns.beams: included_ns.append(ns) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 10e9328f3b..2b700e07c9 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -1991,10 +1991,8 @@ def get_named_selections(self) -> list["NamedSelection"]: list[NamedSelection] List of named selections belonging to the component. """ - named_selections = get_design_from_component(self).named_selections - included_ns = [] - for ns in named_selections: + for ns in get_design_from_component(self).named_selections: if self in ns.components: included_ns.append(ns) diff --git a/src/ansys/geometry/core/designer/designpoint.py b/src/ansys/geometry/core/designer/designpoint.py index dc6bced488..e8fb179e81 100644 --- a/src/ansys/geometry/core/designer/designpoint.py +++ b/src/ansys/geometry/core/designer/designpoint.py @@ -90,11 +90,9 @@ def get_named_selections(self) -> list["NamedSelection"]: """ if self.parent_component is None: raise ValueError("Design point does not have a parent component.") - - named_selections = get_design_from_component(self.parent_component).named_selections - + included_ns = [] - for ns in named_selections: + for ns in get_design_from_component(self.parent_component).named_selections: if self.id in [dp.id for dp in ns.design_points]: included_ns.append(ns) diff --git a/src/ansys/geometry/core/designer/edge.py b/src/ansys/geometry/core/designer/edge.py index d983e59115..8a4b245daf 100644 --- a/src/ansys/geometry/core/designer/edge.py +++ b/src/ansys/geometry/core/designer/edge.py @@ -233,7 +233,6 @@ def bounding_box(self) -> BoundingBox: response.get("min_corner"), response.get("max_corner"), response.get("center") ) - @ensure_design_is_active def get_named_selections(self) -> list["NamedSelection"]: """Get named selections associated with the edge. @@ -242,10 +241,8 @@ def get_named_selections(self) -> list["NamedSelection"]: list[NamedSelection] List of named selections that include the edge. """ - named_selections = get_design_from_body(self.body).named_selections - included_ns = [] - for ns in named_selections: + for ns in get_design_from_body(self.body).named_selections: if self in ns.edges: included_ns.append(ns) diff --git a/src/ansys/geometry/core/designer/face.py b/src/ansys/geometry/core/designer/face.py index f2585b36db..f9eb785511 100644 --- a/src/ansys/geometry/core/designer/face.py +++ b/src/ansys/geometry/core/designer/face.py @@ -533,7 +533,6 @@ def setup_offset_relationship( return result.get("success") - @ensure_design_is_active def get_named_selections(self) -> list["NamedSelection"]: """Get named selections associated with the edge. @@ -542,10 +541,8 @@ def get_named_selections(self) -> list["NamedSelection"]: list[NamedSelection] List of named selections that include the edge. """ - named_selections = get_design_from_body(self.body).named_selections - included_ns = [] - for ns in named_selections: + for ns in get_design_from_body(self.body).named_selections: if self in ns.faces: included_ns.append(ns) diff --git a/src/ansys/geometry/core/designer/selection.py b/src/ansys/geometry/core/designer/selection.py index 921117f7ec..fbc278047c 100644 --- a/src/ansys/geometry/core/designer/selection.py +++ b/src/ansys/geometry/core/designer/selection.py @@ -35,6 +35,7 @@ get_beams_from_ids, get_bodies_from_ids, get_components_from_ids, + get_design_points_from_ids, get_edges_from_ids, get_faces_from_ids, get_vertices_from_ids, @@ -204,10 +205,10 @@ def design_points(self) -> list[DesignPoint]: self.__verify_ns() if self._design_points is None: # Get all design points from the named selection - self._design_points = [ - DesignPoint(dp_id, f"dp: {dp_id}", dp_point) - for dp_id, dp_point in self._ids_cached["design_points"] - ] + self._design_points = get_design_points_from_ids( + self._design, + self._ids_cached["design_points"], + ) return self._design_points diff --git a/src/ansys/geometry/core/designer/vertex.py b/src/ansys/geometry/core/designer/vertex.py index c01ef9b1e4..25071bab8d 100644 --- a/src/ansys/geometry/core/designer/vertex.py +++ b/src/ansys/geometry/core/designer/vertex.py @@ -27,6 +27,7 @@ from ansys.geometry.core.math.point import Point3D from ansys.geometry.core.misc.auxiliary import get_design_from_body +from ansys.geometry.core.misc.checks import ensure_design_is_active from ansys.geometry.core.typing import RealSequence if TYPE_CHECKING: # pragma: no cover @@ -90,11 +91,8 @@ def get_named_selections(self) -> list["NamedSelection"]: list["NamedSelection"] List of named selections that include this vertex. """ - design = get_design_from_body(self.body) - named_selections = design.named_selections - included_ns = [] - for ns in named_selections: + for ns in get_design_from_body(self.body).named_selections: if self in ns.vertices: included_ns.append(ns) @@ -104,6 +102,6 @@ def __repr__(self) -> str: """Return a string representation of the vertex.""" lines = [f"ansys.geometry.core.designer.Vertex {hex(id(self))}"] lines.append(f" Id : {self.id}") - lines.append(f" Position : {self.position}") - lines.append(f" Body Id : {self.body.id}") + lines.append(f" Position : {self.position}") + lines.append(f" Body Id : {self.body.id}") return "\n".join(lines) diff --git a/src/ansys/geometry/core/misc/auxiliary.py b/src/ansys/geometry/core/misc/auxiliary.py index 7f0080077c..ead71064b4 100644 --- a/src/ansys/geometry/core/misc/auxiliary.py +++ b/src/ansys/geometry/core/misc/auxiliary.py @@ -28,6 +28,7 @@ from ansys.geometry.core.designer.body import Body from ansys.geometry.core.designer.component import Component from ansys.geometry.core.designer.design import Design + from ansys.geometry.core.designer.designpoint import DesignPoint from ansys.geometry.core.designer.edge import Edge from ansys.geometry.core.designer.face import Face from ansys.geometry.core.designer.vertex import Vertex @@ -142,6 +143,11 @@ def __traverse_all_beams(comp: Union["Design", "Component"]) -> list["Body"]: return __traverse_component_elem("beams", comp) +def __traverse_all_design_points(comp: Union["Design", "Component"]) -> list["DesignPoint"]: + """Traverse all design points in a design/component and all its subcomponents.""" + return __traverse_component_elem("design_points", comp) + + def get_all_bodies_from_design(design: "Design") -> list["Body"]: """Find all the ``Body`` objects inside a ``Design``. @@ -305,6 +311,29 @@ def get_beams_from_ids(design: "Design", beam_ids: list[str]) -> list["Beam"]: return [beam for beam in __traverse_all_beams(design) if beam.id in beam_ids] # noqa: E501 +def get_design_points_from_ids(design: "Design", design_point_ids: list[str]) -> list["DesignPoint"]: + """Find the ``DesignPoint`` objects inside a ``Design`` from its ids. + + Parameters + ---------- + design : Design + Parent design for the design points. + design_point_ids : list[str] + List of design point ids. + + Returns + ------- + list[DesignPoint] + List of DesignPoint objects. + + Notes + ----- + This method takes a design and design point ids, and gets their corresponding ``DesignPoint`` + objects. + """ + return [dp for dp in __traverse_all_design_points(design) if dp.id in design_point_ids] + + def convert_color_to_hex( color: str | tuple[float, float, float] | tuple[float, float, float, float], ) -> str: diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index abae923499..02be732b36 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -4100,7 +4100,7 @@ def test_faces_get_named_selections(modeler: Modeler): design.create_named_selection("face_ns_1", faces=face_ns1) design.create_named_selection("face_ns_2", faces=face_ns2) - # Check that edges return the correct named selections + # Check that faces return the correct named selections for face in box.faces: ns_list = face.get_named_selections() if face.id in face_ns1: From 820b0ba9d11bf64830b8e15b32da30688c84b74c Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Wed, 12 Nov 2025 15:59:14 -0500 Subject: [PATCH 11/14] fix docstrings --- tests/integration/test_design.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 02be732b36..e107daedfb 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -4090,7 +4090,7 @@ def test_combine_merge(modeler: Modeler): def test_faces_get_named_selections(modeler: Modeler): - # Test getting named selections associated with faces + """Test getting named selections associated with faces.""" design = modeler.create_design("faces_named_selections") box = design.extrude_sketch("box", Sketch().box(Point2D([0, 0]), 1, 1), 1) @@ -4114,7 +4114,7 @@ def test_faces_get_named_selections(modeler: Modeler): def test_body_get_named_selections(modeler: Modeler): - # Test getting named selections associated with bodies + """Test getting named selections associated with bodies.""" design = modeler.create_design("body_named_selections") box1 = design.extrude_sketch("box1", Sketch().box(Point2D([0, 0]), 1, 1), 1) box2 = design.extrude_sketch("box2", Sketch().box(Point2D([2, 2]), 1, 1), 1) @@ -4137,7 +4137,7 @@ def test_body_get_named_selections(modeler: Modeler): def test_beam_get_named_selections(modeler: Modeler): - # Test getting named selections associated with beams + """Test getting named selections associated with beams.""" design = modeler.create_design("beam_named_selections") profile = design.add_beam_circular_profile("profile1", Distance(0.1, UNITS.m)) beam1 = design.create_beam(Point3D([0, 0, 0]), Point3D([1, 0, 0]), profile) @@ -4161,7 +4161,7 @@ def test_beam_get_named_selections(modeler: Modeler): def test_vertices_get_named_selections(modeler: Modeler): - # Test getting named selections associated with vertices + """Test getting named selections associated with vertices.""" design = modeler.create_design("vertex_named_selections") box = design.extrude_sketch("box", Sketch().box(Point2D([0, 0]), 1, 1), 1) @@ -4193,7 +4193,7 @@ def test_vertices_get_named_selections(modeler: Modeler): def test_components_get_named_selections(modeler: Modeler): - # Test getting named selections associated with components + """Test getting named selections associated with components.""" design = modeler.create_design("component_named_selections") comp1 = design.add_component("Component1") comp2 = design.add_component("Component2") @@ -4217,7 +4217,7 @@ def test_components_get_named_selections(modeler: Modeler): def test_design_point_get_named_selections(modeler: Modeler): - # Test getting named selections associated with design points + """Test getting named selections associated with design points.""" design = modeler.create_design("design_point_named_selections") dp1 = design.add_design_point("DesignPoint1", Point3D([0, 0, 0])) dp2 = design.add_design_point("DesignPoint2", Point3D([1, 1, 1])) From 5442275193dafb0ef978587027a0f1d4d7b67fba Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Fri, 14 Nov 2025 09:38:50 -0500 Subject: [PATCH 12/14] adding design point to get_assembly --- src/ansys/geometry/core/_grpc/_services/v0/designs.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/designs.py b/src/ansys/geometry/core/_grpc/_services/v0/designs.py index 5c902f5705..aa50e5d117 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/designs.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/designs.py @@ -428,6 +428,12 @@ def serialize_beam(beam): "properties": serialize_beam_properties(beam.properties), "cross_section": serialize_beam_cross_section(beam.cross_section), } + + def serialize_design_point(design_point): + return { + "id": design_point.id, + "parent": design_point.owner_name, + } parts = getattr(response, "parts", []) transformed_parts = getattr(response, "transformed_parts", []) @@ -438,6 +444,7 @@ def serialize_beam(beam): component_coordinate_systems = getattr(response, "component_coord_systems", []) component_shared_topologies = getattr(response, "component_shared_topologies", []) beams = getattr(response, "beams", []) + design_points = getattr(response, "design_points", []) return { "parts": [serialize_part(part) for part in parts] if len(parts) > 0 else [], "transformed_parts": [serialize_transformed_part(tp) for tp in transformed_parts], @@ -452,6 +459,7 @@ def serialize_beam(beam): component_shared_topologies ), "beams": [serialize_beam(beam) for beam in beams], + "design_points": [serialize_design_point(dp) for dp in design_points], } @protect_grpc From 68f098112c37170fc95535c1f4404869ce09a587 Mon Sep 17 00:00:00 2001 From: Jacob Kerstetter Date: Fri, 14 Nov 2025 15:21:20 -0500 Subject: [PATCH 13/14] add design points from get assembly and remove None type from design point constructor --- .../geometry/core/_grpc/_services/v0/designs.py | 4 +++- src/ansys/geometry/core/designer/design.py | 12 ++++++++++++ src/ansys/geometry/core/designer/designpoint.py | 5 ++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/designs.py b/src/ansys/geometry/core/_grpc/_services/v0/designs.py index aa50e5d117..be802202d3 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/designs.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/designs.py @@ -432,7 +432,9 @@ def serialize_beam(beam): def serialize_design_point(design_point): return { "id": design_point.id, - "parent": design_point.owner_name, + "name": design_point.owner_name, + "point": from_grpc_point_to_point3d(design_point.points[0]), + "parent_id": design_point.parent_id.id, } parts = getattr(response, "parts", []) diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index a44cdb2c95..cb7baa278d 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -1308,6 +1308,18 @@ def __read_existing_design(self) -> None: component.coordinate_systems.append(new_cs) num_created_coord_systems += 1 + # Create DesignPoints + for dp in response.get("design_points"): + created_dp = DesignPoint( + dp.get("id"), + dp.get("name"), + dp.get("point"), + created_components.get(dp.get("parent_id"), self), + ) + + # Append the design point to the component to which it belongs + created_dp.parent_component._design_points.append(created_dp) + end = time.time() # Set SharedTopology diff --git a/src/ansys/geometry/core/designer/designpoint.py b/src/ansys/geometry/core/designer/designpoint.py index e8fb179e81..8628b882fe 100644 --- a/src/ansys/geometry/core/designer/designpoint.py +++ b/src/ansys/geometry/core/designer/designpoint.py @@ -46,13 +46,12 @@ class DesignPoint: User-defined label for the design points. points : Point3D 3D point constituting the design points. - parent_component : Component | None + parent_component : Component Parent component to place the new design point under within the design assembly. - Its default value is None. """ def __init__( - self, id: str, name: str, point: Point3D, parent_component: Union["Component", None] = None + self, id: str, name: str, point: Point3D, parent_component: Union["Component"] ): """Initialize the ``DesignPoints`` class.""" self._id = id From 9306c1b415c9db7fe9a137f4e317d0df97bb149c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 20:21:40 +0000 Subject: [PATCH 14/14] chore: auto fixes from pre-commit hooks --- src/ansys/geometry/core/_grpc/_services/v0/designs.py | 2 +- .../geometry/core/_grpc/_services/v0/named_selection.py | 2 +- src/ansys/geometry/core/designer/designpoint.py | 6 ++---- src/ansys/geometry/core/designer/vertex.py | 1 - src/ansys/geometry/core/misc/auxiliary.py | 4 +++- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/designs.py b/src/ansys/geometry/core/_grpc/_services/v0/designs.py index be802202d3..baa5d348ec 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/designs.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/designs.py @@ -428,7 +428,7 @@ def serialize_beam(beam): "properties": serialize_beam_properties(beam.properties), "cross_section": serialize_beam_cross_section(beam.cross_section), } - + def serialize_design_point(design_point): return { "id": design_point.id, diff --git a/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py b/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py index af137a0764..b38cf5e15b 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py @@ -26,7 +26,7 @@ from ansys.geometry.core.errors import protect_grpc from ..base.named_selection import GRPCNamedSelectionService -from .conversions import build_grpc_id, from_grpc_point_to_point3d +from .conversions import build_grpc_id class GRPCNamedSelectionServiceV0(GRPCNamedSelectionService): diff --git a/src/ansys/geometry/core/designer/designpoint.py b/src/ansys/geometry/core/designer/designpoint.py index 8628b882fe..4ecbe5b7b6 100644 --- a/src/ansys/geometry/core/designer/designpoint.py +++ b/src/ansys/geometry/core/designer/designpoint.py @@ -50,9 +50,7 @@ class DesignPoint: Parent component to place the new design point under within the design assembly. """ - def __init__( - self, id: str, name: str, point: Point3D, parent_component: Union["Component"] - ): + def __init__(self, id: str, name: str, point: Point3D, parent_component: Union["Component"]): """Initialize the ``DesignPoints`` class.""" self._id = id self._name = name @@ -89,7 +87,7 @@ def get_named_selections(self) -> list["NamedSelection"]: """ if self.parent_component is None: raise ValueError("Design point does not have a parent component.") - + included_ns = [] for ns in get_design_from_component(self.parent_component).named_selections: if self.id in [dp.id for dp in ns.design_points]: diff --git a/src/ansys/geometry/core/designer/vertex.py b/src/ansys/geometry/core/designer/vertex.py index 25071bab8d..2b3c6d763f 100644 --- a/src/ansys/geometry/core/designer/vertex.py +++ b/src/ansys/geometry/core/designer/vertex.py @@ -27,7 +27,6 @@ from ansys.geometry.core.math.point import Point3D from ansys.geometry.core.misc.auxiliary import get_design_from_body -from ansys.geometry.core.misc.checks import ensure_design_is_active from ansys.geometry.core.typing import RealSequence if TYPE_CHECKING: # pragma: no cover diff --git a/src/ansys/geometry/core/misc/auxiliary.py b/src/ansys/geometry/core/misc/auxiliary.py index ead71064b4..2ddfbf73c0 100644 --- a/src/ansys/geometry/core/misc/auxiliary.py +++ b/src/ansys/geometry/core/misc/auxiliary.py @@ -311,7 +311,9 @@ def get_beams_from_ids(design: "Design", beam_ids: list[str]) -> list["Beam"]: return [beam for beam in __traverse_all_beams(design) if beam.id in beam_ids] # noqa: E501 -def get_design_points_from_ids(design: "Design", design_point_ids: list[str]) -> list["DesignPoint"]: +def get_design_points_from_ids( + design: "Design", design_point_ids: list[str] +) -> list["DesignPoint"]: """Find the ``DesignPoint`` objects inside a ``Design`` from its ids. Parameters