diff --git a/doc/changelog.d/2083.added.md b/doc/changelog.d/2083.added.md new file mode 100644 index 0000000000..2c4eda37f9 --- /dev/null +++ b/doc/changelog.d/2083.added.md @@ -0,0 +1 @@ +Vertex references \ No newline at end of file diff --git a/src/ansys/geometry/core/_grpc/_services/base/bodies.py b/src/ansys/geometry/core/_grpc/_services/base/bodies.py index f5c690e1e9..42bf6b69ad 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/bodies.py +++ b/src/ansys/geometry/core/_grpc/_services/base/bodies.py @@ -119,6 +119,11 @@ def get_edges(self, **kwargs) -> dict: """Get the edges of a body.""" pass + @abstractmethod + def get_vertices(self, **kwargs) -> dict: + """Get the vertices of a body.""" + pass + @abstractmethod def get_volume(self, **kwargs) -> dict: """Get the volume of a body.""" diff --git a/src/ansys/geometry/core/_grpc/_services/base/edges.py b/src/ansys/geometry/core/_grpc/_services/base/edges.py index 0eedffae28..1214500a29 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/edges.py +++ b/src/ansys/geometry/core/_grpc/_services/base/edges.py @@ -69,6 +69,11 @@ def get_faces(self, **kwargs) -> dict: """Get the faces that are connected to the edge.""" pass + @abstractmethod + def get_vertices(self, **kwargs) -> dict: + """Get the vertices that are connected to the edge.""" + pass + @abstractmethod def get_bounding_box(self, **kwargs) -> dict: """Get the bounding box of the edge.""" diff --git a/src/ansys/geometry/core/_grpc/_services/base/faces.py b/src/ansys/geometry/core/_grpc/_services/base/faces.py index e310c5018b..996e671458 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/faces.py +++ b/src/ansys/geometry/core/_grpc/_services/base/faces.py @@ -59,6 +59,11 @@ def get_edges(self, **kwargs) -> dict: """Get the edges of a face.""" pass + @abstractmethod + def get_vertices(self, **kwargs) -> dict: + """Get the vertices of a face.""" + pass + @abstractmethod def get_loops(self, **kwargs) -> dict: """Get the loops of a face.""" diff --git a/src/ansys/geometry/core/_grpc/_services/v0/bodies.py b/src/ansys/geometry/core/_grpc/_services/v0/bodies.py index 53d5b7b3a6..9216001a4d 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/bodies.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/bodies.py @@ -383,6 +383,22 @@ def get_edges(self, **kwargs) -> dict: # noqa: D102 ] } + @protect_grpc + def get_vertices(self, **kwargs) -> dict: # noqa: D102 + # Call the gRPC service + resp = self.stub.GetVertices(request=build_grpc_id(kwargs["id"])) + + # Return the response - formatted as a dictionary + return { + "vertices": [ + { + "id": vertex.id.id, + "position": from_grpc_point_to_point3d(vertex.position), + } + for vertex in resp.vertices + ] + } + @protect_grpc def get_volume(self, **kwargs) -> dict: # noqa: D102 # Call the gRPC service diff --git a/src/ansys/geometry/core/_grpc/_services/v0/edges.py b/src/ansys/geometry/core/_grpc/_services/v0/edges.py index 36dce751d7..aa64ba3852 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/edges.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/edges.py @@ -138,6 +138,27 @@ def get_faces(self, **kwargs) -> dict: # noqa: D102 ], } + @protect_grpc + def get_vertices(self, **kwargs) -> dict: # noqa: D102 + from ansys.api.geometry.v0.edges_pb2 import GetVerticesRequest + + # Create the request - assumes all inputs are valid and of proper type + request = GetVerticesRequest(edge=build_grpc_id(kwargs["id"])) + + # Call the gRPC service + response = self.stub.GetVertices(request=request) + + # Return the response - formatted as a dictionary + return { + "vertices": [ + { + "id": vertex.id.id, + "position": from_grpc_point_to_point3d(vertex.position), + } + for vertex in response.vertices + ], + } + @protect_grpc def get_bounding_box(self, **kwargs) -> dict: # noqa: D102 # Create the request - assumes all inputs are valid and of the proper type diff --git a/src/ansys/geometry/core/_grpc/_services/v0/faces.py b/src/ansys/geometry/core/_grpc/_services/v0/faces.py index 02e381b8aa..f3337bf6fe 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/faces.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/faces.py @@ -114,6 +114,27 @@ def get_edges(self, **kwargs) -> dict: # noqa: D102 ] } + @protect_grpc + def get_vertices(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + from ansys.api.geometry.v0.faces_pb2 import GetVerticesRequest + + request = GetVerticesRequest(face_id=build_grpc_id(kwargs["id"])) + + # Call the gRPC service + response = self.stub.GetVertices(request=request) + + # Return the response - formatted as a dictionary + return { + "vertices": [ + { + "id": vertex.id.id, + "position": from_grpc_point_to_point3d(vertex.position), + } + for vertex in response.vertices + ] + } + @protect_grpc def get_loops(self, **kwargs) -> dict: # noqa: D102 # Create the request - assumes all inputs are valid and of the proper type 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 32d2b67439..ac31e5b713 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/named_selection.py @@ -67,6 +67,7 @@ def get_named_selection(self, **kwargs): # noqa: D102 "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], + "vertices": [vertex.id.id for vertex in response.vertices], } @protect_grpc @@ -92,6 +93,7 @@ def create_named_selection(self, **kwargs): # noqa: D102 "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], + "vertices": [vertex.id.id for vertex in response.vertices], } @protect_grpc diff --git a/src/ansys/geometry/core/_grpc/_services/v1/bodies.py b/src/ansys/geometry/core/_grpc/_services/v1/bodies.py index 46ba67ed5b..62e6cf1b4a 100644 --- a/src/ansys/geometry/core/_grpc/_services/v1/bodies.py +++ b/src/ansys/geometry/core/_grpc/_services/v1/bodies.py @@ -112,6 +112,10 @@ def get_faces(self, **kwargs) -> dict: # noqa: D102 def get_edges(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc + def get_vertices(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + @protect_grpc def get_volume(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError diff --git a/src/ansys/geometry/core/_grpc/_services/v1/edges.py b/src/ansys/geometry/core/_grpc/_services/v1/edges.py index a3289ee347..c8316195c7 100644 --- a/src/ansys/geometry/core/_grpc/_services/v1/edges.py +++ b/src/ansys/geometry/core/_grpc/_services/v1/edges.py @@ -71,6 +71,10 @@ def get_interval(self, **kwargs) -> dict: # noqa: D102 def get_faces(self, **kwargs) -> dict: # noqa: D102 return NotImplementedError + @protect_grpc + def get_vertices(self, **kwargs) -> dict: # noqa: D102 + return NotImplementedError + @protect_grpc def get_bounding_box(self, **kwargs) -> dict: # noqa: D102 return NotImplementedError diff --git a/src/ansys/geometry/core/_grpc/_services/v1/faces.py b/src/ansys/geometry/core/_grpc/_services/v1/faces.py index aa6efbe648..8c8175bc61 100644 --- a/src/ansys/geometry/core/_grpc/_services/v1/faces.py +++ b/src/ansys/geometry/core/_grpc/_services/v1/faces.py @@ -63,6 +63,10 @@ def get_area(self, **kwargs) -> dict: # noqa: D102 def get_edges(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc + def get_vertices(self, **kwargs): # noqa: D102 + raise NotImplementedError + @protect_grpc def get_loops(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError diff --git a/src/ansys/geometry/core/designer/body.py b/src/ansys/geometry/core/designer/body.py index e4bf135691..4513a4dd46 100644 --- a/src/ansys/geometry/core/designer/body.py +++ b/src/ansys/geometry/core/designer/body.py @@ -52,6 +52,7 @@ ) from ansys.geometry.core.designer.edge import CurveType, Edge from ansys.geometry.core.designer.face import Face, SurfaceType +from ansys.geometry.core.designer.vertex import Vertex from ansys.geometry.core.errors import protect_grpc from ansys.geometry.core.materials.material import Material from ansys.geometry.core.math.bbox import BoundingBox @@ -239,6 +240,16 @@ def edges(self) -> list[Edge]: """ return + @abstractmethod + def vertices(self) -> list[Vertex]: + """Get a list of all vertices within the body. + + Returns + ------- + list[Vertex] + """ + return + @abstractmethod def is_alive(self) -> bool: """Check if the body is still alive and has not been deleted.""" @@ -989,6 +1000,24 @@ def _get_edges_from_id(self, body: Union["Body", "MasterBody"]) -> list[Edge]: for edge_resp in response.get("edges") ] + @property + @min_backend_version(26, 1, 0) + def vertices(self) -> list[Vertex]: # noqa: D102 + return self._get_vertices_from_id(self) + + def _get_vertices_from_id(self, body: Union["Body", "MasterBody"]) -> list[Vertex]: + """Retrieve vertices from a body ID.""" + self._grpc_client.log.debug(f"Retrieving vertices for body {body.id} from server.") + response = self._grpc_client.services.bodies.get_vertices(id=body.id) + + return [ + Vertex( + vertex_resp.get("id"), + vertex_resp.get("position"), + ) + for vertex_resp in response.get("vertices") + ] + @property def is_alive(self) -> bool: # noqa: D102 return self._is_alive @@ -1485,6 +1514,11 @@ def faces(self) -> list[Face]: # noqa: D102 def edges(self) -> list[Edge]: # noqa: D102 return self._template._get_edges_from_id(self) + @property + @ensure_design_is_active + def vertices(self) -> list[Vertex]: # noqa: D102 + return self._template._get_vertices_from_id(self) + @property def _is_alive(self) -> bool: # noqa: D102 return self._template.is_alive diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 716c78dcbb..419d4fbc53 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -63,6 +63,7 @@ from ansys.geometry.core.designer.face import Face from ansys.geometry.core.designer.part import MasterComponent, Part from ansys.geometry.core.designer.selection import NamedSelection +from ansys.geometry.core.designer.vertex import Vertex from ansys.geometry.core.errors import protect_grpc from ansys.geometry.core.materials.material import Material from ansys.geometry.core.materials.property import MaterialProperty, MaterialPropertyType @@ -608,6 +609,7 @@ def create_named_selection( beams: list[Beam] | None = None, design_points: list[DesignPoint] | None = None, components: list[Component] | None = None, + vertices: list[Vertex] | None = None, ) -> NamedSelection: """Create a named selection on the active Geometry server instance. @@ -627,6 +629,8 @@ def create_named_selection( 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. Returns ------- @@ -640,10 +644,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, components]): + if not any([bodies, faces, edges, beams, design_points, components, vertices]): raise ValueError( "At least one of the following must be provided: " - "bodies, faces, edges, beams, design_points, or components." + "bodies, faces, edges, beams, design_points, components, or vertices." ) named_selection = NamedSelection( @@ -656,6 +660,7 @@ def create_named_selection( beams=beams, design_points=design_points, components=components, + vertices=vertices, ) self._named_selections[named_selection.name] = named_selection diff --git a/src/ansys/geometry/core/designer/edge.py b/src/ansys/geometry/core/designer/edge.py index 669976db2c..9b9ddf5228 100644 --- a/src/ansys/geometry/core/designer/edge.py +++ b/src/ansys/geometry/core/designer/edge.py @@ -38,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.vertex import Vertex @unique @@ -176,6 +177,24 @@ def faces(self) -> list["Face"]: for face_resp in response.get("faces") ] + @property + @ensure_design_is_active + @min_backend_version(26, 1, 0) + def vertices(self) -> list["Vertex"]: + """Vertices that define the edge.""" + from ansys.geometry.core.designer.vertex import Vertex + + self._grpc_client.log.debug("Requesting edge vertices from server.") + response = self._grpc_client.services.edges.get_vertices(id=self._id) + + return [ + Vertex( + vertex_resp.get("id"), + vertex_resp.get("position"), + ) + for vertex_resp in response.get("vertices") + ] + @property @ensure_design_is_active def start(self) -> Point3D: diff --git a/src/ansys/geometry/core/designer/face.py b/src/ansys/geometry/core/designer/face.py index fd62578979..66696a7786 100644 --- a/src/ansys/geometry/core/designer/face.py +++ b/src/ansys/geometry/core/designer/face.py @@ -33,6 +33,7 @@ from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub from ansys.geometry.core.connection.client import GrpcClient from ansys.geometry.core.designer.edge import Edge +from ansys.geometry.core.designer.vertex import Vertex from ansys.geometry.core.errors import GeometryRuntimeError, protect_grpc from ansys.geometry.core.math.bbox import BoundingBox from ansys.geometry.core.math.point import Point3D @@ -263,6 +264,22 @@ def edges(self) -> list[Edge]: for edge in response.get("edges") ] + @property + @ensure_design_is_active + @min_backend_version(26, 1, 0) + def vertices(self) -> list[Vertex]: + """List of all vertices of the face.""" + self._grpc_client.log.debug("Requesting face vertices from server.") + response = self._grpc_client.services.faces.get_vertices(id=self.id) + + return [ + Vertex( + vertex_resp.get("id"), + vertex_resp.get("position"), + ) + for vertex_resp in response.get("vertices") + ] + @property @ensure_design_is_active def loops(self) -> list[FaceLoop]: diff --git a/src/ansys/geometry/core/designer/selection.py b/src/ansys/geometry/core/designer/selection.py index 99523ea93b..abbc6908ff 100644 --- a/src/ansys/geometry/core/designer/selection.py +++ b/src/ansys/geometry/core/designer/selection.py @@ -31,12 +31,14 @@ 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 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, + get_vertices_from_ids, ) if TYPE_CHECKING: # pragma: no cover @@ -84,6 +86,7 @@ def __init__( beams: list[Beam] | None = None, design_points: list[DesignPoint] | None = None, components: list[Component] | None = None, + vertices: list[Vertex] | None = None, preexisting_id: str | None = None, ): """Initialize the ``NamedSelection`` class.""" @@ -104,6 +107,8 @@ def __init__( design_points = [] if components is None: components = [] + if vertices is None: + vertices = [] # Instantiate self._bodies = bodies @@ -112,6 +117,7 @@ def __init__( self._beams = beams self._design_points = design_points self._components = components + self._vertices = vertices # Store ids for later use... when verifying if the NS changed. self._ids_cached = { @@ -121,6 +127,7 @@ def __init__( "beams": [beam.id for beam in beams], "design_points": [dp.id for dp in design_points], "components": [component.id for component in components], + "vertices": [vertex.id for vertex in vertices], } if preexisting_id: @@ -219,6 +226,22 @@ def components(self) -> list[Component]: return self._components + @property + def vertices(self) -> list[Vertex]: + """All vertices in the named selection.""" + self.__verify_ns() + if self._grpc_client.backend_version < (26, 1, 0): + self._grpc_client.log.warning( + "Accessing vertices of named selections is only" + " consistent starting in version 2026 R1." + ) + return [] + if self._vertices is None: + # Get all vertices from the named selection + self._vertices = get_vertices_from_ids(self._design, self._ids_cached["vertices"]) + + return self._vertices + 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): @@ -239,6 +262,7 @@ def __verify_ns(self) -> None: "beams": response.get("beams"), "design_points": response.get("design_points"), "components": response.get("components"), + "vertices": response.get("vertices"), } for key in ids: @@ -259,4 +283,5 @@ def __repr__(self) -> str: 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)}") + lines.append(f" N Vertices : {len(self.vertices)}") return "\n".join(lines) diff --git a/src/ansys/geometry/core/designer/vertex.py b/src/ansys/geometry/core/designer/vertex.py new file mode 100644 index 0000000000..d8c1481a9f --- /dev/null +++ b/src/ansys/geometry/core/designer/vertex.py @@ -0,0 +1,75 @@ +# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates. +# 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. +"""Module for managing a vertex.""" + +import numpy as np + +from ansys.geometry.core.math.point import Point3D +from ansys.geometry.core.typing import RealSequence + + +class Vertex(Point3D): + """Represents a single vertex of a body within the design assembly. + + This class synchronizes to a design within a supporting Geometry service instance. + + Parameters + ---------- + id : str + The unique identifier for the vertex. + position : np.ndarray or RealSequence + The position of the vertex in 3D space. + """ + + def __new__( + cls, + id: str, + position: np.ndarray | RealSequence, + ): + """Initialize ``Vertex`` class.""" + # Only pass position and unit to Point3D.__new__ + obj = super().__new__(cls, position) + return obj + + def __init__( + self, + id: str, + position: np.ndarray | RealSequence, + ): + """Initialize the Vertex with a unique identifier.""" + self._id = id + super().__init__(position) + + # Make immutable + self.flags.writeable = False + + @property + def id(self) -> str: + """Get the unique identifier of the vertex.""" + return self._id + + 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}") + return "\n".join(lines) diff --git a/src/ansys/geometry/core/math/point.py b/src/ansys/geometry/core/math/point.py index f0b758c9b6..e3e8715f4d 100644 --- a/src/ansys/geometry/core/math/point.py +++ b/src/ansys/geometry/core/math/point.py @@ -293,6 +293,11 @@ def z(self, z: Quantity) -> None: """Set the Z plane component value.""" self.__set_value(z, 2) + @property + def position(self) -> np.ndarray: + """Get the position of the point as a numpy array.""" + return self.view(type=np.ndarray) + @PhysicalQuantity.unit.getter def unit(self) -> Unit: """Get the unit of the object.""" diff --git a/src/ansys/geometry/core/misc/auxiliary.py b/src/ansys/geometry/core/misc/auxiliary.py index 2f8a9d7c29..7f0080077c 100644 --- a/src/ansys/geometry/core/misc/auxiliary.py +++ b/src/ansys/geometry/core/misc/auxiliary.py @@ -30,6 +30,7 @@ from ansys.geometry.core.designer.design import Design from ansys.geometry.core.designer.edge import Edge from ansys.geometry.core.designer.face import Face + from ansys.geometry.core.designer.vertex import Vertex try: from ansys.tools.visualization_interface.utils.color import Color @@ -255,6 +256,33 @@ def get_edges_from_ids(design: "Design", edge_ids: list[str]) -> list["Edge"]: ] # noqa: E501 +def get_vertices_from_ids(design: "Design", vertex_ids: list[str]) -> list["Vertex"]: + """Find the ``Vertex`` objects inside a ``Design`` from its ids. + + Parameters + ---------- + design : Design + Parent design for the vertices. + vertex_ids : list[str] + List of vertex ids. + + Returns + ------- + list[Vertex] + List of Vertex objects. + + Notes + ----- + This method takes a design and vertex ids, and gets their corresponding ``Vertex`` objects. + """ + return [ + vertex + for body in __traverse_all_bodies(design) + for vertex in body.vertices + if vertex.id in vertex_ids + ] # noqa: E501 + + def get_beams_from_ids(design: "Design", beam_ids: list[str]) -> list["Beam"]: """Find the ``Beam`` objects inside a ``Design`` from its ids. diff --git a/tests/integration/files/NamedSelectionImport.scdocx b/tests/integration/files/NamedSelectionImport.scdocx index 884200137b..5efacbd6fa 100644 Binary files a/tests/integration/files/NamedSelectionImport.scdocx and b/tests/integration/files/NamedSelectionImport.scdocx differ diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 1d7b88e93d..e1f8ddf52a 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -408,6 +408,7 @@ def test_component_body(modeler: Modeler): assert len(design.components) == 0 assert len(design.bodies) == 1 assert len(body.edges) == 15 # 5 top + 5 bottom + 5 sides + assert len(body.vertices) == 10 # 5 top + 5 bottom # We have created this body on the base component. Let's add a new component # and add a planar surface to it @@ -546,9 +547,17 @@ def test_named_selection_contents(modeler: Modeler): Point3D([9, 99, 999], UNITS.mm), Point3D([8, 88, 888], UNITS.mm), circle_profile_1 ) + # Pick vertices from the box to add to the named selection + vertices = box.vertices[0:2] + # Create the NamedSelection ns = design.create_named_selection( - "MyNamedSelection", bodies=[box, box_2], faces=[face], edges=[edge], beams=[beam] + "MyNamedSelection", + bodies=[box, box_2], + faces=[face], + edges=[edge], + beams=[beam], + vertices=vertices, ) # Check that the named selection has everything @@ -566,6 +575,9 @@ def test_named_selection_contents(modeler: Modeler): assert len(ns.design_points) == 0 + assert len(ns.vertices) == 2 + assert (ns.vertices[0].id == vertices[0].id) and (ns.vertices[1].id == vertices[1].id) + def test_add_component_with_instance_name(modeler: Modeler): design = modeler.create_design("DesignHierarchyExample") @@ -644,6 +656,28 @@ def test_faces_edges(modeler: Modeler): ) # The bottom face must be one of them +def test_faces_vertices(modeler: Modeler): + """Test for getting the vertices of a face.""" + # Create your design on the server side + design = modeler.create_design("FacesVertices_Test") + + # Create a Sketch object and draw a polygon (all client side) + sketch = Sketch() + sketch.polygon(Point2D([-30, -30], UNITS.mm), Quantity(10, UNITS.mm), sides=5) + + # Extrude the sketch to create a Body + polygon_comp = design.add_component("PolygonComponent") + body_polygon_comp = polygon_comp.extrude_sketch("Polygon", sketch, Quantity(30, UNITS.mm)) + + # Get all its faces + faces = body_polygon_comp.faces + assert len(faces) == 7 # top + bottom + sides + + # Get the vertices of one of the faces + vertices = faces[0].vertices + assert len(vertices) == 5 # pentagon + + def test_coordinate_system_creation(modeler: Modeler): """Test for verifying the correct creation of ``CoordinateSystem``.""" # Create your design on the server side diff --git a/tests/integration/test_design_import.py b/tests/integration/test_design_import.py index a9f4783627..5bc55cb728 100644 --- a/tests/integration/test_design_import.py +++ b/tests/integration/test_design_import.py @@ -354,7 +354,7 @@ def test_design_import_with_named_selections(modeler: Modeler): design = modeler.open_file(Path(FILES_DIR, "NamedSelectionImport.scdocx")) # Check that there are 5 Named Selections - assert len(design.named_selections) == 5 + assert len(design.named_selections) == 6 # Get full body named selection body = design._named_selections["SolidBody"] @@ -383,6 +383,12 @@ def test_design_import_with_named_selections(modeler: Modeler): assert len(ns3.bodies) == 0 assert len(ns3.design_points) == 1 + # Get mixed named selection 4 + ns4 = design._named_selections["VertexGroup"] + assert len(ns4.bodies) == 0 + assert len(ns4.vertices) == 2 + assert len(ns4.faces) == 1 + def test_design_import_acad_2024(modeler: Modeler): """Test importing a 2024 AutoCAD file.""" diff --git a/tests/integration/test_edges.py b/tests/integration/test_edges.py index 3166be7d96..48a881ee51 100644 --- a/tests/integration/test_edges.py +++ b/tests/integration/test_edges.py @@ -21,8 +21,12 @@ # SOFTWARE. """Test edges.""" +import numpy as np +import pytest + from ansys.geometry.core import Modeler from ansys.geometry.core.math import Point2D, Vector3D +from ansys.geometry.core.math.point import Point3D from ansys.geometry.core.misc.units import UNITS, Quantity from ansys.geometry.core.sketch import Sketch @@ -50,3 +54,37 @@ def test_edges_startend_cylinder(modeler: Modeler): cylinder_body = design.extrude_sketch("JustACyl", cylinder, Quantity(10, UNITS.m)) for edge in cylinder_body.edges: assert edge.shape.start == edge.shape.end + + +def test_edges_get_vertices(modeler: Modeler): + # Create a simple design with a box + design = modeler.create_design("BoxVertices") + sketch = Sketch() + sketch.box(Point2D([0, 0], UNITS.m), Quantity(1, UNITS.m), Quantity(1, UNITS.m)) + body = design.extrude_sketch("BoxBody", sketch, Quantity(1, UNITS.m)) + + # Create array of vertices to match with vertices from edges + body_vertices = [] + for x in [-1, 1]: + for y in [-1, 1]: + for z in [0, 1]: + body_vertices.append(Point3D([x / 2, y / 2, z])) + + # For each edge, get its vertices and check their types and positions + for edge in body.edges: + vertices = edge.vertices + # Should be two vertices per edge for a box + assert len(vertices) == 2 + + # Check the location of each vertex + for vertex in vertices: + print(vertex.position) + assert any(np.allclose(vertex.position, v.position) for v in body_vertices) + + # Test that the vertices are immutable + vertices = body.edges[0].vertices + with pytest.raises(AttributeError): + vertices[0].position = np.array([1, 2, 3]) # Attempt to modify position should raise error + + with pytest.raises(AttributeError): + vertices[0].id = "new_id"