mirror of
https://github.com/gumyr/build123d.git
synced 2026-01-04 00:03:14 -08:00
Adding split_by_perimeter Issue #751
This commit is contained in:
parent
ca3d8fa237
commit
8a91db674a
2 changed files with 100 additions and 0 deletions
|
|
@ -2780,6 +2780,61 @@ class Shape(NodeMixin):
|
|||
|
||||
return result.unwrap(fully=True)
|
||||
|
||||
def split_by_perimeter(
|
||||
self, perimeter: Union[Edge, Wire]
|
||||
) -> tuple[Union[Sketch, Face, None], Union[Sketch, Face, None]]:
|
||||
"""split_by_perimeter
|
||||
|
||||
Divide the faces of this object into those within the perimeter
|
||||
and those outside the perimeter.
|
||||
|
||||
Note: this method may fail if the perimeter intersects shape edges.
|
||||
|
||||
Args:
|
||||
perimeter (Union[Edge,Wire]): closed perimeter
|
||||
|
||||
Raises:
|
||||
ValueError: perimeter must be closed
|
||||
|
||||
Returns:
|
||||
tuple[Union[Sketch, Face, None], Union[Sketch, Face, None]]: inside and outside
|
||||
"""
|
||||
|
||||
def get(los: TopTools_ListOfShape, shape_cls) -> list:
|
||||
shapes = []
|
||||
for _ in range(los.Size()):
|
||||
shapes.append(shape_cls(los.First()))
|
||||
los.RemoveFirst()
|
||||
return shapes
|
||||
|
||||
# Process the perimeter
|
||||
if not perimeter.is_closed:
|
||||
raise ValueError("perimeter must be a closed Wire or Edge")
|
||||
perimeter_edges = TopTools_SequenceOfShape()
|
||||
for perimeter_edge in perimeter.edges():
|
||||
perimeter_edges.Append(perimeter_edge.wrapped)
|
||||
|
||||
# Split the faces by the perimeter edges
|
||||
lefts, rights = [], []
|
||||
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
|
||||
|
||||
# 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_inside = abs(perimeter_length - left_perimeter_length) < abs(
|
||||
perimeter_length - right_perimeter_length
|
||||
)
|
||||
return (left, right) if left_inside else (right, left)
|
||||
|
||||
def distance(self, other: Shape) -> float:
|
||||
"""Minimal distance between two shapes
|
||||
|
||||
|
|
|
|||
|
|
@ -2982,6 +2982,51 @@ class TestShape(DirectApiTestCase):
|
|||
self.assertLess(s2.volume, s.volume)
|
||||
self.assertGreater(s2.volume, 0.0)
|
||||
|
||||
def test_split_by_perimeter(self):
|
||||
# Test 0 - extract a spherical cap
|
||||
target0 = Solid.make_sphere(10).rotate(Axis.Z, 90)
|
||||
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)
|
||||
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)
|
||||
self.assertLess(inside1.area, outside1.area)
|
||||
self.assertEqual(len(outside1.faces()), 2)
|
||||
|
||||
# Test 2 - extract multiple faces
|
||||
with BuildPart() as cross:
|
||||
with BuildSketch(Pos(Z=-5) * Rot(Z=-45)) as skt:
|
||||
Rectangle(5, 1, align=Align.MIN)
|
||||
Rectangle(1, 5, align=Align.MIN)
|
||||
fillet(skt.vertices(), 0.3)
|
||||
extrude(amount=10)
|
||||
target2 = cross.part
|
||||
square = Face.make_rect(3, 3, Plane((12, 0, 0), z_dir=(1, 0, 0)))
|
||||
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))
|
||||
|
||||
# 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)
|
||||
self.assertIsNone(inside3)
|
||||
|
||||
# Test 4 - invalid inputs
|
||||
with self.assertRaises(ValueError):
|
||||
_, _ = target2.split_by_perimeter(projected_perimeter.edges()[0])
|
||||
|
||||
def test_distance(self):
|
||||
sphere1 = Solid.make_sphere(1, Plane((-5, 0, 0)))
|
||||
sphere2 = Solid.make_sphere(1, Plane((5, 0, 0)))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue