Adding Face.remove_holes and Face.total_area property

This commit is contained in:
gumyr 2025-02-04 09:58:40 -05:00
parent 0e3dbbe15b
commit c728124b3b
2 changed files with 75 additions and 1 deletions

View file

@ -73,7 +73,7 @@ from OCP.BRepFilletAPI import BRepFilletAPI_MakeFillet2d
from OCP.BRepGProp import BRepGProp, BRepGProp_Face
from OCP.BRepIntCurveSurface import BRepIntCurveSurface_Inter
from OCP.BRepOffsetAPI import BRepOffsetAPI_MakeFilling, BRepOffsetAPI_MakePipeShell
from OCP.BRepTools import BRepTools
from OCP.BRepTools import BRepTools, BRepTools_ReShape
from OCP.GProp import GProp_GProps
from OCP.Geom import Geom_BezierSurface, Geom_Surface
from OCP.GeomAPI import GeomAPI_PointsToBSplineSurface, GeomAPI_ProjectPointOnSurf
@ -405,6 +405,23 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
result = face_vertices[-1].X - face_vertices[0].X
return result
@property
def total_area(self) -> float:
"""
Calculate the total surface area of the face, including the areas of any holes.
This property returns the overall area of the face as if the inner boundaries (holes)
were filled in.
Returns:
float: The total surface area, including the area of holes. Returns 0.0 if
the face is empty.
"""
if self.wrapped is None:
return 0.0
return self.remove_holes().area
@property
def volume(self) -> float:
"""volume - the volume of this Face, which is always zero"""
@ -1201,6 +1218,28 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
projected_shapes.append(shape)
return projected_shapes
def remove_holes(self) -> Face:
"""remove_holes
Remove all of the holes from this face.
Returns:
Face: A new Face instance identical to the original but without any holes.
"""
if self.wrapped is None:
raise ValueError("Cannot remove holes from an empty face")
if not (inner_wires := self.inner_wires()):
return self
holeless = copy.deepcopy(self)
reshaper = BRepTools_ReShape()
for hole_wire in inner_wires:
reshaper.Remove(hole_wire.wrapped)
modified_shape = downcast(reshaper.Apply(self.wrapped))
holeless.wrapped = modified_shape
return holeless
def to_arcs(self, tolerance: float = 1e-3) -> Face:
"""to_arcs

View file

@ -447,6 +447,41 @@ class TestFace(unittest.TestCase):
face = Cylinder(1, 1).faces().filter_by(GeomType.CYLINDER)[0]
self.assertAlmostEqual(face.normal_at(0, 1), (1, 0, 0), 5)
def test_remove_holes(self):
# Planar test
frame = (Rectangle(1, 1) - Rectangle(0.5, 0.5)).face()
filled = frame.remove_holes()
self.assertEqual(len(frame.inner_wires()), 1)
self.assertEqual(len(filled.inner_wires()), 0)
self.assertAlmostEqual(frame.area, 0.75, 5)
self.assertAlmostEqual(filled.area, 1.0, 5)
# Errors
frame.wrapped = None
with self.assertRaises(ValueError):
frame.remove_holes()
# No holes
rect = Face.make_rect(1, 1)
self.assertEqual(rect, rect.remove_holes())
# Non-planar test
cyl_face = (
(Cylinder(1, 3) - Cylinder(0.5, 3, rotation=(90, 0, 0)))
.faces()
.sort_by(Face.area)[-1]
)
filled = cyl_face.remove_holes()
self.assertEqual(len(cyl_face.inner_wires()), 2)
self.assertEqual(len(filled.inner_wires()), 0)
self.assertTrue(cyl_face.area < filled.area)
self.assertAlmostEqual(cyl_face.total_area, filled.area, 5)
def test_total_area(self):
frame = (Rectangle(1, 1) - Rectangle(0.5, 0.5)).face()
frame.wrapped = None
self.assertAlmostEqual(frame.total_area, 0.0, 5)
if __name__ == "__main__":
unittest.main()