Reapply "Enhanced make_face so faces can have holes. Added BoundBox.measure"

This reverts commit 607efade27.
This commit is contained in:
gumyr 2025-11-20 11:51:04 -05:00
parent 607efade27
commit 02a8c07e0a
4 changed files with 42 additions and 10 deletions

View file

@ -41,7 +41,7 @@ import json
import logging
import warnings
from collections.abc import Callable, Iterable, Sequence
from math import degrees, isclose, log10, pi, radians
from math import degrees, isclose, log10, pi, radians, prod
from typing import TYPE_CHECKING, Any, TypeAlias, overload
import numpy as np
@ -1001,6 +1001,16 @@ class BoundBox:
self.max = Vector(x_max, y_max, z_max) #: location of maximum corner
self.size = Vector(x_max - x_min, y_max - y_min, z_max - z_min) #: overall size
@property
def measure(self) -> float:
"""Return the overall Lebesgue measure of the bounding box.
- For 1D objects: length
- For 2D objects: area
- For 3D objects: volume
"""
return prod([x for x in self.size if x > TOLERANCE])
@property
def diagonal(self) -> float:
"""body diagonal length (i.e. object maximum size)"""

View file

@ -44,6 +44,7 @@ from build123d.topology import (
Sketch,
topo_explore_connected_edges,
topo_explore_common_vertex,
edges_to_wires,
)
from build123d.geometry import Plane, Vector, TOLERANCE
from build123d.build_common import flatten_sequence, validate_inputs
@ -200,26 +201,33 @@ def make_face(
) -> Sketch:
"""Sketch Operation: make_face
Create a face from the given perimeter edges.
Create a face from the given edges.
Args:
edges (Edge): sequence of perimeter edges. Defaults to all
sketch pending edges.
edges (Edge): sequence of edges. Defaults to all sketch pending edges.
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
"""
context: BuildSketch | None = BuildSketch._get_context("make_face")
if edges is not None:
outer_edges = flatten_sequence(edges)
raw_edges = flatten_sequence(edges)
elif context is not None:
outer_edges = context.pending_edges
raw_edges = context.pending_edges
else:
raise ValueError("No objects to create a face")
if not outer_edges:
raise ValueError("No objects to create a hull")
validate_inputs(context, "make_face", outer_edges)
if not raw_edges:
raise ValueError("No objects to create a face")
validate_inputs(context, "make_face", raw_edges)
pending_face = Face(Wire.combine(outer_edges)[0])
wires = list(
edges_to_wires(raw_edges).sort_by(
lambda w: w.bounding_box().measure, reverse=True
)
)
if len(wires) > 1:
pending_face = Face(wires[0], wires[1:])
else:
pending_face = Face(wires[0])
if pending_face.normal_at().Z < 0: # flip up-side-down faces
pending_face = -pending_face

View file

@ -168,6 +168,17 @@ class TestUpSideDown(unittest.TestCase):
sketch = make_face(wire.edges())
self.assertTrue(sketch.faces()[0].normal_at().Z > 0)
def test_make_face_with_holes(self):
with BuildSketch() as skt:
with BuildLine() as perimeter:
CenterArc((0, 0), 3, 0, 360)
with BuildLine() as hole1:
Polyline((-1, 1), (1, 1), (1, 2), (-1, 2), (-1, 1))
with BuildLine() as hole2:
Airfoil("4020")
make_face()
self.assertEqual(len(skt.face().inner_wires()), 2)
class TestBuildSketchExceptions(unittest.TestCase):
"""Test exception handling"""

View file

@ -43,6 +43,7 @@ class TestBoundBox(unittest.TestCase):
# OCC uses some approximations
self.assertAlmostEqual(bb1.size.X, 1.0, 1)
self.assertAlmostEqual(bb1.measure, 1.0, 5)
# Test adding to an existing bounding box
v0 = Vertex(0, 0, 0)
@ -50,6 +51,7 @@ class TestBoundBox(unittest.TestCase):
bb3 = bb1.add(bb2)
self.assertAlmostEqual(bb3.size, (2, 2, 2), 7)
self.assertAlmostEqual(bb3.measure, 8, 5)
bb3 = bb2.add((3, 3, 3))
self.assertAlmostEqual(bb3.size, (3, 3, 3), 7)
@ -61,6 +63,7 @@ class TestBoundBox(unittest.TestCase):
bb1 = Vertex(1, 1, 0).bounding_box().add(Vertex(2, 2, 0).bounding_box())
bb2 = Vertex(0, 0, 0).bounding_box().add(Vertex(3, 3, 0).bounding_box())
bb3 = Vertex(0, 0, 0).bounding_box().add(Vertex(1.5, 1.5, 0).bounding_box())
self.assertAlmostEqual(bb2.measure, 9, 5)
# Test that bb2 contains bb1
self.assertEqual(bb2, BoundBox.find_outside_box_2d(bb1, bb2))
self.assertEqual(bb2, BoundBox.find_outside_box_2d(bb2, bb1))