Skip to content

Commit 7c7d5bb

Browse files
jacobrkerstetterpre-commit-ci[bot]pyansys-ci-bot
authored
feat: move_imprint_edges, offset_edges, draft_faces, thicken_faces implementations (#2214)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pyansys-ci-bot <[email protected]>
1 parent aa5508b commit 7c7d5bb

File tree

4 files changed

+313
-0
lines changed

4 files changed

+313
-0
lines changed

doc/changelog.d/2214.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Move_imprint_edges, offset_edges, draft_faces, thicken_faces implementations

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

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
CreateCircularPatternRequest,
3535
CreateFillPatternRequest,
3636
CreateLinearPatternRequest,
37+
DraftFacesRequest,
3738
ExtrudeEdgesRequest,
3839
ExtrudeEdgesUpToRequest,
3940
ExtrudeFacesRequest,
@@ -42,8 +43,10 @@
4243
FullFilletRequest,
4344
ModifyCircularPatternRequest,
4445
ModifyLinearPatternRequest,
46+
MoveImprintEdgesRequest,
4547
MoveRotateRequest,
4648
MoveTranslateRequest,
49+
OffsetEdgesRequest,
4750
OffsetFacesSetRadiusRequest,
4851
PatternRequest,
4952
RenameObjectRequest,
@@ -53,6 +56,7 @@
5356
RevolveFacesUpToRequest,
5457
RoundInfoRequest,
5558
SplitBodyRequest,
59+
ThickenFacesRequest,
5660
)
5761
from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub
5862
from ansys.geometry.core.connection.client import GrpcClient
@@ -79,6 +83,7 @@
7983
get_design_from_component,
8084
get_design_from_edge,
8185
get_design_from_face,
86+
get_faces_from_ids,
8287
)
8388
from ansys.geometry.core.misc.checks import (
8489
check_is_float_int,
@@ -128,6 +133,16 @@ class FillPatternType(Enum):
128133
SKEWED = 2
129134

130135

136+
@unique
137+
class DraftSide(Enum):
138+
"""Provides values for draft sides."""
139+
140+
NO_SPLIT = 0
141+
THIS = 1
142+
OTHER = 2
143+
BACK = 3
144+
145+
131146
class GeometryCommands:
132147
"""Provides geometry commands for PyAnsys Geometry.
133148
@@ -1666,3 +1681,178 @@ def create_orient_condition(
16661681
result.is_reversed,
16671682
result.is_valid,
16681683
)
1684+
1685+
@protect_grpc
1686+
@min_backend_version(26, 1, 0)
1687+
def move_imprint_edges(
1688+
self, edges: list["Edge"], direction: UnitVector3D, distance: Distance | Quantity | Real
1689+
) -> bool:
1690+
"""Move the imprint edges in the specified direction by the specified distance.
1691+
1692+
Parameters
1693+
----------
1694+
edges : list[Edge]
1695+
The edges to move.
1696+
direction : UnitVector3D
1697+
The direction to move the edges.
1698+
distance : Distance
1699+
The distance to move the edges.
1700+
1701+
Returns
1702+
-------
1703+
bool
1704+
Returns True if the edges were moved successfully, False otherwise.
1705+
"""
1706+
# Convert the distance object
1707+
distance = distance if isinstance(distance, Distance) else Distance(distance)
1708+
move_magnitude = distance.value.m_as(DEFAULT_UNITS.SERVER_LENGTH)
1709+
1710+
# Create the request object
1711+
request = MoveImprintEdgesRequest(
1712+
edges=[edge._grpc_id for edge in edges],
1713+
direction=unit_vector_to_grpc_direction(direction),
1714+
distance=move_magnitude,
1715+
)
1716+
1717+
# Call the gRPC service
1718+
response = self._commands_stub.MoveImprintEdges(request)
1719+
1720+
# Return success flag
1721+
return response.result.success
1722+
1723+
@protect_grpc
1724+
@min_backend_version(26, 1, 0)
1725+
def offset_edges(self, edges: list["Edge"], offset: Distance | Quantity | Real) -> bool:
1726+
"""Offset the specified edges with the specified distance.
1727+
1728+
Parameters
1729+
----------
1730+
edges : list[Edge]
1731+
The edges to offset.
1732+
offset : Distance
1733+
The distance to offset the edges.
1734+
1735+
Returns
1736+
-------
1737+
bool
1738+
Returns True if the edges were offset successfully, False otherwise.
1739+
"""
1740+
# Convert the distance object
1741+
offset = offset if isinstance(offset, Distance) else Distance(offset)
1742+
offset_magnitude = offset.value.m_as(DEFAULT_UNITS.SERVER_LENGTH)
1743+
1744+
# Create the request object
1745+
request = OffsetEdgesRequest(
1746+
edges=[edge._grpc_id for edge in edges],
1747+
value=offset_magnitude,
1748+
)
1749+
1750+
# Call the gRPC service
1751+
response = self._commands_stub.OffsetEdges(request)
1752+
1753+
# Return success flag
1754+
return response.success
1755+
1756+
@protect_grpc
1757+
@min_backend_version(26, 1, 0)
1758+
def draft_faces(
1759+
self,
1760+
faces: list["Face"],
1761+
reference_faces: list["Face"],
1762+
draft_side: DraftSide,
1763+
angle: Angle | Quantity | Real,
1764+
extrude_type: ExtrudeType,
1765+
) -> list["Face"]:
1766+
"""Draft the specified faces in the specified direction by the specified angle.
1767+
1768+
Parameters
1769+
----------
1770+
faces : list[Face]
1771+
The faces to draft.
1772+
reference_faces : list[Face]
1773+
The reference faces to use for the draft.
1774+
draft_side : DraftSide
1775+
The side to draft.
1776+
angle : Angle | Quantity | Real
1777+
The angle to draft the faces.
1778+
extrude_type : ExtrudeType
1779+
The type of extrusion to use.
1780+
1781+
Returns
1782+
-------
1783+
list[Face]
1784+
The faces created by the draft operation.
1785+
"""
1786+
# Convert the angle object
1787+
angle = angle if isinstance(angle, Angle) else Angle(angle)
1788+
angle_magnitude = angle.value.m_as(DEFAULT_UNITS.SERVER_ANGLE)
1789+
1790+
# Create the request object
1791+
request = DraftFacesRequest(
1792+
faces=[face._grpc_id for face in faces],
1793+
reference_faces=[face._grpc_id for face in reference_faces],
1794+
draft_side=draft_side.value,
1795+
draft_angle=angle_magnitude,
1796+
extrude_type=extrude_type.value,
1797+
)
1798+
1799+
# Call the gRPC server
1800+
response = self._commands_stub.DraftFaces(request)
1801+
1802+
# Return the drafted faces
1803+
design = get_design_from_face(faces[0])
1804+
return get_faces_from_ids(design, [face.id for face in response.created_faces])
1805+
1806+
@protect_grpc
1807+
@min_backend_version(26, 1, 0)
1808+
def thicken_faces(
1809+
self,
1810+
faces: list["Face"],
1811+
direction: UnitVector3D,
1812+
thickness: Real,
1813+
extrude_type: ExtrudeType,
1814+
pull_symmetric: bool,
1815+
select_direction: bool,
1816+
) -> bool:
1817+
"""Thicken the specified faces by the specified thickness in the specified direction.
1818+
1819+
Parameters
1820+
----------
1821+
faces : list[Face]
1822+
The faces to thicken.
1823+
direction : UnitVector3D
1824+
The direction to thicken the faces.
1825+
thickness : Real
1826+
The thickness to apply to the faces.
1827+
extrude_type : ExtrudeType
1828+
The type of extrusion to use.
1829+
pull_symmetric : bool
1830+
Whether to pull the faces symmetrically.
1831+
select_direction : bool
1832+
Whether to select the direction.
1833+
1834+
Returns
1835+
-------
1836+
bool
1837+
Returns True if the faces were thickened successfully, False otherwise.
1838+
"""
1839+
# Create the request object
1840+
request = ThickenFacesRequest(
1841+
faces=[face._grpc_id for face in faces],
1842+
direction=unit_vector_to_grpc_direction(direction),
1843+
value=thickness,
1844+
extrude_type=extrude_type.value,
1845+
pull_symmetric=pull_symmetric,
1846+
select_direction=select_direction,
1847+
)
1848+
1849+
# Call the gRPC service
1850+
response = self._commands_stub.ThickenFaces(request)
1851+
1852+
# Update design
1853+
design = get_design_from_face(faces[0])
1854+
if response.success:
1855+
design._update_design_inplace()
1856+
1857+
# Return success flag
1858+
return response.success

tests/_incompatible_tests.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ backends:
8888
- tests/integration/test_design.py::test_component_body
8989
- tests/integration/test_design.py::test_midsurface_properties
9090
- tests/integration/test_issues.py::test_issue_834_design_import_with_surfaces
91+
- tests/integration/test_geometry_commands.py::test_thicken_surface_body
9192
# Ordering of faces/edges changed from 24R2 onwards
9293
- tests/integration/test_design.py::test_faces_edges
9394
# Due to test setup, these tests error out and are not caught by the backend version error
@@ -168,6 +169,7 @@ backends:
168169
- tests/integration/test_design.py::test_stream_upload_file
169170
# No sketch on imprinted curves is only supported from 25.2 onwards
170171
- tests/integration/test_design.py::test_imprint_trimmed_curves
172+
- tests/integration/test_geometry_commands.py::test_move_imprint_edges
171173
# Components and vertex only available from 26.1 onwards
172174
- tests/integration/test_design.py::test_named_selection_contents
173175
- tests/integration/test_design.py::test_named_selections_components
@@ -222,6 +224,7 @@ backends:
222224
- tests/integration/test_design.py::test_stream_upload_file
223225
# No sketch on imprinted curves is only supported from 25.2 onwards
224226
- tests/integration/test_design.py::test_imprint_trimmed_curves
227+
- tests/integration/test_geometry_commands.py::test_move_imprint_edges
225228
# Components and vertex only available from 26.1 onwards
226229
- tests/integration/test_design.py::test_named_selection_contents
227230
- tests/integration/test_design.py::test_named_selections_components

tests/integration/test_geometry_commands.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import pytest
2727

2828
from ansys.geometry.core.designer.geometry_commands import (
29+
DraftSide,
2930
ExtrudeType,
3031
FillPatternType,
3132
OffsetMode,
@@ -1286,3 +1287,121 @@ def test_offset_face_set_radius(modeler: Modeler):
12861287
assert success
12871288

12881289
assert box.volume.m == pytest.approx(Quantity(7.6073, UNITS.m**3).m, rel=1e-6, abs=1e-8)
1290+
1291+
1292+
def test_move_imprint_edges(modeler: Modeler):
1293+
"""Test moving imprint edges."""
1294+
design = modeler.create_design("move_imprint_edges")
1295+
box = design.extrude_sketch("box", Sketch().box(Point2D([0, 0]), 2, 2), 2)
1296+
1297+
# Create a cylinder cutting through the box
1298+
cylinder = design.extrude_sketch("cylinder", Sketch().circle(Point2D([0, 0]), 0.5), 2)
1299+
1300+
# Imprint the top edge of the cylindrical hole
1301+
edges = cylinder.faces[1].edges
1302+
trimmed_curves = [edges[0].shape]
1303+
new_edges, new_faces = box.imprint_curves(faces=[box.faces[1]], trimmed_curves=trimmed_curves)
1304+
1305+
assert len(new_edges) == 1
1306+
assert len(new_faces) == 1
1307+
1308+
# Move the imprinted edge to create new face and edge
1309+
modeler.geometry_commands.move_imprint_edges([edges[0]], UNITVECTOR3D_Y, 0.1)
1310+
1311+
# Verify the new edges and faces
1312+
assert len(box.edges) == 13
1313+
assert len(box.faces) == 7
1314+
1315+
1316+
def test_offset_edges(modeler: Modeler):
1317+
"""Test offsetting edges of a surface body."""
1318+
design = modeler.create_design("offset_edges")
1319+
1320+
# Create rectangular surface
1321+
sketch = Sketch().box(Point2D([0, 0]), 2, 2)
1322+
surface = design.create_surface("surface", sketch)
1323+
1324+
assert (
1325+
modeler.measurement_tools.min_distance_between_objects(
1326+
surface.edges[0], surface.edges[2]
1327+
).distance.value.m
1328+
== 2
1329+
)
1330+
assert len(surface.faces) == 1
1331+
assert len(surface.edges) == 4
1332+
1333+
# Offset the imprinted edge to create new face and edge
1334+
modeler.geometry_commands.offset_edges([surface.edges[0], surface.edges[2]], 5)
1335+
1336+
# Verify the new edges and faces
1337+
assert (
1338+
modeler.measurement_tools.min_distance_between_objects(
1339+
surface.edges[0], surface.edges[2]
1340+
).distance.value.m
1341+
== 12
1342+
)
1343+
assert len(surface.faces) == 1
1344+
assert len(surface.edges) == 4
1345+
1346+
1347+
def test_draft_faces(modeler: Modeler):
1348+
"""Test drafting faces."""
1349+
design = modeler.create_design("draft_faces")
1350+
box = design.extrude_sketch("box", Sketch().box(Point2D([0, 0]), 2, 2), 2)
1351+
1352+
assert box.faces[2].area.m == pytest.approx(Quantity(4, UNITS.m**2).m, rel=1e-6, abs=1e-8)
1353+
1354+
# Create a face to draft
1355+
faces = box.faces[2:]
1356+
ref_faces = [box.faces[1]]
1357+
1358+
# Draft the face
1359+
drafted_faces = modeler.geometry_commands.draft_faces(
1360+
faces, ref_faces, DraftSide.NO_SPLIT, Angle(10, UNITS.deg), ExtrudeType.ADD
1361+
)
1362+
1363+
assert len(drafted_faces) == 0
1364+
assert box.faces[2].area.m == pytest.approx(
1365+
Quantity(4.777895, UNITS.m**2).m, rel=1e-6, abs=1e-8
1366+
)
1367+
1368+
1369+
def test_thicken_faces(modeler: Modeler):
1370+
"""Test thickening faces."""
1371+
design = modeler.create_design("thicken_faces")
1372+
box = design.extrude_sketch("box", Sketch().box(Point2D([0, 0]), 2, 2), 2)
1373+
1374+
assert len(box.faces) == 6
1375+
assert box.volume.m == pytest.approx(Quantity(8, UNITS.m**3).m, rel=1e-6, abs=1e-8)
1376+
1377+
# Thicken the top face
1378+
success = modeler.geometry_commands.thicken_faces(
1379+
[box.faces[1]], UNITVECTOR3D_Z, 0.1, ExtrudeType.ADD, False, False
1380+
)
1381+
assert success
1382+
1383+
assert box.volume.m == pytest.approx(Quantity(8.4, UNITS.m**3).m, rel=1e-6, abs=1e-8)
1384+
1385+
1386+
def test_thicken_surface_body(modeler: Modeler):
1387+
"""Test thickening a surface body."""
1388+
design = modeler.create_design("thicken_surface_body")
1389+
1390+
# Create rectangular surface
1391+
sketch = Sketch().box(Point2D([0, 0]), 2, 2)
1392+
surface = design.create_surface("surface", sketch)
1393+
1394+
assert len(surface.faces) == 1
1395+
assert len(surface.edges) == 4
1396+
assert surface.is_surface
1397+
1398+
# Thicken the surface body
1399+
success = modeler.geometry_commands.thicken_faces(
1400+
[surface.faces[0]], UNITVECTOR3D_Z, 0.1, ExtrudeType.ADD, False, False
1401+
)
1402+
assert success
1403+
1404+
assert len(design.bodies) == 1
1405+
assert design.bodies[0].volume.m == pytest.approx(
1406+
Quantity(0.4, UNITS.m**3).m, rel=1e-6, abs=1e-8
1407+
)

0 commit comments

Comments
 (0)