Skip to content

Commit b3e7124

Browse files
committed
feat: adapt to new boolean grpc stub
1 parent 8f88fa2 commit b3e7124

File tree

2 files changed

+102
-35
lines changed

2 files changed

+102
-35
lines changed

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

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
)
4141
from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub
4242
from beartype import beartype as check_input_types
43-
from beartype.typing import TYPE_CHECKING, List, Optional, Tuple, Union
43+
from beartype.typing import TYPE_CHECKING, Iterable, List, Optional, Tuple, Union
4444
from pint import Quantity
4545

4646
from ansys.geometry.core.connection.client import GrpcClient
@@ -446,9 +446,9 @@ def plot(
446446
"""
447447
return
448448

449-
def intersect(self, other: "Body") -> None:
449+
def intersect(self, other: Union["Body", Iterable["Body"]]) -> None:
450450
"""
451-
Intersect two bodies.
451+
Intersect two (or more) bodies.
452452
453453
Notes
454454
-----
@@ -469,9 +469,9 @@ def intersect(self, other: "Body") -> None:
469469
return
470470

471471
@protect_grpc
472-
def subtract(self, other: "Body") -> None:
472+
def subtract(self, other: Union["Body", Iterable["Body"]]) -> None:
473473
"""
474-
Subtract two bodies.
474+
Subtract two (or more) bodies.
475475
476476
Notes
477477
-----
@@ -492,9 +492,9 @@ def subtract(self, other: "Body") -> None:
492492
return
493493

494494
@protect_grpc
495-
def unite(self, other: "Body") -> None:
495+
def unite(self, other: Union["Body", Iterable["Body"]]) -> None:
496496
"""
497-
Unite two bodies.
497+
Unite two (or more) bodies.
498498
499499
Notes
500500
-----
@@ -803,17 +803,17 @@ def plot(
803803
"MasterBody does not implement plot methods. Call this method on a body instead."
804804
)
805805

806-
def intersect(self, other: "Body") -> None: # noqa: D102
806+
def intersect(self, other: Union["Body", Iterable["Body"]]) -> None: # noqa: D102
807807
raise NotImplementedError(
808808
"MasterBody does not implement Boolean methods. Call this method on a body instead."
809809
)
810810

811-
def subtract(self, other: "Body") -> None: # noqa: D102
811+
def subtract(self, other: Union["Body", Iterable["Body"]]) -> None: # noqa: D102
812812
raise NotImplementedError(
813813
"MasterBody does not implement Boolean methods. Call this method on a body instead."
814814
)
815815

816-
def unite(self, other: "Body") -> None:
816+
def unite(self, other: Union["Body", Iterable["Body"]]) -> None:
817817
# noqa: D102
818818
raise NotImplementedError(
819819
"MasterBody does not implement Boolean methods. Call this method on a body instead."
@@ -1108,40 +1108,37 @@ def plot(
11081108
self, merge_bodies=merge, screenshot=screenshot, **plotting_options
11091109
)
11101110

1111-
@protect_grpc
1112-
@reset_tessellation_cache
1113-
@ensure_design_is_active
1114-
def intersect(self, other: "Body") -> None: # noqa: D102
1115-
response = self._template._bodies_stub.Boolean(
1116-
BooleanRequest(body1=self.id, body2=other.id, method="intersect")
1117-
).empty_result
1111+
def intersect(self, other: Union["Body", Iterable["Body"]]) -> None: # noqa: D102
1112+
self.__generic_boolean_op(other, "intersect", "bodies do not intersect")
11181113

1119-
if response == 1:
1120-
raise ValueError("Bodies do not intersect.")
1114+
def subtract(self, other: Union["Body", Iterable["Body"]]) -> None: # noqa: D102
1115+
self.__generic_boolean_op(other, "subtract", "empty (complete) subtraction")
11211116

1122-
other.parent_component.delete_body(other)
1117+
def unite(self, other: Union["Body", Iterable["Body"]]) -> None: # noqa: D102
1118+
self.__generic_boolean_op(other, "unite", "union operation failed")
11231119

11241120
@protect_grpc
11251121
@reset_tessellation_cache
11261122
@ensure_design_is_active
1127-
def subtract(self, other: "Body") -> None: # noqa: D102
1123+
@check_input_types
1124+
def __generic_boolean_op(
1125+
self, other: Union["Body", Iterable["Body"]], type_bool_op: str, err_bool_op: str
1126+
) -> None:
1127+
grpc_other = other if isinstance(other, Iterable) else [other]
11281128
response = self._template._bodies_stub.Boolean(
1129-
BooleanRequest(body1=self.id, body2=other.id, method="subtract")
1129+
BooleanRequest(
1130+
body1=self.id, tool_bodies=[b.id for b in grpc_other], method=type_bool_op
1131+
)
11301132
).empty_result
11311133

11321134
if response == 1:
1133-
raise ValueError("Subtraction of bodies results in an empty (complete) subtraction.")
1134-
1135-
other.parent_component.delete_body(other)
1135+
raise ValueError(
1136+
f"Boolean operation of type '{type_bool_op}' failed: {err_bool_op}.\n"
1137+
f"Involving bodies:{self}, {grpc_other}"
1138+
)
11361139

1137-
@protect_grpc
1138-
@reset_tessellation_cache
1139-
@ensure_design_is_active
1140-
def unite(self, other: "Body") -> None: # noqa: D102
1141-
self._template._bodies_stub.Boolean(
1142-
BooleanRequest(body1=self.id, body2=other.id, method="unite")
1143-
)
1144-
other.parent_component.delete_body(other)
1140+
for b in grpc_other:
1141+
b.parent_component.delete_body(b)
11451142

11461143
def __repr__(self) -> str:
11471144
"""Represent the ``Body`` as a string."""

tests/integration/test_design.py

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1465,7 +1465,7 @@ def test_boolean_body_operations(modeler: Modeler, skip_not_on_linux_service):
14651465
# 1.a.ii
14661466
copy1 = body1.copy(comp1, "Copy1")
14671467
copy3 = body3.copy(comp3, "Copy3")
1468-
with pytest.raises(ValueError, match="Bodies do not intersect."):
1468+
with pytest.raises(ValueError, match="bodies do not intersect"):
14691469
copy1.intersect(copy3)
14701470

14711471
assert copy1.is_alive
@@ -1574,7 +1574,7 @@ def test_boolean_body_operations(modeler: Modeler, skip_not_on_linux_service):
15741574
# 2.a.ii
15751575
copy1 = body1.copy(comp1_i, "Copy1")
15761576
copy3 = body3.copy(comp3_i, "Copy3")
1577-
with pytest.raises(ValueError, match="Bodies do not intersect."):
1577+
with pytest.raises(ValueError, match="bodies do not intersect"):
15781578
copy1.intersect(copy3)
15791579

15801580
assert copy1.is_alive
@@ -1644,6 +1644,76 @@ def test_boolean_body_operations(modeler: Modeler, skip_not_on_linux_service):
16441644
assert Accuracy.length_is_equal(copy1.volume.m, 1)
16451645

16461646

1647+
def test_multiple_bodies_boolean_operations(modeler: Modeler):
1648+
"""Test boolean operations with multiple bodies."""
1649+
1650+
design = modeler.create_design("TestBooleanOperationsMultipleBodies")
1651+
1652+
comp1 = design.add_component("Comp1")
1653+
comp2 = design.add_component("Comp2")
1654+
comp3 = design.add_component("Comp3")
1655+
1656+
body1 = comp1.extrude_sketch("Body1", Sketch().box(Point2D([0, 0]), 1, 1), 1)
1657+
body2 = comp2.extrude_sketch("Body2", Sketch().box(Point2D([0.5, 0]), 1, 1), 1)
1658+
body3 = comp3.extrude_sketch("Body3", Sketch().box(Point2D([5, 0]), 1, 1), 1)
1659+
1660+
################# Check subtract operation #################
1661+
copy1_sub = body1.copy(comp1, "Copy1_subtract")
1662+
copy2_sub = body2.copy(comp2, "Copy2_subtract")
1663+
copy3_sub = body3.copy(comp3, "Copy3_subtract")
1664+
copy1_sub.subtract([copy2_sub, copy3_sub])
1665+
1666+
assert not copy2_sub.is_alive
1667+
assert not copy3_sub.is_alive
1668+
assert body2.is_alive
1669+
assert body3.is_alive
1670+
assert len(comp1.bodies) == 2
1671+
assert len(comp2.bodies) == 1
1672+
assert len(comp3.bodies) == 1
1673+
1674+
# Cleanup previous subtest
1675+
comp1.delete_body(copy1_sub)
1676+
assert len(comp1.bodies) == 1
1677+
1678+
################# Check unite operation #################
1679+
copy1_uni = body1.copy(comp1, "Copy1_unite")
1680+
copy2_uni = body2.copy(comp2, "Copy2_unite")
1681+
copy3_uni = body3.copy(comp3, "Copy3_unite")
1682+
copy1_uni.unite([copy2_uni, copy3_uni])
1683+
1684+
assert not copy2_uni.is_alive
1685+
assert not copy3_uni.is_alive
1686+
assert body2.is_alive
1687+
assert body3.is_alive
1688+
assert len(comp1.bodies) == 2
1689+
assert len(comp2.bodies) == 1
1690+
assert len(comp3.bodies) == 1
1691+
1692+
# Cleanup previous subtest
1693+
comp1.delete_body(copy1_uni)
1694+
assert len(comp1.bodies) == 1
1695+
1696+
################# Check intersect operation #################
1697+
copy1_int = body1.copy(comp1, "Copy1_intersect")
1698+
copy2_int = body2.copy(comp2, "Copy2_intersect")
1699+
copy3_int = body3.copy(comp3, "Copy3_intersect") # Body 3 does not intersect them
1700+
copy1_int.intersect([copy2_int])
1701+
1702+
assert not copy2_int.is_alive
1703+
assert copy3_int.is_alive
1704+
assert body2.is_alive
1705+
assert body3.is_alive
1706+
assert len(comp1.bodies) == 2
1707+
assert len(comp2.bodies) == 1
1708+
assert len(comp3.bodies) == 2
1709+
1710+
# Cleanup previous subtest
1711+
comp1.delete_body(copy1_int)
1712+
comp3.delete_body(copy3_int)
1713+
assert len(comp1.bodies) == 1
1714+
assert len(comp3.bodies) == 1
1715+
1716+
16471717
def test_child_component_instances(modeler: Modeler):
16481718
"""Test creation of child ``Component`` instances and check the data model reflects
16491719
that."""

0 commit comments

Comments
 (0)