mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
See #3226. New batched remove_deep2() which uses serialisation for faster removal performance on large graphs.
This commit is contained in:
@@ -739,6 +739,72 @@ def remove_deep(ifc_file, element):
|
||||
ifc_file.unbatch()
|
||||
|
||||
|
||||
def batch_remove_deep2(ifc_file):
|
||||
"""Enable batch removal after running remove_deep2 using serialisation
|
||||
|
||||
See #944 and #3226. Removing elements in an IFC graph is slow as a lot of
|
||||
mappings need to be edited. In larger models (>100MB) and when removing
|
||||
many elements (>10000), it is faster to serialise the IFC, remove elements
|
||||
using string replacement, and then reload the modified serialised IFC.
|
||||
|
||||
The trade-off is that extra memory will be used, and string replacement
|
||||
only works with remove_deep2 where the removed elements have no inverses.
|
||||
In addition, transaction history will be lost, and any scripts using this
|
||||
method will have to refetch elements from the reloaded IFC and cannot rely
|
||||
on existing variables in memory.
|
||||
|
||||
:param ifc_file: The IFC file object
|
||||
:type ifc_file: ifcopenshell.file.file
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
element1 = model.by_id(123)
|
||||
element2 = model.by_id(456)
|
||||
|
||||
ifcopenshell.util.element.batch_remove_deep2(model)
|
||||
ifcopenshell.util.element.remove_deep2(model, element2)
|
||||
|
||||
# Notice how we reload the model.
|
||||
model = ifcopenshell.util.element.unbatch_remove_deep2(model)
|
||||
|
||||
print(element1) # Don't call element1!
|
||||
"""
|
||||
ifc_file.to_delete = set()
|
||||
|
||||
|
||||
def unbatch_remove_deep2(ifc_file):
|
||||
"""Finish removing elements batched from remove_deep2 using string replacement
|
||||
|
||||
See documentation for batch_remove_deep2.
|
||||
|
||||
:param ifc_file: The IFC file object
|
||||
:type ifc_file: ifcopenshell.file.file
|
||||
:return: A newly loaded file with the elements removed.
|
||||
:rtype: ifcopenshell.file.file
|
||||
"""
|
||||
ifc_string = ifc_file.to_string()
|
||||
lines = iter(ifc_string.split('\n'))
|
||||
ids_to_delete = iter(sorted([e.id() for e in ifc_file.to_delete]))
|
||||
id_to_delete = next(ids_to_delete, None)
|
||||
result = []
|
||||
|
||||
for line in lines:
|
||||
if id_to_delete is None:
|
||||
result.append(line)
|
||||
continue
|
||||
|
||||
if line.startswith(f"#{id_to_delete}="):
|
||||
id_to_delete = next(ids_to_delete, None)
|
||||
else:
|
||||
result.append(line)
|
||||
|
||||
ifc_file.to_delete = None
|
||||
return ifcopenshell.file.from_string("\n".join(result))
|
||||
|
||||
|
||||
def remove_deep2(ifc_file, element, also_consider=[], do_not_delete=[]):
|
||||
"""Recursively purges a subgraph safely, starting at an element
|
||||
|
||||
@@ -796,6 +862,11 @@ def remove_deep2(ifc_file, element, also_consider=[], do_not_delete=[]):
|
||||
for i, attribute in enumerate(subelement):
|
||||
if isinstance(attribute, tuple) and len(attribute) > 10:
|
||||
subelement[i] = []
|
||||
|
||||
if getattr(ifc_file, "to_delete", None) is not None:
|
||||
ifc_file.to_delete.update(to_delete)
|
||||
return
|
||||
|
||||
# We delete elements from subgraph in reverse order to allow batching to work
|
||||
for subelement in filter(lambda e: e in to_delete, subgraph[::-1]):
|
||||
ifc_file.remove(subelement)
|
||||
|
||||
@@ -660,6 +660,18 @@ class TestRemoveDeep2IFC4(test.bootstrap.IFC4):
|
||||
assert self.file.by_guid("id1")
|
||||
|
||||
|
||||
class TestBatchRemoveDeep2IFC4(test.bootstrap.IFC4):
|
||||
def test_run(self):
|
||||
owner = self.file.createIfcOwnerHistory()
|
||||
element = self.file.createIfcWall(GlobalId="id", OwnerHistory=owner)
|
||||
subject.batch_remove_deep2(self.file)
|
||||
subject.remove_deep2(self.file, element)
|
||||
new = subject.unbatch_remove_deep2(self.file)
|
||||
assert self.file.by_id(1)
|
||||
with pytest.raises(RuntimeError):
|
||||
new.by_id(1)
|
||||
|
||||
|
||||
class TestCopyIFC4(test.bootstrap.IFC4):
|
||||
def test_copying_an_element(self):
|
||||
element = self.file.createIfcWall(GlobalId="id", Name="name")
|
||||
|
||||
Reference in New Issue
Block a user