mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 02:47:48 +00:00
See #1227. Test script for priority-aware wall layer joins.
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.api.type
|
||||
import ifcopenshell.api.unit
|
||||
import ifcopenshell.api.project
|
||||
import ifcopenshell.api.context
|
||||
import ifcopenshell.api.spatial
|
||||
import ifcopenshell.api.material
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.util.shape_builder
|
||||
import ifcopenshell.util.element
|
||||
|
||||
# from ifcopenshell.util.shape_builder import VectorType, SequenceOfVectors
|
||||
from collections import namedtuple
|
||||
|
||||
f = ifcopenshell.api.project.create_file()
|
||||
|
||||
ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject")
|
||||
meters = ifcopenshell.api.unit.add_si_unit(f)
|
||||
ifcopenshell.api.unit.assign_unit(f, units=[meters])
|
||||
|
||||
model = ifcopenshell.api.context.add_context(f, context_type="Model")
|
||||
plan = ifcopenshell.api.context.add_context(f, context_type="Plan")
|
||||
axis = ifcopenshell.api.context.add_context(
|
||||
f, context_type="Plan", context_identifier="Axis", target_view="GRAPH_VIEW", parent=plan
|
||||
)
|
||||
body = ifcopenshell.api.context.add_context(
|
||||
f, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model
|
||||
)
|
||||
concrete = ifcopenshell.api.material.add_material(f, name="concrete", category="concrete")
|
||||
site = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSite")
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(f)
|
||||
|
||||
|
||||
def test_wall(offset, p1, p2, p3, p4):
|
||||
offset *= 1.5
|
||||
wall_type_a = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWallType", name="A")
|
||||
wall_type_b = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWallType", name="B")
|
||||
|
||||
set_a = ifcopenshell.api.material.add_material_set(f, set_type="IfcMaterialLayerSet")
|
||||
structure = ifcopenshell.api.material.add_layer(f, layer_set=set_a, material=concrete, name="structure")
|
||||
structure.Priority = p1
|
||||
structure.LayerThickness = 0.1
|
||||
cladding = ifcopenshell.api.material.add_layer(f, layer_set=set_a, material=concrete, name="cladding")
|
||||
cladding.Priority = p2
|
||||
cladding.LayerThickness = 0.05
|
||||
|
||||
set_b = ifcopenshell.api.material.add_material_set(f, set_type="IfcMaterialLayerSet")
|
||||
structure = ifcopenshell.api.material.add_layer(f, layer_set=set_b, material=concrete, name="structure")
|
||||
structure.Priority = p3
|
||||
structure.LayerThickness = 0.1
|
||||
cladding = ifcopenshell.api.material.add_layer(f, layer_set=set_b, material=concrete, name="cladding")
|
||||
cladding.Priority = p4
|
||||
cladding.LayerThickness = 0.05
|
||||
|
||||
ifcopenshell.api.material.assign_material(f, products=[wall_type_a], material=set_a)
|
||||
ifcopenshell.api.material.assign_material(f, products=[wall_type_b], material=set_b)
|
||||
|
||||
for i, rotation in enumerate((-90, -60, -120, 90, 60, 120)):
|
||||
wall_a = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name=f"A{p1}{p2}")
|
||||
wall_b = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name=f"B{p3}{p4}")
|
||||
|
||||
ifcopenshell.api.spatial.assign_container(f, products=[wall_a, wall_b], relating_structure=site)
|
||||
|
||||
ifcopenshell.api.type.assign_type(f, related_objects=[wall_a], relating_type=wall_type_a)
|
||||
ifcopenshell.api.type.assign_type(f, related_objects=[wall_b], relating_type=wall_type_b)
|
||||
|
||||
axis_a = builder.polyline(((0.0, 0.0), (1.0, 0.0)))
|
||||
axis_b = builder.polyline(((0.0, 0.0), (1.0, 0.0)))
|
||||
rep_a = builder.get_representation(axis, [axis_a])
|
||||
rep_b = builder.get_representation(axis, [axis_b])
|
||||
|
||||
ifcopenshell.api.geometry.assign_representation(f, product=wall_a, representation=rep_a)
|
||||
ifcopenshell.api.geometry.assign_representation(f, product=wall_b, representation=rep_b)
|
||||
|
||||
x_offset = i * 2
|
||||
sign_offset = 0 if rotation < 0 else 1
|
||||
matrix_a = np.eye(4)
|
||||
matrix_a[:, 3][0:3] = (0 + x_offset, 0 + offset + sign_offset, 0)
|
||||
matrix_b = np.eye(4)
|
||||
matrix_b = ifcopenshell.util.placement.rotation(rotation, "Z") @ matrix_b
|
||||
matrix_b[:, 3][0:3] = (1 + x_offset, 1 + offset - sign_offset, 0)
|
||||
ifcopenshell.api.geometry.edit_object_placement(f, product=wall_a, matrix=matrix_a)
|
||||
ifcopenshell.api.geometry.edit_object_placement(f, product=wall_b, matrix=matrix_b)
|
||||
|
||||
ifcopenshell.api.geometry.connect_path(
|
||||
f, relating_element=wall_a, related_element=wall_b, relating_connection="ATEND", related_connection="ATEND"
|
||||
)
|
||||
|
||||
Foo(f, body).regenerate(wall_a)
|
||||
Foo(f, body).regenerate(wall_b)
|
||||
|
||||
|
||||
PrioritisedLayer = namedtuple("PrioritisedLayer", "priority thickness")
|
||||
|
||||
|
||||
class Foo:
|
||||
def __init__(self, file, body):
|
||||
self.file = file
|
||||
self.body = body
|
||||
|
||||
def regenerate(self, wall):
|
||||
print("-" * 100)
|
||||
print(wall)
|
||||
layers = self.get_layers(wall)
|
||||
if not layers:
|
||||
return
|
||||
axes = self.get_axes(wall, layers)
|
||||
self.start_points = []
|
||||
self.end_points = []
|
||||
for rel in wall.ConnectedTo:
|
||||
if rel.is_a("IfcRelConnectsPathElements"):
|
||||
wall2 = rel.RelatedElement
|
||||
layers1 = self.combine_layers(layers.copy(), rel.RelatingPriorities)
|
||||
layers2 = self.combine_layers(self.get_layers(wall2), rel.RelatedPriorities)
|
||||
if not layers1 or not layers2:
|
||||
continue
|
||||
self.join(wall, wall2, layers1, layers2, rel.RelatingConnectionType, rel.RelatedConnectionType)
|
||||
|
||||
for rel in wall.ConnectedFrom:
|
||||
if rel.is_a("IfcRelConnectsPathElements"):
|
||||
wall2 = rel.RelatingElement
|
||||
layers1 = self.combine_layers(layers.copy(), rel.RelatedPriorities)
|
||||
layers2 = self.combine_layers(self.get_layers(wall2), rel.RelatingPriorities)
|
||||
if not layers1 or not layers2:
|
||||
continue
|
||||
self.join(wall, wall2, layers1, layers2, rel.RelatedConnectionType, rel.RelatingConnectionType)
|
||||
|
||||
# for rel in wall.ConnectedFrom:
|
||||
# if rel.is_a("IfcRelConnectsPathElements"):
|
||||
# connection = rel.RelatedConnectionType
|
||||
if not self.start_points:
|
||||
minx = axes[0][0][0]
|
||||
self.start_points = [
|
||||
np.array((minx, axes[0][0][1])),
|
||||
np.array((minx, axes[-1][0][1])),
|
||||
]
|
||||
if not self.end_points:
|
||||
maxx = axes[0][1][0]
|
||||
self.end_points = [
|
||||
np.array((maxx, axes[0][0][1])),
|
||||
np.array((maxx, axes[-1][0][1])),
|
||||
]
|
||||
print("FINISHED")
|
||||
print(self.start_points)
|
||||
print(self.end_points)
|
||||
|
||||
points = []
|
||||
if self.start_points[0][1] < self.start_points[-1][1]:
|
||||
points.extend((self.start_points))
|
||||
else:
|
||||
points.extend(reversed(self.start_points))
|
||||
if self.end_points[0][1] > self.end_points[-1][1]:
|
||||
points.extend((self.end_points))
|
||||
else:
|
||||
points.extend(reversed(self.end_points))
|
||||
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(wall.file)
|
||||
item = builder.extrude(builder.polyline(points, closed=True), magnitude=1.0)
|
||||
rep = builder.get_representation(self.body, items=[item])
|
||||
ifcopenshell.api.geometry.assign_representation(self.file, product=wall, representation=rep)
|
||||
|
||||
def join(self, wall1, wall2, layers1, layers2, connection1, connection2):
|
||||
if connection1 == "NOTDEFINED" or connection2 == "NOTDEFINED":
|
||||
return
|
||||
print("joining", wall1, layers1, connection1)
|
||||
print("to", wall2, layers2, connection2)
|
||||
|
||||
# axes = self.get_axes(wall2, layers2)
|
||||
axes1 = self.get_axes(wall1, layers1)
|
||||
axes2 = self.get_axes(wall2, layers2)
|
||||
matrix1i = np.linalg.inv(ifcopenshell.util.placement.get_local_placement(wall1.ObjectPlacement))
|
||||
matrix2 = ifcopenshell.util.placement.get_local_placement(wall2.ObjectPlacement)
|
||||
print(axes1)
|
||||
print(axes2)
|
||||
|
||||
# Convert wall2 axes to wall1 local coordinates
|
||||
for axis in axes2:
|
||||
axis[0] = (matrix1i @ matrix2 @ np.concatenate((axis[0], (0, 1))))[:2]
|
||||
axis[1] = (matrix1i @ matrix2 @ np.concatenate((axis[1], (0, 1))))[:2]
|
||||
|
||||
# Sort axes from interior to exterior
|
||||
if connection1 == "ATEND":
|
||||
if axes2[0][0][0] > axes2[-1][0][0]: # We process layers in a +X direction
|
||||
axes2 = list(reversed(axes2))
|
||||
layers2 = list(reversed(layers2))
|
||||
elif connection1 == "ATSTART":
|
||||
if axes2[-1][0][0] > axes2[0][0][0]: # We process layers in a -X direction
|
||||
axes2 = list(reversed(axes2))
|
||||
layers2 = list(reversed(layers2))
|
||||
|
||||
# wall2_x = matrix2[:,0][:2]
|
||||
axis2 = axes2[0] # Take an arbitrary axis
|
||||
if connection2 == "ATSTART":
|
||||
axis2 = [axis2[1], axis2[0]] # Flip direction so the axis "points" in the direction of join
|
||||
if axis2[0][1] < axis2[1][1]: # Pointing +Y
|
||||
if axes1[-1][0][1] < axes1[0][0][1]: # We process layers1 in a +Y direction
|
||||
axes1 = list(reversed(axes1))
|
||||
layers1 = list(reversed(layers1))
|
||||
else: # Pointing -Y
|
||||
if axes1[0][0][1] < axes1[-1][0][1]: # We process layers1 in a -Y direction
|
||||
axes1 = list(reversed(axes1))
|
||||
layers1 = list(reversed(layers1))
|
||||
|
||||
print("modified")
|
||||
print(axes1)
|
||||
print(axes2)
|
||||
# Checked
|
||||
|
||||
last_y = axes1[-1][0][1]
|
||||
ys = iter([a[0][1] for a in axes1])
|
||||
print("ys are", [a[0][1] for a in axes1])
|
||||
|
||||
last_axis2 = axes2[-1]
|
||||
axes2 = iter(axes2)
|
||||
axis2 = next(axes2)
|
||||
y = next(ys)
|
||||
x = self.intersect_axis(*axis2, y=y)
|
||||
points = [np.array((x, y))]
|
||||
print("first point", points)
|
||||
|
||||
layers1 = iter(layers1)
|
||||
layers2 = iter(layers2)
|
||||
layer1 = next(layers1, None)
|
||||
layer2 = next(layers2, None)
|
||||
|
||||
while layer1 and layer2:
|
||||
print("considering", layer1, layer2)
|
||||
if layer1.priority > layer2.priority:
|
||||
axis2 = next(axes2)
|
||||
x = self.intersect_axis(*axis2, y=y)
|
||||
layer2 = next(layers2, None)
|
||||
elif layer2.priority > layer1.priority:
|
||||
y = next(ys)
|
||||
x = self.intersect_axis(*axis2, y=y)
|
||||
layer1 = next(layers1, None)
|
||||
else:
|
||||
y = next(ys)
|
||||
x = self.intersect_axis(*next(axes2), y=y)
|
||||
layer1 = next(layers1, None)
|
||||
layer2 = next(layers2, None)
|
||||
points.append(np.array((x, y)))
|
||||
|
||||
print("points", points)
|
||||
if points[-1][1] != last_y:
|
||||
points.append(np.array((self.intersect_axis(*last_axis2, y=last_y), last_y)))
|
||||
print("fpoints", points)
|
||||
if connection1 == "ATSTART":
|
||||
self.start_points = points
|
||||
elif connection1 == "ATEND":
|
||||
self.end_points = points
|
||||
|
||||
def get_layers(self, wall) -> list:
|
||||
material = ifcopenshell.util.element.get_material(wall, should_skip_usage=True)
|
||||
if not material or not material.is_a("IfcMaterialLayerSet"):
|
||||
return []
|
||||
return [PrioritisedLayer(l.Priority or 0, l.LayerThickness) for l in material.MaterialLayers]
|
||||
|
||||
def combine_layers(self, layers, override_priorities):
|
||||
results = []
|
||||
if override_priorities:
|
||||
for i, priority in enumerate(override_priorities[: len(layers)]):
|
||||
layers[i][0] = priority
|
||||
if not layers:
|
||||
return []
|
||||
results = [layers.pop(0)]
|
||||
for layer in layers:
|
||||
if not layer.thickness:
|
||||
continue
|
||||
if layer.priority == results[-1].priority:
|
||||
results[-1] = PrioritisedLayer(layer.priority, results[-1].thickness + layer.thickness)
|
||||
else:
|
||||
results.append(layer)
|
||||
return results
|
||||
|
||||
def intersect_axis(self, p1, p2, y=0):
|
||||
# Assumes lines are horizontal
|
||||
x1, y1 = p1
|
||||
x2, y2 = p2
|
||||
t = (y - y1) / (y2 - y1)
|
||||
return x1 + t * (x2 - x1)
|
||||
|
||||
def get_axes(self, wall, layers: list[PrioritisedLayer]):
|
||||
# I think it's not actually necessary to get the exact axis line here.
|
||||
axes = []
|
||||
# Start by getting Reference line
|
||||
if axis := ifcopenshell.util.representation.get_representation(wall, "Plan", "Axis", "GRAPH_VIEW"):
|
||||
for item in ifcopenshell.util.representation.resolve_representation(axis).Items:
|
||||
if item.is_a("IfcPolyline"):
|
||||
points = item.Points
|
||||
elif item.is_a("IfcIndexedPolyCurve"):
|
||||
points = item.Points.CoordList
|
||||
else:
|
||||
continue
|
||||
if points[0][0] < points[1][0]: # An axis always goes in the +X direction
|
||||
axes.append([np.array(points[0]), np.array(points[1])])
|
||||
else:
|
||||
axes.append([np.array(points[1]), np.array(points[0])])
|
||||
break
|
||||
else:
|
||||
# TODO: derive from existing geometry
|
||||
axes.append([np.array((0.0, 0.0)), np.array((1.0, 0.0))])
|
||||
|
||||
# Apply usage to convert the Reference line into MlsBase
|
||||
sense_factor = 1
|
||||
if (usage := ifcopenshell.util.element.get_material(wall)) and usage.is_a("IfcMaterialLayerSetUage"):
|
||||
for point in axes[0]:
|
||||
point[1] += usage.OffsetFromReferenceLine
|
||||
sense_factor = 1 if usage.DirectionSense == "POSITIVE" else -1
|
||||
|
||||
for layer in layers:
|
||||
axes.append([p.copy() + np.array((0.0, layer.thickness * sense_factor)) for p in axes[-1]])
|
||||
return axes
|
||||
|
||||
|
||||
test_wall(0, 1, 1, 1, 1)
|
||||
test_wall(1, 2, 1, 1, 2)
|
||||
test_wall(2, 2, 1, 1, 1)
|
||||
test_wall(3, 1, 2, 1, 1)
|
||||
test_wall(4, 1, 2, 1, 2)
|
||||
test_wall(5, 3, 1, 2, 4)
|
||||
|
||||
|
||||
f.write("/home/dion/wall.ifc")
|
||||
Reference in New Issue
Block a user