mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-28 07:49:59 +00:00
Add OBB clip-plane and planar tessellation to tool.Cad
Adds geometry primitives the viewport clip-box feature needs: - obb_world_clip_planes / obb_clip_planes_from_matrix: derive the 6 inward clip planes of an oriented bounding box (or unit cube under a matrix_world) in RegionView3D.clip_planes form. expand / expand_rel margins let callers visualising the box with overlapping geometry (an empty CUBE display sharing edges with the planes) keep the box's own wireframe inside the clip volume. - point_is_inside_clip_planes / corners_might_cross_clip_planes: cheap reject tests for the per-mesh capping pass to skip the expensive bisect when an object's AABB is fully outside the box. - newell_normal / plane_basis: robust planar-ring normal for thin near-degenerate cap rings where a two-edge cross product is unstable. - tessellate_ring_planar: triangulate [outer, *inners] 3D rings in the outer ring's best-fit plane, with a shapely constrained-Delaunay fallback for the known failure mode of mathutils.tessellate_polygon on complex concave polygons-with-holes. Tests cover unit-box, translated, rotated, and scaled cases for the OBB-from-matrix builder + the rejection helpers. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -16,7 +16,9 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from mathutils import Vector
|
||||
import math
|
||||
|
||||
from mathutils import Matrix, Vector
|
||||
|
||||
from bonsai.tool.cad import Cad as subject
|
||||
from test.bim.bootstrap import NewFile
|
||||
@@ -88,3 +90,87 @@ class TestClosestPoints(NewFile):
|
||||
edge1 = (V(0, 0, 0), V(0, 0, 0))
|
||||
edge2 = (V(1, 0, 1), V(2, 0, 2))
|
||||
assert subject.closest_points(edge1, edge2)[0] == (edge1[0], edge2[0])
|
||||
|
||||
|
||||
class TestObbWorldClipPlanes(NewFile):
|
||||
def test_unit_box_at_origin_returns_axis_aligned_planes(self):
|
||||
planes = subject.obb_world_clip_planes(
|
||||
V(0, 0, 0),
|
||||
(V(1, 0, 0), V(0, 1, 0), V(0, 0, 1)),
|
||||
V(1, 1, 1),
|
||||
)
|
||||
assert planes[0] == (-1.0, 0.0, 0.0, 1.0)
|
||||
assert planes[1] == (1.0, 0.0, 0.0, 1.0)
|
||||
assert planes[2] == (0.0, -1.0, 0.0, 1.0)
|
||||
assert planes[3] == (0.0, 1.0, 0.0, 1.0)
|
||||
assert planes[4] == (0.0, 0.0, -1.0, 1.0)
|
||||
assert planes[5] == (0.0, 0.0, 1.0, 1.0)
|
||||
|
||||
def test_center_is_inside_all_planes(self):
|
||||
center = V(5, -3, 2)
|
||||
planes = subject.obb_world_clip_planes(
|
||||
center,
|
||||
(V(1, 0, 0), V(0, 1, 0), V(0, 0, 1)),
|
||||
V(2, 1, 0.5),
|
||||
)
|
||||
assert subject.point_is_inside_clip_planes(planes, center)
|
||||
|
||||
def test_point_just_outside_positive_x_face_rejected(self):
|
||||
planes = subject.obb_world_clip_planes(
|
||||
V(0, 0, 0),
|
||||
(V(1, 0, 0), V(0, 1, 0), V(0, 0, 1)),
|
||||
V(1, 1, 1),
|
||||
)
|
||||
assert subject.point_is_inside_clip_planes(planes, V(0.5, 0, 0))
|
||||
assert not subject.point_is_inside_clip_planes(planes, V(1.5, 0, 0))
|
||||
|
||||
def test_rotated_obb_clips_along_rotated_axes(self):
|
||||
s = math.sin(math.radians(45))
|
||||
planes = subject.obb_world_clip_planes(
|
||||
V(0, 0, 0),
|
||||
(V(s, s, 0), V(-s, s, 0), V(0, 0, 1)),
|
||||
V(1, 1, 1),
|
||||
)
|
||||
assert subject.point_is_inside_clip_planes(planes, V(1.2, 0, 0))
|
||||
assert not subject.point_is_inside_clip_planes(planes, V(1.42, 0, 0))
|
||||
|
||||
def test_zero_extent_axis_does_not_raise(self):
|
||||
planes = subject.obb_world_clip_planes(
|
||||
V(0, 0, 0),
|
||||
(V(1, 0, 0), V(0, 1, 0), V(0, 0, 1)),
|
||||
V(1, 1, 0),
|
||||
)
|
||||
assert subject.point_is_inside_clip_planes(planes, V(0, 0, 0))
|
||||
|
||||
|
||||
class TestObbClipPlanesFromMatrix(NewFile):
|
||||
def test_identity_matches_unit_box(self):
|
||||
planes = subject.obb_clip_planes_from_matrix(Matrix.Identity(4))
|
||||
assert subject.point_is_inside_clip_planes(planes, V(0, 0, 0))
|
||||
assert not subject.point_is_inside_clip_planes(planes, V(2, 0, 0))
|
||||
assert not subject.point_is_inside_clip_planes(planes, V(0, -2, 0))
|
||||
|
||||
def test_translated_host_shifts_clip_region(self):
|
||||
translated = Matrix.Translation(V(10, 0, 0))
|
||||
planes = subject.obb_clip_planes_from_matrix(translated)
|
||||
assert not subject.point_is_inside_clip_planes(planes, V(0, 0, 0))
|
||||
assert subject.point_is_inside_clip_planes(planes, V(10, 0, 0))
|
||||
|
||||
def test_z_rotation_rotates_box(self):
|
||||
rot = Matrix.Rotation(math.radians(45), 4, "Z")
|
||||
planes = subject.obb_clip_planes_from_matrix(rot)
|
||||
assert subject.point_is_inside_clip_planes(planes, V(1.2, 0, 0))
|
||||
assert not subject.point_is_inside_clip_planes(planes, V(1.42, 0, 0))
|
||||
|
||||
def test_host_scale_scales_box_extents(self):
|
||||
scaled = Matrix.Diagonal((2.0, 2.0, 2.0, 1.0))
|
||||
planes = subject.obb_clip_planes_from_matrix(scaled)
|
||||
assert subject.point_is_inside_clip_planes(planes, V(1.9, 0, 0))
|
||||
assert not subject.point_is_inside_clip_planes(planes, V(2.1, 0, 0))
|
||||
|
||||
def test_non_uniform_scale_axis_independent(self):
|
||||
scaled = Matrix.Diagonal((3.0, 1.0, 1.0, 1.0))
|
||||
planes = subject.obb_clip_planes_from_matrix(scaled)
|
||||
assert subject.point_is_inside_clip_planes(planes, V(2.9, 0, 0))
|
||||
assert not subject.point_is_inside_clip_planes(planes, V(3.1, 0, 0))
|
||||
assert not subject.point_is_inside_clip_planes(planes, V(0, 1.1, 0))
|
||||
|
||||
Reference in New Issue
Block a user