mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 18:43:26 +00:00
See #1227. Implement ATPATH wall connections (going both ways).
This commit is contained in:
@@ -1053,7 +1053,7 @@ class Loader(bonsai.core.tool.Loader):
|
||||
styles[style] = i
|
||||
for layer in layer_set.MaterialLayers[:-1]:
|
||||
prev_co = co.copy()
|
||||
co.y = layer.LayerThickness * cls.unit_scale * sense_factor
|
||||
co.y += layer.LayerThickness * cls.unit_scale * sense_factor
|
||||
bisect_geom = bmesh.ops.bisect_plane(
|
||||
bm, geom=bm.verts[:] + bm.edges[:] + bm.faces[:], dist=0.0001, plane_co=co, plane_no=no
|
||||
)
|
||||
|
||||
+269
-58
@@ -12,11 +12,12 @@ import ifcopenshell.util.shape_builder
|
||||
import ifcopenshell.util.element
|
||||
|
||||
# from ifcopenshell.util.shape_builder import VectorType, SequenceOfVectors
|
||||
from itertools import cycle
|
||||
from collections import namedtuple
|
||||
|
||||
f = ifcopenshell.api.project.create_file()
|
||||
|
||||
ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject")
|
||||
project = 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])
|
||||
|
||||
@@ -31,6 +32,7 @@ body = ifcopenshell.api.context.add_context(
|
||||
material1 = ifcopenshell.api.material.add_material(f, name="material1", category="material1")
|
||||
material2 = ifcopenshell.api.material.add_material(f, name="material2", category="material2")
|
||||
site = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSite")
|
||||
ifcopenshell.api.aggregate.assign_object(f, products=[site], relating_object=project)
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(f)
|
||||
|
||||
style = ifcopenshell.api.style.add_style(f)
|
||||
@@ -68,23 +70,32 @@ def test_wall(offset, p1, p2, p3, p4):
|
||||
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)):
|
||||
for i, rotation in enumerate((-90, -75, -105, 90, 75, 105)):
|
||||
for i2, connection in enumerate(("ATEND", "ATSTART", "MIX")):
|
||||
# if rotation != -90:
|
||||
# continue
|
||||
# if connection != "ATEND":
|
||||
# continue
|
||||
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}")
|
||||
wall_c = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name=f"C{p3}{p4}")
|
||||
|
||||
ifcopenshell.api.spatial.assign_container(f, products=[wall_a, wall_b], relating_structure=site)
|
||||
ifcopenshell.api.spatial.assign_container(f, products=[wall_a, wall_b, wall_c], 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)
|
||||
ifcopenshell.api.type.assign_type(f, related_objects=[wall_c], 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)))
|
||||
axis_c = 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])
|
||||
rep_c = builder.get_representation(axis, [axis_c])
|
||||
|
||||
ifcopenshell.api.geometry.assign_representation(f, product=wall_a, representation=rep_a)
|
||||
ifcopenshell.api.geometry.assign_representation(f, product=wall_b, representation=rep_b)
|
||||
ifcopenshell.api.geometry.assign_representation(f, product=wall_c, representation=rep_c)
|
||||
|
||||
x_offset = i * 2
|
||||
x_offset += i2 * (2 * 6)
|
||||
@@ -95,8 +106,12 @@ def test_wall(offset, p1, p2, p3, p4):
|
||||
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)
|
||||
matrix_c = np.eye(4)
|
||||
matrix_c = ifcopenshell.util.placement.rotation(rotation, "Z") @ matrix_c
|
||||
matrix_c[:, 3][0:3] = (0.5 + x_offset, 0.5 + 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.edit_object_placement(f, product=wall_c, matrix=matrix_c)
|
||||
|
||||
ifcopenshell.api.geometry.connect_path(
|
||||
f,
|
||||
@@ -105,6 +120,13 @@ def test_wall(offset, p1, p2, p3, p4):
|
||||
relating_connection="ATEND",
|
||||
related_connection="ATEND",
|
||||
)
|
||||
ifcopenshell.api.geometry.connect_path(
|
||||
f,
|
||||
relating_element=wall_c,
|
||||
related_element=wall_a,
|
||||
relating_connection="ATEND",
|
||||
related_connection="ATPATH",
|
||||
)
|
||||
elif connection == "ATSTART":
|
||||
sign_offset = 0 if rotation < 0 else 1
|
||||
matrix_a = np.eye(4)
|
||||
@@ -142,6 +164,69 @@ def test_wall(offset, p1, p2, p3, p4):
|
||||
|
||||
Foo(f, body, axis).regenerate(wall_a)
|
||||
Foo(f, body, axis).regenerate(wall_b)
|
||||
Foo(f, body, axis).regenerate(wall_c)
|
||||
|
||||
|
||||
def create_type(name, layers):
|
||||
wall_type = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWallType", name=name)
|
||||
layer_set = ifcopenshell.api.material.add_material_set(f, set_type="IfcMaterialLayerSet")
|
||||
materials = cycle((material1, material2))
|
||||
for layer in layers:
|
||||
material = next(materials)
|
||||
item = ifcopenshell.api.material.add_layer(f, layer_set=layer_set, material=material, name="structure")
|
||||
item.Priority = layer[0]
|
||||
item.LayerThickness = layer[1]
|
||||
ifcopenshell.api.material.assign_material(f, products=[wall_type], material=layer_set)
|
||||
return wall_type
|
||||
|
||||
|
||||
def test_atpath(offset):
|
||||
offset *= 1.5
|
||||
wall_type_a = create_type("A", [(1, 0.05), (2, 0.1), (3, 0.05)])
|
||||
wall_a = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="A123")
|
||||
ifcopenshell.api.spatial.assign_container(f, products=[wall_a], relating_structure=site)
|
||||
ifcopenshell.api.type.assign_type(f, related_objects=[wall_a], relating_type=wall_type_a)
|
||||
axis_a = builder.polyline(((0.0, 0.0), (30.0, 0.0)))
|
||||
rep_a = builder.get_representation(axis, [axis_a])
|
||||
ifcopenshell.api.geometry.assign_representation(f, product=wall_a, representation=rep_a)
|
||||
matrix_a = np.eye(4)
|
||||
matrix_a[:, 3][0:3] = (0, 0 + offset, 0)
|
||||
ifcopenshell.api.geometry.edit_object_placement(f, product=wall_a, matrix=matrix_a)
|
||||
|
||||
def create_branch(name, p1, p2, p3, x, y, rotation):
|
||||
wall_type = create_type(name, [(p1, 0.05), (p2, 0.1), (p3, 0.05)])
|
||||
wall = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name=f"{name}{p1}{p2}{p3}")
|
||||
ifcopenshell.api.spatial.assign_container(f, products=[wall], relating_structure=site)
|
||||
ifcopenshell.api.type.assign_type(f, related_objects=[wall], relating_type=wall_type)
|
||||
axis_a = builder.polyline(((0.0, 0.0), (1.0, 0.0)))
|
||||
rep_a = builder.get_representation(axis, [axis_a])
|
||||
ifcopenshell.api.geometry.assign_representation(f, product=wall, representation=rep_a)
|
||||
matrix_a = np.eye(4)
|
||||
matrix_a = ifcopenshell.util.placement.rotation(rotation, "Z") @ matrix_a
|
||||
matrix_a[:, 3][0:3] = (x, y + offset, 0)
|
||||
ifcopenshell.api.geometry.edit_object_placement(f, product=wall, matrix=matrix_a)
|
||||
ifcopenshell.api.geometry.connect_path(
|
||||
f,
|
||||
relating_element=wall,
|
||||
related_element=wall_a,
|
||||
relating_connection="ATEND",
|
||||
related_connection="ATPATH",
|
||||
)
|
||||
Foo(f, body, axis).regenerate(wall)
|
||||
|
||||
create_branch("B", 1, 1, 1, 1, 1, -75)
|
||||
create_branch("C", 1, 2, 3, 2, 1, -75)
|
||||
create_branch("D", 1, 4, 2, 3, 1, -75)
|
||||
create_branch("E", 4, 4, 4, 4, 1, -75)
|
||||
create_branch("F", 4, 2, 4, 5, 1, -75)
|
||||
|
||||
create_branch("B", 1, 1, 1, 0.5, -1, 75)
|
||||
create_branch("C", 1, 2, 3, 1.5, -1, 75)
|
||||
create_branch("D", 1, 4, 2, 2.5, -1, 75)
|
||||
create_branch("E", 4, 4, 4, 3.5, -1, 75)
|
||||
create_branch("F", 4, 2, 4, 4.5, -1, 75)
|
||||
|
||||
Foo(f, body, axis).regenerate(wall_a)
|
||||
|
||||
|
||||
PrioritisedLayer = namedtuple("PrioritisedLayer", "priority thickness")
|
||||
@@ -162,8 +247,13 @@ class Foo:
|
||||
reference = self.get_reference_line(wall)
|
||||
self.reference_p1, self.reference_p2 = reference
|
||||
axes = self.get_axes(wall, reference, layers)
|
||||
self.miny = axes[0][0][1]
|
||||
self.maxy = axes[-1][0][1]
|
||||
self.end_point = None
|
||||
self.start_points = []
|
||||
self.split_points = []
|
||||
self.maxpath_points = []
|
||||
self.minpath_points = []
|
||||
self.end_points = []
|
||||
for rel in wall.ConnectedTo:
|
||||
if rel.is_a("IfcRelConnectsPathElements"):
|
||||
@@ -183,9 +273,6 @@ class Foo:
|
||||
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 = [
|
||||
@@ -202,18 +289,60 @@ class Foo:
|
||||
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))
|
||||
if self.start_points[0][1] > self.start_points[-1][1]: # Canonicalise to the +Y direction
|
||||
self.start_points.reverse()
|
||||
if self.end_points[0][1] > self.end_points[-1][1]: # Canonicalise to the +Y direction
|
||||
self.end_points.reverse()
|
||||
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(wall.file)
|
||||
item = builder.extrude(builder.polyline(points, closed=True), magnitude=1.0)
|
||||
# A wall footprint may be multiple profiles if the wall is split into two due to an ATPATH connection
|
||||
profiles = []
|
||||
split_points = sorted(self.split_points, key=lambda x: x[0][0]) # Sort islands in the +X direction
|
||||
split_points.insert(0, self.start_points)
|
||||
split_points.append(self.end_points)
|
||||
split_points = iter(split_points)
|
||||
|
||||
while True:
|
||||
# Draw each profile as clockwise starting from (minx, miny)
|
||||
start_split = next(split_points, None)
|
||||
if not start_split:
|
||||
break
|
||||
end_split = next(split_points, None)
|
||||
if not end_split:
|
||||
break
|
||||
maxy_minx = start_split[-1][0]
|
||||
maxy_maxx = end_split[-1][0]
|
||||
miny_minx = start_split[0][0]
|
||||
miny_maxx = end_split[0][0]
|
||||
# Do more defensive checks here
|
||||
points = start_split
|
||||
remaining_path_points = []
|
||||
for maxpath_points in self.maxpath_points:
|
||||
if maxpath_points[0][0] > maxy_minx and maxpath_points[-1][0] < maxy_maxx:
|
||||
points.extend(maxpath_points)
|
||||
else:
|
||||
remaining_path_points.append(maxpath_points)
|
||||
self.maxpath_points = remaining_path_points
|
||||
points.extend(end_split[::-1])
|
||||
remaining_path_points = []
|
||||
for minpath_points in self.minpath_points:
|
||||
if minpath_points[0][0] < miny_maxx and minpath_points[-1][0] > miny_minx:
|
||||
points.extend(minpath_points)
|
||||
else:
|
||||
remaining_path_points.append(minpath_points)
|
||||
self.minpath_points = remaining_path_points
|
||||
|
||||
profiles.append(builder.profile(builder.polyline(points, closed=True)))
|
||||
|
||||
for points in self.maxpath_points + self.minpath_points:
|
||||
profiles.append(builder.profile(builder.polyline(points, closed=True)))
|
||||
|
||||
if len(profiles) > 1:
|
||||
profile = wall.file.createIfcCompositeProfileDef("AREA", Profiles=profiles)
|
||||
else:
|
||||
profile = profiles[0]
|
||||
|
||||
item = builder.extrude(profile, magnitude=1.0)
|
||||
rep = builder.get_representation(self.body, items=[item])
|
||||
if old_rep := ifcopenshell.util.representation.get_representation(wall, self.body):
|
||||
ifcopenshell.util.element.replace_element(old_rep, rep)
|
||||
@@ -230,6 +359,8 @@ class Foo:
|
||||
def join(self, wall1, wall2, layers1, layers2, connection1, connection2):
|
||||
if connection1 == "NOTDEFINED" or connection2 == "NOTDEFINED":
|
||||
return
|
||||
if connection1 == "ATPATH" and connection2 == "ATPATH":
|
||||
return
|
||||
print("joining", wall1, layers1, connection1)
|
||||
print("to", wall2, layers2, connection2)
|
||||
|
||||
@@ -277,51 +408,130 @@ class Foo:
|
||||
print(axes1)
|
||||
print(axes2)
|
||||
# Checked
|
||||
if connection1 == "ATPATH":
|
||||
first_axis2 = axes2[0]
|
||||
last_axis2 = axes2[-1]
|
||||
first_y = axes1[0][0][1]
|
||||
last_y = axes1[-1][0][1]
|
||||
p0 = np.array((self.intersect_axis(*first_axis2, y=first_y), first_y))
|
||||
pN = np.array((self.intersect_axis(*last_axis2, y=first_y), first_y))
|
||||
|
||||
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:
|
||||
# Generate CurveOnRelating/RelatedElement
|
||||
points = [p0]
|
||||
axes2 = iter(axes2)
|
||||
axis2 = next(axes2)
|
||||
for layer2 in layers2:
|
||||
ys = iter([a[0][1] for a in axes1])
|
||||
y = next(ys)
|
||||
for layer1 in layers1:
|
||||
if layer2.priority <= layer1.priority:
|
||||
break
|
||||
y = next(ys)
|
||||
p1 = np.array((self.intersect_axis(*axis2, y=y), y))
|
||||
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)))
|
||||
p2 = np.array((self.intersect_axis(*axis2, y=y), y))
|
||||
if points and np.allclose(points[-1], p1):
|
||||
points[-1] = p2 # Just slide along previous point
|
||||
else:
|
||||
points.extend((p1, p2))
|
||||
|
||||
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
|
||||
self.reference_p1[0] = self.intersect_axis(*reference2, y=reference1[0][1])
|
||||
elif connection1 == "ATEND":
|
||||
self.end_points = points
|
||||
self.reference_p2[0] = self.intersect_axis(*reference2, y=reference1[0][1])
|
||||
# The curve must end at pN
|
||||
if not np.allclose(points[-1], pN):
|
||||
points.append(pN)
|
||||
|
||||
# Categorise our points into a segment that either splits or cuts the wall
|
||||
split_ys = {first_y, last_y}
|
||||
segment = []
|
||||
for point in points:
|
||||
segment.append(point)
|
||||
if len(segment) == 1: # Not enough points to categorise the segment
|
||||
continue
|
||||
elif {segment[0][1], segment[-1][1]} == split_ys: # This segment splits the wall
|
||||
if segment[0][1] > segment[-1][1]: # Go in the +Y direction
|
||||
segment.reverse()
|
||||
self.split_points.append(segment)
|
||||
segment = []
|
||||
elif segment[0][1] == segment[-1][1]: # This segment cuts some of the wall
|
||||
if segment[0][1] == self.maxy: # Go in the +X direction
|
||||
if segment[0][0] > segment[-1][0]:
|
||||
segment.reverse()
|
||||
self.maxpath_points.append(segment)
|
||||
elif segment[0][1] == self.miny: # Go in the -X direction
|
||||
if segment[-1][0] > segment[0][0]:
|
||||
segment.reverse()
|
||||
self.minpath_points.append(segment)
|
||||
segment = []
|
||||
elif connection2 == "ATPATH":
|
||||
points = []
|
||||
ys = iter([a[0][1] for a in axes1])
|
||||
y = next(ys)
|
||||
for layer1 in layers1:
|
||||
axes2_iter = iter(axes2)
|
||||
axis2 = next(axes2_iter)
|
||||
for layer2 in layers2:
|
||||
if layer1.priority <= layer2.priority:
|
||||
break
|
||||
axis2 = next(axes2_iter)
|
||||
x = self.intersect_axis(*axis2, y=y)
|
||||
p1 = np.array((x, y))
|
||||
y = next(ys)
|
||||
x = self.intersect_axis(*axis2, y=y)
|
||||
p2 = np.array((x, y))
|
||||
if points and np.allclose(points[-1], p1):
|
||||
points.append(p2)
|
||||
else:
|
||||
points.extend((p1, p2))
|
||||
|
||||
if connection1 == "ATSTART":
|
||||
self.start_points = points
|
||||
self.reference_p1[0] = self.intersect_axis(*reference2, y=reference1[0][1])
|
||||
elif connection1 == "ATEND":
|
||||
self.end_points = points
|
||||
self.reference_p2[0] = self.intersect_axis(*reference2, y=reference1[0][1])
|
||||
else:
|
||||
last_y = axes1[-1][0][1]
|
||||
ys = iter([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))]
|
||||
|
||||
layers1 = iter(layers1)
|
||||
layers2 = iter(layers2)
|
||||
layer1 = next(layers1, None)
|
||||
layer2 = next(layers2, None)
|
||||
|
||||
# This creates "mitering" behaviour which is an ambiguity by bSI.
|
||||
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)))
|
||||
|
||||
if connection1 == "ATSTART":
|
||||
self.start_points = points
|
||||
self.reference_p1[0] = self.intersect_axis(*reference2, y=reference1[0][1])
|
||||
elif connection1 == "ATEND":
|
||||
self.end_points = points
|
||||
self.reference_p2[0] = self.intersect_axis(*reference2, y=reference1[0][1])
|
||||
|
||||
def get_layers(self, wall) -> list:
|
||||
material = ifcopenshell.util.element.get_material(wall, should_skip_usage=True)
|
||||
@@ -387,6 +597,7 @@ 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)
|
||||
test_atpath(7)
|
||||
|
||||
|
||||
f.write("/home/dion/wall.ifc")
|
||||
|
||||
Reference in New Issue
Block a user