I'm very new to Blender and I've installed the latest version (2.92). The learning process is extremely hard because all the tutorials I can find seem to have been created under a version that has a completely different keyboard mapping. So trying to follow them produces lots of unexpected results under this version.

The problem raises 2 questions:

  1. Is there any way I can tell exactly what a given keystroke actually does? For example in Emacs I can type C-x h [mystery chord] and help on whatever would be executed by the chord pops up. Or in Visual Studio I can go to the keyboard mapper and try to map the chord and it will tell me what function (if anything) is already using that chord.

  2. Given a demo of someone hitting a given chord in a prior version, how can I discover what keys are required to do it in 2.92?

I did find 'Is key Free' under Preferences | Add-ons but it either doesn't work in 2.92 or I simply can't find where it presents itself in the 2.92 UI.

  • $\begingroup$ You can look at the keyboard mapping in user preferences, and even change it to match earlier versions of blender if you want. You can also use the main interface search engine to look for an operation by name, and the key combination is usually listed next to it. $\endgroup$
    – TheLabCat
    Commented Apr 10, 2021 at 23:04
  • $\begingroup$ That is a REALLY LONG list in a very tiny window with no apparent way to search. Is there a way to extract the list into a spreadsheet where it could be sorted? $\endgroup$
    – bielawski
    Commented Apr 10, 2021 at 23:13
  • $\begingroup$ …you have a very good point there. 😛 I’m not sure. You might be able to scrape the configuration file. But anyway, do you know about blender’s main operations search engine? In the older versions of blender, you can access it with the space bar (ironic I know, but I always change space back to engaging the search engine when I install a new version of blender and it shows me the initial settings splash, so I don’t know the default keystroke). $\endgroup$
    – TheLabCat
    Commented Apr 10, 2021 at 23:17
  • $\begingroup$ Also, often, in the tutorial they just say what key they are hitting but don't necessarily give the official name of the command that the key invokes. Perhaps only a description of what it does. I guess I was hoping, at minimum, that when key mappings changed it would appear in a change log and I just need to be pointed towards it. $\endgroup$
    – bielawski
    Commented Apr 10, 2021 at 23:19
  • $\begingroup$ Yeah, I see what you mean. Well, I guess one thing you could do is get the configuration file for the old blender version by downloading that version of blender, and then… Say, maybe I should do this on GitHub. Create a program to search in the default blender configuration files of different versions for matching operations and keystrokes, and then tell you what the keystroke is for any other version. $\endgroup$
    – TheLabCat
    Commented Apr 10, 2021 at 23:23

1 Answer 1


'Is key Free' really does work. I was just too green to find it once activated. To find it:

  1. Use the 'Editor Type' dropdown on any window's title bar to select 'Text Editor'. (Look at the float-over text first to see it's current type to simplify restoration of old content when done.)
  2. In the text editor window, pull down the View menu and insure "Sidebar" is checked
  3. On the right side of the window, click the 'Dev' sidebar tab
  4. 'Is key Free' should be on the now active sidebar.

Being a programmer rather than an artist I hacked the code up to give more(useful) info. The version below, by default, works identical to the original but has 2 extra buttons. They turn on 'internal values' in 'List all Shortcuts' output. So, for instance, you can see the difference between the two Render hotkeys.

Default output example:

Render --------------------------- Ctrl F12 
Render -------------------------------- F12

With the [Params] toggle on:

render.render(animation=1,use_viewport=1) ---- Ctrl F12
render.render(use_viewport=1) --------------------- F12

As for mapping hotkeys from versions less than 2.8 to those of 2.8 and above, it seems the only way is to look something up in the old version and use output from this to find it in the newer version.

It's not perfect. It doesn't translate enums to text. But these updates should make telling one otherwise identical call from another easier to map.

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  GNU General Public License for more details.
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

# PEP8 compliant (https://www.python.org/dev/peps/pep-0008)

