Page MenuHome

Grease pencil UI gets stuck between draw mode/tool and edit mode.
Closed, ResolvedPublic

Description

System Information
Operating system: Darwin-19.5.0-x86_64-i386-64bit 64 Bits
Graphics card: NVIDIA GeForce GT 650M OpenGL Engine NVIDIA Corporation 4.1 NVIDIA-14.0.32 355.11.11.10.10.143

Blender Version
Broken: version: 2.90.0 Alpha, branch: master, commit date: 2020-07-02 22:21, hash: rB5a13f682ee5f

Short description of error

Blender will sometimes end up in a weird state where the UI for grease pencil's draw tool is displayed incorrectly, it looks like a mix between draw mode and edit mode.

I ran across this while working on an add-on that tries to wrap blender's undo (more context here).

I was surprised my add-on could even get blender into this state (and i don't understand how/why it's happening) so thought i should report it in case it's a blender bug. I haven't been able to reproduce this without using the add-on code. The add-on calls blenders undo, sometimes more than once per invocation.

Here's the add-on:

bl_info = {
    "name": "Keep Active Tool Undo",
    "blender": (2, 90, 0),
    "category": "Object",
}
import bpy

class KeepActiveToolUndo(bpy.types.Operator):
    """Keep Active Tool Undo"""
    bl_idname = "wm.keep_active_tool_undo"
    bl_label = "Keep Active Tool Undo"

    def execute(self, context):
        
        # record active tool and mode
        original_mode = bpy.context.mode
        original_tool_id = bpy.context.workspace.tools.from_space_view3d_mode(original_mode, create=False).idname
        print("mode: "+original_mode)
        print("tool id: "+original_tool_id)
        
        # undo
        try:
            print("undo")
            bpy.ops.ed.undo() #this line fails if undo stack is empty
        except:
            print("FAILED TO UNDO")
            self.report({'INFO'}, 'Failed to undo')
            
        # record new active tool and mode
        current_mode = bpy.context.mode
        current_tool_id = bpy.context.workspace.tools.from_space_view3d_mode(current_mode, create=False).idname
        print("mode: "+current_mode)
        print("tool id: "+current_tool_id)

        # change active tool and mode back to previous ones if they've changed after the undo
        if original_tool_id != current_tool_id:
            for area in bpy.context.screen.areas:
                if area.type == "VIEW_3D":
                    override = bpy.context.copy()
                    override["space_data"] = area.spaces[0]
                    override["area"] = area
                    bpy.ops.object.mode_set ( mode = original_mode )
                    bpy.ops.wm.tool_set_by_id(override, name=original_tool_id)
                     
            print("tool id different **")
            print("")

            #recursively undo, because we want to undo the previous action in the stack that changed a stroke
            self.execute(context)
        print("done --------------------------------------") 
        print("")   
        return {'FINISHED'}

# store keymaps here to access after registration
addon_keymaps = []

def register():
    bpy.utils.register_class(KeepActiveToolUndo)
    wm=bpy.context.window_manager
    kc=wm.keyconfigs.addon
    if kc:
       km=kc.keymaps.new(name = "Window",space_type='EMPTY', region_type='WINDOW')
       kmi = km.keymap_items.new("wm.keep_active_tool_undo", type='Z', value='PRESS',oskey=True)
       addon_keymaps.append((km,kmi)) 

def unregister():
    for km,kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
        addon_keymaps.clear()
    bpy.utils.unregister_class(KeepActiveToolUndo)

if __name__ == "__main__":
    register()

Exact steps for others to reproduce the error

  • file > new > 2d animation
  • preferences > keymap. Unmap cmd z from the undo command (the add-on uses this shortcut)
  • install the add-on above, activate it
  • draw a line using the grease pencil draw tool

  • switch to grease pencil edit mode, select the entire line and move it a little using G

  • switch back to draw mode and draw a second shape

  • press cmd+z twice (with the wrapper add-on active) and you get to this weird state. The interface shows a mixture of paint and edit buttons, and you can’t paint.

Event Timeline

It seems like calling the execute method recursively was causing the problem (are you not supposed to do that?). When I changed the code around to use a while loop instead of recursion it worked as expected.

bl_info = {
    "name": "Keep Active Tool Undo",
    "version": (1, 0),
    "blender": (2, 90, 0),
    "category": "Object",
}
import bpy

class KeepActiveToolUndo(bpy.types.Operator):
    """Keep Active Tool Undo"""
    bl_idname = "wm.keep_active_tool_undo"
    bl_label = "Keep Active Tool Undo"

    def execute(self, context):
        
        # record active tool and mode
        original_mode = context.mode
        original_tool_id = context.workspace.tools.from_space_view3d_mode(original_mode, create=False).idname
        print("mode: "+original_mode)
        print("tool id: "+original_tool_id)
        
        done=False
        current_mode = original_mode
        current_tool_id = original_tool_id

        # keep trying to undo until the tool id hasn't changed after the undo
        while done==False:
            # undo
            try:
                print("undo")
                bpy.ops.ed.undo() #this line fails if undo stack is empty
            except:
                print("FAILED TO UNDO")
                self.report({'INFO'}, 'Failed to undo')
            # check new active tool and mode
            current_mode = context.mode
            current_tool_id = context.workspace.tools.from_space_view3d_mode(current_mode, create=False).idname
            print("mode: "+current_mode)
            print("tool id: "+current_tool_id)
            if original_tool_id == current_tool_id:
                done=True
                print("done")
                
            
        # change active tool back to the one we were on when this operator was called
        if original_tool_id != current_tool_id:
            print("tool id different **")
            print("")
            for area in context.screen.areas:
                if area.type == "VIEW_3D":
                    override = context.copy()
                    override["space_data"] = area.spaces[0]
                    override["area"] = area
                    bpy.ops.object.mode_set ( mode = original_mode )
                    bpy.ops.wm.tool_set_by_id(override, name=original_tool_id)

        print("done --------------------------------------") 
        print("")   
        return {'FINISHED'}

# store keymaps here to access after registration
addon_keymaps = []

def register():
    bpy.utils.register_class(KeepActiveToolUndo)
    wm=bpy.context.window_manager
    kc=wm.keyconfigs.addon
    if kc:
       km=kc.keymaps.new(name = "Window",space_type='EMPTY', region_type='WINDOW')
       kmi = km.keymap_items.new("wm.keep_active_tool_undo", type='Z', value='PRESS',oskey=True)
       addon_keymaps.append((km,kmi)) 

def unregister():
    for km,kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
        addon_keymaps.clear()
    bpy.utils.unregister_class(KeepActiveToolUndo)

if __name__ == "__main__":
    register()
Richard Antalik (ISS) changed the task status from Needs Triage to Needs Information from User.Jul 15 2020, 1:57 AM

It is possible to get some invalid states or even crashes with python code. We try to sanitize these situations if possible but won't necessarily consider them to be bugs.

I don't think problem is calling operator execute function recursively. Just looking at code I see that you did not include everyting (setting original_mode) in a loop so perhaps that may be the issue.

Should we consider this issue resolved? Please keep in mind that this is not site to provide user support.

Tomasz Kaye (bitbutter) closed this task as Resolved.Jul 15 2020, 8:13 AM
Tomasz Kaye (bitbutter) claimed this task.

i've set it to resolved. I'll open a new issue if i can get the same problem with more simple code in the future.