From b8015644e95de650512570352eb0b778f62e3067 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 4 Jul 2021 19:45:34 +1000 Subject: [PATCH] Implement undo for context and geometry operators. See #1475. --- .../blenderbim/bim/module/context/operator.py | 8 +++++++ .../bim/module/geometry/operator.py | 10 ++++++++ src/ifcopenshell-python/ifcopenshell/file.py | 24 ++++++++----------- .../ifcopenshell/util/element.py | 21 +++++++++------- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/context/operator.py b/src/blenderbim/blenderbim/bim/module/context/operator.py index 27ee39ec74..11096912d1 100644 --- a/src/blenderbim/blenderbim/bim/module/context/operator.py +++ b/src/blenderbim/blenderbim/bim/module/context/operator.py @@ -7,11 +7,15 @@ from ifcopenshell.api.context.data import Data class AddSubcontext(bpy.types.Operator): bl_idname = "bim.add_subcontext" bl_label = "Add Subcontext" + bl_options = {"REGISTER", "UNDO"} context: bpy.props.StringProperty() subcontext: bpy.props.StringProperty() target_view: bpy.props.StringProperty() def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): self.file = IfcStore.get_file() ifcopenshell.api.run( "context.add_context", @@ -29,9 +33,13 @@ class AddSubcontext(bpy.types.Operator): class RemoveSubcontext(bpy.types.Operator): bl_idname = "bim.remove_subcontext" bl_label = "Remove Context" + bl_options = {"REGISTER", "UNDO"} ifc_definition_id: bpy.props.IntProperty() def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): self.file = IfcStore.get_file() ifcopenshell.api.run( "context.remove_context", self.file, **{"context": self.file.by_id(self.ifc_definition_id)} diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 3b24fdd890..d533adb430 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -228,10 +228,14 @@ class SwitchRepresentation(bpy.types.Operator): class RemoveRepresentation(bpy.types.Operator): bl_idname = "bim.remove_representation" bl_label = "Remove Representation" + bl_options = {"REGISTER", "UNDO"} obj: bpy.props.StringProperty() representation_id: bpy.props.IntProperty() def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): self.file = IfcStore.get_file() representation = self.file.by_id(self.representation_id) obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object @@ -264,10 +268,14 @@ class RemoveRepresentation(bpy.types.Operator): class UpdateRepresentation(bpy.types.Operator): bl_idname = "bim.update_representation" bl_label = "Update Representation" + bl_options = {"REGISTER", "UNDO"} obj: bpy.props.StringProperty() ifc_representation_class: bpy.props.StringProperty() def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): if not ContextData.is_loaded: ContextData.load(IfcStore.get_file()) @@ -342,6 +350,7 @@ class UpdateRepresentation(bpy.types.Operator): class UpdateParametricRepresentation(bpy.types.Operator): bl_idname = "bim.update_parametric_representation" bl_label = "Update Parametric Representation" + bl_options = {"REGISTER", "UNDO"} index: bpy.props.IntProperty() def execute(self, context): @@ -357,6 +366,7 @@ class UpdateParametricRepresentation(bpy.types.Operator): class GetRepresentationIfcParameters(bpy.types.Operator): bl_idname = "bim.get_representation_ifc_parameters" bl_label = "Get Representation IFC Parameters" + bl_options = {"REGISTER", "UNDO"} def execute(self, context): self.file = IfcStore.get_file() diff --git a/src/ifcopenshell-python/ifcopenshell/file.py b/src/ifcopenshell-python/ifcopenshell/file.py index 7df5e14231..4e57a9830c 100644 --- a/src/ifcopenshell-python/ifcopenshell/file.py +++ b/src/ifcopenshell-python/ifcopenshell/file.py @@ -71,15 +71,19 @@ class Transaction: for inverse in self.file.get_inverse(element): inverse_references = [] for i, attribute in enumerate(inverse): - if attribute == element: - inverse_references.append((i, "single")) - elif isinstance(attribute, tuple) and element in attribute: - inverse_references.append((i, "multiple")) + if self.has_element_reference(attribute, element): + inverse_references.append((i, self.serialise_value(inverse, attribute))) inverses[inverse.id()] = inverse_references self.operations.append( {"action": "delete", "inverses": inverses, "value": self.serialise_entity_instance(element)} ) + def has_element_reference(self, value, element): + if isinstance(value, (tuple, list)): + for v in value: + return self.has_element_reference(v, element) + return value == element + def rollback(self): for operation in self.operations[::-1]: if operation["action"] == "create": @@ -105,16 +109,8 @@ class Transaction: pass for inverse_id, data in operation["inverses"].items(): inverse = self.file.by_id(inverse_id) - for index, data_type in data: - if data_type == "single": - inverse[index] = e - elif data_type == "multiple": - if inverse[index] is None: - inverse[index] = e - else: - new = list(inverse[index]) - new.append(e) - inverse[index] = new + for index, value in data: + inverse[index] = self.unserialise_value(inverse, value) def commit(self): for operation in self.operations: diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 53e2bdba23..b2d113c7ef 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -91,14 +91,19 @@ def get_aggregate(element): def replace_attribute(element, old, new): for i, attribute in enumerate(element): - if attribute == old: - element[i] = new - elif isinstance(attribute, tuple): - new_attribute = list(attribute) - for j, item in enumerate(attribute): - if item == old: - new_attribute[j] = new - element[i] = new_attribute + if has_element_reference(attribute, old): + new_attribute = element.walk(lambda v: v == old, lambda v: new, attribute) + # TODO: make this unnecessary + if element.wrapped_data.file.transaction: + element.wrapped_data.file.transaction.store_edit(element, i, new_attribute) + element[i] = new_attribute + + +def has_element_reference(value, element): + if isinstance(value, (tuple, list)): + for v in value: + return has_element_reference(v, element) + return value == element def remove_deep(ifc_file, element):