From b5aeab6404b91839ba8a85114937a40c13bb2b09 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 28 Aug 2025 17:04:21 +0500 Subject: [PATCH] Remove project library from UI #7057 Example - https://files.catbox.moe/940mhn.mp4 --- .../bonsai/bim/module/project/__init__.py | 1 + .../bonsai/bim/module/project/operator.py | 39 +++++++++++++++++++ src/bonsai/bonsai/bim/module/project/ui.py | 1 + src/bonsai/bonsai/tool/project.py | 39 +++++++++++++++++++ 4 files changed, 80 insertions(+) diff --git a/src/bonsai/bonsai/bim/module/project/__init__.py b/src/bonsai/bonsai/bim/module/project/__init__.py index 81bf9e9ec7..1316eb0325 100644 --- a/src/bonsai/bonsai/bim/module/project/__init__.py +++ b/src/bonsai/bonsai/bim/module/project/__init__.py @@ -55,6 +55,7 @@ classes = ( operator.RefreshClippingPlanes, operator.RefreshLibrary, operator.ReloadLink, + operator.RemoveProjectLibrary, operator.RevertProject, operator.RewindLibrary, operator.SaveLibraryFile, diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index ec068c9040..79cd6873ed 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -774,6 +774,45 @@ class AddProjectLibrary(bpy.types.Operator): IfcStore.library_file.redo() +class RemoveProjectLibrary(bpy.types.Operator): + bl_idname = "bim.remove_project_library" + bl_label = "Remove Project Library" + bl_description = "Remove the currently selected IfcProjectLibrary." + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + IfcStore.begin_transaction(self) + library_file = IfcStore.library_file + assert library_file + library_file.begin_transaction() + result = self._execute(context) + library_file.end_transaction() + IfcStore.add_transaction_operation(self) + IfcStore.end_transaction(self) + return result + + def _execute(self, context): + props = tool.Project.get_project_props() + library_file = IfcStore.library_file + assert library_file + + project_library = library_file.by_id(int(props.selected_project_library)) + tool.Project.remove_project_library(project_library) + + ProjectLibraryData.load() # Update enum. + enum_len = len(ProjectLibraryData.data["project_libraries_enum"]) + if props.is_property_set("selected_project_library"): + props["selected_project_library"] = min(enum_len - 1, props["selected_project_library"]) + bpy.ops.bim.refresh_library() + return {"FINISHED"} + + def rollback(self, data): + IfcStore.library_file.undo() + + def commit(self, data): + IfcStore.library_file.redo() + + class EnableEditingHeader(bpy.types.Operator): bl_idname = "bim.enable_editing_header" bl_label = "Enable Editing Header" diff --git a/src/bonsai/bonsai/bim/module/project/ui.py b/src/bonsai/bonsai/bim/module/project/ui.py index 88164e86c6..0c305302f2 100644 --- a/src/bonsai/bonsai/bim/module/project/ui.py +++ b/src/bonsai/bonsai/bim/module/project/ui.py @@ -406,6 +406,7 @@ class BIM_PT_project_library(Panel): if library_is_selected and not props.is_editing_project_library: row.prop(props, "is_editing_project_library", text="", icon="GREASEPENCIL") + row.operator("bim.remove_project_library", text="", icon="X") row.prop(self.props, "show_library_tree", text="", icon="OUTLINER") diff --git a/src/bonsai/bonsai/tool/project.py b/src/bonsai/bonsai/tool/project.py index a83f4d2fdc..f8c13e93b5 100644 --- a/src/bonsai/bonsai/tool/project.py +++ b/src/bonsai/bonsai/tool/project.py @@ -21,6 +21,7 @@ import os import bpy import ifcopenshell import ifcopenshell.api.document +import ifcopenshell.util.element import ifcopenshell.util.representation import ifcopenshell.util.unit import bonsai.core.aggregate @@ -398,3 +399,41 @@ class Project(bonsai.core.tool.Project): if library_element.is_a("IfcProfileDef"): return "ProfileName" return "Name" + + # TODO: Move to ifcopenshell.api and add tests. + @classmethod + def remove_project_library(cls, project_library: ifcopenshell.entity_instance) -> None: + ifc_file = project_library.file + roots_to_remove = {project_library} + rels_to_clean_up: set[ifcopenshell.entity_instance] = set() + queue = [project_library] + while queue: + current = queue.pop() + inverses = ifc_file.get_inverse(current) + rels_to_clean_up.update(i for i in inverses if i.is_a() in ("IfcRelNests", "IfcRelDeclares")) + + children = ifcopenshell.util.element.get_components(current) + queue.extend(children) + roots_to_remove.update(children) + + def remove_root(root: ifcopenshell.entity_instance) -> None: + history = root.OwnerHistory + ifc_file.remove(root) + if history: + ifcopenshell.util.element.remove_deep2(ifc_file, history) + + for root in roots_to_remove: + remove_root(root) + + # Clean up invalidated rels. + for rel in rels_to_clean_up: + if rel.is_a("IfcRelDeclares"): + # Project library was either assigned to Project or some types were assigned to it. + if not rel.RelatingContext or not rel.RelatedDefinitions: + remove_root(rel) + elif rel.is_a("IfcRelNests"): + # Project library was either parent or child to other project libraries. + if not rel.RelatingObject or not rel.RelatedObjects: + remove_root(rel) + else: + assert False, f"Shouldn't be here, {rel}"