diff --git a/src/bonsai/bonsai/bim/module/model/wall.py b/src/bonsai/bonsai/bim/module/model/wall.py index 136d09bfd8..ac44904e82 100644 --- a/src/bonsai/bonsai/bim/module/model/wall.py +++ b/src/bonsai/bonsai/bim/module/model/wall.py @@ -1008,7 +1008,6 @@ class DumbWallJoiner: ifcopenshell.api.geometry.edit_object_placement( tool.Ifc.get(), product=element1, matrix=matrix, is_si=False, should_transform_children=False ) - self.import_position(element1, wall1) self.recreate_wall(element1, wall1) def merge(self, wall1, wall2): @@ -1111,13 +1110,6 @@ class DumbWallJoiner: else: ifcopenshell.api.geometry.assign_representation(self.file, product=wall, representation=rep) - def import_position(self, element, obj): - unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) - matrix = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement) - matrix[:, 3] *= unit_scale - obj.matrix_world = tool.Loader.apply_blender_offset_to_matrix_world(obj, matrix) - tool.Geometry.record_object_position(obj) - def join_E(self, wall1, target): if tool.Ifc.is_moved(wall1): bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=wall1) @@ -1198,6 +1190,12 @@ class DumbWallJoiner: should_sync_changes_first=False, ) tool.Geometry.record_object_materials(obj) + + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + matrix = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement) + matrix[:, 3] *= unit_scale + obj.matrix_world = tool.Loader.apply_blender_offset_to_matrix_world(obj, matrix) + tool.Geometry.record_object_position(obj) return wall_moved = tool.Ifc.is_moved(obj) diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py index f5a52f13a4..11d53364b3 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py @@ -34,6 +34,27 @@ def edit_object_placement( is_si: bool = True, should_transform_children: bool = False, ) -> ifcopenshell.entity_instance: + """Changes the object placement matrix of an element + + The placement matrix is a 4x4 matrix describing the location and + orientation of an element in 3D. See + https://docs.ifcopenshell.org/ifcopenshell-python/geometry_creation.html#object-placements + for more details. + + This only supports local placements. Grid and linear placements are not + supported. + + :param matrix: A 4x4 matrix in numpy. If left blank, it is the identity + matrix (equivalent to ``np.eye(4)``). + :param is_si: If True, the matrix is given in SI units. If false, in + project units. + :param should_transform_children: A child element is a nested element, + opening, filling, etc. If true, child elements will move along with the + parent. If false, child elements will stay where they are. Because most + placements in IFC are relative, this means that if a child moves, we + actually don't change their placement. + :return: The new or updated IfcLocalPlacement entity + """ usecase = Usecase() usecase.file = file usecase.settings = { 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 c3f826c219..4702912b0e 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/regenerate_wall_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/regenerate_wall_representation.py @@ -19,9 +19,10 @@ import numpy as np import ifcopenshell import ifcopenshell.api.geometry -import ifcopenshell.util.shape_builder -import ifcopenshell.util.element import ifcopenshell.util.unit +import ifcopenshell.util.element +import ifcopenshell.util.placement +import ifcopenshell.util.shape_builder from collections import namedtuple from math import sin, cos from typing import Optional @@ -63,6 +64,14 @@ def regenerate_wall_representation( additional extrusions are generated for each connection that boolean difference the base extrusion. + This will also update the axis line representation (e.g. trim the axis line + to any connections). + + The wall's object placement will also be updated such that the placement is + equivalent to the axis line's start point (which therefore becomes (0.0, + 0.0)). This is a logical, consistent, and useful placement coordinate + (especially for apps that can pivot using this point). + :param wall: The IfcWall for the representation, only Model/Body/MODEL_VIEW type of representations are currently supported. :param length: If the wall doesn't have an axis length, this is the default @@ -165,7 +174,7 @@ class Regenerator: end_points.reverse() points.extend(end_points) item = builder.extrude( - builder.polyline(points, closed=True), + builder.polyline(points, closed=True, position_offset=self.reference_p1 * -1), magnitude=self.wall_vectors["d"], extrusion_vector=self.wall_vectors["z"], ) @@ -186,7 +195,9 @@ class Regenerator: magnitude = np.linalg.norm(self.start_vector * (self.wall_vectors["h"] / self.start_vector[2])) operands.append( builder.extrude( - builder.polyline(points, closed=True), magnitude=magnitude, extrusion_vector=self.start_vector + builder.polyline(points, closed=True, position_offset=self.reference_p1 * -1), + magnitude=magnitude, + extrusion_vector=self.start_vector, ) ) @@ -206,7 +217,9 @@ class Regenerator: magnitude = np.linalg.norm(self.end_vector * (self.wall_vectors["h"] / self.end_vector[2])) operands.append( builder.extrude( - builder.polyline(points, closed=True), magnitude=magnitude, extrusion_vector=self.end_vector + builder.polyline(points, closed=True, position_offset=self.reference_p1 * -1), + magnitude=magnitude, + extrusion_vector=self.end_vector, ) ) @@ -216,7 +229,9 @@ class Regenerator: magnitude = np.linalg.norm(atpath_vector * (self.wall_vectors["h"] / atpath_vector[2])) operands.append( builder.extrude( - builder.polyline(points, closed=True), magnitude=magnitude, extrusion_vector=atpath_vector + builder.polyline(points, closed=True, position_offset=self.reference_p1 * -1), + magnitude=magnitude, + extrusion_vector=atpath_vector, ) ) @@ -265,10 +280,14 @@ class Regenerator: remaining_path_points.append(minpath_points) self.minpath_points = remaining_path_points - profiles.append(builder.profile(builder.polyline(points, closed=True))) + profiles.append( + builder.profile(builder.polyline(points, closed=True, position_offset=self.reference_p1 * -1)) + ) for points in self.maxpath_points + self.minpath_points: - profiles.append(builder.profile(builder.polyline(points, closed=True))) + profiles.append( + builder.profile(builder.polyline(points, closed=True, position_offset=self.reference_p1 * -1)) + ) if len(profiles) > 1: profile = self.file.createIfcCompositeProfileDef("AREA", Profiles=profiles) @@ -283,13 +302,21 @@ class Regenerator: else: ifcopenshell.api.geometry.assign_representation(self.file, product=wall, representation=body_rep) - item = builder.polyline([self.reference_p1, self.reference_p2]) + item = builder.polyline([self.reference_p1, self.reference_p2], position_offset=self.reference_p1 * -1) axis_rep = builder.get_representation(self.axis, items=[item]) if old_rep := ifcopenshell.util.representation.get_representation(wall, self.axis): ifcopenshell.util.element.replace_element(old_rep, axis_rep) ifcopenshell.util.element.remove_deep2(self.file, old_rep) else: ifcopenshell.api.geometry.assign_representation(self.file, product=wall, representation=axis_rep) + + if not np.allclose(self.reference_p1, np.array((0.0, 0.0))): + matrix = ifcopenshell.util.placement.get_local_placement(wall.ObjectPlacement) + matrix[:, 3] = matrix @ np.concatenate((self.reference_p1, (0, 1))) + ifcopenshell.api.geometry.edit_object_placement( + self.file, product=wall, matrix=matrix, is_si=False, should_transform_children=False + ) + return body_rep def join(self, wall1, wall2, layers1, layers2, connection1, connection2):