diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/__init__.py index 3dea99d8d2..0f17323634 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/__init__.py @@ -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", diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_wall.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_wall.py new file mode 100644 index 0000000000..a84d70fff8 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_wall.py @@ -0,0 +1,63 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2025 Dion Moult +# +# 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 . + +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, + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/regenerate_wall_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/regenerate_wall_representation.py index c5ffbe5998..c3f826c219 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/regenerate_wall_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/regenerate_wall_representation.py @@ -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 diff --git a/src/ifcopenshell-python/ifcopenshell/util/representation.py b/src/ifcopenshell-python/ifcopenshell/util/representation.py index b032501a20..1b1423cf1f 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/representation.py +++ b/src/ifcopenshell-python/ifcopenshell/util/representation.py @@ -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))]