From a8729933af961aab448b2342ed5dea2d5eac2e80 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 23 May 2024 20:15:01 +1000 Subject: [PATCH] Optimise remove_deep, which can make removing lots of elements 100x faster in scripts. --- src/ifcopenshell-python/ifcopenshell/file.py | 8 ++++++++ src/ifcopenshell-python/ifcopenshell/util/element.py | 9 ++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/ifcopenshell-python/ifcopenshell/file.py b/src/ifcopenshell-python/ifcopenshell/file.py index 66894bc2a1..cf8e5491a2 100644 --- a/src/ifcopenshell-python/ifcopenshell/file.py +++ b/src/ifcopenshell-python/ifcopenshell/file.py @@ -505,6 +505,11 @@ class file: ) -> list[ifcopenshell.entity_instance]: """Return a list of entities that reference this entity + Warning: this is a slow function, especially when there is a large + number of inverses (such as for a shared owner history). If you are + only interested in the total number of inverses (typically 0, 1, or N), + consider using :func:`get_total_inverses`. + :param inst: The entity instance to get inverse relationships :type inst: ifcopenshell.entity_instance :param allow_duplicate: Returns a `list` when True, `set` when False @@ -530,6 +535,9 @@ class file: def get_total_inverses(self, inst: ifcopenshell.entity_instance) -> int: """Returns the number of entities that reference this entity + This is equivalent to `len(model.get_inverse(element))`, but + significantly faster. + :param inst: The entity instance to get inverse relationships :type inst: ifcopenshell.entity_instance :returns: The total number of references diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index f0df9b16f5..3e7864d90d 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -1315,6 +1315,8 @@ def remove_deep2( :type element: ifcopenshell.entity_instance """ # ifc_file.batch() + if ifc_file.get_total_inverses(element): + return to_delete = set() subgraph = list(ifc_file.traverse(element, breadth_first=True)) subgraph.extend(also_consider) @@ -1325,7 +1327,12 @@ def remove_deep2( if ( subelement.id() and subelement not in do_not_delete - and len(set(ifc_file.get_inverse(subelement)) - subgraph_set) == 0 + and ( + # 0 or 1 inverses means it only exists in this subgraph + ifc_file.get_total_inverses(subelement) < 2 + # Alternatively, let's ensure all inverses are within the subgrpah + or len(set(ifc_file.get_inverse(subelement)) - subgraph_set) == 0 + ) ): to_delete.add(subelement) subelement_queue.extend(ifc_file.traverse(subelement, max_levels=1)[1:])