Skip to content

Commit

Permalink
Replaced unwrap with unwrap_topods_compound in topology.py Issue #788
Browse files Browse the repository at this point in the history
  • Loading branch information
gumyr committed Nov 18, 2024
1 parent 5fb86b8 commit 3b0fcb0
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 26 deletions.
73 changes: 48 additions & 25 deletions src/build123d/topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -1732,13 +1732,6 @@ def __add__(self, other: Union[list[Shape], Shape]) -> Self:
else:
sum_shape = self.fuse(*summands)

# Simplify Compounds if possible
sum_shape = (
sum_shape.unwrap(fully=True)
if isinstance(sum_shape, Compound)
else sum_shape
)

if SkipClean.clean:
sum_shape = sum_shape.clean()

Expand Down Expand Up @@ -1782,12 +1775,6 @@ def __sub__(self, other: Union[Shape, Iterable[Shape]]) -> Self:
# Do the actual cut operation
difference = self.cut(*subtrahends)

# Simplify Compounds if possible
difference = (
difference.unwrap(fully=True)
if isinstance(difference, Compound)
else difference
)
# To allow the @, % and ^ operators to work 1D objects must be type Curve
if minuend_dim == 1:
difference = Curve(Compound(difference.edges()).wrapped)
Expand All @@ -1805,13 +1792,6 @@ def __and__(self, other: Shape) -> Self:
if new_shape.wrapped is not None and SkipClean.clean:
new_shape = new_shape.clean()

# Simplify Compounds if possible
new_shape = (
new_shape.unwrap(fully=True)
if isinstance(new_shape, Compound)
else new_shape
)

# To allow the @, % and ^ operators to work 1D objects must be type Curve
if self._dim == 1:
new_shape = Curve(Compound(new_shape.edges()).wrapped)
Expand Down Expand Up @@ -2621,7 +2601,11 @@ def _bool_op(
operation.SetRunParallel(True)
operation.Build()

result = Shape.cast(operation.Shape())
result = downcast(operation.Shape())
# Remove unnecessary TopoDS_Compound around single shape
if isinstance(result, TopoDS_Compound):
result = unwrap_topods_compound(result, True)
result = Shape.cast(result)

base = args[0] if isinstance(args, tuple) else args
base.copy_attributes_to(result, ["wrapped", "_NodeMixin__children"])
Expand Down Expand Up @@ -2829,7 +2813,12 @@ def split(self, tool: TrimmingTool, keep: Keep = Keep.TOP) -> Self:
# Perform the splitting operation
splitter.Build()

result = Compound(downcast(splitter.Shape())).unwrap(fully=False)
result = downcast(splitter.Shape())
# Remove unnecessary TopoDS_Compound around single shape
if isinstance(result, TopoDS_Compound):
result = unwrap_topods_compound(result, False)
result = Shape.cast(result)

if keep != Keep.BOTH:
if not isinstance(tool, Plane):
# Create solids from the surfaces for sorting
Expand All @@ -2843,7 +2832,8 @@ def split(self, tool: TrimmingTool, keep: Keep = Keep.TOP) -> Self:
(tops if is_up else bottoms).append(part)
result = Compound(tops) if keep == Keep.TOP else Compound(bottoms)

return result.unwrap(fully=True)
result_wrapped = unwrap_topods_compound(result.wrapped, fully=True)
return Shape.cast(result_wrapped)

@overload
def split_by_perimeter(
Expand Down Expand Up @@ -4415,7 +4405,9 @@ def position_face(orig_face: "Face") -> "Face":

# Align the text from the bounding box
align = tuplify(align, 2)
text_flat = text_flat.translate(text_flat.bounding_box().to_align_offset(align))
text_flat = text_flat.translate(
Vector(*text_flat.bounding_box().to_align_offset(align))
)

if text_path is not None:
path_length = text_path.length
Expand Down Expand Up @@ -8464,7 +8456,10 @@ def closest_to_end(current: Wire, unplaced_edges: list[Edge]) -> Edge:
wire_builder.Build()
if not wire_builder.IsDone():
if wire_builder.Error() == BRepBuilderAPI_NonManifoldWire:
warnings.warn("Wire is non manifold", stacklevel=2)
warnings.warn(
"Wire is non manifold (e.g. branching, self intersecting)",
stacklevel=2,
)
elif wire_builder.Error() == BRepBuilderAPI_EmptyWire:
raise RuntimeError("Wire is empty")
elif wire_builder.Error() == BRepBuilderAPI_DisconnectedWire:
Expand Down Expand Up @@ -9232,6 +9227,34 @@ def topo_explore_common_vertex(
return None # No common vertex found


def unwrap_topods_compound(
compound: TopoDS_Compound, fully: bool = True
) -> Union[TopoDS_Compound, TopoDS_Shape]:
"""Strip unnecessary Compound wrappers
Args:
compound (TopoDS_Compound): The TopoDS_Compound to unwrap.
fully (bool, optional): return base shape without any TopoDS_Compound
wrappers (otherwise one TopoDS_Compound is left). Defaults to True.
Returns:
Union[TopoDS_Compound, TopoDS_Shape]: base shape
"""

if compound.NbChildren() == 1:
iterator = TopoDS_Iterator(compound)
single_element = downcast(iterator.Value())

# If the single element is another TopoDS_Compound, unwrap it recursively
if isinstance(single_element, TopoDS_Compound):
return unwrap_topods_compound(single_element, fully)

return single_element if fully else compound

# If there are no elements or more than one element, return TopoDS_Compound
return compound


class SkipClean:
"""Skip clean context for use in operator driven code where clean=False wouldn't work"""

Expand Down
25 changes: 24 additions & 1 deletion tests/test_direct_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
polar,
new_edges,
delta,
unwrap_topods_compound,
)
from build123d.jupyter_tools import display

Expand Down Expand Up @@ -1577,6 +1578,28 @@ def test_parse_intersect_args(self):
with self.assertRaises(TypeError):
Vector(1, 1, 1) & ("x", "y", "z")

def test_unwrap_topods_compound(self):
# Complex Compound
b1 = Box(1, 1, 1).solid()
b2 = Box(2, 2, 2).solid()
c1 = Compound([b1, b2])
c2 = Compound([b1, c1])
c3 = Compound([c2])
c4 = Compound([c3])
self.assertEqual(c4.wrapped.NbChildren(), 1)
c5 = Compound(unwrap_topods_compound(c4.wrapped, False))
self.assertEqual(c5.wrapped.NbChildren(), 2)

# unwrap fully
c0 = Compound([b1])
c1 = Compound([c0])
result = Shape.cast(unwrap_topods_compound(c1.wrapped, True))
self.assertTrue(isinstance(result, Solid))

# unwrap not fully
result = Shape.cast(unwrap_topods_compound(c1.wrapped, False))
self.assertTrue(isinstance(result, Compound))


class TestImportExport(DirectApiTestCase):
def test_import_export(self):
Expand Down Expand Up @@ -2579,7 +2602,7 @@ def test_plane_init(self):
(
Axis.X.direction, # plane x_dir
Axis.Z.direction, # plane y_dir
-Axis.Y.direction, # plane z_dir
-Axis.Y.direction, # plane z_dir
),
# Trapezoid face, positive y coordinate
(
Expand Down

0 comments on commit 3b0fcb0

Please sign in to comment.