1
$\begingroup$

When I execute an operator that changes mouse pointer with cursor_set("WAIT") from Toolshelf and keep the mouse pointer over the Toolshelf, the mouse pointer stays as WAIT after the modal operation has finished. When the mouse is then moved over to the viewport region, it reverts to default.
If the mouse is moved over to the viewport region during the operation, the mouse pointer will revert as soon the operation finishes.

If I add bpy.context.window.cursor_set('DEFAULT') before returning, the mouse pointer will immediately revert to default, of course.

Is there a way to revert the mouse pointer to default over the Toolshelf after the modal operator finishes?

(It is not just modal operators, same thing applies to Blender's "internal" operations that sometimes take long time and "run in background", like setting a modifier's show_viewport option to True.)

Example code:

import bpy
from bpy.types import Operator, Panel

class MyOperator(Operator):
    """This is my operator"""
    bl_idname = "my.operator"
    bl_label = "My Operator"

    def execute(self, context):
        context.window.cursor_set("WAIT")
        bpy.ops.transform.translate('INVOKE_DEFAULT')
        return {'FINISHED'}

class MyTestPanel(Panel):
    bl_idname = "mytestpanel"
    bl_category = "My Stuff"
    bl_label = "My Test Panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'
    bl_context = "objectmode"

    def draw(self, context):
        layout = self.layout
        col = layout.column(align=True)
        col.label(text="My operator:")
        col.operator("my.operator", text="Run my operator")

def register(): 
    bpy.utils.register_module(__name__)

def unregister(): 
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    register()
$\endgroup$

1 Answer 1

1
$\begingroup$

In the good old tradition I'll answer my own question. :)

Updated (fixed issues explained in my other question)!

The solution is to use a modal operator invoked before invoking the built-in operator.

I'm not an expert in this, but what appears to be happening is that the operators are invoked "on top of each other" so the user first deals with the last one invoked - and the first one (here my.cursorsetdefaultmodal) finishes after the second one (here transform.translate) finishes.

To make this work without hiccups in all cases, however, the modal operator must use a timer to, apparently, allow enough time for Blender to process all mouse events properly. Without the timer it will work only when invoked from a property update function, not from an operator (see why in that other question).

The bonus is that this also works for long Blender's "internal" operations (those "running in background" but still blocking Blender UI, like setting a modifier's show_viewport option to True) even though they are not really modal.
I found out that Blender will run modal() at most once before it starts working on the long internal operation - and it will continue running modal() again after the long operation has finished.

Example code:

import bpy
from bpy.types import Operator, Panel

class CursorSetDefaultModal(Operator):
    bl_idname = "my.cursorsetdefaultmodal"
    bl_label = "CursorSetDefaultModal Operator"

    timer = None

    def invoke(self, context, event):
        self.timer = context.window_manager.event_timer_add(0.1, context.window)
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def modal(self, context, event):
        if event.type == 'TIMER':
            context.window_manager.event_timer_remove(self.timer)
            context.window.cursor_set("DEFAULT")
            return {'FINISHED'}
        return {'PASS_THROUGH'}

class MyOperator(Operator):
    """This is my operator"""
    bl_idname = "my.operator"
    bl_label = "My Operator"

    def execute(self, context):
        context.window.cursor_set("WAIT")
        bpy.ops.my.cursorsetdefaultmodal('INVOKE_DEFAULT')
        bpy.ops.transform.translate('INVOKE_DEFAULT')
        return {'FINISHED'}

class MyTestPanel(Panel):
    bl_idname = "mytestpanel"
    bl_category = "My Stuff"
    bl_label = "My Test Panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'
    bl_context = "objectmode"

    def draw(self, context):
        layout = self.layout
        col = layout.column(align=True)
        col.label(text="My operator:")
        col.operator("my.operator", text="Run my operator")

def register(): 
    bpy.utils.register_module(__name__)

def unregister(): 
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    register()
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .