Adding split_by_perimeter Issue #751

This commit is contained in:
gumyr 2024-10-27 11:15:36 -04:00
parent ca3d8fa237
commit 8a91db674a
2 changed files with 100 additions and 0 deletions

View file

@ -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

View file

@ -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)))