Skip to content

feat: add components to NS #2068

New issue

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

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

Already on GitHub? Sign in to your account

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/2068.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add components to ns
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def get_named_selection(self, **kwargs): # noqa: D102
"edges": [edge.id for edge in response.edges],
"beams": [beam.id.id for beam in response.beams],
"design_points": [(dp.id, dp.points[0]) for dp in response.design_points],
"components": [comp.id for comp in response.components],
}

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

@protect_grpc
Expand Down
8 changes: 6 additions & 2 deletions src/ansys/geometry/core/designer/design.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ def create_named_selection(
edges: list[Edge] | None = None,
beams: list[Beam] | None = None,
design_points: list[DesignPoint] | None = None,
components: list[Component] | None = None,
) -> NamedSelection:
"""Create a named selection on the active Geometry server instance.

Expand All @@ -624,6 +625,8 @@ def create_named_selection(
All beams to include in the named selection.
design_points : list[DesignPoint], default: None
All design points to include in the named selection.
components : list[Component], default: None
All components to include in the named selection.

Returns
-------
Expand All @@ -637,10 +640,10 @@ def create_named_selection(
one of the optional parameters must be provided.
"""
# Verify that at least one entity is provided
if not any([bodies, faces, edges, beams, design_points]):
if not any([bodies, faces, edges, beams, design_points, components]):
raise ValueError(
"At least one of the following must be provided: "
"bodies, faces, edges, beams, or design_points."
"bodies, faces, edges, beams, design_points, or components."
)

named_selection = NamedSelection(
Expand All @@ -652,6 +655,7 @@ def create_named_selection(
edges=edges,
beams=beams,
design_points=design_points,
components=components,
)

self._named_selections[named_selection.name] = named_selection
Expand Down
1 change: 1 addition & 0 deletions src/ansys/geometry/core/designer/geometry_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
point3d_to_grpc_point,
unit_vector_to_grpc_direction,
)
from ansys.geometry.core.designer.component import Component
from ansys.geometry.core.designer.selection import NamedSelection
from ansys.geometry.core.errors import protect_grpc
from ansys.geometry.core.math.plane import Plane
Expand Down
27 changes: 27 additions & 0 deletions src/ansys/geometry/core/designer/selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@
from ansys.geometry.core.connection.conversions import grpc_point_to_point3d
from ansys.geometry.core.designer.beam import Beam
from ansys.geometry.core.designer.body import Body
from ansys.geometry.core.designer.component import Component
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.misc.auxiliary import (
get_beams_from_ids,
get_bodies_from_ids,
get_components_from_ids,
get_edges_from_ids,
get_faces_from_ids,
)
Expand Down Expand Up @@ -67,6 +69,8 @@ class NamedSelection:
All beams to include in the named selection.
design_points : list[DesignPoints], default: None
All design points to include in the named selection.
components: list[Component], default: None
All components to include in the named selection.
"""

def __init__(
Expand All @@ -79,6 +83,7 @@ def __init__(
edges: list[Edge] | None = None,
beams: list[Beam] | None = None,
design_points: list[DesignPoint] | None = None,
components: list[Component] | None = None,
preexisting_id: str | None = None,
):
"""Initialize the ``NamedSelection`` class."""
Expand All @@ -97,13 +102,16 @@ def __init__(
beams = []
if design_points is None:
design_points = []
if components is None:
components = []

# Instantiate
self._bodies = bodies
self._faces = faces
self._edges = edges
self._beams = beams
self._design_points = design_points
self._components = components

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

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

return self._design_points

@property
def components(self) -> list[Component]:
"""All components in the named selection."""
self.__verify_ns()
if self._grpc_client.backend_version < (26, 1, 0):
self._grpc_client.log.warning(
"Accessing components in named selections is only"
" consistent starting in version 2026 R1."
)
return []
if self._components is None:
# Get all components from the named selection
self._components = get_components_from_ids(self._design, self._ids_cached["components"])

return self._components

def __verify_ns(self) -> None:
"""Verify that the contents of the named selection are up to date."""
if self._grpc_client.backend_version < (25, 2, 0):
Expand All @@ -213,6 +238,7 @@ def __verify_ns(self) -> None:
"edges": response.get("edges"),
"beams": response.get("beams"),
"design_points": response.get("design_points"),
"components": response.get("components"),
}

for key in ids:
Expand All @@ -232,4 +258,5 @@ def __repr__(self) -> str:
lines.append(f" N Edges : {len(self.edges)}")
lines.append(f" N Beams : {len(self.beams)}")
lines.append(f" N Design Points : {len(self.design_points)}")
lines.append(f" N Components : {len(self.components)}")
return "\n".join(lines)
24 changes: 24 additions & 0 deletions src/ansys/geometry/core/misc/auxiliary.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,30 @@ def get_bodies_from_ids(design: "Design", body_ids: list[str]) -> list["Body"]:
return [body for body in __traverse_all_bodies(design) if body.id in body_ids]


def get_components_from_ids(design: "Design", component_ids: list[str]) -> list["Component"]:
"""Find the ``Component`` objects inside a ``Design`` from its ids.

