From 0ef255bc3c5473ee6d6351798c75188aecf13aa8 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 22 Jul 2025 14:18:55 +0500 Subject: [PATCH] Fix linking project missing offsets for instances of complex geometry After ba2456a if main IFC project had an offset and linked object had more than 333 faces, then it was imported at it's original location instead, missing an offset. Also processing occurrences should be now more optimized, since we reuse the same verts buffer instead of recreating it 10 times. --- .../bonsai/bim/module/project/operator.py | 36 +++++++------------ .../ifcopenshell/util/shape_builder.py | 14 ++++++++ 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index 1851ab7269..9571337ad2 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -41,6 +41,7 @@ import ifcopenshell.util.representation import ifcopenshell.util.element import ifcopenshell.util.representation import ifcopenshell.util.shape +import ifcopenshell.util.shape_builder import ifcopenshell.util.unit import bonsai.bim.handler import bonsai.bim.helper @@ -1888,42 +1889,32 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper): def process_occurrence(self, shape: W.TriangulationElement) -> None: element = self.file.by_id(shape.id) - faces = shape.geometry.faces - verts = shape.geometry.verts - materials = shape.geometry.materials - material_ids = shape.geometry.material_ids mat = ifcopenshell.util.shape.get_shape_matrix(shape) - mesh = self.meshes.get(shape.geometry.id, None) + geometry = shape.geometry + mesh = self.meshes.get(geometry.id, None) if not mesh: - mesh = bpy.data.meshes.new("Mesh") + verts = ifcopenshell.util.shape.get_vertices(geometry) + material_ids = geometry.material_ids + mesh = tool.Loader.create_mesh_from_shape(geometry, mesh) - geometry = shape.geometry gprops = tool.Georeference.get_georeference_props() - if ( - gprops.has_blender_offset - and geometry.verts - and tool.Loader.is_point_far_away( - (geometry.verts[0], geometry.verts[1], geometry.verts[2]), is_meters=True - ) - ): + if gprops.has_blender_offset and verts.size and tool.Loader.is_point_far_away(verts[0], is_meters=True): + vert: np.ndarray = verts[0] # Shift geometry close to the origin based off that first vert it found - verts_array = np.array(geometry.verts) - offset = np.array([-geometry.verts[0], -geometry.verts[1], -geometry.verts[2]]) - offset_verts = verts_array + np.tile(offset, len(verts_array) // 3) - verts = offset_verts.tolist() + offset = ifcopenshell.util.shape_builder.np_translation_matrix(-vert) + mat = offset @ mat mesh["has_cartesian_point_offset"] = True - mesh["cartesian_point_offset"] = f"{geometry.verts[0]},{geometry.verts[1]},{geometry.verts[2]}" + mesh["cartesian_point_offset"] = ",".join(vert.astype(str)) else: - verts = geometry.verts mesh["has_cartesian_point_offset"] = False material_to_slot: dict[int, int] = {} max_slot_index = 0 - for i, material in enumerate(materials): + for i, material in enumerate(geometry.materials): alpha = 1.0 if material.has_transparency and material.transparency > 0: alpha = 1.0 - material.transparency @@ -1946,11 +1937,10 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper): material_index = np.array([(material_to_slot[i] if i != -1 else 0) for i in material_ids], dtype="I") - mesh = tool.Loader.create_mesh_from_shape(geometry, mesh) mesh.polygons.foreach_set("material_index", material_index) mesh.update() - self.meshes[shape.geometry.id] = mesh + self.meshes[geometry.id] = mesh obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh) obj.matrix_world = tool.Loader.apply_blender_offset_to_matrix_world(obj, mat) diff --git a/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py b/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py index 8218efe725..fa058834e4 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py +++ b/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py @@ -157,6 +157,20 @@ def np_angle_signed(a: VectorType, b: VectorType) -> float: return np.arctan2(det, dot) +def np_translation_matrix(vector: VectorType) -> npt.NDArray[np.float64]: + """Get translation matrix. + + Designed to be similar to mathutils Matrix.Rotation but to use numpy. + + :param vector: 3D translation vector. + :return: An 4x4 identity matrix with a translation + """ + eye = np.eye(4, dtype=np.float64) + M_TRANSLATION = (slice(0, 3), 3) + eye[M_TRANSLATION] = vector + return eye + + def np_rotation_matrix( angle: float, size: int, axis: Optional[Union[Literal["X", "Y", "Z"], VectorType]] = None ) -> np.ndarray: