1
$\begingroup$

When I invoke my modal operator from my other operator (invoked from a panel button) and the modal operator finishes, Blender's UI is blocked until the mouse moves (i.e. click on the same button has no effect unless the mouse is moved or clicked first).

Also, if the modal operator is invoked from a property update function then the UI is blocked until LMB click (moving or clicking other buttons has no effect).

However, occasionally blocking does not happen.

When looking at events in modal(), event.type/event.value for the first two events are:

  • UI blocked - MOUSMOVE/RELEASE, MOUSMOVE/RELEASE
  • UI not blocked - MOUSMOVE/RELEASE, NONE/NOTHING

An obvious question is why are there two MOUSMOVE/RELEASE events anyway ...

I am wondering why is this happening and even more is there a way to avoid this UI blocking?

(The reason I'm doing all this is in my other question.)

Example code:

import bpy
from bpy.types import Operator

class TheModalOperator(Operator):
    bl_idname = "my.themodaloperator"
    bl_label = "TheModalOperator Operator"

    # counter of calls to modal()
    countmodal = 0

    def __del__(self):
        print("TheModalOperator End")

    def invoke(self, context, event):
        print("TheModalOperator invoke")
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def modal(self, context, event):
        self.countmodal += 1
        print("TheModalOperator modal", self.countmodal, event.type, event.value)
        if self.countmodal >= 2:
            print("---TheModalOperator modal finished")
            return {'FINISHED'}
        else:
            print("---TheModalOperator modal running")
            return {'RUNNING_MODAL'}       


class MyOperator(Operator):
    """This is my operator"""
    bl_idname = "my.operator"
    bl_label = "My Operator"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        print("MyOperator execute")
        self.report({'INFO'}, "MyOperator executed")
        bpy.ops.my.themodaloperator('INVOKE_DEFAULT')
        return {'FINISHED'}


# button on the toolshelf
class MyTestPanel(bpy.types.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="This blocks UI until a mouse event:")
        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$
  • $\begingroup$ Found solution: modal should return PASS_THROUGH (solves invoking from a property update function) and a timer (solves invoking from another operator). Will post this as an answer tomorrow. $\endgroup$ – spacer May 8 '17 at 22:25
1
$\begingroup$

There are two fixes needed.

1. The modal() must return PASS_THROUGH instead of RUNNING_MODAL.
That allows Blender to process mouse events, however, fixes the problem only when invoked from property update function.

2. Using a timer instead of the counter.
I'm not clear why not just changing the counter limit to 3 does not work (that just requires another mouse event, i.e. need to move or click the mouse).
Nevertheless, the modal timer does fix the problem for invoking from other operator too.

Fixed code:

import bpy
from bpy.types import Operator

class TheModalOperator(Operator):
    bl_idname = "my.themodaloperator"
    bl_label = "TheModalOperator Operator"

    timer = None

    def invoke(self, context, event):
        print("TheModalOperator invoke")
        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):
        print("TheModalOperator modal", event.type, event.value)
        if event.type == 'TIMER':
            print("---TheModalOperator modal finished")
            context.window_manager.event_timer_remove(self.timer)
            return {'FINISHED'}
        else:
            print("---TheModalOperator modal pass-through")
            return {'PASS_THROUGH'}       


class MyOperator(Operator):
    """This is my operator"""
    bl_idname = "my.operator"
    bl_label = "My Operator"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        print("MyOperator execute")
        self.report({'INFO'}, "MyOperator executed")
        bpy.ops.my.themodaloperator('INVOKE_DEFAULT')
        return {'FINISHED'}


# button on the toolshelf
class MyTestPanel(bpy.types.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="This blocks UI until a mouse event:")
        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$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.