1
$\begingroup$

I'm writing below script which starts modal() function in register() function, however I could not stop modal() function when disable add-on.

is there any way to stop modal mode from unregister() function?

import bpy
from bpy.app.handlers import persistent


def get_invoke_context(area_type, region_type):
    for window in bpy.context.window_manager.windows:
        screen = window.screen
        for area in screen.areas:
            if area.type == area_type:
                break
        else:
            continue
        for region in area.regions:
            if region.type == region_type:
                break
        else:
            continue
        return {'window': window, 'screen': screen, 'area': area, 'region': region}


class ModalTest(bpy.types.Operator):

    bl_idname = "system.modal_test"
    bl_label = "Test"
    bl_description = "Test"

    def __init__(self):
        self.__timer = None

    def __handle_add(self, context):
        if self.__timer is None:
            self.__timer = context.window_manager.event_timer_add(
                0.1, context.window
            )
            context.window_manager.modal_handler_add(self)

    def __handle_remove(self, context):
        if self.__timer is not None:
            context.window_manager.event_timer_remove(self.__timer)
            self.__timer = None

    def modal(self, context, event):
        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        self.__handle_add(context)
        return {'RUNNING_MODAL'}


@persistent
def start_fn(scene):
    bpy.app.handlers.scene_update_pre.remove(start_fn)
    context = get_invoke_context('VIEW_3D', 'WINDOW')
    bpy.ops.system.modal_test(context, 'INVOKE_DEFAULT')


def register():
    bpy.utils.register_module(__name__)
    bpy.app.handlers.scene_update_pre.append(start_fn)


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


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

I would not recommend starting your modal operator in the register() function. Typically you want to add a button or something to a UI panel, which the user can use to invoke your operator.

But if you're sure you want to start and stop your operator using register() and unregister(), then you could try something like this:

#insert this after your imports:
still_running = True

#insert this in your modal() method:
global still_running
if still_running == False:
    return {'FINISHED'}

#insert this in your unregister() method:
global still_running
still_running = False
$\endgroup$
1
  • $\begingroup$ Have you actually tried this solution? In current version of Blender it does not finish modal operator during unregistering, it never returns "Finished". After registering again modal operator does not work anymore. If I add "still_running = True" to the register() function, I end up with multiple modal operators running after multiple unregister()/register() calls. The same thing happens if I do not add anything to the code to attempt to finish the modal operator. In my case using a handler directly crashes Blender, so I have to run timer-based modal operator and periodically check a flag. $\endgroup$ Oct 4 '20 at 4:51
0
$\begingroup$

To stop the modal operator properly, just remove the timer in the unregister() function (instead of attempting to do that in the modal operator itself).

Here is an example how to start a modal operator after an add-on is registered (it initializes after the first depsgraph update) and to stop it when unregistering. It will print a message in the console every second while running.

bl_info = {
    "name": "Modal Timer Test",
    "author": "Lissanro Rayen (Dragon.Studio)",
    "version": (1, 0),
    "blender": (2, 90, 0),
    "location": "The console",
    "description": "Print a debug message in the console every second. It starts after the first depsgraph update after registering and continues until unregistered.",
    "warning": "",
    "doc_url": "",
    "category": "System",
}

import bpy
from bpy.app.handlers import persistent

def my(): pass
my.modal_timer = None

class ModalTimer(bpy.types.Operator):
    bl_idname = "scene.modal_timer"
    bl_label = "Modal Timer Operator"
    _timer = None

    def modal(self, context, event):
        if event.type == "TIMER":
            print("Modal operator timer event:", self._timer)
        return {"PASS_THROUGH"}

    def execute(self, context):
        my.modal_timer = self._timer = context.window_manager.event_timer_add(1, window=context.window)
        context.window_manager.modal_handler_add(self)
        return {"RUNNING_MODAL"}

@persistent
def my_handler2(scene):
    bpy.ops.scene.modal_timer()
    bpy.app.handlers.frame_change_post.remove(my_handler2)

@persistent
def my_handler(scene):
    bpy.app.handlers.frame_change_post.append(my_handler2)
    bpy.context.scene.frame_current=bpy.context.scene.frame_current
    bpy.app.handlers.depsgraph_update_pre.remove(my_handler)

classes = (
    ModalTimer,
)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.app.handlers.depsgraph_update_pre.append(my_handler)

def unregister():
    if my.modal_timer:
        bpy.context.window_manager.event_timer_remove(my.modal_timer)
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)

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.