0
$\begingroup$

I want to make an add-on using the Timer of ModalOperator.

I want to prevent the same ModalOperator from running if the ModalOperator was already running.

What should I do now?

https://github.com/dfelinto/blender/blob/master/release/scripts/templates_py/operator_modal_timer.py

import bpy


class ModalTimerOperator(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.modal_timer_operator"
    bl_label = "Modal Timer Operator"

    _timer = None

    def modal(self, context, event):
        if event.type in {'RIGHTMOUSE', 'ESC'}:
            self.cancel(context)
            return {'CANCELLED'}

        if event.type == 'TIMER':
            # change theme color, silly!
            color = context.preferences.themes[0].view_3d.space.gradients.high_gradient
            color.s = 1.0
            color.h += 0.01

        return {'PASS_THROUGH'}

    def execute(self, context):
        wm = context.window_manager
        self._timer = wm.event_timer_add(0.1, window=context.window)
        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)


def register():
    bpy.utils.register_class(ModalTimerOperator)


def unregister():
    bpy.utils.unregister_class(ModalTimerOperator)


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.wm.modal_timer_operator()
$\endgroup$
1
$\begingroup$

This can be solved by checking whether the timer has been set before. Unfortunately the example has a little confusing mistake. self._timer = wm.event_timer_add(0.1, window=context.window) does not assign a value to the class attribute _timer as declared at the beginning of the class. Instead it creates an instance attribute named _timer. You can see this by adding the following two lines after the assignment:

print(f"self._timer = {self._timer}")
print(f"ModalTimerOperator._timer = {ModalTimerOperator._timer}")

The first print() will show the the content of the instance attribute, the second print() the content of the class attribute. If you try this you will see that the class attribute will always be None, it's never assigned the value. Therefore this needs to be fixed before you add the check to see if a timer has been previously assigned.

One way of solving this, is making all reads and writes happen to the class attribute by replacing self with ModalTimerOperator and thereby actually using the class attribute declared at the beginning. Then you can check in execute whether the timer is None and only then add a new timer when it is. Set timer to None when the modal operator is canceled.

Update: In order to follow the naming convention I've renamed ModalTimerOperator to WM_OT_modal_timer_operator.

import bpy


class WM_OT_modal_timer_operator(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.modal_timer_operator"
    bl_label = "Modal Timer Operator"

    timer = None

    def modal(self, context, event):
        if event.type in {'RIGHTMOUSE', 'ESC'}:
            self.cancel(context)
            return {'CANCELLED'}

        if event.type == 'TIMER':
            # change theme color, silly!
            color = context.preferences.themes[0].view_3d.space.gradients.high_gradient
            color.s = 1.0
            color.h += 0.01

        return {'PASS_THROUGH'}

    def execute(self, context):
        wm = context.window_manager
        if WM_OT_modal_timer_operator.timer is None:
            WM_OT_modal_timer_operator.timer = wm.event_timer_add(0.1, window=context.window)
            wm.modal_handler_add(self)
            print("Adding new timer")
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self.timer)
        WM_OT_modal_timer_operator.timer = None

def register():
    bpy.utils.register_class(WM_OT_modal_timer_operator)


def unregister():
    bpy.utils.unregister_class(WM_OT_modal_timer_operator)


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.wm.modal_timer_operator()

If you register the operator and then run it multiple times through F3 or Edit > Operator Search, you will see that a new timer is only added when there hasn't been a previous one or it has been canceled.

| improve this answer | |
$\endgroup$
  • $\begingroup$ You could also use type(self) instead of ModalTimerOperator, which makes refactoring a bit quicker in case you decide to rename the class. Not sure what's considered more pythonic. $\endgroup$ – Robert Gützkow Oct 13 '19 at 19:34

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.