Skip to content

Commit d25808d

Browse files
RobPasMuepyansys-ci-botPipKatpre-commit-ci[bot]
authored
feat: implement cut operation in extrude sketch (#1510)
Co-authored-by: pyansys-ci-bot <[email protected]> Co-authored-by: Kathy Pippert <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 8b919dd commit d25808d

File tree

9 files changed

+226
-5
lines changed

9 files changed

+226
-5
lines changed

doc/changelog.d/1510.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
implement cut operation in extrude sketch
86.2 KB
Loading

doc/source/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ def intersphinx_pyansys_geometry(switcher_version: str):
283283
"examples/02_sketching/advanced_sketching_gears": "_static/thumbnails/advanced_sketching_gears.png", # noqa: E501
284284
"examples/03_modeling/add_design_material": "_static/thumbnails/add_design_material.png",
285285
"examples/03_modeling/plate_with_hole": "_static/thumbnails/plate_with_hole.png",
286+
"examples/03_modeling/cut_operation_on_extrude": "_static/thumbnails/cut_operation_on_extrude.png", # noqa: E501
286287
"examples/03_modeling/tessellation_usage": "_static/thumbnails/tessellation_usage.png",
287288
"examples/03_modeling/design_organization": "_static/thumbnails/design_organization.png",
288289
"examples/03_modeling/boolean_operations": "_static/thumbnails/boolean_operations.png",

doc/source/examples.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ These examples demonstrate service-based modeling operations.
3838

3939
examples/03_modeling/add_design_material.mystnb
4040
examples/03_modeling/plate_with_hole.mystnb
41+
examples/03_modeling/cut_operation_on_extrude.mystnb
4142
examples/03_modeling/tessellation_usage.mystnb
4243
examples/03_modeling/design_organization.mystnb
4344
examples/03_modeling/boolean_operations.mystnb
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
---
2+
jupytext:
3+
text_representation:
4+
extension: .mystnb
5+
format_name: myst
6+
format_version: 0.13
7+
jupytext_version: 1.14.1
8+
kernelspec:
9+
display_name: Python 3 (ipykernel)
10+
language: python
11+
name: python3
12+
---
13+
14+
# Modeling: Extruded plate with cut operations
15+
16+
As seen in the [Rectangular plate with multiple bodies](./plate_with_hole.mystnb) example,
17+
you can create a complex sketch with holes and extrude it to create a body. However, you can also
18+
perform cut operations on the extruded body to achieve similar results.
19+
20+
+++
21+
22+
## Perform required imports
23+
24+
Perform the required imports.
25+
26+
```{code-cell} ipython3
27+
from pint import Quantity
28+
29+
from ansys.geometry.core import launch_modeler
30+
from ansys.geometry.core.math import Plane, Point3D, Point2D
31+
from ansys.geometry.core.misc import UNITS
32+
from ansys.geometry.core.sketch import Sketch
33+
```
34+
35+
## Define sketch profile without holes
36+
37+
Create a sketch profile for the proposed design. The sketch is the same as the
38+
[Rectangular plate with multiple bodies](./plate_with_hole.mystnb) example, but without the holes.
39+
40+
These holes are created by performing cut operations on the extruded body in the next steps.
41+
42+
43+
```{code-cell} ipython3
44+
sketch = Sketch()
45+
(sketch.segment(Point2D([-4, 5], unit=UNITS.m), Point2D([4, 5], unit=UNITS.m))
46+
.segment_to_point(Point2D([4, -5], unit=UNITS.m))
47+
.segment_to_point(Point2D([-4, -5], unit=UNITS.m))
48+
.segment_to_point(Point2D([-4, 5], unit=UNITS.m))
49+
.box(Point2D([0,0], unit=UNITS.m), Quantity(3, UNITS.m), Quantity(3, UNITS.m))
50+
)
51+
52+
modeler = launch_modeler()
53+
design = modeler.create_design("ExtrudedPlateNoHoles")
54+
body = design.extrude_sketch(f"PlateLayer", sketch, Quantity(2, UNITS.m))
55+
56+
design.plot()
57+
```
58+
59+
## Define sketch profile for holes
60+
61+
Create a sketch profile for the holes in the proposed design. The holes are created by
62+
sketching circles at the four corners of the plate. First create a reference sketch
63+
for all the circles. This sketch is translated to the four corners of the plate.
64+
65+
```{code-cell} ipython3
66+
sketch_hole = Sketch()
67+
sketch_hole.circle(Point2D([0, 0], unit=UNITS.m), Quantity(0.5, UNITS.m))
68+
69+
hole_centers = [
70+
Plane(Point3D([3, 4, 0], unit=UNITS.m)),
71+
Plane(Point3D([-3, 4, 0], unit=UNITS.m)),
72+
Plane(Point3D([-3, -4, 0], unit=UNITS.m)),
73+
Plane(Point3D([3, -4, 0], unit=UNITS.m)),
74+
]
75+
```
76+
77+
## Perform cut operations on the extruded body
78+
79+
Perform cut operations on the extruded body to create holes at the four corners of the plate.
80+
81+
```{code-cell} ipython3
82+
for center in hole_centers:
83+
sketch_hole.plane = center
84+
design.extrude_sketch(
85+
name= f"H_{center.origin.x}_{center.origin.y}",
86+
sketch=sketch_hole,
87+
distance=Quantity(2, UNITS.m),
88+
cut=True,
89+
)
90+
91+
design.plot()
92+
```
93+
94+
## Close the modeler
95+
96+
Close the modeler to free up resources and release the connection.
97+
98+
```{code-cell} ipython3
99+
modeler.close()
100+
```

doc/source/examples/03_modeling/plate_with_hole.mystnb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ kernelspec:
1616
You can create multiple bodies from a single sketch by extruding the same sketch in different planes.
1717

1818
The sketch is designed as an effective *functional-style* API with all operations receiving 2D configurations.
19-
For more information, see the :ref:`Sketch <ref_sketch>` subpackage.
19+
For more information, see the [Sketch](../../user_guide/shapes.rst) user guide.
2020

2121
In this example, a box is located in the center of the plate, with the default origin of a sketch plane
2222
(origin at ``(0, 0, 0)``). Four holes of equal radius are sketched at the corners of the plate.

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

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
unit_vector_to_grpc_direction,
6464
)
6565
from ansys.geometry.core.designer.beam import Beam, BeamProfile
66-
from ansys.geometry.core.designer.body import Body, MasterBody
66+
from ansys.geometry.core.designer.body import Body, CollisionType, MasterBody
6767
from ansys.geometry.core.designer.coordinate_system import CoordinateSystem
6868
from ansys.geometry.core.designer.designpoint import DesignPoint
6969
from ansys.geometry.core.designer.face import Face
@@ -324,6 +324,20 @@ def __create_children(self, template: "Component") -> None:
324324
)
325325
self.components.append(new)
326326

