Grease pencil UI gets stuck between draw mode/tool and edit mode. #78897

Closed
opened 2020-07-13 21:20:28 +02:00 by Tomasz Kaye · 7 comments

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: blender/blender@5a13f682ee

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.

image.png

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
    image.png
  • switch to grease pencil edit mode, select the entire line and move it a little using G
    image.png
  • switch back to draw mode and draw a second shape
    image.png
  • 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.
    image.png
**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: `blender/blender@5a13f682ee` **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. ![image.png](https://archive.blender.org/developer/F8691970/image.png) I ran across this while working on an add-on that tries to wrap blender's undo (more context [here ](https://blenderartists.org/t/my-undo-wrapper-add-on-breaks-the-grease-pencil-ui-help-needed/1240787)). 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 ![image.png](https://archive.blender.org/developer/F8692027/image.png) * switch to grease pencil edit mode, select the entire line and move it a little using G ![image.png](https://archive.blender.org/developer/F8692033/image.png) * switch back to draw mode and draw a second shape ![image.png](https://archive.blender.org/developer/F8692037/image.png) * 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. ![image.png](https://archive.blender.org/developer/F8692041/image.png)
Author

Added subscriber: @info-27

Added subscriber: @info-27
Author

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()
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() ```

Added subscriber: @iss

Added subscriber: @iss

Changed status from 'Needs Triage' to: 'Needs User Info'

Changed status from 'Needs Triage' to: 'Needs User Info'

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.

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.
Author

Changed status from 'Needs User Info' to: 'Resolved'

Changed status from 'Needs User Info' to: 'Resolved'
Tomasz Kaye self-assigned this 2020-07-15 08:13:00 +02:00
Author

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.

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.
Sign in to join this conversation.
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender-addons#78897
No description provided.