Undo/redo within multi-tool modal operator

I’m working on an add-on that features a modeling multi-tool modal operator, github file

im currenlty running into a sever problem which is , undo and redo, act on everything done while the operator was running, meaning the operator could have been running for 10 minutes and it would undo everything done in that time frame if not stopped with esc and such

this is the code i’ve been using to handle the undo/redo process, ignoring the double triggering of the event
as im aware press and release will cause it to be called two times per button press

    if (event.type == "Z") and (event.shift == False) and (event.ctrl == True):
        print("key undo")
        try:
            bpy.ops.ed.undo()
            print("undo")
        except Exception as error:
            print(error)
        
        return {"RUNNING_MODAL"}

    if (event.type == "Z") and (event.shift == True) and (event.ctrl == True):
        print("key redo")
        try:
            bpy.ops.ed.redo()
            print("redo")
        except Exception as error:
            print(error)
        
        return {"RUNNING_MODAL"}

this seems to not be registering the changes done by the modal and only the before invoke and after completed, instead of every action in between, which considering the nature of the tool breaks pretty much everything

You can insert items into the undo list with bpy.ops.ed.undo_push(message='Your Undo Step')

It is not perfect, and may not capture every event, especially if it involves switching back and forth between Edit and Object mode, but it should allow you to add undo ‘steps’ to your modal operator.

1 Like

i will try this thanks, i’ll get back with an update if it fixed it or not, there’s no mode switching just a lot of bpy.ops being called to remove the need of shortcuts, still can’t upload attachments as the video would make it a bit clearer

it does indeed work thank you, adding this to each singular action/for-loop even tho it might be heavy it allows much more control than i expected
fixed exampe:

if (event.type == "LEFTMOUSE") and (event.ctrl == False) and (event.value == "CLICK"):
    if (context.mode == "EDIT_MESH"):
        override_window = context.window
        override_screen = override_window.screen
        override_area = [area for area in override_screen.areas if area.type == "VIEW_3D"]
        override_region = [region for region in override_area[0].regions if region.type == 'WINDOW']
    
        with context.temp_override(window=override_window, area=override_area[0], region=override_region[0]):
            if (leftclick_selection_mode == "NONE"):
                bpy.ops.view3d.select("INVOKE_DEFAULT", extend=leftclick_extend_selection)
                bpy.ops.ed.undo_push(message=f"AlxUM Select extend:{leftclick_extend_selection}")
                return {"RUNNING_MODAL"}
    
            elif (leftclick_selection_mode == "LOOP_SELECTION"):
                bpy.ops.mesh.loop_select("INVOKE_DEFAULT", extend=leftclick_extend_selection, deselect=False, toggle=False, ring=False)
                bpy.ops.ed.undo_push(message=f"AlxUM Loop Select extend:{leftclick_extend_selection}")
                return {"RUNNING_MODAL"}
            elif (leftclick_selection_mode == "LINKED_SELECTION"):
                bpy.ops.view3d.select("INVOKE_DEFAULT")
                bpy.ops.mesh.select_linked("INVOKE_DEFAULT", delimit={"SEAM"})
                bpy.ops.ed.undo_push(message=f"AlxUM Linked Select extend:{leftclick_extend_selection}")
                return {"RUNNING_MODAL"}
              return {"RUNNING_MODAL"}
1 Like