diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py index 9f615158ab..1f208585bf 100644 --- a/src/blenderbim/blenderbim/bim/handler.py +++ b/src/blenderbim/blenderbim/bim/handler.py @@ -157,12 +157,12 @@ def loadIfcStore(scene): if not IfcStore.get_file(): return IfcStore.get_schema() - IfcStore.reload_linked_elements() + IfcStore.relink_all_objects() @persistent def undo_pre(scene): - IfcStore.update_undo_redo_stack_objects() + IfcStore.track_undo_redo_stack_object_map() @persistent @@ -171,13 +171,13 @@ def undo_post(scene): IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction IfcStore.undo() purge_module_data() - IfcStore.update_undo_redo_stack_objects() - IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects]) + IfcStore.track_undo_redo_stack_selected_objects() + IfcStore.reload_undo_redo_stack_objects() @persistent def redo_pre(scene): - IfcStore.update_undo_redo_stack_objects() + IfcStore.track_undo_redo_stack_object_map() @persistent @@ -186,8 +186,8 @@ def redo_post(scene): IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction IfcStore.redo() purge_module_data() - IfcStore.update_undo_redo_stack_objects() - IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects]) + IfcStore.track_undo_redo_stack_selected_objects() + IfcStore.reload_undo_redo_stack_objects() @persistent diff --git a/src/blenderbim/blenderbim/bim/ifc.py b/src/blenderbim/blenderbim/bim/ifc.py index f5be2ea01f..e4b86ede7f 100644 --- a/src/blenderbim/blenderbim/bim/ifc.py +++ b/src/blenderbim/blenderbim/bim/ifc.py @@ -43,6 +43,7 @@ class IfcStore: library_file = None element_listeners = set() undo_redo_stack_objects = set() + undo_redo_stack_object_names = {} current_transaction = "" last_transaction = "" history = [] @@ -147,14 +148,32 @@ class IfcStore: IfcStore.element_listeners.add(callback) @staticmethod - def update_undo_redo_stack_objects(): + def track_undo_redo_stack_object_map(): + """Keeps track of currently mapped object names, typically during undo and redo + + When any Blender object is stored outside a Blender PointerProperty, such as + in a regular Python list, there is the likely probability that the object + will be invalidated when undo or redo occurs. Object invalidation seems to + occur whenever an object is affected during an operation. + + For example, if an operator deletes a modifier on o1, then o1 will be invalidated. + """ + for key, value in IfcStore.id_map.items(): + try: + IfcStore.undo_redo_stack_object_names[key] = value.name + except: + continue + + @staticmethod + def track_undo_redo_stack_selected_objects(): """Keeps track of selected object names, typically during undo and redo When any Blender object is stored outside a Blender PointerProperty, such as in a regular Python list, there is the likely probability that the object will be invalidated when undo or redo occurs. Object invalidation seems to - only occur for selected objects either pre/post undo/redo event, including - selected objects for consecutive undo/redos, and all children. + occur for selected objects either pre/post undo/redo event, including + selected objects for consecutive undo/redos, and all children. This is + important because selected objects are often deleted from the scene. So if I first select o1, then o2, then o3, then press undo, o3 will be invalidated. If instead I press undo twice, o3 and o2 will be invalidated. @@ -169,14 +188,23 @@ class IfcStore: IfcStore.undo_redo_stack_objects |= objects @staticmethod - def reload_linked_elements(objects=None): + def reload_undo_redo_stack_objects(): + """Reloads any invalidated objects after undo or redo + + After an undo or redo operation, objects may have been invalidated in + our id_map and guid_map. Invalidated objects are typically those that + have been manipulated or deleted. This checks the cache of mapped and + selected objects prior to the operation and ensures that if the object + is invalidated, they are reloaded based on the object name that was + tracked prior to the undo / redo. + """ file = IfcStore.get_file() if not file: return - if objects is None: - objects = bpy.data.objects - for obj in objects: + # First, reload objects that were selected or active + for name in IfcStore.undo_redo_stack_objects: + obj = bpy.data.objects.get(name) if not obj: continue if not obj.BIMObjectProperties.ifc_definition_id: @@ -187,6 +215,38 @@ class IfcStore: data["guid"] = element.GlobalId IfcStore.commit_link_element(data) + # Scan for any straggling invalidated objects which were indirectly affected and reload them too. + for key, value in IfcStore.id_map.items(): + try: + value.name + except: + obj = bpy.data.objects.get(IfcStore.undo_redo_stack_object_names[key]) + if not obj or not obj.BIMObjectProperties.ifc_definition_id: + continue + element = file.by_id(obj.BIMObjectProperties.ifc_definition_id) + data = {"id": element.id(), "obj": obj.name} + if hasattr(element, "GlobalId"): + data["guid"] = element.GlobalId + IfcStore.commit_link_element(data) + + + @staticmethod + def relink_all_objects(): + file = IfcStore.get_file() + if not file: + return + for obj in bpy.data.objects: + if not obj: + continue + if not obj.BIMObjectProperties.ifc_definition_id: + continue + element = file.by_id(obj.BIMObjectProperties.ifc_definition_id) + data = {"id": element.id(), "obj": obj.name} + if hasattr(element, "GlobalId"): + data["guid"] = element.GlobalId + IfcStore.commit_link_element(data) + + @staticmethod def delete_element(element): IfcStore.deleted_ids.add(element.id()) @@ -312,6 +372,7 @@ class IfcStore: @staticmethod def begin_transaction(operator): IfcStore.undo_redo_stack_objects = set() + IfcStore.undo_redo_stack_object_names = {} IfcStore.current_transaction = str(uuid.uuid4()) operator.transaction_key = IfcStore.current_transaction