See #1227. New connect wall API function since this can be done generically.

This will supersede the butt/mitre join functions we used to have.
This commit is contained in:
Dion Moult
2025-03-08 11:25:07 +11:00
parent 8c9a657a8c
commit 01ec750814
4 changed files with 88 additions and 18 deletions
@@ -43,6 +43,7 @@ from .add_window_representation import add_window_representation
from .assign_representation import assign_representation
from .connect_element import connect_element
from .connect_path import connect_path
from .connect_wall import connect_wall
from .create_2pt_wall import create_2pt_wall
from .disconnect_element import disconnect_element
from .disconnect_path import disconnect_path
@@ -72,6 +73,7 @@ __all__ = [
"assign_representation",
"connect_element",
"connect_path",
"connect_wall",
"create_2pt_wall",
"disconnect_element",
"disconnect_path",
@@ -0,0 +1,63 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import numpy as np
import ifcopenshell
import ifcopenshell.api.owner
import ifcopenshell.guid
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
from typing import Optional
def connect_wall(
file: ifcopenshell.file,
wall1: ifcopenshell.entity_instance,
wall2: ifcopenshell.entity_instance,
is_atpath: bool = False,
) -> Optional[ifcopenshell.entity_instance]:
matrix1i = np.linalg.inv(ifcopenshell.util.placement.get_local_placement(wall1.ObjectPlacement))
matrix2 = ifcopenshell.util.placement.get_local_placement(wall2.ObjectPlacement)
axis1 = ifcopenshell.util.representation.get_reference_line(wall1)
axis2 = ifcopenshell.util.representation.get_reference_line(wall2)
axis2[0] = (matrix1i @ matrix2 @ np.concatenate((axis2[0], (0, 1))))[:2]
axis2[1] = (matrix1i @ matrix2 @ np.concatenate((axis2[1], (0, 1))))[:2]
midx = (axis1[0][0] + axis1[1][0]) / 2
starty = axis2[0][1]
endy = axis2[1][1]
y = axis1[0][1]
if (x := ifcopenshell.util.shape_builder.intersect_x_axis_2d(*axis2, y=y)) is None:
return
wall1_end = "ATEND" if x > midx else "ATSTART"
if is_atpath:
wall2_end = "ATPATH"
elif abs(y - starty) < abs(y - endy):
wall2_end = "ATSTART"
else:
wall2_end = "ATEND"
return ifcopenshell.api.geometry.connect_path(
file,
relating_element=wall1,
related_element=wall2,
relating_connection=wall1_end,
related_connection=wall2_end,
)
@@ -93,7 +93,7 @@ class Regenerator:
layers = self.get_layers(wall)
if not layers:
return
reference = self.get_reference_line(wall)
reference = ifcopenshell.util.representation.get_reference_line(wall, self.fallback_length)
self.reference_p1, self.reference_p2 = reference
self.wall_vectors = self.get_wall_vectors(wall)
axes = self.get_axes(wall, reference, layers, self.wall_vectors["a"])
@@ -300,8 +300,8 @@ class Regenerator:
print("joining", wall1, layers1, connection1)
print("to", wall2, layers2, connection2)
reference1 = self.get_reference_line(wall1)
reference2 = self.get_reference_line(wall2)
reference1 = ifcopenshell.util.representation.get_reference_line(wall1, self.fallback_length)
reference2 = ifcopenshell.util.representation.get_reference_line(wall2, self.fallback_length)
wall_vectors2 = self.get_wall_vectors(wall2)
axes1 = self.get_axes(wall1, reference1, layers1, self.wall_vectors["a"])
axes2 = self.get_axes(wall2, reference2, layers2, wall_vectors2["a"])
@@ -516,20 +516,6 @@ class Regenerator:
results.append(layer)
return results
def get_reference_line(self, wall):
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
return [np.array(points[0]), np.array(points[1])]
return [np.array(points[1]), np.array(points[0])]
return [np.array((0.0, 0.0)), np.array((self.fallback_length, 0.0))]
def get_wall_vectors(self, wall):
if body := ifcopenshell.util.representation.get_representation(wall, "Model", "Body", "MODEL_VIEW"):
for item in ifcopenshell.util.representation.resolve_representation(body).Items:
@@ -572,7 +558,7 @@ class Regenerator:
axes = [[p.copy() for p in reference]]
# 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"):
if (usage := ifcopenshell.util.element.get_material(wall)) and usage.is_a("IfcMaterialLayerSetUsage"):
for point in axes[0]:
point[1] += usage.OffsetFromReferenceLine
sense_factor = 1 if usage.DirectionSense == "POSITIVE" else -1
@@ -461,3 +461,22 @@ def get_material_style(
for style in item.Styles:
if style.is_a(ifc_class):
return style
def get_reference_line(wall: ifcopenshell.entity_instance, fallback_length: float = 1.0):
"""Fetch the reference axis that goes in the +X direction
:param wall: ifcopenshell.entity_instance
"""
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
return [np.array(points[0]), np.array(points[1])]
return [np.array(points[1]), np.array(points[0])]
return [np.array((0.0, 0.0)), np.array((fallback_length, 0.0))]