Finding out keymap bindings at register in 2.80

How do you retrieve keymap bindings in an add-on’s register function? For example, in Blender 2.79 you could do this inside register:

    keymaps = bpy.context.window_manager.keyconfigs.active.keymaps
    print(keymaps['Object Mode'].keymap_items['object.hide_view_set'].type)

In 2.80, this is no longer possible. Using the same code at register results in this error:
KeyError: 'bpy_prop_collection[key]: key "object.hide_view_set" not found'

I tried replacing keyconfigs.active with keyconfigs.default and had the same error. In 2.80 the keymaps values have nothing assigned to the when register is called when Blender loads. Inserting this in an add-on’s register function prints out an empty list:

    keymaps = bpy.context.window_manager.keyconfigs.active.keymaps
    print(keymaps['Object Mode'].keymap_items.keys())

This type of operation was useful for setting an add-on’s keymaps to match with existing Blender settings.

This annoyed me as well. I disable a lot of defaults using an addon instead of dealing with dirty exported keyconfigs.

As much as I hate to admit, I keep going back to using a timer every time I’m brickwalled by RestrictContext, RestrictData, and of course blender loading addons before the default keymaps.

Curiously enough, even with a very low interval the timer seems to always succeed on the second attempt.

def register():

    ...

    register_keymaps()


def register_keymaps():
    kc = bpy.context.window_manager.keyconfigs
    areas = 'Window', 'Text', 'Object Mode', '3D View'

    if not all(i in kc.active.keymaps for i in areas):
        bpy.app.timers.register(register_keys, first_interval=0.1)

    else:
        # can now proceed with checking default kmis
        pass

2 Likes

same. I find myself having to hack around this and other issues, such that I hope at some point in the near future a discussion can be started on revamping the entire keymap/keyconfig system to be a bit more forward thinking.

Wild, this looks buggy. Not so much the workaround you listed, but that a workaround like this is needed. I thought the loss of keymap data was an unintended change with the move from 2.79 to 2.80 or some new interface I overlooked, but are you saying this change in behavior was intentional?

I think there was a small spelling mistake in your code, I was getting a “register_keys not found” error. Replacing “register_keys” with “register_keymaps” worked:

def register():

    ...

    register_keymaps()


def register_keymaps():
    kc = bpy.context.window_manager.keyconfigs
    areas = 'Window', 'Text', 'Object Mode', '3D View'

    if not all(i in kc.active.keymaps for i in areas):
        bpy.app.timers.register(register_keymaps, first_interval=0.1)

    else:
        # can now proceed with checking default kmis
        print(kc.active.keymaps['Object Mode'].keymap_items['object.hide_view_set'].type)
        pass

Since 2.80 the order of loading keymaps has changed. I’ve made a patch that makes add-ons load after the keymap, I’ll talk to Brecht about this since we both worked on this area.

diff --git a/release/scripts/modules/addon_utils.py b/release/scripts/modules/addon_utils.py
index e212df17f60..daaf9afddf9 100644
--- a/release/scripts/modules/addon_utils.py
+++ b/release/scripts/modules/addon_utils.py
@@ -23,6 +23,7 @@ __all__ = (
     "modules",
     "check",
     "enable",
+    "enable_all",
     "disable",
     "disable_all",
     "reset_all",
@@ -43,6 +44,9 @@ def _initialize():
     path_list = paths()
     for path in path_list:
         _bpy.utils._sys_path_ensure_append(path)
+
+
+def enable_all():
     for addon in _preferences.addons:
         enable(addon.module)
 
diff --git a/release/scripts/modules/bpy/utils/__init__.py b/release/scripts/modules/bpy/utils/__init__.py
index 04aaa7bd69d..4cb2377112b 100644
--- a/release/scripts/modules/bpy/utils/__init__.py
+++ b/release/scripts/modules/bpy/utils/__init__.py
@@ -275,7 +275,7 @@ def load_scripts(reload_scripts=False, refresh_scripts=False):
     # deal with addons separately
     _initialize = getattr(_addon_utils, "_initialize", None)
     if _initialize is not None:
-        # first time, use fast-path
+        # first time, initialize add-on paths (doesn't load add-ons).
         _initialize()
         del _addon_utils._initialize
     else:
diff --git a/source/creator/creator.c b/source/creator/creator.c
index 3632eb9eb9a..d9b7da4e210 100644
--- a/source/creator/creator.c
+++ b/source/creator/creator.c
@@ -59,6 +59,8 @@
 #include "BKE_image.h"
 #include "BKE_particle.h"
 
+#include "BPY_extern.h"
+
 #include "DEG_depsgraph.h"
 
 #include "IMB_imbuf.h" /* for IMB_init */
@@ -445,6 +447,10 @@ int main(int argc,
   CTX_py_init_set(C, true);
   WM_keyconfig_init(C);
 
+#ifdef WITH_PYTHON
+  BPY_execute_string(C, (const char *[]){"addon_utils", NULL}, "addon_utils.enable_all()");
+#endif
+
 #ifdef WITH_FREESTYLE
   /* initialize Freestyle */
   FRS_initialize();
3 Likes

Note that checking the add-ons on register is unreliable since users may change the key-map at run-time.

Wouldn’t it be better to have handlers:

  • bpy.app.handlers.keyconfig_load_pre
  • bpy.app.handlers.keyconfig_load_post
1 Like

Interesting idea!

When would these pre and post handlers be run?
a) before and after the keymaps are loaded into memory
b) during and after changes were made to a keymap
c) something else?

I think the situation I was originally trying to plan around with keymap checking at register is when someone manually installs an addon by copying its files into Blender’s addon directory and then launches Blender. This is not likely the way most people installing addons would choose, but still a possibility.

In your original post you say:

How do you retrieve keymap bindings in an add-on’s register function?

If these values can change, wouldn’t whatever actions you made from reading the key-map also change?


pre/post is a convention for handlers, although in this case we might only need a keyconfig_load_post handler. This would run after loading the keyconfig.

Ideally yes, if Blender’s key-map was changed, the add-on’s key-map would also be updated to match with the new values. Is that doable through the existing Python API though? Wouldn’t you need something else, like a keyconfig_updated handler, for this to work? (this is getting more and more complicated, isn’t it?)

I think I had originally just written that off as a rare unsolvable fail scenario. That is, if an end-user changed their key-map after the add-on was registered, then the add-on would just not work (at least until the add-on’s key-map was manually changed to match the new Blender key-map or until Blender was restarted and the add-on’s register function ran again).

For some background details, the addon this is for was originally written as a sort “wrapper” (sort of like a Python decorators) around an existing Blender operator. I can’t remember the specifics of how the “wrapping” functionality worked offhand though. When I have some more time I’ll look back through the code.