From 6f55cd516989ec15d8e44aa4da3ca3d78348cf11 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 11 Nov 2021 17:31:16 +1100 Subject: [PATCH] Optimise object placement change checking, so medium sized project exports are 5 seconds faster at minimum, but can be much higher since false positives are culled. --- src/blenderbim/blenderbim/bim/export_ifc.py | 31 ++++++++++----------- src/blenderbim/blenderbim/bim/import_ifc.py | 17 +++++++---- src/blenderbim/blenderbim/bim/prop.py | 5 ++-- src/blenderbim/blenderbim/core/geometry.py | 1 + src/blenderbim/blenderbim/core/tool.py | 1 + src/blenderbim/blenderbim/tool/geometry.py | 7 +++++ src/blenderbim/test/core/test_geometry.py | 1 + src/blenderbim/test/tool/test_geometry.py | 10 ++++++- 8 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/export_ifc.py b/src/blenderbim/blenderbim/bim/export_ifc.py index 6d78d53402..4c6604b134 100644 --- a/src/blenderbim/blenderbim/bim/export_ifc.py +++ b/src/blenderbim/blenderbim/bim/export_ifc.py @@ -132,6 +132,8 @@ class IfcExporter: return results def sync_object_placement(self, obj): + if not self.has_object_moved(obj): + return blender_matrix = np.array(obj.matrix_world) element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) if (obj.scale - Vector((1.0, 1.0, 1.0))).length > 1e-4: @@ -141,24 +143,19 @@ class IfcExporter: return self.sync_grid_axis_object_placement(obj, element) if not hasattr(element, "ObjectPlacement"): return - ifc_matrix = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement) - ifc_matrix[0][3] *= self.unit_scale - ifc_matrix[1][3] *= self.unit_scale - ifc_matrix[2][3] *= self.unit_scale + blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj) + return element - props = bpy.context.scene.BIMGeoreferenceProperties - if props.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "OBJECT_PLACEMENT": - ifc_matrix = ifcopenshell.util.geolocation.global2local( - ifc_matrix, - float(props.blender_eastings) * self.unit_scale, - float(props.blender_northings) * self.unit_scale, - float(props.blender_orthogonal_height) * self.unit_scale, - float(props.blender_x_axis_abscissa), - float(props.blender_x_axis_ordinate), - ) - if not np.allclose(ifc_matrix, blender_matrix, atol=0.0001): - blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj) - return element + def has_object_moved(self, obj): + if not obj.BIMObjectProperties.location_checksum: + return True # Let's be conservative + loc_check = np.frombuffer(eval(obj.BIMObjectProperties.location_checksum)) + rot_check = np.frombuffer(eval(obj.BIMObjectProperties.rotation_checksum)) + loc_real = np.array(obj.matrix_world.translation).flatten() + rot_real = np.array(obj.matrix_world.to_3x3()).flatten() + if np.allclose(loc_check, loc_real, atol=1e-4) and np.allclose(rot_check, rot_real, atol=1e-2): + return False + return True def sync_grid_axis_object_placement(self, obj, element): grid = (element.PartOfU or element.PartOfV or element.PartOfW)[0] diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 8c9f181bde..3592e11846 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -31,6 +31,7 @@ import ifcopenshell.util.unit import ifcopenshell.util.element import ifcopenshell.util.selector import ifcopenshell.util.geolocation +import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.drawing.prop import get_diagram_scales @@ -509,7 +510,7 @@ class IfcImporter: mesh = self.create_mesh(axis, shape) obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh) self.link_element(axis, obj) - obj.matrix_world = grid_obj.matrix_world + self.set_matrix_world(obj, grid_obj.matrix_world) grid_collection.objects.link(obj) def create_type_products(self): @@ -700,7 +701,7 @@ class IfcImporter: mesh.from_pydata([mathutils.Vector(vertex) * self.unit_scale], [], []) obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh) - obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, placement_matrix) + self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, placement_matrix)) self.link_element(product, obj) def get_pointcloud_representation(self, product): @@ -759,7 +760,7 @@ class IfcImporter: mesh.from_pydata(vertex_list, [], []) obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh) - obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, placement_matrix) + self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, placement_matrix)) self.link_element(product, obj) return product @@ -826,13 +827,13 @@ class IfcImporter: mat = np.array( ([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1]) ) - obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, mat) + self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, mat)) self.material_creator.create(element, obj, mesh) elif mesh: - obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element)) + self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))) self.material_creator.create(element, obj, mesh) elif hasattr(element, "ObjectPlacement"): - obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element)) + self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))) self.add_opening_relation(element, obj) @@ -1518,6 +1519,10 @@ class IfcImporter: else: mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id) + def set_matrix_world(self, obj, matrix_world): + obj.matrix_world = matrix_world + tool.Geometry.record_object_position(obj) + class IfcImportSettings: def __init__(self): diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index cc12751c03..abc94b8a21 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -318,9 +318,8 @@ class BIMObjectProperties(PropertyGroup): default="NONE", ) is_reassigning_class: BoolProperty(name="Is Reassigning Class") - global_ids: CollectionProperty(name="GlobalIds", type=GlobalId) - psets: CollectionProperty(name="Psets", type=PsetQto) - qtos: CollectionProperty(name="Qtos", type=PsetQto) + location_checksum: StringProperty(name="Location Checksum") + rotation_checksum: StringProperty(name="Rotation Checksum") class BIMMaterialProperties(PropertyGroup): diff --git a/src/blenderbim/blenderbim/core/geometry.py b/src/blenderbim/blenderbim/core/geometry.py index 1d9074c0af..ce72664a04 100644 --- a/src/blenderbim/blenderbim/core/geometry.py +++ b/src/blenderbim/blenderbim/core/geometry.py @@ -25,6 +25,7 @@ def edit_object_placement(ifc, geometry, surveyor, obj=None): geometry.clear_cache(element) geometry.clear_scale(obj) ifc.run("geometry.edit_object_placement", product=element, matrix=surveyor.get_absolute_matrix(obj)) + geometry.record_object_position(obj) def add_representation( diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 2cde394cf0..89191f0ba1 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -100,6 +100,7 @@ class Geometry: def is_mapped_representation(cls, representation): pass def is_type_product(cls, element): pass def link(cls, element, obj): pass + def record_object_position(cls, obj): pass def rename_object(cls, obj, name): pass def replace_object_with_empty(cls, obj): pass def resolve_mapped_representation(cls, representation): pass diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index f7fd10b648..addac8bed8 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -18,6 +18,7 @@ import bpy import logging +import numpy as np import ifcopenshell import blenderbim.core.tool import blenderbim.core.style @@ -223,6 +224,12 @@ class Geometry(blenderbim.core.tool.Geometry): def link(cls, element, obj): obj.BIMMeshProperties.ifc_definition_id = element.id() + @classmethod + def record_object_position(cls, obj): + # These are recorded separately because they have different numerical tolerances + obj.BIMObjectProperties.location_checksum = repr(np.array(obj.matrix_world.translation).tobytes()) + obj.BIMObjectProperties.rotation_checksum = repr(np.array(obj.matrix_world.to_3x3()).tobytes()) + @classmethod def rename_object(cls, obj, name): obj.name = name diff --git a/src/blenderbim/test/core/test_geometry.py b/src/blenderbim/test/core/test_geometry.py index ab565b3b7b..68a6e480ab 100644 --- a/src/blenderbim/test/core/test_geometry.py +++ b/src/blenderbim/test/core/test_geometry.py @@ -28,6 +28,7 @@ class TestEditObjectPlacement: geometry.clear_scale("obj").should_be_called() surveyor.get_absolute_matrix("obj").should_be_called().will_return("matrix") ifc.run("geometry.edit_object_placement", product="element", matrix="matrix").should_be_called() + geometry.record_object_position("obj").should_be_called() def test_run(self, ifc, geometry, surveyor): self.predict(ifc, geometry, surveyor) diff --git a/src/blenderbim/test/tool/test_geometry.py b/src/blenderbim/test/tool/test_geometry.py index e928ec0abf..249a353cf8 100644 --- a/src/blenderbim/test/tool/test_geometry.py +++ b/src/blenderbim/test/tool/test_geometry.py @@ -18,7 +18,7 @@ import bpy import math -import numpy +import numpy as np import ifcopenshell import blenderbim.core.tool import blenderbim.tool as tool @@ -352,6 +352,14 @@ class TestLink(NewFile): assert obj.BIMMeshProperties.ifc_definition_id == element.id() +class TestRecordObjectPosition(NewFile): + def test_run(self): + obj = bpy.data.objects.new("Object", None) + subject.record_object_position(obj) + assert obj.BIMObjectProperties.location_checksum == repr(np.array(obj.matrix_world.translation).tobytes()) + assert obj.BIMObjectProperties.rotation_checksum == repr(np.array(obj.matrix_world.to_3x3()).tobytes()) + + class TestRenameObject(NewFile): def test_run(self): obj = bpy.data.meshes.new("Mesh")