Parameters
----------
design : Design
Parent design for the components.
component_ids : list[str]
List of component ids.

Returns
-------
list[Component]
List of Component objects.

Notes
-----
This method takes a design and component ids, and gets their corresponding ``Component`` object.
"""
return [
comp for comp in __traverse_component_elem("components", design) if comp.id in component_ids
] # noqa: E501


def get_faces_from_ids(design: "Design", face_ids: list[str]) -> list["Face"]:
"""Find the ``Face`` objects inside a ``Design`` from its ids.

Expand Down
23 changes: 23 additions & 0 deletions tests/integration/test_design.py
Original file line number Diff line number Diff line change
Expand Up @@ -1718,6 +1718,29 @@ def test_named_selections_design_points(modeler: Modeler):
assert len(design.named_selections) == 0


def test_named_selections_components(modeler: Modeler):
"""Test for verifying the correct creation of ``NamedSelection`` with
components.
"""
# Create your design on the server side
design = modeler.create_design("NamedSelectionComponents_Test")

# Test creating a named selection out of components
comp1 = design.add_component("Comp1")
comp2 = design.add_component("Comp2")
ns_components = design.create_named_selection("Components", components=[comp1, comp2])
assert len(design.named_selections) == 1
assert design.named_selections[0].name == "Components"

# Fetch the component from the named selection
assert ns_components.components[0].id == comp1.id
assert ns_components.components[1].id == comp2.id

# Try deleting this named selection
design.delete_named_selection(ns_components)
assert len(design.named_selections) == 0


def test_component_instances(modeler: Modeler):
"""Test creation of ``Component`` instances and the effects this has."""
design_name = "ComponentInstance_Test"
Expand Down
40 changes: 40 additions & 0 deletions tests/integration/test_geometry_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,46 @@ def test_move_translate(modeler: Modeler):
assert np.isin(expected_vertices, translated_vertices).all()


def test_move_translate_component(modeler: Modeler):
# Create a design and a component, then add a box to the component
design = modeler.create_design("MyDesign")
component = design.add_component("MyComponent")

# Create a sketch and extrude
sketch = Sketch().box(Point2D([0, 0]), 1, 1)
box_body = component.extrude_sketch("BoxBody", sketch, 1)

# Add the component to a named selection
ns = design.create_named_selection("ComponentNS", components=[component])

# Move the component
success = modeler.geometry_commands.move_translate(
ns,
UNITVECTOR3D_Z,
Distance(2, UNITS.m),
)
assert success

# Check the new location of the box body
expected_vertices = [
Point3D([-0.5, -0.5, 2.0]),
Point3D([0.5, -0.5, 2.0]),
Point3D([-0.5, 0.5, 2.0]),
Point3D([0.5, 0.5, 2.0]),
Point3D([-0.5, -0.5, 3.0]),
Point3D([0.5, -0.5, 3.0]),
Point3D([-0.5, 0.5, 3.0]),
Point3D([0.5, 0.5, 3.0]),
]

translated_vertices = []
for edge in box_body.edges:
translated_vertices.extend([edge.start, edge.end])

# Check that the vertices have been translated
assert np.isin(expected_vertices, translated_vertices).all()


def test_move_rotate(modeler: Modeler):
design = modeler.create_design("move_rotate_box")
body = design.extrude_sketch("box", Sketch().box(Point2D([0, 0]), 2, 2), 2)
Expand Down
Loading