How to flush last keypress before entering modal loop?

When launching an add-on with a modal loop, is there a “standard method” for flushing out the last key press in an invoke method before entering a modal loop? For example, if you launch the add-on from Blender’s search bar by pressing Return, the “RET PRESS” event is removed from event space by the search bar, but not the “RET RELEASE” event. What would be a good method to clear the “RET RELEASE” to prevent it from being read in the modal loop?

The current code I am using is this, but it filters out all “RELEASE” event values which is not ideal:

import bpy
found_key = None

class MOD_OT_GetKey(bpy.types.Operator):
    bl_idname = "object.mod_operator_get_key"
    bl_label = "Get Pressed Key"

    def modal(self, context, event):
        if event.type == 'ESC':
            return {'CANCELLED'}
        elif event.type != 'MOUSEMOVE' and event.type != 'INBETWEEN_MOUSEMOVE':
            if event.value != 'RELEASE':
                global found_key
                found_key = event.type
                print("event.type", event.type, "event.value", event.value)
                return {'FINISHED'}
        return {'RUNNING_MODAL'}

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


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

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

if __name__ == "__main__":
    register()

The initial RET PRESS is captured by the operator, but gets swallowed in the invoke method. You can try print(event.type, event.value) under invoke to verify this.

Once we know this, we can just store this fact as a state so that when the modal is called the first time, presumably by the RET upstroke, we can block it with RUNNING_MODAL and reset the state.

import bpy
found_key = None
ignore = {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE', 'TIMER', 'TIMER_REPORT'}

class MOD_OT_GetKey(bpy.types.Operator):
    bl_idname = "object.mod_operator_get_key"
    bl_label = "Get Pressed Key"

    def modal(self, context, event):

        # block the initial 'RET' and 'RELEASE'
        if self.init_ret and event.type == 'RET' and event.value == 'RELEASE':
            self.init_ret = False
            return {'RUNNING_MODAL'}

        if event.type == 'ESC':
            return {'CANCELLED'}

        elif event.type not in ignore:
            global found_key
            found_key = event.type
            print("event.type", event.type, "event.value", event.value)

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        
        # get whether the op was called with 'RET' and 'PRESS'
        self.init_ret = event.type == 'RET' and event.value == 'PRESS'
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


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

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

if __name__ == "__main__":
    register()
1 Like

Ah, I did not realize invoke was what removed it. I did use a print statement like that, but just in the modal definition. All I knew was the “PRESS” was removed before the “def modal” block executed.

Bah, I wasted some time thinking your code was broken until I realized you had removed the return {'FINISHED'} from the elif event.type not in ignore: code block. :confounded:

Interesting setup though! I tried something similar to this earlier in testing, but could not make it work. It was probably not accounting for invoke capturing the “PRESS” value. :thinking:

Can you think of any issues that modifying the script to just check for PRESS events would cause? This type of modification would account for different keymappings and other selection hotkey events (like LEFTMOUSE) instead of just RET:

import bpy
found_key = None
ignored_events = {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE', 'TIMER', 'TIMER_REPORT'}

class MOD_OT_GetKey(bpy.types.Operator):
    bl_idname = "object.mod_operator_get_key"
    bl_label = "Get Pressed Key"

    def modal(self, context, event):

        # skip the first 'RELEASE' event if invoke detected a 'PRESS' event
        if self.init_press and event.value == 'RELEASE':
            self.init_press = False
            return {'RUNNING_MODAL'}

        elif event.type == 'ESC':
            return {'CANCELLED'}

        elif event.type not in ignored_events:
            global found_key
            found_key = event.type
            print("event.type", event.type, "event.value", event.value)
            return {'FINISHED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):

        # get whether the op was called with a 'PRESS' event
        self.init_press = event.value == 'PRESS'
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


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

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

if __name__ == "__main__":
    register()

My bad, I removed that so I could continue testing. Forgot to put it back!

In theory any arbitrary keymap types can be filtered out - you’d just store the initial event.type in the invoke and check against it in the modal.

Edit: Also added check against event.value != 'RELEASE' in case of modifier keys.

import bpy
found_key = None
ignored_events = {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE', 'TIMER', 'TIMER_REPORT'}

class MOD_OT_GetKey(bpy.types.Operator):
    bl_idname = "object.mod_operator_get_key"
    bl_label = "Get Pressed Key"

    def modal(self, context, event):

        # skip the first 'RELEASE' event if invoke detected a 'PRESS' event
        # added self.init_type (see invoke)
        if self.init_press and event.value == 'RELEASE' and self.init_type == event.type:
            self.init_press = False
            return {'RUNNING_MODAL'}

        elif event.type == 'ESC':
            return {'CANCELLED'}

        elif event.type not in ignored_events and event.value != 'RELEASE':
            global found_key
            found_key = event.type
            print("event.type", event.type, "event.value", event.value)
            return {'FINISHED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):

        # get whether the op was called with a 'PRESS' event
        self.init_press = event.value == 'PRESS'

        # store the key that initiated the operator - could be any key
        self.init_type = event.type
#        print(event.type, event.value)
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


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

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

if __name__ == "__main__":
    register()
1 Like