bl_info = {
    "name": "Is key Free",
    "author": "Antonio Vazquez (antonioya) + Rick Bielawski",
    "version": (1, 2, 0),
    "blender": (2, 80, 0),
    "location": "Text Editor > Sidebar > Dev Tab",
    "description": "Find free shortcuts, inform about used and print a key list",
    "doc_url": "{BLENDER_MANUAL_URL}/addons/development/is_key_free.html",
    "category": "Development",

import bpy
from bpy.props import (
from bpy.types import (

# ------------------------------------------------------
# Class to find keymaps
# ------------------------------------------------------

class MyChecker():
    lastfind = None
    lastkey = None
    mylist = []

    # Init
    def __init__(self):
        self.var = 5

    # Verify if the key is used
    def check(cls, findkey, ctrl, alt, shift, oskey):
        if len(findkey) > 0:
            cmd = ""
            if ctrl is True:
                cmd += "Ctrl+"
            if alt is True:
                cmd += "Alt+"
            if shift is True:
                cmd += "Shift+"
            if oskey is True:
                cmd += "OsKey+"
            cls.lastfind = cmd + findkey.upper()
            cls.lastkey = findkey.upper()
            cls.lastfind = None
            cls.lastkey = None

        wm = bpy.context.window_manager
        mykeys = []

        for context, keyboardmap in wm.keyconfigs.user.keymaps.items():
            for myitem in keyboardmap.keymap_items:
                if myitem.active is True and myitem.type == findkey:
                    if ctrl is True and myitem.ctrl is not True:
                    if alt is True and myitem.alt is not True:
                    if shift is True and myitem.shift is not True:
                    if oskey is True and myitem.oskey is not True:
                    t = (context,
                         "Ctrl" if myitem.ctrl is True else "",
                         "Alt" if myitem.alt is True else "",
                         "Shift" if myitem.shift is True else "",
                         "OsKey" if myitem.oskey is True else "",


        sortkeys = sorted(mykeys, key=lambda key: (key[0], key[1], key[2], key[3], key[4], key[5]))

        for e in sortkeys:
            cmd = ""
            if e[2] != "":
                cmd += e[2] + "+"
            if e[3] != "":
                cmd += e[3] + "+"
            if e[4] != "":
                cmd += e[4] + "+"
            if e[5] != "":
                cmd += e[5] + "+"

            cmd += e[1]

            if e[6] != "":
                cmd += "  " + e[6]
            cls.mylist.append([e[0], cmd])

    # return context
    def getcontext(cls):
        return str(bpy.context.screen.name)

    # return last search
    def getlast(cls):
        return cls.lastfind

    # return last key
    def getlastkey(cls):
        return cls.lastkey

    # return result of last search
    def getlist(cls):
        return cls.mylist

    # verify if key is valid
    def isvalidkey(cls, txt):
        allkeys = [
            "EVT_TWEAK_M", "EVT_TWEAK_R", "A", "B", "C", "D", "E", "F", "G", "H",
            "I", "J",
            "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "ZERO", "ONE", "TWO",
            "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "LEFT_CTRL", "LEFT_ALT", "LEFT_SHIFT",
            "NUMPAD_4", "NUMPAD_6", "NUMPAD_8", "NUMPAD_1", "NUMPAD_3", "NUMPAD_5", "NUMPAD_7", "NUMPAD_9",
            "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15",
            "F16", "F17",
            "F18", "F19", "PAUSE", "INSERT", "HOME", "PAGE_UP", "PAGE_DOWN", "END", "MEDIA_PLAY", "MEDIA_STOP",
            "NDOF_BUTTON_B", "NDOF_BUTTON_C"
            return True
        except ValueError:
            return False

mychecker = MyChecker()  # Global class handler

# ------------------------------------------------------
# Button: Class for search button
# ------------------------------------------------------
class RunActionCheck(Operator):
    bl_idname = "iskeyfree.action_check"
    bl_label = ""
    bl_description = "Verify if the selected shortcut is free"

    # noinspection PyUnusedLocal
    def execute(self, context):
        scene = context.scene.is_keyfree
        txt = scene.data.upper()
        global mychecker
        mychecker.check(txt, scene.use_crtl, scene.use_alt, scene.use_shift,

        return {'FINISHED'}

# ------------------------------------------------------
# Defines UI panel
# ------------------------------------------------------
class UIControlPanel(Panel):
    bl_idname = "DEVISKEYFREE_PT_ui"
    bl_space_type = "TEXT_EDITOR"
    bl_region_type = "UI"
    bl_label = "Is Key Free"
    bl_category = 'Dev'
    bl_options = {'DEFAULT_CLOSED'}

    # noinspection PyUnusedLocal
    def draw(self, context):
        layout = self.layout
        scene = context.scene.is_keyfree

        row = layout.row(align=True)
        row.prop(scene, "data")
        row.operator("iskeyfree.action_check", icon="VIEWZOOM")

        row = layout.row(align=True)
        row.prop(scene, "use_crtl", toggle=True)
        row.prop(scene, "use_alt", toggle=True)
        row.prop(scene, "use_shift", toggle=True)
        row.prop(scene, "use_oskey", toggle=True)

        row = layout.row()
        row.prop(scene, "numpad")

        layout.operator("iskeyfree.run_export_keys", icon="FILE_TEXT")

        row = layout.row(align=True)
        row.prop(scene, "report_params", toggle=True)
        row.prop(scene, "report_keyname", toggle=True)

        global mychecker
        mylist = mychecker.getlist()
        oldcontext = None

        box = None
        if len(mylist) > 0:
            cmd = mychecker.getlast()
            if cmd is not None:
                row = layout.row()
                row.label(text="Current uses of " + str(cmd), icon="PARTICLE_DATA")
            for e in mylist:
                if oldcontext != e[0]:
                    box = layout.box()
                    box.label(text=e[0], icon="UNPINNED")
                    oldcontext = e[0]

                row = box.row(align=True)
            cmd = mychecker.getlast()
            if cmd is not None:
                box = layout.box()
                if mychecker.isvalidkey(mychecker.getlastkey()) is False:
                    box.label(text=str(mychecker.getlastkey()) + " looks not valid key", icon="ERROR")
                    box.label(text=str(cmd) + " is free", icon="FILE_TICK")

# ------------------------------------------------------
# Update key (special values) event handler
# ------------------------------------------------------
# noinspection PyUnusedLocal
def update_data(self, context):
    scene = context.scene.is_keyfree
    if scene.numpad != "NONE":
        scene.data = scene.numpad

class IskeyFreeProperties(PropertyGroup):
    data: StringProperty(
        name="Key", maxlen=32,
        description="Shortcut to verify"
    use_crtl: BoolProperty(
        description="Ctrl key used in shortcut",
    use_alt: BoolProperty(
        description="Alt key used in shortcut",
    use_shift: BoolProperty(
        description="Shift key used in shortcut",
    use_oskey: BoolProperty(
        description="Operating system key used in shortcut",
    report_params: BoolProperty(
        description="Report invoked function and params",
    report_keyname: BoolProperty(
        description="Report internal key name vs interpreted name",
    numpad: EnumProperty(
            ('NONE', "Select key", ""),
            ("LEFTMOUSE", "LEFTMOUSE", ""),
            ("MIDDLEMOUSE", "MIDDLEMOUSE", ""),
            ("RIGHTMOUSE", "RIGHTMOUSE", ""),
            ("BUTTON4MOUSE", "BUTTON4MOUSE", ""),
            ("BUTTON5MOUSE", "BUTTON5MOUSE", ""),
            ("BUTTON6MOUSE", "BUTTON6MOUSE", ""),
            ("BUTTON7MOUSE", "BUTTON7MOUSE", ""),
            ("MOUSEMOVE", "MOUSEMOVE", ""),
            ("TRACKPADPAN", "TRACKPADPAN", ""),
            ("TRACKPADZOOM", "TRACKPADZOOM", ""),
            ("MOUSEROTATE", "MOUSEROTATE", ""),
            ("WHEELUPMOUSE", "WHEELUPMOUSE", ""),
            ("WHEELINMOUSE", "WHEELINMOUSE", ""),
            ("EVT_TWEAK_L", "EVT_TWEAK_L", ""),
            ("EVT_TWEAK_M", "EVT_TWEAK_M", ""),
            ("EVT_TWEAK_R", "EVT_TWEAK_R", ""),
            ("A", "A", ""),
            ("B", "B", ""),
            ("C", "C", ""),
            ("D", "D", ""),
            ("E", "E", ""),
            ("F", "F", ""),
            ("G", "G", ""),
            ("H", "H", ""),
            ("I", "I", ""),
            ("J", "J", ""),
            ("K", "K", ""),
            ("L", "L", ""),
            ("M", "M", ""),
            ("N", "N", ""),
            ("O", "O", ""),
            ("P", "P", ""),
            ("Q", "Q", ""),
            ("R", "R", ""),
            ("S", "S", ""),
            ("T", "T", ""),
            ("U", "U", ""),
            ("V", "V", ""),
            ("W", "W", ""),
            ("X", "X", ""),
            ("Y", "Y", ""),
            ("Z", "Z", ""),
            ("ZERO", "ZERO", ""),
            ("ONE", "ONE", ""),
            ("TWO", "TWO", ""),
            ("THREE", "THREE", ""),
            ("FOUR", "FOUR", ""),
            ("FIVE", "FIVE", ""),
            ("SIX", "SIX", ""),
            ("SEVEN", "SEVEN", ""),
            ("EIGHT", "EIGHT", ""),
            ("NINE", "NINE", ""),
            ("LEFT_CTRL", "LEFT_CTRL", ""),
            ("LEFT_ALT", "LEFT_ALT", ""),
            ("LEFT_SHIFT", "LEFT_SHIFT", ""),
            ("RIGHT_ALT", "RIGHT_ALT", ""),
            ("RIGHT_CTRL", "RIGHT_CTRL", ""),
            ("RIGHT_SHIFT", "RIGHT_SHIFT", ""),
            ("OSKEY", "OSKEY", ""),
            ("GRLESS", "GRLESS", ""),
            ("ESC", "ESC", ""),
            ("TAB", "TAB", ""),
            ("RET", "RET", ""),
            ("SPACE", "SPACE", ""),
            ("LINE_FEED", "LINE_FEED", ""),
            ("BACK_SPACE", "BACK_SPACE", ""),
            ("DEL", "DEL", ""),
            ("SEMI_COLON", "SEMI_COLON", ""),
            ("PERIOD", "PERIOD", ""),
            ("COMMA", "COMMA", ""),
            ("QUOTE", "QUOTE", ""),
            ("ACCENT_GRAVE", "ACCENT_GRAVE", ""),
            ("MINUS", "MINUS", ""),
            ("SLASH", "SLASH", ""),
            ("BACK_SLASH", "BACK_SLASH", ""),
            ("EQUAL", "EQUAL", ""),
            ("LEFT_BRACKET", "LEFT_BRACKET", ""),
            ("RIGHT_BRACKET", "RIGHT_BRACKET", ""),
            ("LEFT_ARROW", "LEFT_ARROW", ""),
            ("DOWN_ARROW", "DOWN_ARROW", ""),
            ("RIGHT_ARROW", "RIGHT_ARROW", ""),
            ("UP_ARROW", "UP_ARROW", ""),
            ("NUMPAD_1", "NUMPAD_1", ""),
            ("NUMPAD_2", "NUMPAD_2", ""),
            ("NUMPAD_3", "NUMPAD_3", ""),
            ("NUMPAD_4", "NUMPAD_4", ""),
            ("NUMPAD_5", "NUMPAD_5", ""),
            ("NUMPAD_6", "NUMPAD_6", ""),
            ("NUMPAD_7", "NUMPAD_7", ""),
            ("NUMPAD_8", "NUMPAD_8", ""),
            ("NUMPAD_9", "NUMPAD_9", ""),
            ("NUMPAD_0", "NUMPAD_0", ""),
            ("NUMPAD_PERIOD", "NUMPAD_PERIOD", ""),
            ("NUMPAD_SLASH", "NUMPAD_SLASH", ""),
            ("NUMPAD_ASTERIX", "NUMPAD_ASTERIX", ""),
            ("NUMPAD_MINUS", "NUMPAD_MINUS", ""),
            ("NUMPAD_ENTER", "NUMPAD_ENTER", ""),
            ("NUMPAD_PLUS", "NUMPAD_PLUS", ""),
            ("F1", "F1", ""),
            ("F2", "F2", ""),
            ("F3", "F3", ""),
            ("F4", "F4", ""),
            ("F5", "F5", ""),
            ("F6", "F6", ""),
            ("F7", "F7", ""),
            ("F8", "F8", ""),
            ("F9", "F9", ""),
            ("F10", "F10", ""),
            ("F11", "F11", ""),
            ("F12", "F12", ""),
            ("F13", "F13", ""),
            ("F14", "F14", ""),
            ("F15", "F15", ""),
            ("F16", "F16", ""),
            ("F17", "F17", ""),
            ("F18", "F18", ""),
            ("F19", "F19", ""),
            ("PAUSE", "PAUSE", ""),
            ("INSERT", "INSERT", ""),
            ("HOME", "HOME", ""),
            ("PAGE_UP", "PAGE_UP", ""),
            ("PAGE_DOWN", "PAGE_DOWN", ""),
            ("END", "END", ""),
            ("MEDIA_PLAY", "MEDIA_PLAY", ""),
            ("MEDIA_STOP", "MEDIA_STOP", ""),
            ("MEDIA_FIRST", "MEDIA_FIRST", ""),
            ("MEDIA_LAST", "MEDIA_LAST", ""),
            ("TEXTINPUT", "TEXTINPUT", ""),
            ("TIMER", "TIMER", ""),
            ("TIMER0", "TIMER0", ""),
            ("TIMER1", "TIMER1", ""),
            ("TIMER2", "TIMER2", ""),
            ("TIMER_JOBS", "TIMER_JOBS", ""),
            ("TIMER_AUTOSAVE", "TIMER_AUTOSAVE", ""),
            ("TIMER_REPORT", "TIMER_REPORT", ""),
            ("TIMERREGION", "TIMERREGION", ""),
            ("NDOF_MOTION", "NDOF_MOTION", ""),
            ("NDOF_BUTTON_MENU", "NDOF_BUTTON_MENU", ""),
            ("NDOF_BUTTON_FIT", "NDOF_BUTTON_FIT", ""),
            ("NDOF_BUTTON_TOP", "NDOF_BUTTON_TOP", ""),
            ("NDOF_BUTTON_LEFT", "NDOF_BUTTON_LEFT", ""),
            ("NDOF_BUTTON_BACK", "NDOF_BUTTON_BACK", ""),
            ("NDOF_BUTTON_ISO1", "NDOF_BUTTON_ISO1", ""),
            ("NDOF_BUTTON_ISO2", "NDOF_BUTTON_ISO2", ""),
            ("NDOF_BUTTON_PLUS", "NDOF_BUTTON_PLUS", ""),
            ("NDOF_BUTTON_ESC", "NDOF_BUTTON_ESC", ""),
            ("NDOF_BUTTON_ALT", "NDOF_BUTTON_ALT", ""),
            ("NDOF_BUTTON_CTRL", "NDOF_BUTTON_CTRL", ""),
            ("NDOF_BUTTON_1", "NDOF_BUTTON_1", ""),
            ("NDOF_BUTTON_2", "NDOF_BUTTON_2", ""),
            ("NDOF_BUTTON_3", "NDOF_BUTTON_3", ""),
            ("NDOF_BUTTON_4", "NDOF_BUTTON_4", ""),
            ("NDOF_BUTTON_5", "NDOF_BUTTON_5", ""),
            ("NDOF_BUTTON_6", "NDOF_BUTTON_6", ""),
            ("NDOF_BUTTON_7", "NDOF_BUTTON_7", ""),
            ("NDOF_BUTTON_8", "NDOF_BUTTON_8", ""),
            ("NDOF_BUTTON_9", "NDOF_BUTTON_9", ""),
            ("NDOF_BUTTON_10", "NDOF_BUTTON_10", ""),
            ("NDOF_BUTTON_A", "NDOF_BUTTON_A", ""),
            ("NDOF_BUTTON_B", "NDOF_BUTTON_B", ""),
            ("NDOF_BUTTON_C", "NDOF_BUTTON_C", "")
        name="Quick Type",
        description="Enter key code in find text",

class IsKeyFreeRunExportKeys(Operator):
    bl_idname = "iskeyfree.run_export_keys"
    bl_label = "List all Shortcuts"
    bl_description = ("List all shortcuts with their function in a text block\n"
                      "The newly generated list will be made active in the Text Editor\n"
                      "To access the previous ones, select them from the Header dropdown")

    def all_shortcuts_name(self, context):
        new_name, def_name, ext = "", "All_Shortcuts", ".txt"
        suffix = 1
            # first slap a simple linear count + 1 for numeric suffix, if it fails
            # harvest for the rightmost numbers and append the max value
            list_txt = []
            data_txt = bpy.data.texts
            list_txt = [txt.name for txt in data_txt if txt.name.startswith("All_Shortcuts")]
            new_name = "{}_{}{}".format(def_name, len(list_txt) + 1, ext)

            if new_name in list_txt:
                from re import findall
                test_num = [findall("\d+", words) for words in list_txt]
                suffix += max([int(l[-1]) for l in test_num])
                new_name = "{}_{}{}".format(def_name, suffix, ext)
            return new_name
            return None

    def execute(self, context):
        wm = bpy.context.window_manager
        scene = context.scene.is_keyfree
        from collections import defaultdict
        mykeys = defaultdict(list)
        file_name = self.all_shortcuts_name(context) or "All_Shortcut.txt"
        start_note = "# Note: Some of the shortcuts entries don't have a name. Mostly Modal stuff\n"
        col_width, col_shortcuts = 2, 2

        for ctx_type, keyboardmap in wm.keyconfigs.user.keymaps.items():
            for myitem in keyboardmap.keymap_items:

                if scene.report_params:
                    desc = myitem.idname if myitem.idname else "Not Available"
                    description = "{}(".format(desc)
                    if myitem.properties:
                        if len(myitem.properties.items()):
                            for prop in myitem.properties.items():
                                description += "{}={},".format(prop[0],prop[1])
                            description = description[:-1]
                    description += ")"
                    desc = myitem.name if myitem.name else "No Name"
                    description = "{}".format(desc)

                padding = len(description)
                col_width = padding + 2 if padding > col_width else col_width

                short_type = myitem.type if myitem.type else "UNKNOWN"
                is_ctrl = " Ctrl" if myitem.ctrl is True else ""
                is_alt = " Alt" if myitem.alt is True else ""
                is_shift = " Shift" if myitem.shift is True else ""
                is_oskey = " OsKey" if myitem.oskey is True else ""
                if scene.report_keyname:
                    short_cuts = "{}".format(myitem.to_string())
                    short_cuts = "{}{}{}{}{}".format(short_type, is_ctrl, is_alt, is_shift, is_oskey)

                t = (
                padding_s = len(short_cuts) + 2
                col_shortcuts = padding_s if padding_s > col_shortcuts else col_shortcuts

        max_line = col_shortcuts + col_width + 4
        textblock = bpy.data.texts.new(file_name)
        total = sum([len(mykeys[ctxs]) for ctxs in mykeys])
        textblock.write('# %d Total Shortcuts\n\n' % total)

        for ctx in mykeys:
            textblock.write("\n[%s]\nEntries: %s\n\n" % (ctx, len(mykeys[ctx])))
            line_k = sorted(mykeys[ctx])
            for keys in line_k:
                add_ticks = "-" * (max_line - (len(keys[0]) + len(keys[1])))
                entries = "{ticks} {entry}".format(ticks=add_ticks, entry=keys[1])
                textblock.write("{name} {entry}\n".format(name=keys[0], entry=entries))


        # try to set the created text block to active
        if context.area.type in {"TEXT_EDITOR"}:
            bpy.context.space_data.text = bpy.data.texts[file_name]

        self.report({'INFO'}, "See %s textblock" % file_name)

        return {"FINISHED"}

# -----------------------------------------------------
# Registration
# ------------------------------------------------------
classes = (

def register():
    for cls in classes:
    bpy.types.Scene.is_keyfree = PointerProperty(type=IskeyFreeProperties)

def unregister():
    for cls in classes:
    del bpy.types.Scene.is_keyfree

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .