When writing a modal operator, handling each event runs the 'modal()' callback which handles a single event.

While this makes sense, its not always convenient compared to a simple loop that consumes events (local variables need to be stored in the operator and restored every time, and you can't use flow control (break/continue) - for example).

Is there a way to handle events for a modal operator in a Python loop?

psudo-code example:

def modal_loop():
    for event in modal_events():
        # handle event
        if event.type == 'ESC':
            return {'CANCELLED'}

        if other_test:
    return {'FINISHED'}

Note, this question is a little contrived. Since it should be possible using some of Python's coroutines, posting here since its probably a useful reference for others too.


Yes, Python's coroutines can be used make the operators callbacks interact with a generator.

Here's an example of a generic re-usable operator mix-in class, and an example usage.

See modal_iter function for an example of how (invoke, execute, modal, cancel) can be handled from within a single generator.

import bpy
from bpy.types import Operator

# -----------------------------
# Generic Modal Iterator Mix-In

class ModalIterOperator:
    """ Defines Operator callbacks (invoke, execute, modal, cancel).
        Sub-classes only need to define `modal_iter` generator.

    def invoke(self, context, event):
        self._iter = iter(self.modal_iter(context))
        result = self._iter.send(event)

        if 'RUNNING_MODAL' in result:
            wm = context.window_manager
        return result

    def execute(self, context):
        return self.invoke(context, None)

    def modal(self, context, event):
        return self._iter.send(event)

    def cancel(self, context):

# ----------------------------------
# Example of ModalIterOperator Usage

class MyIterTest(Operator, ModalIterOperator):
    bl_idname = "wm.my_modal_iter_operator"
    bl_label = "My Modal Iter Operator"

    # -------------------------------------------
    # Entire modal operator in a single function!

    def modal_iter(self, context):

        event = yield

        if event is not None:
            # -------------------------------------
            # Section typically handled by invoke()

            print("Operator.invoke(..., event=%r)" % event.type)

            # Check we would typically do inside invoke()
            # to see if we want to exit, or continue and run modal()
            if event.type == 'ESC':
                yield {'CANCELLED'}
            # --------------------------------------
            # Section typically handled by execute()


            # We may want to cancel the operator here too.
            if 0:
                yield {'CANCELLED'}

        # ------------------------------------
        # Section typically handled by modal()
        event = yield {'RUNNING_MODAL'}

        while event is not None:
            print("Operator.modal(..., event=%r)" % event.type)

            if event.type in {'RIGHTMOUSE', 'ESC'}:
                yield {'CANCELLED'}

            # ------------------------------------
            # Main modal operator logic goes here!
            # ------------------------------------                

            event = yield {'RUNNING_MODAL'}

        # cancel may be initiated externally
        if event is None:
            # -------------------------------------
            # Section typically handled by cancel()
            yield  # --> None, since we're closed externally

        yield {'FINISHED'}

def register():

def unregister():

if __name__ == "__main__":

    # test call


  • None of the return's in modal_iter are needed, since the way the generator is used these points will never be passed anyway. I've added them in so as to be clear whats happening and so any accidental iteration past yield {'CANCELLED'} will raise a StopIteration exception, which intentionally isn't handled.
  • This code example may seem overly verbose, this is done for completeness.
    In practice you may not need all of the callbacks (cancel or execute for eg).
  • This article was used in writing the example above.
  • $\begingroup$ Nice one. I'm going to have to read up on docs.python.org/3/library/functions.html#iter. $\endgroup$ – batFINGER Feb 25 '16 at 4:09
  • $\begingroup$ While the referenced article is focused on using python 3.5, this example will work fine on older versions of blender as well (2.50+) not just 2.77. $\endgroup$ – sambler Feb 26 '16 at 14:37
  • $\begingroup$ I'm not quite sure, what the reasoning is? What is the advantage? Speed? I'm not solid on coroutiines so I'm sorry if I miss something obvious. It looks interesting, but for me, the goal is unclear. $\endgroup$ – Teck-freak Mar 27 '20 at 16:53
  • $\begingroup$ The advantage is that you don't need to store and load state for each modal call, instead, all variables persist. As well as the ability to use more flow control to manage state in a more Pythonic way. $\endgroup$ – ideasman42 Mar 27 '20 at 22:00
  • $\begingroup$ @ideasman42 would this solution work for my issue here blender.stackexchange.com/questions/220073/… or it not really relevant? $\endgroup$ – ttsesm Apr 20 at 15:39

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.