Skip to content

Commit 443a380

Browse files
jacobrkerstetterpre-commit-ci[bot]pyansys-ci-botjonahrb
authored
feat: add components to NS (#2068)
Co-authored-by: Jacob Kerstetter <jacob.kerstetter@ansys.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Co-authored-by: Jonah Boling <56607167+jonahrb@users.noreply.github.com>
1 parent 72c56fa commit 443a380

File tree

8 files changed

+124
-2
lines changed

8 files changed

+124
-2
lines changed

doc/changelog.d/2068.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add components to ns

src/ansys/geometry/core/_grpc/_services/v0/named_selection.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def get_named_selection(self, **kwargs): # noqa: D102
6666
"edges": [edge.id for edge in response.edges],
6767
"beams": [beam.id.id for beam in response.beams],
6868
"design_points": [(dp.id, dp.points[0]) for dp in response.design_points],
69+
"components": [comp.id for comp in response.components],
6970
}
7071

7172
@protect_grpc
@@ -90,6 +91,7 @@ def create_named_selection(self, **kwargs): # noqa: D102
9091
"edges": [edge.id for edge in response.edges],
9192
"beams": [beam.id.id for beam in response.beams],
9293
"design_points": [dp.id for dp in response.design_points],
94+
"components": [comp.id for comp in response.components],
9395
}
9496

9597
@protect_grpc

src/ansys/geometry/core/designer/design.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,7 @@ def create_named_selection(
607607
edges: list[Edge] | None = None,
608608
beams: list[Beam] | None = None,
609609
design_points: list[DesignPoint] | None = None,
610+
components: list[Component] | None = None,
610611
) -> NamedSelection:
611612
"""Create a named selection on the active Geometry server instance.
612613
@@ -624,6 +625,8 @@ def create_named_selection(
624625
All beams to include in the named selection.
625626
design_points : list[DesignPoint], default: None
626627
All design points to include in the named selection.
628+
components : list[Component], default: None
629+
All components to include in the named selection.
627630
628631
Returns
629632
-------
@@ -637,10 +640,10 @@ def create_named_selection(
637640
one of the optional parameters must be provided.
638641
"""
639642
# Verify that at least one entity is provided
640-
if not any([bodies, faces, edges, beams, design_points]):
643+
if not any([bodies, faces, edges, beams, design_points, components]):
641644
raise ValueError(
642645
"At least one of the following must be provided: "
643-
"bodies, faces, edges, beams, or design_points."
646+
"bodies, faces, edges, beams, design_points, or components."
644647
)
645648

646649
named_selection = NamedSelection(
@@ -652,6 +655,7 @@ def create_named_selection(
652655
edges=edges,
653656
beams=beams,
654657
design_points=design_points,
658+
components=components,
655659
)
656660

657661
self._named_selections[named_selection.name] = named_selection

src/ansys/geometry/core/designer/geometry_commands.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
point3d_to_grpc_point,
6363
unit_vector_to_grpc_direction,
6464
)
65+
from ansys.geometry.core.designer.component import Component
6566
from ansys.geometry.core.designer.mating_conditions import (
6667
AlignCondition,
6768
OrientCondition,

src/ansys/geometry/core/designer/selection.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@
2727
from ansys.geometry.core.connection.conversions import grpc_point_to_point3d
2828
from ansys.geometry.core.designer.beam import Beam
2929
from ansys.geometry.core.designer.body import Body
30+
from ansys.geometry.core.designer.component import Component
3031
from ansys.geometry.core.designer.designpoint import DesignPoint
3132
from ansys.geometry.core.designer.edge import Edge
3233
from ansys.geometry.core.designer.face import Face
3334
from ansys.geometry.core.misc.auxiliary import (
3435
get_beams_from_ids,
3536
get_bodies_from_ids,
37+
get_components_from_ids,
3638
get_edges_from_ids,
3739
get_faces_from_ids,
3840
)
@@ -67,6 +69,8 @@ class NamedSelection:
6769
All beams to include in the named selection.
6870
design_points : list[DesignPoints], default: None
6971
All design points to include in the named selection.
72+
components: list[Component], default: None
73+
All components to include in the named selection.
7074
"""
7175

7276
def __init__(
@@ -79,6 +83,7 @@ def __init__(
7983
edges: list[Edge] | None = None,
8084
beams: list[Beam] | None = None,
8185
design_points: list[DesignPoint] | None = None,
86+
components: list[Component] | None = None,
8287
preexisting_id: str | None = None,
8388
):
8489
"""Initialize the ``NamedSelection`` class."""
@@ -97,13 +102,16 @@ def __init__(
97102
beams = []
98103
if design_points is None:
99104
design_points = []
105+
if components is None:
106+
components = []
100107

101108
# Instantiate
102109
self._bodies = bodies
103110
self._faces = faces
104111
self._edges = edges
105112
self._beams = beams
106113
self._design_points = design_points
114+
self._components = components
107115

108116
# Store ids for later use... when verifying if the NS changed.
109117
self._ids_cached = {
@@ -112,6 +120,7 @@ def __init__(
112120
"edges": [edge.id for edge in edges],
113121
"beams": [beam.id for beam in beams],
114122
"design_points": [dp.id for dp in design_points],
123+
"components": [component.id for component in components],
115124
}
116125

117126
if preexisting_id:
@@ -194,6 +203,22 @@ def design_points(self) -> list[DesignPoint]:
194203

195204
return self._design_points
196205

206+
@property
207+
def components(self) -> list[Component]:
208+
"""All components in the named selection."""
209+
self.__verify_ns()
210+
if self._grpc_client.backend_version < (26, 1, 0):
211+
self._grpc_client.log.warning(
212+
"Accessing components in named selections is only"
213+
" consistent starting in version 2026 R1."
214+
)
215+
return []
216+
if self._components is None:
217+
# Get all components from the named selection
218+
self._components = get_components_from_ids(self._design, self._ids_cached["components"])
219+
220+
return self._components
221+
197222
def __verify_ns(self) -> None:
198223
"""Verify that the contents of the named selection are up to date."""
199224
if self._grpc_client.backend_version < (25, 2, 0):
@@ -213,6 +238,7 @@ def __verify_ns(self) -> None:
213238
"edges": response.get("edges"),
214239
"beams": response.get("beams"),
215240
"design_points": response.get("design_points"),
241+
"components": response.get("components"),
216242
}
217243

218244
for key in ids:
@@ -232,4 +258,5 @@ def __repr__(self) -> str:
232258
lines.append(f" N Edges : {len(self.edges)}")
233259
lines.append(f" N Beams : {len(self.beams)}")
234260
lines.append(f" N Design Points : {len(self.design_points)}")
261+
lines.append(f" N Components : {len(self.components)}")
235262
return "\n".join(lines)

src/ansys/geometry/core/misc/auxiliary.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,30 @@ def get_bodies_from_ids(design: "Design", body_ids: list[str]) -> list["Body"]:
183183
return [body for body in __traverse_all_bodies(design) if body.id in body_ids]
184184

185185

186+
def get_components_from_ids(design: "Design", component_ids: list[str]) -> list["Component"]:
187+
"""Find the ``Component`` objects inside a ``Design`` from its ids.
188+
189+
Parameters
190+
----------
191+
design : Design
192+
Parent design for the components.
193+
component_ids : list[str]
194+
List of component ids.
195+
196+
Returns
197+
-------
198+
list[Component]
199+
List of Component objects.
200+
201+
Notes
202+
-----
203+
This method takes a design and component ids, and gets their corresponding ``Component`` object.
204+
"""
205+
return [
206+
comp for comp in __traverse_component_elem("components", design) if comp.id in component_ids
207+
] # noqa: E501
208+
209+
186210
def get_faces_from_ids(design: "Design", face_ids: list[str]) -> list["Face"]:
187211
"""Find the ``Face`` objects inside a ``Design`` from its ids.
188212

tests/integration/test_design.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1718,6 +1718,29 @@ def test_named_selections_design_points(modeler: Modeler):
17181718
assert len(design.named_selections) == 0
17191719

17201720

1721+
def test_named_selections_components(modeler: Modeler):
1722+
"""Test for verifying the correct creation of ``NamedSelection`` with
1723+
components.
1724+
"""
1725+
# Create your design on the server side
1726+
design = modeler.create_design("NamedSelectionComponents_Test")
1727+
1728+
# Test creating a named selection out of components
1729+
comp1 = design.add_component("Comp1")
1730+
comp2 = design.add_component("Comp2")
1731+
ns_components = design.create_named_selection("Components", components=[comp1, comp2])
1732+
assert len(design.named_selections) == 1
1733+
assert design.named_selections[0].name == "Components"
1734+
1735+
# Fetch the component from the named selection
1736+
assert ns_components.components[0].id == comp1.id
1737+
assert ns_components.components[1].id == comp2.id
1738+
1739+
# Try deleting this named selection
1740+
design.delete_named_selection(ns_components)
1741+
assert len(design.named_selections) == 0
1742+
1743+
17211744
def test_component_instances(modeler: Modeler):
17221745
"""Test creation of ``Component`` instances and the effects this has."""
17231746
design_name = "ComponentInstance_Test"

tests/integration/test_geometry_commands.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,6 +1177,46 @@ def test_move_translate(modeler: Modeler):
11771177
assert np.isin(expected_vertices, translated_vertices).all()
11781178

11791179

1180+
def test_move_translate_component(modeler: Modeler):
1181+
# Create a design and a component, then add a box to the component
1182+
design = modeler.create_design("MyDesign")
1183+
component = design.add_component("MyComponent")
1184+
1185+
# Create a sketch and extrude
1186+
sketch = Sketch().box(Point2D([0, 0]), 1, 1)
1187+
box_body = component.extrude_sketch("BoxBody", sketch, 1)
1188+
1189+
# Add the component to a named selection
1190+
ns = design.create_named_selection("ComponentNS", components=[component])
1191+
1192+
# Move the component
1193+
success = modeler.geometry_commands.move_translate(
1194+
ns,
1195+
UNITVECTOR3D_Z,
1196+
Distance(2, UNITS.m),
1197+
)
1198+
assert success
1199+
1200+
# Check the new location of the box body
1201+
expected_vertices = [
1202+
Point3D([-0.5, -0.5, 2.0]),
1203+
Point3D([0.5, -0.5, 2.0]),
1204+
Point3D([-0.5, 0.5, 2.0]),
1205+
Point3D([0.5, 0.5, 2.0]),
1206+
Point3D([-0.5, -0.5, 3.0]),
1207+
Point3D([0.5, -0.5, 3.0]),
1208+
Point3D([-0.5, 0.5, 3.0]),
1209+
Point3D([0.5, 0.5, 3.0]),
1210+
]
1211+
1212+
translated_vertices = []
1213+
for edge in box_body.edges:
1214+
translated_vertices.extend([edge.start, edge.end])
1215+
1216+
# Check that the vertices have been translated
1217+
assert np.isin(expected_vertices, translated_vertices).all()
1218+
1219+
11801220
def test_move_rotate(modeler: Modeler):
11811221
design = modeler.create_design("move_rotate_box")
11821222
body = design.extrude_sketch("box", Sketch().box(Point2D([0, 0]), 2, 2), 2)

0 commit comments

Comments
 (0)