Skip to content

Commit

Permalink
Updating split_by_perimeter & minor docs fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
gumyr committed Oct 28, 2024
1 parent 120041a commit 5ed1499
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 51 deletions.
36 changes: 18 additions & 18 deletions docs/cheat_sheet.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,23 +136,23 @@ Cheat Sheet
.. card:: Selector Operators

+----------+-----------------------------------------------------------------------------------+------------------------------------------------+
| Operator | Operand | Method |
+==========+===================================================================================+================================================+
| > | :class:`~geometry.Axis`, :class:`~topology.Edge`, :class:`~topology.Wire`, :class:`~build_enums.SortBy` | :meth:`~topology.ShapeList.sort_by` |
+----------+-----------------------------------------------------------------------------------+------------------------------------------------+
| < | :class:`~geometry.Axis`, :class:`~topology.Edge`, :class:`~topology.Wire`, :class:`~build_enums.SortBy` | :meth:`~topology.ShapeList.sort_by` |
+----------+-----------------------------------------------------------------------------------+------------------------------------------------+
| >> | :class:`~geometry.Axis`, :class:`~topology.Edge`, :class:`~topology.Wire`, :class:`~build_enums.SortBy` | :meth:`~topology.ShapeList.group_by`\[-1\] |
+----------+-----------------------------------------------------------------------------------+------------------------------------------------+
| << | :class:`~geometry.Axis`, :class:`~topology.Edge`, :class:`~topology.Wire`, :class:`~build_enums.SortBy` | :meth:`~topology.ShapeList.group_by`\[0\] |
+----------+-----------------------------------------------------------------------------------+------------------------------------------------+
| \| | :class:`~geometry.Axis`, :class:`~geometry.Plane`, :class:`~build_enums.GeomType` | :meth:`~topology.ShapeList.filter_by` |
+----------+-----------------------------------------------------------------------------------+------------------------------------------------+
| [] | | python indexing / slicing |
+----------+-----------------------------------------------------------------------------------+------------------------------------------------+
| | :class:`~geometry.Axis` | :meth:`~topology.ShapeList.filter_by_position` |
+----------+-----------------------------------------------------------------------------------+------------------------------------------------+
+----------+---------------------------------------------------------------------------------------------------------+------------------------------------------------+
| Operator | Operand | Method |
+==========+=========================================================================================================+================================================+
| > | :class:`~geometry.Axis`, :class:`~topology.Edge`, :class:`~topology.Wire`, :class:`~build_enums.SortBy` | :meth:`~topology.ShapeList.sort_by` |
+----------+---------------------------------------------------------------------------------------------------------+------------------------------------------------+
| < | :class:`~geometry.Axis`, :class:`~topology.Edge`, :class:`~topology.Wire`, :class:`~build_enums.SortBy` | :meth:`~topology.ShapeList.sort_by` |
+----------+---------------------------------------------------------------------------------------------------------+------------------------------------------------+
| >> | :class:`~geometry.Axis`, :class:`~topology.Edge`, :class:`~topology.Wire`, :class:`~build_enums.SortBy` | :meth:`~topology.ShapeList.group_by`\[-1\] |
+----------+---------------------------------------------------------------------------------------------------------+------------------------------------------------+
| << | :class:`~geometry.Axis`, :class:`~topology.Edge`, :class:`~topology.Wire`, :class:`~build_enums.SortBy` | :meth:`~topology.ShapeList.group_by`\[0\] |
+----------+---------------------------------------------------------------------------------------------------------+------------------------------------------------+
| \| | :class:`~geometry.Axis`, :class:`~geometry.Plane`, :class:`~build_enums.GeomType` | :meth:`~topology.ShapeList.filter_by` |
+----------+---------------------------------------------------------------------------------------------------------+------------------------------------------------+
| [] | | python indexing / slicing |
+----------+---------------------------------------------------------------------------------------------------------+------------------------------------------------+
| | :class:`~geometry.Axis` | :meth:`~topology.ShapeList.filter_by_position` |
+----------+---------------------------------------------------------------------------------------------------------+------------------------------------------------+

.. card:: Edge and Wire Operators

Expand Down Expand Up @@ -231,7 +231,7 @@ Cheat Sheet
+----------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------+
| :class:`~build_enums.HeadType` | CURVED, FILLETED, STRAIGHT |
+----------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------+
| :class:`~build_enums.Keep` | TOP, BOTTOM, BOTH |
| :class:`~build_enums.Keep` | TOP, BOTTOM, BOTH, INSIDE, OUTSIDE |
+----------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------+
| :class:`~build_enums.Kind` | ARC, INTERSECTION, TANGENT |
+----------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------+
Expand Down
2 changes: 1 addition & 1 deletion docs/external.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Studio Code and the ocp-vscode viewer extension in a timed event from the TooTal
`build123d entry video <https://www.youtube.com/watch?v=UhUmMInlJic>`_

cq-editor fork
=========
==============

GUI editor based on PyQT. This fork has changes from jdegenstein to allow easier use with build123d.

Expand Down
2 changes: 2 additions & 0 deletions src/build123d/build_enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ class Keep(Enum):

TOP = auto()
BOTTOM = auto()
INSIDE = auto()
OUTSIDE = auto()
BOTH = auto()

def __repr__(self):
Expand Down
34 changes: 17 additions & 17 deletions src/build123d/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -1958,23 +1958,23 @@ class Plane(metaclass=PlaneMeta):
Planes can be created from faces as workplanes for feature creation on objects.
======= ====== ====== ======
Name x_dir y_dir z_dir
======= ====== ====== ======
XY +x +y +z
YZ +y +z +x
ZX +z +x +y
XZ +x +z -y
YX +y +x -z
ZY +z +y -x
front +x +z -y
back -x +z +y
left -y +z -x
right +y +z +x
top +x +y +z
bottom +x -y -z
isometric +x+y -x+y+z +x+y-z
======= ====== ====== ======
========= ====== ======== ========
Name x_dir y_dir z_dir
========= ====== ======== ========
XY +x +y +z
YZ +y +z +x
ZX +z +x +y
XZ +x +z -y
YX +y +x -z
ZY +z +y -x
front +x +z -y
back -x +z +y
left -y +z -x
right +y +z +x
top +x +y +z
bottom +x -y -z
isometric +x+y -x+y+z +x+y-z
========= ====== ======== ========
Args:
gp_pln (gp_Pln): an OCCT plane object
Expand Down
44 changes: 35 additions & 9 deletions src/build123d/topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -2781,8 +2781,14 @@ def split(self, surface: Union[Plane, Face], keep: Keep = Keep.TOP) -> Self:
return result.unwrap(fully=True)

def split_by_perimeter(
self, perimeter: Union[Edge, Wire]
) -> tuple[Union[Sketch, Face, None], Union[Sketch, Face, None]]:
self, perimeter: Union[Edge, Wire], keep: Keep = Keep.INSIDE
) -> Union[
Union[Optional[Shell], Optional[Face]],
tuple[
Union[Optional[Shell], Optional[Face]],
Union[Optional[Shell], Optional[Face]],
],
]:
"""split_by_perimeter
Divide the faces of this object into those within the perimeter
Expand All @@ -2792,12 +2798,17 @@ def split_by_perimeter(
Args:
perimeter (Union[Edge,Wire]): closed perimeter
keep (Keep, optional): which object(s) to return. Defaults to Keep.INSIDE.
Raises:
ValueError: perimeter must be closed
ValueError: keep must be one of Keep.INSIDE|OUTSIDE|BOTH
Returns:
tuple[Union[Sketch, Face, None], Union[Sketch, Face, None]]: inside and outside
Union[Union[Optional[Shell], Optional[Face]],tuple[Union[Optional[Shell],
Optional[Face]],Union[Optional[Shell], Optional[Face]]]:]: either inside,
outside or both
"""

def get(los: TopTools_ListOfShape, shape_cls) -> list:
Expand All @@ -2807,6 +2818,11 @@ def get(los: TopTools_ListOfShape, shape_cls) -> list:
los.RemoveFirst()
return shapes

if keep not in {Keep.INSIDE, Keep.OUTSIDE, Keep.BOTH}:
raise ValueError(
"keep must be one of Keep.INSIDE, Keep.OUTSIDE, or Keep.BOTH"
)

# Process the perimeter
if not perimeter.is_closed:
raise ValueError("perimeter must be a closed Wire or Edge")
Expand All @@ -2815,25 +2831,35 @@ def get(los: TopTools_ListOfShape, shape_cls) -> list:
perimeter_edges.Append(perimeter_edge.wrapped)

# Split the faces by the perimeter edges
lefts, rights = [], []
lefts: list[Face] = []
rights: list[Face] = []
for target_face in self.faces():
constructor = BRepFeat_SplitShape(target_face.wrapped)
constructor.Add(perimeter_edges)
constructor.Build()
lefts.extend(get(constructor.Left(), Face))
rights.extend(get(constructor.Right(), Face))

left = Sketch(lefts).unwrap(fully=True) if lefts else None
right = Sketch(rights).unwrap(fully=True) if rights else None
left = None if not lefts else lefts[0] if len(lefts) == 1 else Shell(lefts)
right = None if not rights else rights[0] if len(rights) == 1 else Shell(rights)

# Is left or right the inside?
perimeter_length = perimeter.length
left_perimeter_length = sum(e.length for e in left.edges()) if lefts else 0
right_perimeter_length = sum(e.length for e in right.edges()) if rights else 0
left_perimeter_length = (
sum(e.length for e in left.edges()) if not left is None else 0
)
right_perimeter_length = (
sum(e.length for e in right.edges()) if not right is None else 0
)
left_inside = abs(perimeter_length - left_perimeter_length) < abs(
perimeter_length - right_perimeter_length
)
return (left, right) if left_inside else (right, left)
if keep == Keep.BOTH:
return (left, right) if left_inside else (right, left)
elif keep == Keep.INSIDE:
return left if left_inside else right
else: # keep == Keep.OUTSIDE:
return right if left_inside else left

def distance(self, other: Shape) -> float:
"""Minimal distance between two shapes
Expand Down
17 changes: 11 additions & 6 deletions tests/test_direct_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2988,14 +2988,14 @@ def test_split_by_perimeter(self):
circle = Plane.YZ.offset(15) * Circle(5).face()
circle_projected = circle.project_to_shape(target0, (-1, 0, 0))[0]
circle_outerwire = circle_projected.edge()
inside0, outside0 = target0.split_by_perimeter(circle_outerwire)
inside0, outside0 = target0.split_by_perimeter(circle_outerwire, Keep.BOTH)
self.assertLess(inside0.area, outside0.area)

# Test 1 - extract ring of a sphere
ring = Pos(Z=15) * (Circle(5) - Circle(3)).face()
ring_projected = ring.project_to_shape(target0, (0, 0, -1))[0]
ring_outerwire = ring_projected.outer_wire()
inside1, outside1 = target0.split_by_perimeter(ring_outerwire)
inside1, outside1 = target0.split_by_perimeter(ring_outerwire, Keep.BOTH)
self.assertLess(inside1.area, outside1.area)
self.assertEqual(len(outside1.faces()), 2)

Expand All @@ -3011,21 +3011,26 @@ def test_split_by_perimeter(self):
square_projected = square.project_to_shape(cross.part, (-1, 0, 0))[0]
projected_edges = square_projected.edges().sort_by(SortBy.DISTANCE)[2:]
projected_perimeter = Wire(projected_edges)
inside2, outside2 = target2.split_by_perimeter(projected_perimeter)
self.assertTrue(isinstance(inside2, Sketch))
inside2 = target2.split_by_perimeter(projected_perimeter, Keep.INSIDE)
self.assertTrue(isinstance(inside2, Shell))

# Test 3 - Invalid, wire on shape edge
target3 = Solid.make_cylinder(5, 10, Plane((0, 0, -5)))
square_projected = square.project_to_shape(target3, (-1, 0, 0))[0].unwrap(
fully=True
)
project_perimeter = square_projected.outer_wire()
inside3, outside3 = target3.split_by_perimeter(project_perimeter)
inside3 = target3.split_by_perimeter(project_perimeter, Keep.INSIDE)
self.assertIsNone(inside3)
outside3 = target3.split_by_perimeter(project_perimeter, Keep.OUTSIDE)
self.assertAlmostEqual(outside3.area, target3.shell().area, 5)

# Test 4 - invalid inputs
with self.assertRaises(ValueError):
_, _ = target2.split_by_perimeter(projected_perimeter.edges()[0])
_, _ = target2.split_by_perimeter(projected_perimeter.edges()[0], Keep.BOTH)

with self.assertRaises(ValueError):
_, _ = target3.split_by_perimeter(projected_perimeter, Keep.TOP)

def test_distance(self):
sphere1 = Solid.make_sphere(1, Plane((-5, 0, 0)))
Expand Down

0 comments on commit 5ed1499

Please sign in to comment.