From 74598b805b66c474b62b1adcef86a1742b06f065 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 20 Jun 2025 11:44:03 +0500 Subject: [PATCH] Purge/merge IfcPerson entities --- .../bonsai/bim/module/debug/operator.py | 49 +++++------------ src/bonsai/bonsai/bim/module/project/ui.py | 2 +- src/bonsai/bonsai/core/debug.py | 3 ++ src/bonsai/bonsai/tool/debug.py | 52 ++++++++++++++++++- 4 files changed, 66 insertions(+), 40 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/debug/operator.py b/src/bonsai/bonsai/bim/module/debug/operator.py index e9fe6cf811..d5dfeb61a0 100644 --- a/src/bonsai/bonsai/bim/module/debug/operator.py +++ b/src/bonsai/bonsai/bim/module/debug/operator.py @@ -739,9 +739,6 @@ class PurgeUnusedElementsByClass(bpy.types.Operator, tool.Ifc.Operator, ExportHe tool.Ifc.get().write(self.filepath) -PurgeObjectType = Literal["TYPE", "PROFILE", "STYLE", "MATERIAL", "ORGANIZATION", "APPLICATION"] - - class PurgeUnusedObjects(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.purge_unused_objects" bl_label = "Purge Unused Objects" @@ -749,11 +746,11 @@ class PurgeUnusedObjects(bpy.types.Operator, tool.Ifc.Operator): object_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration] name="Object Type", - items=((s, s.capitalize(), "") for s in get_args(PurgeObjectType)), + items=((s, s.capitalize(), "") for s in get_args(tool.Debug.PurgeMergeObjectType)), ) if TYPE_CHECKING: - object_type: PurgeObjectType + object_type: tool.Debug.PurgeMergeObjectType def _execute(self, context): object_type = self.object_type @@ -767,6 +764,8 @@ class PurgeUnusedObjects(bpy.types.Operator, tool.Ifc.Operator): purged = tool.Material.purge_unused_materials() elif object_type in ("APPLICATION", "ORGANIZATION"): purged = core.purge_unused_elements(tool.Ifc, tool.Debug, "IfcApplication") + elif object_type == "PERSON": + purged = core.purge_unused_elements(tool.Ifc, tool.Debug, "IfcPerson") else: assert_never(object_type) @@ -775,22 +774,7 @@ class PurgeUnusedObjects(bpy.types.Operator, tool.Ifc.Operator): if purged == 0: return - if object_type == "PROFILE": - props = tool.Profile.get_profile_props() - if props.is_editing: - bpy.ops.bim.load_profiles() - elif object_type == "STYLE": - props = tool.Style.get_style_props() - if props.is_editing: - bpy.ops.bim.load_styles() - elif object_type == "MATERIAL": - props = tool.Material.get_material_props() - if props.is_editing: - bpy.ops.bim.load_materials() - elif object_type == "ORGANIZATION": - props = tool.Owner.get_owner_props() - if tool.Ifc.get_entity_by_id(props.active_organisation_id) is None: - props.active_organisation_id = 0 + tool.Debug.refresh_ui_after_purge_merge(object_type) class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator): @@ -801,11 +785,11 @@ class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator): object_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration] name="Object Type", - items=((s, s.capitalize(), "") for s in get_args(PurgeObjectType)), + items=((s, s.capitalize(), "") for s in get_args(tool.Debug.PurgeMergeObjectType)), ) if TYPE_CHECKING: - object_type: PurgeObjectType + object_type: tool.Debug.PurgeMergeObjectType def _execute(self, context): object_type: str = self.object_type @@ -817,8 +801,10 @@ class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator): plural_object_type = f"{object_type.lower()}s" if merged_data: for element_type, element_names in merged_data.items(): - names = ", ".join([n or "Unnamed" for n in element_names]) - print(f"- {element_type}: {names}") + print(f"- {element_type}:") + for name in element_names: + name = name or "Unnamed" + print(f" - '{name}'") merged = sum(len(v) for v in merged_data.values()) msg = " See system console for details." if merged else "" @@ -827,18 +813,7 @@ class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator): if merged == 0: return - if object_type == "PROFILE": - props = tool.Profile.get_profile_props() - if props.is_editing: - bpy.ops.bim.load_profiles() - elif object_type == "STYLE": - props = tool.Style.get_style_props() - if props.is_editing: - bpy.ops.bim.load_styles() - elif object_type == "MATERIAL": - props = tool.Material.get_material_props() - if props.is_editing: - bpy.ops.bim.load_materials() + tool.Debug.refresh_ui_after_purge_merge(object_type) class PipInstall(bpy.types.Operator): diff --git a/src/bonsai/bonsai/bim/module/project/ui.py b/src/bonsai/bonsai/bim/module/project/ui.py index 8d74a36514..b5c7fe5023 100644 --- a/src/bonsai/bonsai/bim/module/project/ui.py +++ b/src/bonsai/bonsai/bim/module/project/ui.py @@ -635,7 +635,7 @@ class BIM_PT_purge(Panel): layout.operator("bim.purge_unused_objects", text="Purge Unused Types").object_type = "TYPE" layout.operator("bim.purge_unused_openings", text="Purge Unused Openings in Selected Objects") - MERGEABLE_OBJECT_TYPES = ("MATERIAL", "STYLE", "ORGANIZATION", "APPLICATION") + MERGEABLE_OBJECT_TYPES = ("MATERIAL", "STYLE", "ORGANIZATION", "APPLICATION", "PERSON") for object_type in MERGEABLE_OBJECT_TYPES: row = layout.row(align=True) diff --git a/src/bonsai/bonsai/core/debug.py b/src/bonsai/bonsai/core/debug.py index 4a5abda91f..6c1d02b1f6 100644 --- a/src/bonsai/bonsai/core/debug.py +++ b/src/bonsai/bonsai/core/debug.py @@ -41,6 +41,9 @@ def purge_unused_elements(ifc: type[tool.Ifc], debug: type[tool.Debug], ifc_clas if ifc_class == "IfcApplication": for element in unused_elements: ifc.run("owner.remove_application", application=element) + elif ifc_class == "IfcPerson": + for element in unused_elements: + ifc.run("owner.remove_person", person=element) else: debug.remove_unused_elements(unused_elements) return unused_elements_amount diff --git a/src/bonsai/bonsai/tool/debug.py b/src/bonsai/bonsai/tool/debug.py index 5dd09b1780..5cce67af4d 100644 --- a/src/bonsai/bonsai/tool/debug.py +++ b/src/bonsai/bonsai/tool/debug.py @@ -125,7 +125,7 @@ class Debug(bonsai.core.tool.Debug): @classmethod def merge_identical_objects( cls, - object_type: Literal["STYLE", "MATERIAL", "ORGANIZATION", "APPLICATION"], + object_type: Literal["STYLE", "MATERIAL", "ORGANIZATION", "APPLICATION", "PERSON"], ) -> dict[str, list[str]]: """Merge identical objects. @@ -153,6 +153,8 @@ class Debug(bonsai.core.tool.Debug): element_types = ["IfcOrganization"] elif object_type == "APPLICATION": element_types = ["IfcApplication"] + elif object_type == "PERSON": + element_types = ["IfcPerson"] else: assert_never(object_type) @@ -163,8 +165,13 @@ class Debug(bonsai.core.tool.Debug): hash_to_elements: defaultdict[int, list[ifcopenshell.entity_instance]] = defaultdict(list) for element in elements: # Except for styles, ignore unnamed elements as they may be not safe to merge + merge_optional_names = ("STYLE", "PERSON") not_optional_name = ("APPLICATION", "ORGANIZATION") - if object_type != "STYLE" and object_type not in not_optional_name and not element.Name: + if ( + object_type not in merge_optional_names + and object_type not in not_optional_name + and not element.Name + ): continue element_hash = get_hash(element) hash_to_elements[element_hash].append(element) @@ -206,9 +213,50 @@ class Debug(bonsai.core.tool.Debug): merged_elements_names.append(application.ApplicationFullName) ifcopenshell.api.owner.remove_application(ifc_file, application) + elif object_type == "PERSON": + for person in elements[1:]: + ifcopenshell.util.element.replace_element(person, main_element) + merged_elements_names.append( + f"{person.Identification} / {person.FamilyName} / {person.GivenName}" + ) + ifcopenshell.api.owner.remove_person(ifc_file, person) + else: assert_never(object_type) if merged_elements_names: merged_element_types[element_type] = merged_elements_names return merged_element_types + + PurgeMergeObjectType = Literal["TYPE", "PROFILE", "STYLE", "MATERIAL", "ORGANIZATION", "APPLICATION", "PERSON"] + + @classmethod + def refresh_ui_after_purge_merge(cls, object_type: PurgeMergeObjectType) -> None: + if object_type == "PROFILE": + props = tool.Profile.get_profile_props() + if props.is_editing: + bpy.ops.bim.load_profiles() + elif object_type == "STYLE": + props = tool.Style.get_style_props() + if props.is_editing: + bpy.ops.bim.load_styles() + elif object_type == "MATERIAL": + props = tool.Material.get_material_props() + if props.is_editing: + bpy.ops.bim.load_materials() + elif object_type == "TYPE": + pass + elif object_type == "ORGANIZATION": + props = tool.Owner.get_owner_props() + if tool.Ifc.get_entity_by_id(props.active_organisation_id) is None: + props.active_organisation_id = 0 + elif object_type == "APPLICATION": + props = tool.Owner.get_owner_props() + if tool.Ifc.get_entity_by_id(props.active_application_id) is None: + tool.Owner.clear_application() + elif object_type == "PERSON": + props = tool.Owner.get_owner_props() + if tool.Ifc.get_entity_by_id(props.active_person_id) is None: + tool.Owner.clear_person() + else: + assert_never(object_type)