mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-09 13:52:23 +00:00
Add clipped extrusion space builder
Build IfcExtrudedAreaSolid clipped by top/bottom half-space planes. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -29,8 +29,11 @@ from __future__ import annotations
|
|||||||
from typing import Literal, Optional, Union
|
from typing import Literal, Optional, Union
|
||||||
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
|
import ifcopenshell.api
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
import ifcopenshell.util.shape
|
import ifcopenshell.util.shape
|
||||||
|
import ifcopenshell.util.shape_builder
|
||||||
|
import ifcopenshell.util.unit
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import shapely
|
import shapely
|
||||||
|
|
||||||
@@ -402,3 +405,65 @@ def detect_space_volume_strategy(
|
|||||||
return "BREP", None, None
|
return "BREP", None, None
|
||||||
|
|
||||||
return "EXTRUDE_CLIP", top_planes, bottom_planes
|
return "EXTRUDE_CLIP", top_planes, bottom_planes
|
||||||
|
|
||||||
|
|
||||||
|
def build_extruded_clipped_space(
|
||||||
|
ifc_file: ifcopenshell.file,
|
||||||
|
space_polygon: shapely.Polygon,
|
||||||
|
base_z: float,
|
||||||
|
top_planes: list[tuple[np.ndarray, np.ndarray]],
|
||||||
|
bottom_planes: list[tuple[np.ndarray, np.ndarray]],
|
||||||
|
) -> ifcopenshell.entity_instance:
|
||||||
|
"""Build an IfcExtrudedAreaSolid clipped to top/bottom planes.
|
||||||
|
|
||||||
|
:param ifc_file: The IFC file.
|
||||||
|
:param space_polygon: Footprint polygon in world XY.
|
||||||
|
:param base_z: Base elevation in SI.
|
||||||
|
:param top_planes: List of (point, normal) tuples for top clipping planes.
|
||||||
|
:param bottom_planes: List of (point, normal) tuples for bottom clipping planes.
|
||||||
|
:return: IfcBooleanClippingResult chain.
|
||||||
|
"""
|
||||||
|
builder = ifcopenshell.util.shape_builder.ShapeBuilder(ifc_file)
|
||||||
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||||
|
|
||||||
|
centroid = np.array([space_polygon.centroid.x, space_polygon.centroid.y])
|
||||||
|
exterior = [
|
||||||
|
(float(p[0] - centroid[0]) / unit_scale, float(p[1] - centroid[1]) / unit_scale)
|
||||||
|
for p in space_polygon.exterior.coords[:-1]
|
||||||
|
]
|
||||||
|
inner_curves = []
|
||||||
|
for interior in space_polygon.interiors:
|
||||||
|
inner = [
|
||||||
|
(float(p[0] - centroid[0]) / unit_scale, float(p[1] - centroid[1]) / unit_scale)
|
||||||
|
for p in interior.coords[:-1]
|
||||||
|
]
|
||||||
|
inner_curve = builder.polyline(inner, closed=True)
|
||||||
|
inner_curves.append(inner_curve)
|
||||||
|
|
||||||
|
outer_curve = builder.polyline(exterior, closed=True)
|
||||||
|
profile = builder.profile(outer_curve, inner_curves=inner_curves)
|
||||||
|
|
||||||
|
all_z = [base_z]
|
||||||
|
for point, _ in top_planes + bottom_planes:
|
||||||
|
all_z.append(float(point[2]))
|
||||||
|
min_z = min(all_z)
|
||||||
|
max_z = max(all_z)
|
||||||
|
height = max_z - min_z
|
||||||
|
|
||||||
|
extrusion = builder.extrude(
|
||||||
|
profile,
|
||||||
|
magnitude=height / unit_scale,
|
||||||
|
position=[(centroid[0] / unit_scale), (centroid[1] / unit_scale), min_z / unit_scale],
|
||||||
|
)
|
||||||
|
|
||||||
|
result = extrusion
|
||||||
|
for point, normal in top_planes + bottom_planes:
|
||||||
|
# clip_solid takes location in SI; it converts to project units internally.
|
||||||
|
result = ifcopenshell.api.geometry.clip_solid(
|
||||||
|
ifc_file,
|
||||||
|
item=result,
|
||||||
|
location=[float(point[i]) for i in range(3)],
|
||||||
|
normal=[float(normal[i]) for i in range(3)],
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|||||||
@@ -265,3 +265,16 @@ class TestDetectSpaceVolumeStrategy(test.bootstrap.IFC4):
|
|||||||
self.file, shapes, tree, shapely.box(-4, -4, 4, 4), 0.0, [wall]
|
self.file, shapes, tree, shapely.box(-4, -4, 4, 4), 0.0, [wall]
|
||||||
)
|
)
|
||||||
assert strategy == "BREP"
|
assert strategy == "BREP"
|
||||||
|
|
||||||
|
|
||||||
|
class TestBuildExtrudedClippedSpace(test.bootstrap.IFC4):
|
||||||
|
def test_shed_roof_clips_to_sloped_plane(self):
|
||||||
|
space_polygon = shapely.box(-5, -5, 5, 5)
|
||||||
|
top_plane = (np.array([0.0, 0.0, 4.0]), np.array([0.0, 0.0, 1.0]))
|
||||||
|
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])
|
||||||
|
assert item.is_a("IfcBooleanClippingResult")
|
||||||
|
assert item.SecondOperand.is_a("IfcHalfSpaceSolid")
|
||||||
|
# Chain: bottom clip -> top clip -> IfcExtrudedAreaSolid.
|
||||||
|
assert item.FirstOperand.is_a("IfcBooleanClippingResult")
|
||||||
|
assert item.FirstOperand.FirstOperand.is_a("IfcExtrudedAreaSolid")
|
||||||
|
|||||||
Reference in New Issue
Block a user