diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 5f25a89bf7..c0898720b2 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -542,42 +542,6 @@ class IfcImporter: return tool.Loader.set_manual_blender_offset(self.file) return tool.Loader.guess_false_origin(self.file) - def apply_blender_offset_to_matrix_world(self, obj: bpy.types.Object, matrix: np.ndarray) -> mathutils.Matrix: - props = bpy.context.scene.BIMGeoreferenceProperties - if props.has_blender_offset: - if ( - not obj.data - and tool.Cad.is_x(matrix[0][3], 0) - and tool.Cad.is_x(matrix[1][3], 0) - and tool.Cad.is_x(matrix[2][3], 0) - ): - # We assume any non-geometric matrix at 0,0,0 is not - # positionally significant and is left alone. This handles - # scenarios where often spatial elements are left at 0,0,0 and - # everything else is at map coordinates. - obj.BIMObjectProperties.blender_offset_type = "NOT_APPLICABLE" - return mathutils.Matrix(matrix.tolist()) - elif obj.data and obj.data.get("has_cartesian_point_offset", None): - obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT" - if cartesian_point_offset := obj.data.get("cartesian_point_offset", None): - obj.BIMObjectProperties.cartesian_point_offset = cartesian_point_offset - offset_x, offset_y, offset_z = map(float, cartesian_point_offset.split(",")) - matrix[0][3] += offset_x - matrix[1][3] += offset_y - matrix[2][3] += offset_z - else: - obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT" - matrix = ifcopenshell.util.geolocation.global2local( - matrix, - float(props.blender_offset_x) * self.unit_scale, - float(props.blender_offset_y) * self.unit_scale, - float(props.blender_offset_z) * self.unit_scale, - float(props.blender_x_axis_abscissa), - float(props.blender_x_axis_ordinate), - ) - - return mathutils.Matrix(matrix.tolist()) - def create_grids(self): if not self.ifc_import_settings.should_load_geometry: return @@ -609,7 +573,7 @@ class IfcImporter: obj.lock_location = (True, True, True) obj.lock_rotation = (True, True, True) self.link_element(axis, obj) - self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, grid_placement.copy())) + self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, grid_placement.copy())) def create_element_types(self): for element_type in self.element_types: @@ -835,7 +799,7 @@ class IfcImporter: mesh.from_pydata([mathutils.Vector(vertex) * self.unit_scale], [], []) obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh) - self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, placement_matrix)) + self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, placement_matrix)) self.link_element(product, obj) def get_pointcloud_representation(self, product): @@ -901,7 +865,7 @@ class IfcImporter: tool.Ifc.link(representation, mesh) obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh) - self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, placement_matrix)) + self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, placement_matrix)) self.link_element(product, obj) return product @@ -942,13 +906,17 @@ class IfcImporter: if shape: # We use numpy here because Blender mathutils.Matrix is not accurate enough mat = np.array(shape.transformation.matrix).reshape((4, 4), order="F") - self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, mat)) + self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, mat)) self.material_creator.create(element, obj, mesh) elif mesh: - self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))) + self.set_matrix_world( + obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element)) + ) self.material_creator.create(element, obj, mesh) elif hasattr(element, "ObjectPlacement"): - self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))) + self.set_matrix_world( + obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element)) + ) return obj diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index 6760beec13..dce7b02dba 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -1326,7 +1326,8 @@ class LoadLinkedProject(bpy.types.Operator): shape = iterator.get() results.add(self.file.by_id(shape.id)) - if len(shape.geometry.faces) > 1000 and self.is_local(shape): # 333 tris + # Elements with a lot of geometry benefit from instancing to save memory + if len(shape.geometry.faces) > 1000: # 333 tris self.process_occurrence(shape) if not iterator.next(): if not chunked_verts: @@ -1373,10 +1374,19 @@ class LoadLinkedProject(bpy.types.Operator): chunked_material_ids.append(mi + material_offset + 1) material_offset += len(ms) - M4 = np.frombuffer(shape.transformation_buffer).reshape((4, 4), order="F") + matrix = np.frombuffer(shape.transformation_buffer).reshape((4, 4), order="F") + if gprops.has_blender_offset: + matrix = ifcopenshell.util.geolocation.global2local( + matrix, + float(gprops.blender_offset_x) * self.unit_scale, + float(gprops.blender_offset_y) * self.unit_scale, + float(gprops.blender_offset_z) * self.unit_scale, + float(gprops.blender_x_axis_abscissa), + float(gprops.blender_x_axis_ordinate), + ) vs = np.frombuffer(shape.geometry.verts_buffer).reshape((-1, 3)) vs = np.hstack((vs, np.ones((len(vs), 1)))) - vs = (np.asmatrix(M4) * np.asmatrix(vs).T).T.A + vs = (np.asmatrix(matrix) * np.asmatrix(vs).T).T.A vs = vs[:, :3].flatten() fs = np.frombuffer(shape.geometry.faces_buffer, dtype=np.int32) chunked_verts.append(vs) @@ -1457,14 +1467,6 @@ class LoadLinkedProject(bpy.types.Operator): print("Finished", time.time() - start) return {"FINISHED"} - def is_local(self, shape: ShapeElementType) -> bool: - m = shape.transformation.matrix - if max([abs(co) for co in (m[9], m[10], m[11])]) > 1000: - return False - elif max([abs(co) for co in shape.geometry.verts[0:3]]) > 1000: - return False - return True - def process_occurrence(self, shape: ShapeElementType) -> None: element = self.file.by_id(shape.id) faces = shape.geometry.faces @@ -1478,6 +1480,27 @@ class LoadLinkedProject(bpy.types.Operator): if not mesh: mesh = bpy.data.meshes.new("Mesh") + geometry = shape.geometry + gprops = bpy.context.scene.BIMGeoreferenceProperties + 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 + ) + ): + # 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() + + mesh["has_cartesian_point_offset"] = True + mesh["cartesian_point_offset"] = f"{geometry.verts[0]},{geometry.verts[1]},{geometry.verts[2]}" + else: + verts = geometry.verts + mesh["has_cartesian_point_offset"] = False + material_to_slot = {} max_slot_index = 0 @@ -1525,7 +1548,7 @@ class LoadLinkedProject(bpy.types.Operator): self.meshes[shape.geometry.id] = mesh obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh) - obj.matrix_world = Matrix(mat.tolist()) + obj.matrix_world = tool.Loader.apply_blender_offset_to_matrix_world(obj, mat) obj["guids"] = [shape.guid] obj["guid_ids"] = [len(mesh.polygons)] diff --git a/src/blenderbim/blenderbim/tool/loader.py b/src/blenderbim/blenderbim/tool/loader.py index 2d21db661e..5ddcd7a2d3 100644 --- a/src/blenderbim/blenderbim/tool/loader.py +++ b/src/blenderbim/blenderbim/tool/loader.py @@ -26,7 +26,7 @@ import blenderbim.core.tool import blenderbim.tool as tool import numpy as np import numpy.typing as npt -from mathutils import Vector +from mathutils import Vector, Matrix from pathlib import Path from typing import Union @@ -572,7 +572,9 @@ class Loader(blenderbim.core.tool.Loader): props.has_blender_offset = True @classmethod - def guess_false_origin_and_project_north(cls, ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> None: + def guess_false_origin_and_project_north( + cls, ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance + ) -> None: if not element.ObjectPlacement or not element.ObjectPlacement.is_a("IfcLocalPlacement"): return placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement) @@ -661,3 +663,41 @@ class Loader(blenderbim.core.tool.Loader): if building and cls.is_element_far_away(building): return cls.guess_false_origin_and_project_north(ifc_file, building) return cls.guess_false_origin_from_elements(ifc_file) + + @classmethod + def apply_blender_offset_to_matrix_world(cls, obj: bpy.types.Object, matrix: np.ndarray) -> Matrix: + props = bpy.context.scene.BIMGeoreferenceProperties + if props.has_blender_offset: + if ( + not obj.data + and tool.Cad.is_x(matrix[0][3], 0) + and tool.Cad.is_x(matrix[1][3], 0) + and tool.Cad.is_x(matrix[2][3], 0) + ): + # We assume any non-geometric matrix at 0,0,0 is not + # positionally significant and is left alone. This handles + # scenarios where often spatial elements are left at 0,0,0 and + # everything else is at map coordinates. + obj.BIMObjectProperties.blender_offset_type = "NOT_APPLICABLE" + return Matrix(matrix.tolist()) + elif obj.data and obj.data.get("has_cartesian_point_offset", None): + obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT" + if cartesian_point_offset := obj.data.get("cartesian_point_offset", None): + obj.BIMObjectProperties.cartesian_point_offset = cartesian_point_offset + offset_xyz = list(map(float, cartesian_point_offset.split(","))) + [1.0] + offset_xyz = matrix @ offset_xyz + matrix[0][3] = offset_xyz[0] + matrix[1][3] = offset_xyz[1] + matrix[2][3] = offset_xyz[2] + else: + obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT" + matrix = ifcopenshell.util.geolocation.global2local( + matrix, + float(props.blender_offset_x) * cls.unit_scale, + float(props.blender_offset_y) * cls.unit_scale, + float(props.blender_offset_z) * cls.unit_scale, + float(props.blender_x_axis_abscissa), + float(props.blender_x_axis_ordinate), + ) + + return Matrix(matrix.tolist())