mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
Add footprint corner sampling for roof detection
get_vertical_bounding_planes now also casts rays from the footprint polygon's corner vertices, offset slightly inward toward the centroid. This catches bounding elements (e.g. sloped roofs) that only cover a corner of the space. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -685,10 +685,16 @@ class TestRegenerateSpaceFromRealIfc2x3(NewFile):
|
|||||||
ifc = self.load_house_with_garage()
|
ifc = self.load_house_with_garage()
|
||||||
(original_bounds, original_origin), (regen_bounds, regen_origin) = self._regenerate_space(ifc, 2363)
|
(original_bounds, original_origin), (regen_bounds, regen_origin) = self._regenerate_space(ifc, 2363)
|
||||||
assert (regen_origin - original_origin).length < 0.02
|
assert (regen_origin - original_origin).length < 0.02
|
||||||
# X, Y, Z-min stable; Z-max may differ because the regenerated space
|
# X and Y stable; Z may differ because the regenerated space detects the
|
||||||
# correctly detects the roof and clips to a different top elevation.
|
# sloped roof and clips the extrusion.
|
||||||
for j in (0, 1, 4):
|
for j in (0, 1, 2, 3, 4):
|
||||||
assert regen_bounds[j] == pytest.approx(original_bounds[j], abs=0.02)
|
assert regen_bounds[j] == pytest.approx(original_bounds[j], abs=0.02)
|
||||||
|
# Verify the regenerated body contains boolean clipping (roof clipping).
|
||||||
|
space = ifc.by_id(2363)
|
||||||
|
body = ifcopenshell.util.representation.get_representation(space, "Model", "Body", "MODEL_VIEW")
|
||||||
|
assert body is not None
|
||||||
|
boolean_items = [i for i in (body.Items or []) if i.is_a("IfcBooleanClippingResult")]
|
||||||
|
assert len(boolean_items) >= 1, "Expected roof clipping but got no boolean result"
|
||||||
|
|
||||||
def test_regenerate_space_twice_does_not_duplicate_half_spaces(self):
|
def test_regenerate_space_twice_does_not_duplicate_half_spaces(self):
|
||||||
ifc = self.load_house_with_garage()
|
ifc = self.load_house_with_garage()
|
||||||
|
|||||||
@@ -302,15 +302,22 @@ def get_vertical_bounding_planes(
|
|||||||
bounds = space_polygon.bounds
|
bounds = space_polygon.bounds
|
||||||
cx = (bounds[0] + bounds[2]) / 2.0
|
cx = (bounds[0] + bounds[2]) / 2.0
|
||||||
cy = (bounds[1] + bounds[3]) / 2.0
|
cy = (bounds[1] + bounds[3]) / 2.0
|
||||||
sample_offsets = [(0.0, 0.0)]
|
sample_points = [(cx, cy)]
|
||||||
if bounds[2] - bounds[0] > 0.1:
|
if bounds[2] - bounds[0] > 0.1:
|
||||||
sample_offsets.append((0.25 * (bounds[2] - bounds[0]), 0.0))
|
sample_points.append((cx + 0.25 * (bounds[2] - bounds[0]), cy))
|
||||||
sample_offsets.append((-0.25 * (bounds[2] - bounds[0]), 0.0))
|
sample_points.append((cx - 0.25 * (bounds[2] - bounds[0]), cy))
|
||||||
if bounds[3] - bounds[1] > 0.1:
|
if bounds[3] - bounds[1] > 0.1:
|
||||||
sample_offsets.append((0.0, 0.25 * (bounds[3] - bounds[1])))
|
sample_points.append((cx, cy + 0.25 * (bounds[3] - bounds[1])))
|
||||||
sample_offsets.append((0.0, -0.25 * (bounds[3] - bounds[1])))
|
sample_points.append((cx, cy - 0.25 * (bounds[3] - bounds[1])))
|
||||||
|
|
||||||
hits = _nearest_ray_hits(tree, [(cx + dx, cy + dy, origin_z) for dx, dy in sample_offsets], ray_dir)
|
# Sample from footprint corners, offset toward centroid so the ray origin
|
||||||
|
# sits inside the space (not on a bounding wall). This catches elements
|
||||||
|
# (e.g. sloped roofs) that only cover a corner of the space.
|
||||||
|
for coords in space_polygon.exterior.coords[:-1]:
|
||||||
|
vx, vy = coords[0], coords[1]
|
||||||
|
sample_points.append((vx + 0.05 * (cx - vx), vy + 0.05 * (cy - vy)))
|
||||||
|
|
||||||
|
hits = _nearest_ray_hits(tree, [(x, y, origin_z) for x, y in sample_points], ray_dir)
|
||||||
if not hits:
|
if not hits:
|
||||||
return "EXTRUDE_CLIP", [] # open top / void below: no bounding planes
|
return "EXTRUDE_CLIP", [] # open top / void below: no bounding planes
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user