327+
def get_all_bodies(self) -> list[Body]:
328+
"""Get all bodies in the component hierarchy.
329+
330+
Returns
331+
-------
332+
list[Body]
333+
List of all bodies in the component hierarchy.
334+
"""
335+
bodies = []
336+
for comp in self.components:
337+
bodies.extend(comp.get_all_bodies())
338+
bodies.extend(self.bodies)
339+
return bodies
340+
327341
def get_world_transform(self) -> Matrix44:
328342
"""Get the full transformation matrix of the component in world space.
329343
@@ -468,7 +482,8 @@ def extrude_sketch(
468482
sketch: Sketch,
469483
distance: Quantity | Distance | Real,
470484
direction: ExtrusionDirection | str = ExtrusionDirection.POSITIVE,
471-
) -> Body:
485+
cut: bool = False,
486+
) -> Body | None:
472487
"""Create a solid body by extruding the sketch profile a distance.
473488
474489
Notes
@@ -487,11 +502,16 @@ def extrude_sketch(
487502
Direction for extruding the solid body.
488503
The default is to extrude in the positive normal direction of the sketch.
489504
Options are "+" and "-" as a string, or the enum values.
505+
cut : bool, default: False
506+
Whether to cut the extrusion from the existing component.
507+
By default, the extrusion is added to the existing component.
490508
491509
Returns
492510
-------
493511
Body
494512
Extruded body from the given sketch.
513+
None
514+
If the cut parameter is ``True``, the function returns ``None``.
495515
"""
496516
# Sanity checks on inputs
497517
distance = distance if isinstance(distance, Distance) else Distance(distance)
@@ -516,7 +536,26 @@ def extrude_sketch(
516536
tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False)
517537
self._master_component.part.bodies.append(tb)
518538
self._clear_cached_bodies()
519-
return Body(response.id, response.name, self, tb)
539+
540+
created_body = Body(response.id, response.name, self, tb)
541+
if not cut:
542+
return created_body
543+
else:
544+
# If cut is True, subtract the created body from all existing bodies
545+
# in the component...
546+
for existing_body in self.get_all_bodies():
547+
# Skip the created body
548+
if existing_body.id == created_body.id:
549+
continue
550+
# Check for collision
551+
if existing_body.get_collision(created_body) != CollisionType.NONE:
552+
existing_body.subtract(created_body, keep_other=True)
553+
554+
# Finally, delete the created body
555+
self.delete_body(created_body)
556+
557+
# And obviously return None... since no body is created
558+
return None
520559

521560
@min_backend_version(24, 2, 0)
522561
@protect_grpc

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ def __init__(self, value: Real | Quantity, unit: Unit, dimensions: Unit):
174174
# Store the value
175175
self._value = self._base_units_magnitude(value)
176176

177+
def __repr__(self):
178+
"""Representation of the ``Measurement`` class."""
179+
return f"{self.value}"
180+
177181
@property
178182
def value(self) -> Quantity:
179183
"""Value of the measurement."""

tests/integration/test_design.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1991,7 +1991,6 @@ def test_get_active_design(modeler: Modeler):
19911991

19921992
def test_get_collision(modeler: Modeler):
19931993
"""Test the collision state between two bodies."""
1994-
skip_if_linux(modeler, test_get_collision.__name__, "get_collision") # Skip test on Linux
19951994
design = modeler.open_file(FILES_DIR / "MixingTank.scdocx")
19961995
body1 = design.bodies[0]
19971996
body2 = design.bodies[1]
@@ -2784,3 +2783,79 @@ def test_cached_bodies(modeler: Modeler):
27842783
for body1, body3 in zip(my_bodies, my_bodies_3):
27852784
assert body1 is not body3
27862785
assert id(body1) != id(body3)
2786+
2787+
2788+
def test_extrude_sketch_with_cut_request(modeler: Modeler):
2789+
"""Test the cut argument when performing a sketch extrusion.
2790+
2791+
This method mimics a cut operation.
2792+
2793+
Behind the scenes, a subtraction operation is performed on the bodies. After extruding the
2794+
sketch, the resulting body should be a cut body.
2795+
"""
2796+
# Define a sketch
2797+
origin = Point3D([0, 0, 10])
2798+
plane = Plane(origin, direction_x=[1, 0, 0], direction_y=[0, 1, 0])
2799+
2800+
# Create a sketch
2801+
sketch_box = Sketch(plane)
2802+
sketch_box.box(Point2D([20, 20]), 30 * UNITS.m, 30 * UNITS.m)
2803+
2804+
sketch_cylinder = Sketch(plane)
2805+
sketch_cylinder.circle(Point2D([20, 20]), 5 * UNITS.m)
2806+
2807+
# Create a design
2808+
design = modeler.create_design("ExtrudeSketchWithCut")
2809+
2810+
box_body = design.extrude_sketch(
2811+
name="BoxBody", sketch=sketch_box, distance=Distance(30, unit=UNITS.m)
2812+
)
2813+
volume_box = box_body.volume
2814+
2815+
design.extrude_sketch(
2816+
name="CylinderBody", sketch=sketch_cylinder, distance=Distance(60, unit=UNITS.m), cut=True
2817+
)
2818+
2819+
# Verify there is only one body
2820+
assert len(design.bodies) == 1
2821+
2822+
# Verify the volume of the resulting body is less than the volume of the box
2823+
assert design.bodies[0].volume < volume_box
2824+
2825+
2826+
def test_extrude_sketch_with_cut_request_no_collision(modeler: Modeler):
2827+
"""Test the cut argument when performing a sketch extrusion (with no collision).
2828+
2829+
This method mimics an unsuccessful cut operation.
2830+
2831+
The sketch extrusion should not result in a cut body since there is no collision between the
2832+
original body and the extruded body.
2833+
"""
2834+
# Define a sketch
2835+
origin = Point3D([0, 0, 10])
2836+
plane = Plane(origin, direction_x=[1, 0, 0], direction_y=[0, 1, 0])
2837+
2838+
# Create a sketch
2839+
sketch_box = Sketch(plane)
2840+
sketch_box.box(Point2D([20, 20]), 30 * UNITS.m, 30 * UNITS.m)
2841+
2842+
sketch_cylinder = Sketch(plane)
2843+
sketch_cylinder.circle(Point2D([100, 100]), 5 * UNITS.m)
2844+
2845+
# Create a design
2846+
design = modeler.create_design("ExtrudeSketchWithCutNoCollision")
2847+
2848+
box_body = design.extrude_sketch(
2849+
name="BoxBody", sketch=sketch_box, distance=Distance(30, unit=UNITS.m)
2850+
)
2851+
volume_box = box_body.volume
2852+
2853+
design.extrude_sketch(
2854+
name="CylinderBody", sketch=sketch_cylinder, distance=Distance(60, unit=UNITS.m), cut=True
2855+
)
2856+
2857+
# Verify there is only one body... the cut operation should delete it
2858+
assert len(design.bodies) == 1
2859+
2860+
# Verify the volume of the resulting body is exactly the same
2861+
assert design.bodies[0].volume == volume_box

0 commit comments

Comments
 (0)