mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 10:33:20 +00:00
Fix clipped space top reaching sloped planes
The extrusion height was capped at the top/bottom plane anchor z (the mean of the ray-cast hits, near the footprint centre), so a sloped ceiling's high side stopped short of the plane (e.g. 5.5 m instead of 6.88 m for the shed roof test). Extend the extrusion to the plane's z at every footprint vertex before clipping, falling back to the base z as before. Also correct the mirrored profile-to-world mapping comment in the shed roof test helper (the ridge is at world y=-5, not y=+5). Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -511,9 +511,12 @@ class TestGenerateSpaceSlopedRoof(NewFile):
|
||||
|
||||
Triangular prism: vertical profile (in the y-z plane) extruded along +x.
|
||||
Profile points (u, v) with placement loc=(-5, 0, z), axis=(1,0,0),
|
||||
ref=(0,0,1): (0,-5) -> world (-5,-5,z) eave bottom
|
||||
(rise,-5) -> world (-5,-5,z+rise) eave top
|
||||
(rise,5) -> world (-5,5,z+rise) ridge
|
||||
ref=(0,0,1). The local frame maps u to world +z (u=0 -> z, u=rise ->
|
||||
z+rise) and v to world -y (v=-5 -> y=+5, v=+5 -> y=-5):
|
||||
(0,-5) -> world (-5, +5, z) eave (low) at north
|
||||
(rise,-5) -> world (-5, +5, z+rise) vertical edge
|
||||
(rise,5) -> world (-5, -5, z+rise) ridge at south
|
||||
The underside is the sloped face from (y=+5, z) to (y=-5, z+rise).
|
||||
ExtrudedDirection (0,0,1) is local, mapping to world +x; depth 10 spans
|
||||
x in [-5, 5].
|
||||
"""
|
||||
|
||||
@@ -410,6 +410,13 @@ def detect_space_volume_strategy(
|
||||
return "EXTRUDE_CLIP", top_planes, bottom_planes
|
||||
|
||||
|
||||
def _footprint_coords(space_polygon: shapely.Polygon):
|
||||
"""Yield (x, y) boundary coordinates of a footprint polygon (exterior then holes)."""
|
||||
for coords in [space_polygon.exterior.coords, *[ring.coords for ring in space_polygon.interiors]]:
|
||||
for point in coords:
|
||||
yield point[0], point[1]
|
||||
|
||||
|
||||
def build_extruded_clipped_space(
|
||||
ifc_file: ifcopenshell.file,
|
||||
space_polygon: shapely.Polygon,
|
||||
@@ -447,8 +454,17 @@ def build_extruded_clipped_space(
|
||||
profile = builder.profile(outer_curve, inner_curves=inner_curves)
|
||||
|
||||
all_z = [base_z]
|
||||
for point, _ in top_planes + bottom_planes:
|
||||
for point, normal in top_planes + bottom_planes:
|
||||
all_z.append(float(point[2]))
|
||||
for x, y in _footprint_coords(space_polygon):
|
||||
# A sloped plane's height varies across the footprint. The anchor
|
||||
# point is near the centre, so also cover the plane at the polygon
|
||||
# vertices, otherwise the high side of a sloped ceiling is capped
|
||||
# below the plane it should reach.
|
||||
if abs(normal[2]) < 1e-6:
|
||||
continue
|
||||
plane_z = (float(np.dot(normal, point)) - float(normal[0]) * x - float(normal[1]) * y) / float(normal[2])
|
||||
all_z.append(plane_z)
|
||||
min_z = min(all_z)
|
||||
max_z = max(all_z)
|
||||
height = max_z - min_z
|
||||
|
||||
@@ -297,6 +297,19 @@ class TestBuildExtrudedClippedSpace(test.bootstrap.IFC4):
|
||||
assert item.FirstOperand.is_a("IfcBooleanClippingResult")
|
||||
assert item.FirstOperand.FirstOperand.is_a("IfcExtrudedAreaSolid")
|
||||
|
||||
def test_sloped_top_plane_reaches_footprint_max(self):
|
||||
# A sloped ceiling's high side must reach the plane at the footprint
|
||||
# edge (z=7.0 at y=-5), not be capped at the plane anchor z=5.5.
|
||||
# 3D coords guard against the polygon carrying Z from the bisection.
|
||||
space_polygon = shapely.Polygon([(-5, -5, 0), (5, -5, 0), (5, 5, 0), (-5, 5, 0)])
|
||||
top_plane = (np.array([0.0, 0.0, 5.5]), np.array([0.0, 0.287, 0.958]))
|
||||
bottom_plane = (np.array([0.0, 0.0, 0.0]), np.array([0.0, 0.0, -1.0]))
|
||||
item = subject.build_extruded_clipped_space(self.file, space_polygon, 0.0, [top_plane], [bottom_plane])
|
||||
shape = ifcopenshell.geom.create_shape(ifcopenshell.geom.settings(), item)
|
||||
verts = ifcopenshell.util.shape.get_vertices(shape)
|
||||
assert verts[:, 2].min() == pytest.approx(0.0, abs=0.1)
|
||||
assert verts[:, 2].max() == pytest.approx(7.0, abs=0.1)
|
||||
|
||||
|
||||
class TestBuildBrepSpace(test.bootstrap.IFC4):
|
||||
def test_sloped_wall_brep_has_faces(self):
|
||||
|
||||
Reference in New Issue
Block a user