Skip to content

Commit 42b45b3

Browse files
FEAT: Add magick method to EdgePrimitive (#6819)
Co-authored-by: pyansys-ci-bot <[email protected]>
1 parent 47e2b8a commit 42b45b3

File tree

4 files changed

+263
-15
lines changed

4 files changed

+263
-15
lines changed

doc/changelog.d/6819.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add magick method to EdgePrimitive

src/ansys/aedt/core/modeler/cad/elements_3d.py

Lines changed: 230 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,17 @@
3131
from ansys.aedt.core.modeler.geometry_operators import GeometryOperators
3232

3333

34-
class EdgeTypePrimitive(PyAedtBase):
35-
"""Provides common methods for EdgePrimitive and FacePrimitive."""
34+
class ModifiablePrimitive(PyAedtBase):
35+
"""Base class for geometric primitives that support modification operations.
36+
37+
Provides fillet and chamfer operations for:
38+
- EdgePrimitive (3D designs only)
39+
- VertexPrimitive (2D designs only)
40+
41+
"""
3642

3743
@pyaedt_function_handler()
38-
def fillet(self, radius=0.1, setback=0.0):
44+
def fillet(self, radius: float = 0.1, setback: float = 0.0) -> bool:
3945
"""Add a fillet to the selected edges in 3D/vertices in 2D.
4046
4147
Parameters
@@ -81,7 +87,9 @@ def fillet(self, radius=0.1, setback=0.0):
8187
return True
8288

8389
@pyaedt_function_handler()
84-
def chamfer(self, left_distance=1, right_distance=None, angle=45, chamfer_type=0):
90+
def chamfer(
91+
self, left_distance: float = 1, right_distance: float | None = None, angle: float = 45, chamfer_type: int = 0
92+
) -> bool:
8593
"""Add a chamfer to the selected edges in 3D/vertices in 2D.
8694
8795
Parameters
@@ -179,7 +187,7 @@ def chamfer(self, left_distance=1, right_distance=None, angle=45, chamfer_type=0
179187
return True
180188

181189

182-
class VertexPrimitive(EdgeTypePrimitive, PyAedtBase):
190+
class VertexPrimitive(ModifiablePrimitive, PyAedtBase):
183191
"""Contains the vertex object within the AEDT Desktop Modeler.
184192
185193
Parameters
@@ -234,7 +242,7 @@ def __repr__(self):
234242
return str(self.id)
235243

236244

237-
class EdgePrimitive(EdgeTypePrimitive, PyAedtBase):
245+
class EdgePrimitive(ModifiablePrimitive, PyAedtBase):
238246
"""Contains the edge object within the AEDT Desktop Modeler.
239247
240248
Parameters
@@ -251,6 +259,78 @@ def __init__(self, object3d, edge_id):
251259
self._object3d = object3d
252260
self.oeditor = object3d._oeditor
253261

262+
def __str__(self):
263+
return str(self.id)
264+
265+
def __repr__(self):
266+
return str(self.id)
267+
268+
def __iter__(self):
269+
"""Return an iterator for the vertices of the edge.
270+
271+
Returns
272+
-------
273+
iterator
274+
Iterator over the vertices of the edge.
275+
276+
Examples
277+
--------
278+
>>> for vertex in edge:
279+
... print(f"Vertex ID: {vertex.id}, Position: {vertex.position}")
280+
"""
281+
return iter(self.vertices)
282+
283+
def __getitem__(self, index):
284+
"""Get a vertex by index.
285+
286+
Parameters
287+
----------
288+
index : int
289+
290+
Returns
291+
-------
292+
:class:`ansys.aedt.core.modeler.cad.elements_3d.VertexPrimitive`
293+
Vertex at the specified index.
294+
295+
Examples
296+
--------
297+
>>> first_vertex = edge[0]
298+
>>> last_vertex = edge[-1]
299+
"""
300+
return self.vertices[index]
301+
302+
def __contains__(self, item: int | VertexPrimitive) -> bool:
303+
"""Check if a vertex is contained in the edge.
304+
305+
Parameters
306+
----------
307+
item : :class:`ansys.aedt.core.modeler.cad.elements_3d.VertexPrimitive` or int
308+
Vertex object or vertex ID to check for containment.
309+
310+
Returns
311+
-------
312+
bool
313+
``True`` if the item is part of this edge, ``False`` otherwise.
314+
315+
Examples
316+
--------
317+
>>> edge = obj.edges[0]
318+
>>> vertex = obj.vertices[0]
319+
>>> if vertex in edge:
320+
... print("Vertex is part of this edge")
321+
322+
>>> # Check by vertex ID
323+
>>> vertex_id = 123
324+
>>> if vertex_id in edge:
325+
... print("Vertex ID is part of this edge")
326+
"""
327+
if isinstance(item, VertexPrimitive):
328+
item_id = item.id
329+
return any(v.id == item_id for v in self)
330+
elif isinstance(item, int):
331+
return any(v.id == item for v in self)
332+
return False
333+
254334
@property
255335
def name(self):
256336
"""Name of the object."""
@@ -364,12 +444,6 @@ def length(self):
364444
except Exception:
365445
return False
366446

367-
def __str__(self):
368-
return str(self.id)
369-
370-
def __repr__(self):
371-
return str(self.id)
372-
373447
@pyaedt_function_handler()
374448
def create_object(self, non_model=False):
375449
"""Return a new object from the selected edge.
@@ -412,6 +486,150 @@ def move_along_normal(self, offset=1.0):
412486
return False
413487
return self._object3d._primitives.move_edge(self, offset)
414488

489+
@pyaedt_function_handler()
490+
def fillet(self, radius=0.1, setback=0.0):
491+
"""Add a fillet to the selected edges in 3D/vertices in 2D.
492+
493+
Parameters
494+
----------
495+
radius : float, optional
496+
Radius of the fillet. The default is ``0.1``.
497+
setback : float, optional
498+
Setback value for the file. The default is ``0.0``.
499+
500+
Returns
501+
-------
502+
bool
503+
``True`` when successful, ``False`` when failed.
504+
505+
References
506+
----------
507+
>>> oEditor.Fillet
508+
509+
"""
510+
edge_id_list = []
511+
vertex_id_list = []
512+
513+
if isinstance(self, VertexPrimitive):
514+
vertex_id_list = [self.id]
515+
else:
516+
if self._object3d.is3d:
517+
edge_id_list = [self.id]
518+
else:
519+
self._object3d.logger.error("Fillet is possible only on a vertex in 2D designs.")
520+
return False
521+
522+
vArg1 = ["NAME:Selections", "Selections:=", self._object3d.name, "NewPartsModelFlag:=", "Model"]
523+
vArg2 = ["NAME:FilletParameters"]
524+
vArg2.append("Edges:="), vArg2.append(edge_id_list)
525+
vArg2.append("Vertices:="), vArg2.append(vertex_id_list)
526+
vArg2.append("Radius:="), vArg2.append(self._object3d._primitives._app.value_with_units(radius))
527+
vArg2.append("Setback:="), vArg2.append(self._object3d._primitives._app.value_with_units(setback))
528+
self._object3d._oeditor.Fillet(vArg1, ["NAME:Parameters", vArg2])
529+
if self._object3d.name in list(self._object3d._oeditor.GetObjectsInGroup("UnClassified")):
530+
self._object3d._primitives._odesign.Undo()
531+
self._object3d.logger.error("Operation failed, generating an unclassified object. Check and retry.")
532+
return False
533+
return True
534+
535+
@pyaedt_function_handler()
536+
def chamfer(self, left_distance=1, right_distance=None, angle=45, chamfer_type=0):
537+
"""Add a chamfer to the selected edges in 3D/vertices in 2D.
538+
539+
Parameters
540+
----------
541+
left_distance : float, optional
542+
Left distance from the edge. The default is ``1``.
543+
right_distance : float, optional
544+
Right distance from the edge. The default is ``None``.
545+
angle : float, optional.
546+
Angle value for chamfer types 2 and 3. The default is ``0``.
547+
chamfer_type : int, optional
548+
Type of the chamfer. Options are:
549+
* 0 - Symmetric
550+
* 1 - Left Distance-Right Distance
551+
* 2 - Left Distance-Angle
552+
* 3 - Right Distance-Angle
553+
554+
The default is ``0``.
555+
556+
Returns
557+
-------
558+
bool
559+
``True`` when successful, ``False`` when failed.
560+
561+
References
562+
----------
563+
>>> oEditor.Chamfer
564+
565+
"""
566+
edge_id_list = []
567+
vertex_id_list = []
568+
569+
if isinstance(self, VertexPrimitive):
570+
vertex_id_list = [self.id]
571+
else:
572+
if self._object3d.is3d:
573+
edge_id_list = [self.id]
574+
else:
575+
self._object3d.logger.error("chamfer is possible only on Vertex in 2D Designs ")
576+
return False
577+
vArg1 = ["NAME:Selections", "Selections:=", self._object3d.name, "NewPartsModelFlag:=", "Model"]
578+
vArg2 = ["NAME:ChamferParameters"]
579+
vArg2.append("Edges:="), vArg2.append(edge_id_list)
580+
vArg2.append("Vertices:="), vArg2.append(vertex_id_list)
581+
if right_distance is None:
582+
right_distance = left_distance
583+
if chamfer_type == 0:
584+
if left_distance != right_distance:
585+
self._object3d.logger.error(
586+
"Do not set right distance or ensure that left distance equals right distance."
587+
)
588+
(
589+
vArg2.append("LeftDistance:="),
590+
vArg2.append(self._object3d._primitives._app.value_with_units(left_distance)),
591+
)
592+
(
593+
vArg2.append("RightDistance:="),
594+
vArg2.append(self._object3d._primitives._app.value_with_units(right_distance)),
595+
)
596+
vArg2.append("ChamferType:="), vArg2.append("Symmetric")
597+
elif chamfer_type == 1:
598+
(
599+
vArg2.append("LeftDistance:="),
600+
vArg2.append(self._object3d._primitives._app.value_with_units(left_distance)),
601+
)
602+
(
603+
vArg2.append("RightDistance:="),
604+
vArg2.append(self._object3d._primitives._app.value_with_units(right_distance)),
605+
)
606+
vArg2.append("ChamferType:="), vArg2.append("Left Distance-Right Distance")
607+
elif chamfer_type == 2:
608+
(
609+
vArg2.append("LeftDistance:="),
610+
vArg2.append(self._object3d._primitives._app.value_with_units(left_distance)),
611+
)
612+
# NOTE: Seems like there is a bug in the API as Angle can't be used
613+
vArg2.append("RightDistance:="), vArg2.append(f"{angle}deg")
614+
vArg2.append("ChamferType:="), vArg2.append("Left Distance-Angle")
615+
elif chamfer_type == 3:
616+
# NOTE: Seems like there is a bug in the API as Angle can't be used
617+
vArg2.append("LeftDistance:="), vArg2.append(f"{angle}deg")
618+
(
619+
vArg2.append("RightDistance:="),
620+
vArg2.append(self._object3d._primitives._app.value_with_units(right_distance)),
621+
)
622+
vArg2.append("ChamferType:="), vArg2.append("Right Distance-Angle")
623+
else:
624+
self._object3d.logger.error("Wrong chamfer_type provided. Value must be an integer from 0 to 3.")
625+
return False
626+
self._object3d._oeditor.Chamfer(vArg1, ["NAME:Parameters", vArg2])
627+
if self._object3d.name in list(self._object3d._oeditor.GetObjectsInGroup("UnClassified")):
628+
self._object3d.odesign.Undo()
629+
self._object3d.logger.error("Operation Failed generating Unclassified object. Check and retry")
630+
return False
631+
return True
632+
415633

416634
class FacePrimitive(PyAedtBase):
417635
"""Contains the face object within the AEDT Desktop Modeler.

src/ansys/aedt/core/modeler/cad/object_3d.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,8 @@
2424

2525
"""
2626
This module contains these classes: `Components3DLayout`,`CircuitComponent',
27-
`EdgePrimitive`, `EdgeTypePrimitive`, `FacePrimitive`, `Geometries3DLayout`,
28-
`Nets3DLayout`, `Object3DLayout`, `Object3d`, `Padstack`, `PDSHole`, `PDSLayer`,
29-
`Pins3DLayout', and `VertexPrimitive`.
27+
`Geometries3DLayout`, `Nets3DLayout`, `Object3DLayout`, `Object3d`, `Padstack`,
28+
`PDSHole`, `PDSLayer` and `Pins3DLayout'.
3029
3130
This module provides methods and data structures for managing all properties of
3231
objects (points, lines, sheets, and solids) within the AEDT 3D Modeler.

tests/system/general/test_02_3D_modeler.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from ansys.aedt.core.generic.numbers_utils import decompose_variable_value
3333
from ansys.aedt.core.generic.quaternion import Quaternion
3434
from ansys.aedt.core.modeler.cad.elements_3d import FacePrimitive
35+
from ansys.aedt.core.modeler.cad.elements_3d import VertexPrimitive
3536
from ansys.aedt.core.modeler.cad.modeler import FaceCoordinateSystem
3637
from ansys.aedt.core.modeler.cad.object_3d import Object3d
3738
from ansys.aedt.core.modeler.cad.primitives import CoordinateSystem as cs
@@ -1311,3 +1312,32 @@ def test_project_sheet_failure(self):
13111312

13121313
assert not self.aedtapp.modeler.project_sheet(rect, box_0, 5, "10deg")
13131314
self.aedtapp.modeler.delete(self.aedtapp.modeler.object_names)
1315+
1316+
def test_edge_primitives_getitem(self):
1317+
"""Test EdgePrimitive __getitem__ method."""
1318+
box = self.aedtapp.modeler.create_box([0, 0, 0], [10, 10, 10], "box_edge_primitive_getitem")
1319+
edge = box.edges[0]
1320+
1321+
vertex_0, vertex_1 = edge[0], edge[1]
1322+
1323+
assert isinstance(vertex_0, VertexPrimitive)
1324+
assert isinstance(vertex_1, VertexPrimitive)
1325+
1326+
def test_edge_primitives_iter(self):
1327+
"""Test EdgePrimitive __iter__ method."""
1328+
box = self.aedtapp.modeler.create_box([0, 0, 0], [10, 10, 10], "box_edge_primitive_iter")
1329+
edge = box.edges[0]
1330+
1331+
for vertex in edge:
1332+
assert isinstance(vertex, VertexPrimitive)
1333+
1334+
def test_edge_primitives_contains(self):
1335+
"""Test EdgePrimitive __contains__ method."""
1336+
box = self.aedtapp.modeler.create_box([0, 0, 0], [10, 10, 10], "box_edge_primitive_contains")
1337+
edge = box.edges[0]
1338+
vertex_0, vertex_1 = edge[0], edge[1]
1339+
1340+
assert vertex_0 in edge
1341+
assert vertex_0.id in edge
1342+
assert vertex_1 in edge
1343+
assert vertex_1.id in edge

0 commit comments

Comments
 (0)