From 75f8e75a8e5f9e551494af8fcc18e518101d25d0 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 14 Jun 2024 12:25:40 +0500 Subject: [PATCH] ifc.unlink fixes after 3df75e9af Also some changes related to #4843 add --- src/blenderbim/blenderbim/bim/ifc.py | 11 ++++-- .../bim/module/boundary/operator.py | 6 ++-- .../bim/module/material/operator.py | 2 +- .../blenderbim/bim/module/model/opening.py | 10 +++--- .../blenderbim/bim/module/style/operator.py | 2 +- .../blenderbim/bim/module/void/operator.py | 5 +-- src/blenderbim/blenderbim/core/material.py | 13 ++++--- src/blenderbim/blenderbim/core/style.py | 10 +++--- src/blenderbim/blenderbim/core/type.py | 2 +- src/blenderbim/blenderbim/tool/geometry.py | 5 ++- src/blenderbim/blenderbim/tool/ifc.py | 1 + src/blenderbim/blenderbim/tool/material.py | 4 --- src/blenderbim/blenderbim/tool/root.py | 11 ++++-- src/blenderbim/blenderbim/tool/style.py | 4 +++ src/blenderbim/test/core/test_material.py | 35 ++++--------------- src/blenderbim/test/core/test_style.py | 25 +++++-------- src/blenderbim/test/core/test_type.py | 2 +- 17 files changed, 64 insertions(+), 84 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/ifc.py b/src/blenderbim/blenderbim/bim/ifc.py index bf5cfbe909..e67069d334 100644 --- a/src/blenderbim/blenderbim/bim/ifc.py +++ b/src/blenderbim/blenderbim/bim/ifc.py @@ -322,9 +322,7 @@ class IfcStore: raise TypeError("Only one argument must be provided - element or obj.") if obj: - if isinstance(obj, bpy.types.Material): - obj.BIMMaterialProperties.ifc_style_id = 0 - obj.BIMObjectProperties.ifc_definition_id = 0 + IfcStore.purge_blender_ifc_data(obj) return assert element # Type checker. @@ -333,6 +331,7 @@ class IfcStore: potential_obj = IfcStore.id_map[element.id()] if tool.Blender.is_valid_data_block(potential_obj): obj = potential_obj + IfcStore.purge_blender_ifc_data(obj) except: pass @@ -355,6 +354,12 @@ class IfcStore: Operation(rollback=IfcStore.rollback_unlink_element, commit=IfcStore.commit_unlink_element, data=data) ) + @staticmethod + def purge_blender_ifc_data(obj: IFC_CONNECTED_TYPE) -> None: + if isinstance(obj, bpy.types.Material): + obj.BIMMaterialProperties.ifc_style_id = 0 + obj.BIMObjectProperties.ifc_definition_id = 0 + @staticmethod def execute_ifc_operator(operator: bpy.types.Operator, context: bpy.types.Context, is_invoke=False): blenderbim.last_actions.append({"type": "operator", "name": operator.bl_idname}) diff --git a/src/blenderbim/blenderbim/bim/module/boundary/operator.py b/src/blenderbim/blenderbim/bim/module/boundary/operator.py index f67a0ba31c..e8536076e0 100644 --- a/src/blenderbim/blenderbim/bim/module/boundary/operator.py +++ b/src/blenderbim/blenderbim/bim/module/boundary/operator.py @@ -551,9 +551,9 @@ class HideBoundaries(bpy.types.Operator, tool.Ifc.Operator): for boundary in element.BoundedBy or []: boundary_obj = tool.Ifc.get_object(boundary) if boundary_obj: - to_delete.add(boundary_obj) - for boundary_obj in to_delete: - tool.Ifc.unlink(obj=boundary_obj) + to_delete.add((boundary, boundary_obj)) + for boundary, boundary_obj in to_delete: + tool.Ifc.unlink(element=boundary) bpy.data.objects.remove(boundary_obj) context.scene.BIMBoundaryProperties.boundaries.clear() return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 3866ff263e..fa9816174e 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -182,7 +182,7 @@ class RemoveMaterial(bpy.types.Operator, tool.Ifc.Operator): material: bpy.props.IntProperty() def _execute(self, context): - res = core.remove_material(tool.Ifc, tool.Material, tool.Style, material=tool.Ifc.get().by_id(self.material)) + res = core.remove_material(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material)) if not res: self.report({"ERROR"}, "Material is used in material sets and cannot be removed.") return {"CANCELLED"} diff --git a/src/blenderbim/blenderbim/bim/module/model/opening.py b/src/blenderbim/blenderbim/bim/module/model/opening.py index 930c322913..fd77f7159c 100644 --- a/src/blenderbim/blenderbim/bim/module/model/opening.py +++ b/src/blenderbim/blenderbim/bim/module/model/opening.py @@ -224,7 +224,7 @@ class FilledOpeningGenerator: else: opening_obj = tool.Ifc.get_object(opening) if opening_obj: - tool.Ifc.unlink(obj=opening_obj) + tool.Ifc.unlink(element=opening) bpy.data.objects.remove(opening_obj) filling_obj = tool.Ifc.get_object(filling) @@ -772,9 +772,9 @@ class HideOpenings(Operator, tool.Ifc.Operator): for opening in openings: opening_obj = tool.Ifc.get_object(opening) if opening_obj: - to_delete.add(opening_obj) - for opening_obj in to_delete: - tool.Ifc.unlink(obj=opening_obj) + to_delete.add((opening, opening_obj)) + for opening, opening_obj in to_delete: + tool.Ifc.unlink(element=opening) bpy.data.objects.remove(opening_obj) tool.Model.clear_scene_openings() return {"FINISHED"} @@ -824,7 +824,7 @@ class EditOpenings(Operator, tool.Ifc.Operator): building_objs.update( self.get_all_building_objects_of_similar_openings(opening) ) # NB this has nothing to do with clone similar_opening - tool.Ifc.unlink(element=opening, obj=opening_obj) + tool.Ifc.unlink(element=opening) bpy.data.objects.remove(opening_obj) tool.Model.reload_body_representation(building_objs) diff --git a/src/blenderbim/blenderbim/bim/module/style/operator.py b/src/blenderbim/blenderbim/bim/module/style/operator.py index 02ad4b2af0..79d20bacc4 100644 --- a/src/blenderbim/blenderbim/bim/module/style/operator.py +++ b/src/blenderbim/blenderbim/bim/module/style/operator.py @@ -80,7 +80,7 @@ class RemoveStyle(bpy.types.Operator, tool.Ifc.Operator): style: bpy.props.IntProperty() def _execute(self, context): - core.remove_style(tool.Ifc, tool.Material, tool.Style, style=tool.Ifc.get().by_id(self.style)) + core.remove_style(tool.Ifc, tool.Style, style=tool.Ifc.get().by_id(self.style)) class AddStyle(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/void/operator.py b/src/blenderbim/blenderbim/bim/module/void/operator.py index af631ace63..80e768201a 100644 --- a/src/blenderbim/blenderbim/bim/module/void/operator.py +++ b/src/blenderbim/blenderbim/bim/module/void/operator.py @@ -74,6 +74,7 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator): obj1, obj2 = obj2, obj1 element1, element2 = element2, element1 + # element1 - voided element, element2 - opening. if element1.is_a("IfcOpeningElement"): self.report({"INFO"}, "You can't add an opening to another opening.") continue @@ -127,7 +128,7 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator): ) if not has_visible_openings: - tool.Ifc.unlink(obj=obj2) + tool.Ifc.unlink(element=element2) bpy.data.objects.remove(obj2) context.view_layer.objects.active = obj1 @@ -148,7 +149,7 @@ class RemoveOpening(bpy.types.Operator, tool.Ifc.Operator): if opening_obj: opening_obj.name = "/".join(opening_obj.name.split("/")[1:]) - tool.Ifc.unlink(obj=opening_obj) + tool.Ifc.unlink(element=opening) ifcopenshell.api.run("void.remove_opening", tool.Ifc.get(), opening=opening) diff --git a/src/blenderbim/blenderbim/core/material.py b/src/blenderbim/blenderbim/core/material.py index eb81c6df98..0651e9a46c 100644 --- a/src/blenderbim/blenderbim/core/material.py +++ b/src/blenderbim/blenderbim/core/material.py @@ -58,17 +58,16 @@ def add_material_set(ifc: tool.Ifc, material: tool.Material, set_type: str) -> i def remove_material( - ifc: tool.Ifc, material_tool: tool.Material, style: tool.Style, material: ifcopenshell.entity_instance + ifc: tool.Ifc, material_tool: tool.Material, material: ifcopenshell.entity_instance ) -> bool: - """returns True after deleting False,\n - returns False if material used in material sets and cannot be removed""" + """Remove an IFC material. + + Return True if deletion succeeded,\n + return False if material is used in material sets and cannot be removed. + """ if material_tool.is_material_used_in_sets(material): return False - obj = ifc.get_object(material) - ifc.unlink(element=material) ifc.run("material.remove_material", material=material) - if obj and not style.get_style(obj): - material_tool.delete_object(obj) if material_tool.is_editing_materials(): material_tool.import_material_definitions(material_tool.get_active_material_type()) return True diff --git a/src/blenderbim/blenderbim/core/style.py b/src/blenderbim/blenderbim/core/style.py index f22b4b15d9..5b59063b97 100644 --- a/src/blenderbim/blenderbim/core/style.py +++ b/src/blenderbim/blenderbim/core/style.py @@ -59,13 +59,12 @@ def update_external_style( def remove_style( - ifc: tool.Ifc, material: tool.Material, style_tool: tool.Style, style: ifcopenshell.entity_instance + ifc: tool.Ifc, style_tool: tool.Style, style: ifcopenshell.entity_instance ) -> None: obj = ifc.get_object(style) - ifc.unlink(obj=obj, element=style) + ifc.unlink(element=style) ifc.run("style.remove_style", style=style) - if obj and not ifc.get_entity(obj): - material.delete_object(obj) + style_tool.delete_object(obj) if style_tool.is_editing_styles(): style_tool.import_presentation_styles(style_tool.get_active_style_type()) @@ -130,8 +129,7 @@ def update_style_textures( def unlink_style(ifc: tool.Ifc, style: ifcopenshell.entity_instance) -> None: - obj = ifc.get_object(style) - ifc.unlink(obj=obj, element=style) + ifc.unlink(element=style) def enable_editing_style(style: tool.Style, obj: bpy.types.Material) -> None: diff --git a/src/blenderbim/blenderbim/core/type.py b/src/blenderbim/blenderbim/core/type.py index 969dc3c125..8660e52cfb 100644 --- a/src/blenderbim/blenderbim/core/type.py +++ b/src/blenderbim/blenderbim/core/type.py @@ -39,6 +39,6 @@ def purge_unused_types(ifc, type): ifc.run("root.remove_product", product=element_type) purged_types += 1 if obj: - ifc.unlink(obj=obj) + ifc.unlink(element=element_type) type.remove_object(obj) return purged_types diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index afb8c55f91..3d4d4fc052 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -656,9 +656,12 @@ class Geometry(blenderbim.core.tool.Geometry): def replace_object_with_empty(cls, obj): element = tool.Ifc.get_entity(obj) name = obj.name - tool.Ifc.unlink(obj=obj, element=element) + if element: + tool.Ifc.unlink(element=element) + obj.name = ifcopenshell.guid.new() new_obj = bpy.data.objects.new(name, None) + if element: tool.Ifc.link(element, new_obj) for collection in obj.users_collection: diff --git a/src/blenderbim/blenderbim/tool/ifc.py b/src/blenderbim/blenderbim/tool/ifc.py index f961d03a02..2beb93520b 100644 --- a/src/blenderbim/blenderbim/tool/ifc.py +++ b/src/blenderbim/blenderbim/tool/ifc.py @@ -191,6 +191,7 @@ class Ifc(blenderbim.core.tool.Ifc): def unlink( cls, element: Optional[ifcopenshell.entity_instance] = None, obj: Optional[IFC_CONNECTED_TYPE] = None ) -> None: + """See IfcStore.unlink_element doc for details.""" IfcStore.unlink_element(element, obj) @classmethod diff --git a/src/blenderbim/blenderbim/tool/material.py b/src/blenderbim/blenderbim/tool/material.py index a927e99bd6..adce9962b1 100644 --- a/src/blenderbim/blenderbim/tool/material.py +++ b/src/blenderbim/blenderbim/tool/material.py @@ -38,10 +38,6 @@ class Material(blenderbim.core.tool.Material): def add_default_material_object(cls, name: Union[str, None]) -> bpy.types.Material: return bpy.data.materials.new(name or "Default") - @classmethod - def delete_object(cls, obj: bpy.types.Material) -> None: - bpy.data.materials.remove(obj) - @classmethod def disable_editing_materials(cls) -> None: bpy.context.scene.BIMMaterialProperties.is_editing = False diff --git a/src/blenderbim/blenderbim/tool/root.py b/src/blenderbim/blenderbim/tool/root.py index ccbac3c166..bac67eac83 100644 --- a/src/blenderbim/blenderbim/tool/root.py +++ b/src/blenderbim/blenderbim/tool/root.py @@ -344,12 +344,17 @@ class Root(blenderbim.core.tool.Root): @classmethod def unlink_object(cls, obj: bpy.types.Object) -> None: + """Purge all IFC data associated with a Blender object. + + This method is safe to run on objects from other Blender sessions + as it won't try to find related IFC elements from the IFC data + to unlink them. + """ tool.Ifc.unlink(obj=obj) if hasattr(obj.data, "BIMMeshProperties"): obj.data.BIMMeshProperties.ifc_definition_id = 0 for material_slot in obj.material_slots: - if material_slot.material: - blenderbim.core.style.unlink_style(tool.Ifc, style=material_slot.material) - blenderbim.core.material.unlink_material(tool.Ifc, obj=material_slot.material) + if material := material_slot.material: + tool.Ifc.unlink(obj=material) if "Ifc" in obj.name and "/" in obj.name: obj.name = obj.name.split("/", 1)[1] diff --git a/src/blenderbim/blenderbim/tool/style.py b/src/blenderbim/blenderbim/tool/style.py index 7fb6c3a080..0a162cb409 100644 --- a/src/blenderbim/blenderbim/tool/style.py +++ b/src/blenderbim/blenderbim/tool/style.py @@ -49,6 +49,10 @@ class Style(blenderbim.core.tool.Style): def can_support_rendering_style(cls, obj: bpy.types.Material) -> bool: return obj.use_nodes and hasattr(obj.node_tree, "nodes") + @classmethod + def delete_object(cls, obj: bpy.types.Material) -> None: + bpy.data.materials.remove(obj) + @classmethod def disable_editing(cls, obj: bpy.types.Material) -> None: obj.BIMStyleProperties.is_editing = False diff --git a/src/blenderbim/test/core/test_material.py b/src/blenderbim/test/core/test_material.py index ee7d5189cc..9c0d4ae715 100644 --- a/src/blenderbim/test/core/test_material.py +++ b/src/blenderbim/test/core/test_material.py @@ -87,46 +87,23 @@ class TestAddMaterialSet: class TestRemoveMaterial: - def test_removing_a_material(self, ifc, material, style): + def test_removing_a_material(self, ifc, material): material.is_material_used_in_sets("material").should_be_called().will_return(False) - ifc.get_object("material").should_be_called().will_return(None) - ifc.unlink(element="material").should_be_called() ifc.run("material.remove_material", material="material").should_be_called() material.is_editing_materials().should_be_called().will_return(False) - subject.remove_material(ifc, material, style, material="material") + subject.remove_material(ifc, material, material="material") - def test_removing_a_material_and_reloading_imported_materials(self, ifc, material, style): + def test_removing_a_material_and_reloading_imported_materials(self, ifc, material): material.is_material_used_in_sets("material").should_be_called().will_return(False) - ifc.get_object("material").should_be_called().will_return(None) - ifc.unlink(element="material").should_be_called() ifc.run("material.remove_material", material="material").should_be_called() material.is_editing_materials().should_be_called().will_return(True) material.get_active_material_type().should_be_called().will_return("material_type") material.import_material_definitions("material_type").should_be_called() - subject.remove_material(ifc, material, style, material="material") + subject.remove_material(ifc, material, material="material") - def test_removing_a_material_object_if_it_has_no_style(self, ifc, material, style): - material.is_material_used_in_sets("material").should_be_called().will_return(False) - ifc.get_object("material").should_be_called().will_return("obj") - ifc.unlink(element="material").should_be_called() - ifc.run("material.remove_material", material="material").should_be_called() - style.get_style("obj").should_be_called().will_return(None) - material.delete_object("obj").should_be_called() - material.is_editing_materials().should_be_called().will_return(False) - subject.remove_material(ifc, material, style, material="material") - - def test_preserving_a_material_object_if_it_is_still_used_as_a_style(self, ifc, material, style): - material.is_material_used_in_sets("material").should_be_called().will_return(False) - ifc.get_object("material").should_be_called().will_return("obj") - ifc.unlink(element="material").should_be_called() - ifc.run("material.remove_material", material="material").should_be_called() - style.get_style("obj").should_be_called().will_return("style") - material.is_editing_materials().should_be_called().will_return(False) - subject.remove_material(ifc, material, style, material="material") - - def test_not_removing_a_material_if_it_is_used_in_a_material_set(self, ifc, material, style): + def test_not_removing_a_material_if_it_is_used_in_a_material_set(self, ifc, material): material.is_material_used_in_sets("material").should_be_called().will_return(True) - subject.remove_material(ifc, material, style, material="material") + subject.remove_material(ifc, material, material="material") class TestRemoveMaterialSet: diff --git a/src/blenderbim/test/core/test_style.py b/src/blenderbim/test/core/test_style.py index e2bcdbe8fc..0c8c9126f4 100644 --- a/src/blenderbim/test/core/test_style.py +++ b/src/blenderbim/test/core/test_style.py @@ -53,32 +53,23 @@ class TestAddStyle: class TestRemoveStyle: - def test_removing_a_style(self, ifc, material, style): + def test_removing_a_style(self, ifc, style): ifc.get_object("style").should_be_called().will_return("obj") - ifc.unlink(obj="obj", element="style").should_be_called() + ifc.unlink(element="style").should_be_called() ifc.run("style.remove_style", style="style").should_be_called() - ifc.get_entity("obj").should_be_called().will_return("material") + style.delete_object("obj").should_be_called() style.is_editing_styles().should_be_called().will_return(False) - subject.remove_style(ifc, material, style, style="style") + subject.remove_style(ifc, style, style="style") - def test_removing_a_style_and_reloading_imported_styles(self, ifc, material, style): + def test_removing_a_style_and_reloading_imported_styles(self, ifc, style): ifc.get_object("style").should_be_called().will_return("obj") - ifc.unlink(obj="obj", element="style").should_be_called() + ifc.unlink(element="style").should_be_called() ifc.run("style.remove_style", style="style").should_be_called() - ifc.get_entity("obj").should_be_called().will_return("material") + style.delete_object("obj").should_be_called() style.is_editing_styles().should_be_called().will_return(True) style.get_active_style_type().should_be_called().will_return("style_type") style.import_presentation_styles("style_type").should_be_called() - subject.remove_style(ifc, material, style, style="style") - - def test_removing_an_object_if_it_is_not_still_used_for_a_material(self, ifc, material, style): - ifc.get_object("style").should_be_called().will_return("obj") - ifc.unlink(obj="obj", element="style").should_be_called() - ifc.run("style.remove_style", style="style").should_be_called() - ifc.get_entity("obj").should_be_called().will_return(None) - material.delete_object("obj").should_be_called() - style.is_editing_styles().should_be_called().will_return(False) - subject.remove_style(ifc, material, style, style="style") + subject.remove_style(ifc, style, style="style") class TestUpdateStyleColours: diff --git a/src/blenderbim/test/core/test_type.py b/src/blenderbim/test/core/test_type.py index 5800f04ea4..d5687ac3c9 100644 --- a/src/blenderbim/test/core/test_type.py +++ b/src/blenderbim/test/core/test_type.py @@ -47,6 +47,6 @@ class TestPurgeUnusedTypes: type.get_type_occurrences("element_type").should_be_called().will_return([]) ifc.run("root.remove_product", product="element_type").should_be_called() ifc.get_object("element_type").should_be_called().will_return("obj") - ifc.unlink(obj="obj").should_be_called() + ifc.unlink(element="element_type").should_be_called() type.remove_object("obj").should_be_called() subject.purge_unused_types(ifc, type)