From 10672f3e1a107a77fa3c8e5c3447ca67d13dcfc8 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 27 Sep 2023 15:41:51 +0500 Subject: [PATCH] Print unused elements stats / purge unused elements Added to Purge Data section in Quality Control some tools - https://i.imgur.com/Nz3YoiH.png 1) Print unused elements - useful to investigate how much unused data you have in IFC file. https://i.imgur.com/9Fkn0JD.png 2) purge unused elements by ifc class, it's doing it in pretty naive way and can be dangerous, so maybe it should be moved to debug section. --- .../blenderbim/bim/module/debug/__init__.py | 2 + .../blenderbim/bim/module/debug/operator.py | 41 +++++++++++++++++++ .../blenderbim/bim/module/debug/prop.py | 1 + .../blenderbim/bim/module/project/ui.py | 7 ++++ src/blenderbim/blenderbim/core/debug.py | 8 ++++ src/blenderbim/blenderbim/tool/debug.py | 40 ++++++++++++++++++ 6 files changed, 99 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/debug/__init__.py b/src/blenderbim/blenderbim/bim/module/debug/__init__.py index 74d59a6eec..a05c74561a 100644 --- a/src/blenderbim/blenderbim/bim/module/debug/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/debug/__init__.py @@ -38,6 +38,8 @@ classes = ( operator.SelectHighPolygonMeshes, operator.SelectHighestPolygonMeshes, operator.ValidateIfcFile, + operator.PrintUnusedElementStats, + operator.PurgeUnusedElementsByClass, prop.BIMDebugProperties, ui.BIM_PT_debug, ) diff --git a/src/blenderbim/blenderbim/bim/module/debug/operator.py b/src/blenderbim/blenderbim/bim/module/debug/operator.py index 32cfbce6d3..bad542c984 100644 --- a/src/blenderbim/blenderbim/bim/module/debug/operator.py +++ b/src/blenderbim/blenderbim/bim/module/debug/operator.py @@ -454,3 +454,44 @@ class OverrideDisplayType(bpy.types.Operator): for obj in context.selected_objects: obj.display_type = self.display return {"FINISHED"} + + +class PrintUnusedElementStats(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.print_unused_elements_stats" + bl_label = "Purge Unused Elements Stats" + bl_options = {"REGISTER", "UNDO"} + bl_description = ( + "Print all unused elements in current IFC project in system console, not limited to the selected class" + ) + + ignore_contexts: bpy.props.BoolProperty(name="Ignore Contexts", default=True) + ignore_relationships: bpy.props.BoolProperty(name="Ignore Relationships", default=True) + ignore_types: bpy.props.BoolProperty(name="Ignore Types", default=True) + + def _execute(self, context): + props = context.scene.BIMDebugProperties + # ignore some classes that could have zero 0 inverse references by their nature + ignore_classes = [] + if self.ignore_contexts: + ignore_classes += ["IfcRepresentationContext"] + if self.ignore_relationships: + ignore_classes += ["IfcRelationship"] + if self.ignore_types: + ignore_classes += ["IfcTypeProduct"] + + unused_elements = tool.Debug.print_unused_elements_stats(props.ifc_class_purge, ignore_classes) + self.report({"INFO"}, f"{unused_elements} unused elements found, check the system console for the details.") + + +class PurgeUnusedElementsByClass(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.purge_unused_elements_by_class" + bl_label = "Purge Unused Elements By Class" + bl_description = ( + "Will find all elements of class that have no inverse refernces and will remove them, use very carefully." + ) + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + props = context.scene.BIMDebugProperties + purged_elements = core.purge_unused_elements(tool.Ifc, tool.Debug, props.ifc_class_purge) + self.report({"INFO"}, f"{purged_elements} unused elements found and removed.") diff --git a/src/blenderbim/blenderbim/bim/module/debug/prop.py b/src/blenderbim/blenderbim/bim/module/debug/prop.py index 0825b0f0f6..fff62ef57b 100644 --- a/src/blenderbim/blenderbim/bim/module/debug/prop.py +++ b/src/blenderbim/blenderbim/bim/module/debug/prop.py @@ -50,3 +50,4 @@ class BIMDebugProperties(PropertyGroup): name="Display Type", default="BOUNDS", ) + ifc_class_purge: StringProperty(name="Unused Elements IFC Class", default="") diff --git a/src/blenderbim/blenderbim/bim/module/project/ui.py b/src/blenderbim/blenderbim/bim/module/project/ui.py index 8b61c4320d..0797238bd1 100644 --- a/src/blenderbim/blenderbim/bim/module/project/ui.py +++ b/src/blenderbim/blenderbim/bim/module/project/ui.py @@ -398,3 +398,10 @@ class BIM_PT_purge(Panel): layout.operator("bim.purge_unused_profiles") layout.operator("bim.purge_unused_types") layout.operator("bim.purge_unused_representations") + layout.separator() + + layout.label(text="Purge unused elements by class:") + row = layout.row(align=True) + row.prop(context.scene.BIMDebugProperties, "ifc_class_purge", text="") + row.operator("bim.purge_unused_elements_by_class", text="", icon="TRASH") + row.operator("bim.print_unused_elements_stats", text="", icon="INFO") diff --git a/src/blenderbim/blenderbim/core/debug.py b/src/blenderbim/blenderbim/core/debug.py index 65e7ac0607..fd6356aa0d 100644 --- a/src/blenderbim/blenderbim/core/debug.py +++ b/src/blenderbim/blenderbim/core/debug.py @@ -23,3 +23,11 @@ def parse_express(debug, filename): def purge_hdf5_cache(debug): debug.purge_hdf5_cache() + + +def purge_unused_elements(ifc, debug, ifc_class): + ifc_file = ifc.get() + unused_elements = [i for i in ifc_file.by_type(ifc_class) if ifc_file.get_total_inverses(i) == 0] + unused_elements_amount = len(unused_elements) + debug.remove_unused_elements(unused_elements) + return unused_elements_amount diff --git a/src/blenderbim/blenderbim/tool/debug.py b/src/blenderbim/blenderbim/tool/debug.py index 7f0888e2ef..3bf44856a1 100644 --- a/src/blenderbim/blenderbim/tool/debug.py +++ b/src/blenderbim/blenderbim/tool/debug.py @@ -20,6 +20,7 @@ import os import bpy import ifcopenshell.express import blenderbim.core.tool +import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore @@ -48,3 +49,42 @@ class Debug(blenderbim.core.tool.Debug): obj = bpy.data.objects.new(name, mesh) bpy.context.scene.collection.objects.link(obj) return obj + + @classmethod + def remove_unused_elements(cls, elements): + ifc_file = tool.Ifc.get() + for element in elements: + ifcopenshell.util.element.remove_deep2(ifc_file, element) + + @classmethod + def print_unused_elements_stats(cls, requested_ifc_class="", ignore_classes=tuple()): + ifc_file = tool.Ifc.get() + + # get list of ifc classes used in model + classes = set() + requested_ifc_classes = set() + for el in ifc_file: + if any(el.is_a(i) for i in ignore_classes): + continue + classes.add(el.is_a()) + if requested_ifc_class and el.is_a(requested_ifc_class): + requested_ifc_classes.add(el.is_a()) + + # count unused elements for each class + unused = dict() + for c in classes: + uses = [i for i in ifc_file.by_type(c) if ifc_file.get_total_inverses(i) == 0] + if not uses: + continue + unused[c] = len(uses) + + # print classes and their unsued elements in ascending order + if unused: + print("Unused elements by classes:") + for ifc_class in sorted(unused.keys(), key=lambda x: unused[x]): + class_string = ifc_class + if ifc_class in requested_ifc_classes: + class_string = "---> " + class_string + print(f"{class_string: <50} {unused[ifc_class]: >5}") + + return sum(unused.values())