From 2ec3c4c06bc44e4d78dd3c23b0cb26e0662f0d2c Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 11 Nov 2021 18:21:02 +1100 Subject: [PATCH] Swapping a Blender material for another is now auto synchronised. --- src/blenderbim/blenderbim/bim/export_ifc.py | 21 ++++++++++++++++--- src/blenderbim/blenderbim/bim/import_ifc.py | 1 + .../blenderbim/bim/module/gis/operator.py | 5 +++-- src/blenderbim/blenderbim/bim/prop.py | 1 + src/blenderbim/blenderbim/core/geometry.py | 1 + src/blenderbim/blenderbim/core/tool.py | 1 + src/blenderbim/blenderbim/tool/geometry.py | 4 ++++ .../test/bim/feature/project.feature | 15 +++++++++++++ src/blenderbim/test/bim/test_feature.py | 1 + src/blenderbim/test/core/test_geometry.py | 1 + src/blenderbim/test/tool/test_geometry.py | 13 ++++++++++++ 11 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/export_ifc.py b/src/blenderbim/blenderbim/bim/export_ifc.py index 4c6604b134..c16839a3ab 100644 --- a/src/blenderbim/blenderbim/bim/export_ifc.py +++ b/src/blenderbim/blenderbim/bim/export_ifc.py @@ -45,7 +45,7 @@ class IfcExporter: IfcStore.update_cache() if bpy.context.scene.BIMProjectProperties.is_authoring: self.sync_deletions() - self.sync_object_placements() + self.sync_all_objects() self.sync_edited_objects() extension = self.ifc_export_settings.output_file.split(".")[-1] if extension == "ifczip": @@ -98,10 +98,11 @@ class IfcExporter: ifcopenshell.api.run("root.remove_product", self.file, **{"product": product}) return results - def sync_object_placements(self): + def sync_all_objects(self): results = [] self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file) - for ifc_definition_id, obj in IfcStore.id_map.items(): + for ifc_definition_id in list(IfcStore.id_map.keys()): + obj = IfcStore.id_map[ifc_definition_id] try: if isinstance(obj, bpy.types.Material): continue @@ -109,6 +110,9 @@ class IfcExporter: result = self.sync_object_placement(obj) if result: results.append(result) + result = self.sync_object_material(obj) + if result: + results.append(result) except ReferenceError: pass # The object is likely deleted return results @@ -131,6 +135,17 @@ class IfcExporter: IfcStore.edited_objs.clear() return results + def sync_object_material(self, obj): + if not obj.data or not isinstance(obj.data, bpy.types.Mesh): + return + if not self.has_changed_materials(obj): + return + bpy.ops.bim.update_representation(obj=obj.name) + + def has_changed_materials(self, obj): + checksum = obj.data.BIMMeshProperties.material_checksum + return checksum != str([s.id() for s in tool.Geometry.get_styles(obj) if s]) + def sync_object_placement(self, obj): if not self.has_object_moved(obj): return diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 3592e11846..b0df15536e 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -68,6 +68,7 @@ class MaterialCreator: self.add_default_material(element) if self.parse_representations(element): self.assign_material_slots_to_faces() + tool.Geometry.record_object_materials(obj) def add_default_material(self, element): element_material = ifcopenshell.util.element.get_material(element) diff --git a/src/blenderbim/blenderbim/bim/module/gis/operator.py b/src/blenderbim/blenderbim/bim/module/gis/operator.py index ae51f7abe2..801a69ee48 100644 --- a/src/blenderbim/blenderbim/bim/module/gis/operator.py +++ b/src/blenderbim/blenderbim/bim/module/gis/operator.py @@ -19,6 +19,7 @@ import bpy from bpy.types import Operator + class BIM_OT_cityjson2ifc(Operator): bl_idname = "bim.convert_cityjson2ifc" bl_label = "Convert CityJSON to IFC" @@ -27,7 +28,7 @@ class BIM_OT_cityjson2ifc(Operator): def execute(self, context): from cjio import cityjson from ifccityjson.cityjson2ifc.cityjson2ifc import Cityjson2ifc - + props = context.scene.ifccityjson_properties city_model = cityjson.load(props.input, transform=False) data = { @@ -53,7 +54,7 @@ class BIM_OT_find_cityjson_lod(Operator): def execute(self, context): from cjio import cityjson - + props = context.scene.ifccityjson_properties city_model = cityjson.load(props.input) lods = set() diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index abc94b8a21..8f8928f545 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -341,3 +341,4 @@ class BIMMeshProperties(PropertyGroup): is_parametric: BoolProperty(name="Is Parametric", default=False) ifc_definition: StringProperty(name="IFC Definition") ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter) + material_checksum: StringProperty(name="Material Checksum") diff --git a/src/blenderbim/blenderbim/core/geometry.py b/src/blenderbim/blenderbim/core/geometry.py index ce72664a04..ed7bc4efe9 100644 --- a/src/blenderbim/blenderbim/core/geometry.py +++ b/src/blenderbim/blenderbim/core/geometry.py @@ -62,6 +62,7 @@ def add_representation( styles=geometry.get_styles(obj), should_use_presentation_style_assignment=geometry.should_use_presentation_style_assignment(), ) + geometry.record_object_materials(obj) ifc.run("geometry.assign_representation", product=element, representation=representation) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 89191f0ba1..d3741a2dbe 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_materials(cls, obj): pass def record_object_position(cls, obj): pass def rename_object(cls, obj, name): pass def replace_object_with_empty(cls, obj): pass diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index addac8bed8..67edbb8ec9 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -224,6 +224,10 @@ class Geometry(blenderbim.core.tool.Geometry): def link(cls, element, obj): obj.BIMMeshProperties.ifc_definition_id = element.id() + @classmethod + def record_object_materials(cls, obj): + obj.data.BIMMeshProperties.material_checksum = str([s.id() for s in cls.get_styles(obj) if s]) + @classmethod def record_object_position(cls, obj): # These are recorded separately because they have different numerical tolerances diff --git a/src/blenderbim/test/bim/feature/project.feature b/src/blenderbim/test/bim/feature/project.feature index 508fdd92ea..02d09a56c1 100644 --- a/src/blenderbim/test/bim/feature/project.feature +++ b/src/blenderbim/test/bim/feature/project.feature @@ -401,3 +401,18 @@ Scenario: Export IFC - with changed style colour synchronised And an empty Blender session is started And I press "bim.load_project(filepath='{cwd}/test/files/export.ifc')" Then the material "Material" colour is "1,0,0,1" + +Scenario: Export IFC - with changed style element synchronised + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I add a material + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And the object "IfcWall/Cube" is selected + When I add a material + And the material "Material.001" colour is set to "1,0,0,1" + And I press "export_ifc.bim(filepath='{cwd}/test/files/export.ifc')" + And an empty Blender session is started + And I press "bim.load_project(filepath='{cwd}/test/files/export.ifc')" + Then the material "Material.001" colour is "1,0,0,1" diff --git a/src/blenderbim/test/bim/test_feature.py b/src/blenderbim/test/bim/test_feature.py index 251640514e..ca78641e76 100644 --- a/src/blenderbim/test/bim/test_feature.py +++ b/src/blenderbim/test/bim/test_feature.py @@ -91,6 +91,7 @@ def i_add_a_sun(): @given("I add a material") +@when("I add a material") def i_add_a_material(): bpy.context.active_object.active_material = bpy.data.materials.new("Material") diff --git a/src/blenderbim/test/core/test_geometry.py b/src/blenderbim/test/core/test_geometry.py index 68a6e480ab..d4606d5261 100644 --- a/src/blenderbim/test/core/test_geometry.py +++ b/src/blenderbim/test/core/test_geometry.py @@ -74,6 +74,7 @@ class TestAddRepresentation: styles=["style"], should_use_presentation_style_assignment=False, ).should_be_called() + geometry.record_object_materials("obj").should_be_called() # Assign representation to product ifc.run("geometry.assign_representation", product="element", representation="representation").should_be_called() diff --git a/src/blenderbim/test/tool/test_geometry.py b/src/blenderbim/test/tool/test_geometry.py index 249a353cf8..88f409d93b 100644 --- a/src/blenderbim/test/tool/test_geometry.py +++ b/src/blenderbim/test/tool/test_geometry.py @@ -352,6 +352,19 @@ class TestLink(NewFile): assert obj.BIMMeshProperties.ifc_definition_id == element.id() +class TestRecordObjectMaterials(NewFile): + def test_run(self): + obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + style = ifc.createIfcSurfaceStyle() + material = bpy.data.materials.new("Material") + material.BIMMaterialProperties.ifc_style_id = style.id() + obj.data.materials.append(material) + subject.record_object_materials(obj) + assert obj.data.BIMMeshProperties.material_checksum == str([style.id()]) + + class TestRecordObjectPosition(NewFile): def test_run(self): obj = bpy.data.objects.new("Object", None)