5
$\begingroup$

Creating a modal operator is not an issue, but hard-coding the user tools, interactions, and other events is quite cumbersome and not flexible for the user.

Is there a way to check against modal keymap properties within a modal operator through Python?

For example, in my modal I let users select elements with one of the mouse buttons. This is fine for most users but it becomes problematic if a user changes their selection mouse defaults (as an example).

I know how to create a modal keymap, like so:

kc = bpy.context.window_manager.keyconfigs.addon

# Create the mode switch menu hotkey
km = kc.keymaps.new(name='3D View', space_type='VIEW_3D')
kmi = km.keymap_items.new('wm.call_menu', 'V', 'PRESS', ctrl=True, shift=True)
kmi.properties.name = 'object.menu_name' 
kmi.active = True

km = kc.keymaps.new('Addon Modal Keymap', space_type='EMPTY', region_type='WINDOW', modal=True)

kmi = km.keymap_items.new_modal('SELECTION', 'LEFTMOUSE', 'PRESS', any=True)
kmi = km.keymap_items.new_modal('REMOVE', 'X', 'PRESS')

But how do I check against these keymaps in the event system? Normally I would just hard-code it by checking for the event type, but this is not flexible.

$\endgroup$
4
$\begingroup$

I don't know how to use modal keymaps, but it is quite easy to write such an event system on your own.

Here is how I would do it now (I just wrote this the first time, so maybe I forgot something):

at first we need 2 classes to store the keymap information:

class EventCollection:
    def __init__(self):
        self.events = {}

    def new_event(self, name, event_settings):
        self.events[name] = event_settings

    # assumes that only one event can happen at the same time    
    def get_matching_event_name(self, event):
        names = self.get_matching_event_names(event)
        if len(names) > 0:
            return names[0]
        return None

    def get_matching_event_names(self, event):
        return [name for name, description in self.events.items() if description.fits_event(event)]


class EventSettings:
    def __init__(self, event_type, event_value, shift = False, ctrl = False, alt = False):
        self.type = event_type
        self.value = event_value
        self.shift = shift
        self.ctrl = ctrl
        self.alt = alt

    def fits_event(self, event):
        return event.type == self.type and \
                event.value == self.value and \
                event.shift == self.shift and \
                event.ctrl == self.ctrl and \
                event.alt == self.alt

Here is how you can use these two classes in a modal operator

class TestKeymap(bpy.types.Operator):
    bl_idname = "my.modal_operator"
    bl_label = "label"
    bl_description = ""
    bl_options = {"REGISTER"}

    @classmethod
    def poll(cls, context):
        return True

    def modal(self, context, event):

        name = self.events.get_matching_event_name(event)
        if name == "my event":
            print("hey")
        if name == "select":
            print("select")

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

        return {"RUNNING_MODAL"}

    def invoke(self, context, event):
        context.window_manager.modal_handler_add(self)

        e1 = EventSettings("A", "PRESS", ctrl = True)

        select_type = context.user_preferences.inputs.select_mouse
        if select_type == "RIGHT": event_type = "RIGHTMOUSE"
        else: event_type = "LEFTMOUSE"
        e2 = EventSettings(event_type, "PRESS")

        self.events = EventCollection()
        self.events.new_event("my event", e1)          
        self.events.new_event("select", e2)  

        return {"RUNNING_MODAL"}

I think this is mostly self-explanatory, so I won't go over it in detail. Basicly you create an EventCollection when you invoke the operator. This makes sure that it is always up to date to latest changes in userprefs.

In this example I create two events (like KeyMapItems in normal keymaps). The one is very simple and the second one shows how to mimic the selection method. It checks wether you normally select with left or right and creates a corresponding EventSettings instance. You may rename these classes

In the modal-function you can always check if the current event matches one of the predefined ones.

This makes the system very flexible and easy to extend/change.

Is that a bit like what you wanted?

$\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.