Need Help With Event Argument To modal() Method of ModalOperator Class

Hi folks. I’m struggling with the documentation for the ModalOperator subclass of the bpy.types.Operator Class in the Python documentation for Blender 2.8. The modal method on the page looks like it is the same code from the file in Blender’s Text Editor window template file (Templates > Python > Operator Modal) in which any details about the only keyboard-based event ('ESC') are irrelevant because it is for terminating the script.

I would like to use keyboard events to determine what the user intends to do while the operator is running, but the documentation only seems to show how to find out that something happened with a keyboard key; it doesn’t say anything about finding out what that something was. For example, adding the following tiny bit of code to the modal method in the the above mentioned Modal Operator template file and then running it, shows that an event is fired any time that a key is pressed or released and it will fire repeatedly when the key is held down.

elif event.type == 'X': print('Something happened with the x key') return {'RUNNING_MODAL'}
(For some reason the code tag formatter is putting the return statement as the previous line. I’ve no idea why)

I can’t find any documentation for this event argument or it’s type property, so I can’t find any way of differentiating one type of key event from the other. For something like the Java documentation, the object types that are passed to a method would normally be documented as links to the documentation for that object type, but the Python documentation doesn’t seem to have that so I can’t find the documentation for this event argument. Is there a way of telling the difference beween someone pomeone tapping a key as normal, someone olding the key down and someone releasing a key, or do I need to maintain some information in the operator object itself, in an effort to try and guess what’s going on?

Double backticks for inline code.

Triple backticks for code blocks.

Modal events documentation here.
https://docs.blender.org/api/master/bpy.types.Event.html

You can also browse the event members in the interactive console by looking at the rna definitions in:

bpy.types.Event.bl_rna.properties

Auto-completing this will list all members as keys. Some of these have descriptions:

>>> bpy.types.Event.bl_rna.properties['value'].description
'The type of event, only applies to some'

>>> bpy.types.Event.bl_rna.properties['mouse_x'].description
'The window relative horizontal location of the mouse'

The event system doesn’t specifically distinguish between holding and tapping a regular key. When you press a key, event.value will return PRESS. When the key is released, it will return RELEASE. If a regular key is held down, PRESS is repeatedly returned. I personally don’t like this behavior, but it does simplify executing several operations in a row easy.

Only for modifier keys like ctrl, alt, shift are natively supported as being held down. When any of these are held down, their events (event.shift, event.alt, event.ctrl) return True.

You could stage a semaphore for each key you want to use as a modifier:

Example pressing F key during modal:

import bpy

class ModalOperator(bpy.types.Operator):
    bl_idname = "object.modal_operator"
    bl_label = "Simple Modal Operator"
    
    _escape = {'SPACE', 'RET', 'RIGHTMOUSE', 'ESC'}

    def modal(self, context, event):
        if event.type == 'F':
            if not self.f:
                if 'PRESS' in event.value:
                    self.f = True
                    print("Paying respects")
            elif 'RELEASE' in event.value:
                self.f = False
                print("F released")
            else:
                pass
#                print("F is being held down")
        
        if event.type in self._escape:
            return {'FINISHED'}

        return {'RUNNING_MODAL'}

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


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

if '__main__' in __name__:
    register()

    bpy.ops.object.modal_operator('INVOKE_DEFAULT')
2 Likes

Awesome. Thatks for the help again. I guess I’m a bit too used to the method of organising documentation that Java used; just scroll down an enormous list of packages and then find the class’ documentation from there. Wasn’t exactly compact, but it was straight forward enough for me to follow.

Oh and magnificent example code use there, btw. :wink:

I agree. Going from being used to max sdk to blender api was tough. It would have been unbearable without auto-complete in the interactive console. I don’t think people appreciate this feature enough :stuck_out_tongue:

1 Like

Awesome! It never worked for me before because I’m an absolute itiot. I just assumed autocomplete was broken on my computer because it didn’t work when I pressed the tab key. I guess the Linux CLI and Cisco hardware got me so habituated on pressing tab for auto complete that I just thought “That’s weird. Autocomplete doesn’t work on this computer”, rather than just hovering the mouse over the console’s autocomplete button and waiting for the little tooltip to appear that would tell me that it’s Ctrl + Space. There’s a reason for this name…

1 Like