1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # PEP8 compliant (https://www.python.org/dev/peps/pep-0008)
7 "author": "Antonio Vazquez (antonioya)",
10 "location": "Text Editor > Sidebar > Dev Tab",
11 "description": "Find free shortcuts, inform about used and print a key list",
12 "doc_url": "{BLENDER_MANUAL_URL}/addons/development/is_key_free.html",
13 "category": "Development",
17 from bpy
.props
import (
23 from bpy
.types
import (
30 # ------------------------------------------------------
31 # Class to find keymaps
32 # ------------------------------------------------------
43 # Verify if the key is used
45 def check(cls
, findkey
, ctrl
, alt
, shift
, oskey
):
56 cls
.lastfind
= cmd
+ findkey
.upper()
57 cls
.lastkey
= findkey
.upper()
62 wm
= bpy
.context
.window_manager
65 for context
, keyboardmap
in wm
.keyconfigs
.user
.keymaps
.items():
66 for myitem
in keyboardmap
.keymap_items
:
67 if myitem
.active
is True and myitem
.type == findkey
:
68 if ctrl
is True and myitem
.ctrl
is not True:
70 if alt
is True and myitem
.alt
is not True:
72 if shift
is True and myitem
.shift
is not True:
74 if oskey
is True and myitem
.oskey
is not True:
78 "Ctrl" if myitem
.ctrl
is True else "",
79 "Alt" if myitem
.alt
is True else "",
80 "Shift" if myitem
.shift
is True else "",
81 "OsKey" if myitem
.oskey
is True else "",
86 sortkeys
= sorted(mykeys
, key
=lambda key
: (key
[0], key
[1], key
[2], key
[3], key
[4], key
[5]))
104 cls
.mylist
.append([e
[0], cmd
])
109 return str(bpy
.context
.screen
.name
)
121 # return result of last search
126 # verify if key is valid
128 def isvalidkey(cls
, txt
):
130 "LEFTMOUSE", "MIDDLEMOUSE", "RIGHTMOUSE", "BUTTON4MOUSE", "BUTTON5MOUSE", "BUTTON6MOUSE",
132 "MOUSEMOVE", "INBETWEEN_MOUSEMOVE", "TRACKPADPAN", "TRACKPADZOOM",
133 "MOUSEROTATE", "WHEELUPMOUSE", "WHEELDOWNMOUSE", "WHEELINMOUSE", "WHEELOUTMOUSE",
134 "A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
135 "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "ZERO", "ONE", "TWO",
136 "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "LEFT_CTRL", "LEFT_ALT", "LEFT_SHIFT",
138 "RIGHT_CTRL", "RIGHT_SHIFT", "OSKEY", "GRLESS", "ESC", "TAB", "RET", "SPACE", "LINE_FEED",
140 "DEL", "SEMI_COLON", "PERIOD", "COMMA", "QUOTE", "ACCENT_GRAVE", "MINUS", "SLASH", "BACK_SLASH",
142 "LEFT_BRACKET", "RIGHT_BRACKET", "LEFT_ARROW", "DOWN_ARROW", "RIGHT_ARROW", "UP_ARROW", "NUMPAD_2",
143 "NUMPAD_4", "NUMPAD_6", "NUMPAD_8", "NUMPAD_1", "NUMPAD_3", "NUMPAD_5", "NUMPAD_7", "NUMPAD_9",
144 "NUMPAD_PERIOD", "NUMPAD_SLASH", "NUMPAD_ASTERIX", "NUMPAD_0", "NUMPAD_MINUS", "NUMPAD_ENTER",
146 "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15",
148 "F18", "F19", "PAUSE", "INSERT", "HOME", "PAGE_UP", "PAGE_DOWN", "END", "MEDIA_PLAY", "MEDIA_STOP",
149 "MEDIA_FIRST", "MEDIA_LAST", "TEXTINPUT", "WINDOW_DEACTIVATE", "TIMER", "TIMER0", "TIMER1", "TIMER2",
150 "TIMER_JOBS", "TIMER_AUTOSAVE", "TIMER_REPORT", "TIMERREGION", "NDOF_MOTION", "NDOF_BUTTON_MENU",
151 "NDOF_BUTTON_FIT", "NDOF_BUTTON_TOP", "NDOF_BUTTON_BOTTOM", "NDOF_BUTTON_LEFT", "NDOF_BUTTON_RIGHT",
152 "NDOF_BUTTON_FRONT", "NDOF_BUTTON_BACK", "NDOF_BUTTON_ISO1", "NDOF_BUTTON_ISO2",
153 "NDOF_BUTTON_ROLL_CW",
154 "NDOF_BUTTON_ROLL_CCW", "NDOF_BUTTON_SPIN_CW", "NDOF_BUTTON_SPIN_CCW", "NDOF_BUTTON_TILT_CW",
155 "NDOF_BUTTON_TILT_CCW", "NDOF_BUTTON_ROTATE", "NDOF_BUTTON_PANZOOM", "NDOF_BUTTON_DOMINANT",
156 "NDOF_BUTTON_PLUS", "NDOF_BUTTON_MINUS", "NDOF_BUTTON_ESC", "NDOF_BUTTON_ALT", "NDOF_BUTTON_SHIFT",
157 "NDOF_BUTTON_CTRL", "NDOF_BUTTON_1", "NDOF_BUTTON_2", "NDOF_BUTTON_3", "NDOF_BUTTON_4",
159 "NDOF_BUTTON_6", "NDOF_BUTTON_7", "NDOF_BUTTON_8", "NDOF_BUTTON_9", "NDOF_BUTTON_10",
161 "NDOF_BUTTON_B", "NDOF_BUTTON_C"
170 mychecker
= MyChecker() # Global class handler
173 # ------------------------------------------------------
174 # Button: Class for search button
175 # ------------------------------------------------------
176 class RunActionCheck(Operator
):
177 bl_idname
= "iskeyfree.action_check"
179 bl_description
= "Verify if the selected shortcut is free"
181 # noinspection PyUnusedLocal
182 def execute(self
, context
):
183 scene
= context
.scene
.is_keyfree
184 txt
= scene
.data
.upper()
186 mychecker
.check(txt
, scene
.use_crtl
, scene
.use_alt
, scene
.use_shift
,
192 # ------------------------------------------------------
194 # ------------------------------------------------------
195 class UIControlPanel(Panel
):
196 bl_idname
= "DEVISKEYFREE_PT_ui"
197 bl_space_type
= "TEXT_EDITOR"
198 bl_region_type
= "UI"
199 bl_label
= "Is Key Free"
201 bl_options
= {'DEFAULT_CLOSED'}
203 # noinspection PyUnusedLocal
204 def draw(self
, context
):
206 scene
= context
.scene
.is_keyfree
208 row
= layout
.row(align
=True)
209 row
.prop(scene
, "data")
210 row
.operator("iskeyfree.action_check", icon
="VIEWZOOM")
212 row
= layout
.row(align
=True)
213 row
.prop(scene
, "use_crtl", toggle
=True)
214 row
.prop(scene
, "use_alt", toggle
=True)
215 row
.prop(scene
, "use_shift", toggle
=True)
216 row
.prop(scene
, "use_oskey", toggle
=True)
219 row
.prop(scene
, "numpad")
221 layout
.operator("iskeyfree.run_export_keys", icon
="FILE_TEXT")
224 mylist
= mychecker
.getlist()
229 cmd
= mychecker
.getlast()
232 row
.label(text
="Current uses of " + str(cmd
), icon
="PARTICLE_DATA")
234 if oldcontext
!= e
[0]:
236 box
.label(text
=e
[0], icon
="UNPINNED")
239 row
= box
.row(align
=True)
242 cmd
= mychecker
.getlast()
245 if mychecker
.isvalidkey(mychecker
.getlastkey()) is False:
246 box
.label(text
=str(mychecker
.getlastkey()) + " looks not valid key", icon
="ERROR")
248 box
.label(text
=str(cmd
) + " is free", icon
="FILE_TICK")
251 # ------------------------------------------------------
252 # Update key (special values) event handler
253 # ------------------------------------------------------
254 # noinspection PyUnusedLocal
255 def update_data(self
, context
):
256 scene
= context
.scene
.is_keyfree
257 if scene
.numpad
!= "NONE":
258 scene
.data
= scene
.numpad
261 class IskeyFreeProperties(PropertyGroup
):
262 data
: StringProperty(
263 name
="Key", maxlen
=32,
264 description
="Shortcut to verify"
266 use_crtl
: BoolProperty(
268 description
="Ctrl key used in shortcut",
271 use_alt
: BoolProperty(
273 description
="Alt key used in shortcut",
276 use_shift
: BoolProperty(
278 description
="Shift key used in shortcut",
281 use_oskey
: BoolProperty(
283 description
="Operating system key used in shortcut",
286 numpad
: EnumProperty(
288 ('NONE', "Select key", ""),
289 ("LEFTMOUSE", "LEFTMOUSE", ""),
290 ("MIDDLEMOUSE", "MIDDLEMOUSE", ""),
291 ("RIGHTMOUSE", "RIGHTMOUSE", ""),
292 ("BUTTON4MOUSE", "BUTTON4MOUSE", ""),
293 ("BUTTON5MOUSE", "BUTTON5MOUSE", ""),
294 ("BUTTON6MOUSE", "BUTTON6MOUSE", ""),
295 ("BUTTON7MOUSE", "BUTTON7MOUSE", ""),
296 ("MOUSEMOVE", "MOUSEMOVE", ""),
297 ("INBETWEEN_MOUSEMOVE", "INBETWEEN_MOUSEMOVE", ""),
298 ("TRACKPADPAN", "TRACKPADPAN", ""),
299 ("TRACKPADZOOM", "TRACKPADZOOM", ""),
300 ("MOUSEROTATE", "MOUSEROTATE", ""),
301 ("WHEELUPMOUSE", "WHEELUPMOUSE", ""),
302 ("WHEELDOWNMOUSE", "WHEELDOWNMOUSE", ""),
303 ("WHEELINMOUSE", "WHEELINMOUSE", ""),
304 ("WHEELOUTMOUSE", "WHEELOUTMOUSE", ""),
331 ("ZERO", "ZERO", ""),
334 ("THREE", "THREE", ""),
335 ("FOUR", "FOUR", ""),
336 ("FIVE", "FIVE", ""),
338 ("SEVEN", "SEVEN", ""),
339 ("EIGHT", "EIGHT", ""),
340 ("NINE", "NINE", ""),
341 ("LEFT_CTRL", "LEFT_CTRL", ""),
342 ("LEFT_ALT", "LEFT_ALT", ""),
343 ("LEFT_SHIFT", "LEFT_SHIFT", ""),
344 ("RIGHT_ALT", "RIGHT_ALT", ""),
345 ("RIGHT_CTRL", "RIGHT_CTRL", ""),
346 ("RIGHT_SHIFT", "RIGHT_SHIFT", ""),
347 ("OSKEY", "OSKEY", ""),
348 ("GRLESS", "GRLESS", ""),
352 ("SPACE", "SPACE", ""),
353 ("LINE_FEED", "LINE_FEED", ""),
354 ("BACK_SPACE", "BACK_SPACE", ""),
356 ("SEMI_COLON", "SEMI_COLON", ""),
357 ("PERIOD", "PERIOD", ""),
358 ("COMMA", "COMMA", ""),
359 ("QUOTE", "QUOTE", ""),
360 ("ACCENT_GRAVE", "ACCENT_GRAVE", ""),
361 ("MINUS", "MINUS", ""),
362 ("SLASH", "SLASH", ""),
363 ("BACK_SLASH", "BACK_SLASH", ""),
364 ("EQUAL", "EQUAL", ""),
365 ("LEFT_BRACKET", "LEFT_BRACKET", ""),
366 ("RIGHT_BRACKET", "RIGHT_BRACKET", ""),
367 ("LEFT_ARROW", "LEFT_ARROW", ""),
368 ("DOWN_ARROW", "DOWN_ARROW", ""),
369 ("RIGHT_ARROW", "RIGHT_ARROW", ""),
370 ("UP_ARROW", "UP_ARROW", ""),
371 ("NUMPAD_1", "NUMPAD_1", ""),
372 ("NUMPAD_2", "NUMPAD_2", ""),
373 ("NUMPAD_3", "NUMPAD_3", ""),
374 ("NUMPAD_4", "NUMPAD_4", ""),
375 ("NUMPAD_5", "NUMPAD_5", ""),
376 ("NUMPAD_6", "NUMPAD_6", ""),
377 ("NUMPAD_7", "NUMPAD_7", ""),
378 ("NUMPAD_8", "NUMPAD_8", ""),
379 ("NUMPAD_9", "NUMPAD_9", ""),
380 ("NUMPAD_0", "NUMPAD_0", ""),
381 ("NUMPAD_PERIOD", "NUMPAD_PERIOD", ""),
382 ("NUMPAD_SLASH", "NUMPAD_SLASH", ""),
383 ("NUMPAD_ASTERIX", "NUMPAD_ASTERIX", ""),
384 ("NUMPAD_MINUS", "NUMPAD_MINUS", ""),
385 ("NUMPAD_ENTER", "NUMPAD_ENTER", ""),
386 ("NUMPAD_PLUS", "NUMPAD_PLUS", ""),
406 ("PAUSE", "PAUSE", ""),
407 ("INSERT", "INSERT", ""),
408 ("HOME", "HOME", ""),
409 ("PAGE_UP", "PAGE_UP", ""),
410 ("PAGE_DOWN", "PAGE_DOWN", ""),
412 ("MEDIA_PLAY", "MEDIA_PLAY", ""),
413 ("MEDIA_STOP", "MEDIA_STOP", ""),
414 ("MEDIA_FIRST", "MEDIA_FIRST", ""),
415 ("MEDIA_LAST", "MEDIA_LAST", ""),
416 ("TEXTINPUT", "TEXTINPUT", ""),
417 ("WINDOW_DEACTIVATE", "WINDOW_DEACTIVATE", ""),
418 ("TIMER", "TIMER", ""),
419 ("TIMER0", "TIMER0", ""),
420 ("TIMER1", "TIMER1", ""),
421 ("TIMER2", "TIMER2", ""),
422 ("TIMER_JOBS", "TIMER_JOBS", ""),
423 ("TIMER_AUTOSAVE", "TIMER_AUTOSAVE", ""),
424 ("TIMER_REPORT", "TIMER_REPORT", ""),
425 ("TIMERREGION", "TIMERREGION", ""),
426 ("NDOF_MOTION", "NDOF_MOTION", ""),
427 ("NDOF_BUTTON_MENU", "NDOF_BUTTON_MENU", ""),
428 ("NDOF_BUTTON_FIT", "NDOF_BUTTON_FIT", ""),
429 ("NDOF_BUTTON_TOP", "NDOF_BUTTON_TOP", ""),
430 ("NDOF_BUTTON_BOTTOM", "NDOF_BUTTON_BOTTOM", ""),
431 ("NDOF_BUTTON_LEFT", "NDOF_BUTTON_LEFT", ""),
432 ("NDOF_BUTTON_RIGHT", "NDOF_BUTTON_RIGHT", ""),
433 ("NDOF_BUTTON_FRONT", "NDOF_BUTTON_FRONT", ""),
434 ("NDOF_BUTTON_BACK", "NDOF_BUTTON_BACK", ""),
435 ("NDOF_BUTTON_ISO1", "NDOF_BUTTON_ISO1", ""),
436 ("NDOF_BUTTON_ISO2", "NDOF_BUTTON_ISO2", ""),
437 ("NDOF_BUTTON_ROLL_CW", "NDOF_BUTTON_ROLL_CW", ""),
438 ("NDOF_BUTTON_ROLL_CCW", "NDOF_BUTTON_ROLL_CCW", ""),
439 ("NDOF_BUTTON_SPIN_CW", "NDOF_BUTTON_SPIN_CW", ""),
440 ("NDOF_BUTTON_SPIN_CCW", "NDOF_BUTTON_SPIN_CCW", ""),
441 ("NDOF_BUTTON_TILT_CW", "NDOF_BUTTON_TILT_CW", ""),
442 ("NDOF_BUTTON_TILT_CCW", "NDOF_BUTTON_TILT_CCW", ""),
443 ("NDOF_BUTTON_ROTATE", "NDOF_BUTTON_ROTATE", ""),
444 ("NDOF_BUTTON_PANZOOM", "NDOF_BUTTON_PANZOOM", ""),
445 ("NDOF_BUTTON_DOMINANT", "NDOF_BUTTON_DOMINANT", ""),
446 ("NDOF_BUTTON_PLUS", "NDOF_BUTTON_PLUS", ""),
447 ("NDOF_BUTTON_MINUS", "NDOF_BUTTON_MINUS", ""),
448 ("NDOF_BUTTON_ESC", "NDOF_BUTTON_ESC", ""),
449 ("NDOF_BUTTON_ALT", "NDOF_BUTTON_ALT", ""),
450 ("NDOF_BUTTON_SHIFT", "NDOF_BUTTON_SHIFT", ""),
451 ("NDOF_BUTTON_CTRL", "NDOF_BUTTON_CTRL", ""),
452 ("NDOF_BUTTON_1", "NDOF_BUTTON_1", ""),
453 ("NDOF_BUTTON_2", "NDOF_BUTTON_2", ""),
454 ("NDOF_BUTTON_3", "NDOF_BUTTON_3", ""),
455 ("NDOF_BUTTON_4", "NDOF_BUTTON_4", ""),
456 ("NDOF_BUTTON_5", "NDOF_BUTTON_5", ""),
457 ("NDOF_BUTTON_6", "NDOF_BUTTON_6", ""),
458 ("NDOF_BUTTON_7", "NDOF_BUTTON_7", ""),
459 ("NDOF_BUTTON_8", "NDOF_BUTTON_8", ""),
460 ("NDOF_BUTTON_9", "NDOF_BUTTON_9", ""),
461 ("NDOF_BUTTON_10", "NDOF_BUTTON_10", ""),
462 ("NDOF_BUTTON_A", "NDOF_BUTTON_A", ""),
463 ("NDOF_BUTTON_B", "NDOF_BUTTON_B", ""),
464 ("NDOF_BUTTON_C", "NDOF_BUTTON_C", "")
467 description
="Enter key code in find text",
472 class IsKeyFreeRunExportKeys(Operator
):
473 bl_idname
= "iskeyfree.run_export_keys"
474 bl_label
= "List all Shortcuts"
475 bl_description
= ("List all existing shortcuts in a text block\n"
476 "The newly generated list will be made active in the Text Editor\n"
477 "To access the previous ones, select them from the Header dropdown")
479 def all_shortcuts_name(self
, context
):
480 new_name
, def_name
, ext
= "", "All_Shortcuts", ".txt"
483 # first slap a simple linear count + 1 for numeric suffix, if it fails
484 # harvest for the rightmost numbers and append the max value
486 data_txt
= bpy
.data
.texts
487 list_txt
= [txt
.name
for txt
in data_txt
if txt
.name
.startswith("All_Shortcuts")]
488 new_name
= "{}_{}{}".format(def_name
, len(list_txt
) + 1, ext
)
490 if new_name
in list_txt
:
491 from re
import findall
492 test_num
= [findall(r
"\d+", words
) for words
in list_txt
]
493 suffix
+= max([int(l
[-1]) for l
in test_num
])
494 new_name
= "{}_{}{}".format(def_name
, suffix
, ext
)
499 def execute(self
, context
):
500 wm
= bpy
.context
.window_manager
501 from collections
import defaultdict
502 mykeys
= defaultdict(list)
503 file_name
= self
.all_shortcuts_name(context
) or "All_Shortcut.txt"
504 start_note
= "# Note: Some of the shortcuts entries don't have a name. Mostly Modal stuff\n"
505 col_width
, col_shortcuts
= 2, 2
507 for ctx_type
, keyboardmap
in wm
.keyconfigs
.user
.keymaps
.items():
508 for myitem
in keyboardmap
.keymap_items
:
509 padding
= len(myitem
.name
)
510 col_width
= padding
+ 2 if padding
> col_width
else col_width
512 short_type
= myitem
.type if myitem
.type else "UNKNOWN"
513 is_ctrl
= " Ctrl" if myitem
.ctrl
is True else ""
514 is_alt
= " Alt" if myitem
.alt
is True else ""
515 is_shift
= " Shift" if myitem
.shift
is True else ""
516 is_oskey
= " OsKey" if myitem
.oskey
is True else ""
517 short_cuts
= "{}{}{}{}{}".format(short_type
, is_ctrl
, is_alt
, is_shift
, is_oskey
)
520 myitem
.name
if myitem
.name
else "No Name",
523 mykeys
[ctx_type
].append(t
)
524 padding_s
= len(short_cuts
) + 2
525 col_shortcuts
= padding_s
if padding_s
> col_shortcuts
else col_shortcuts
527 max_line
= col_shortcuts
+ col_width
+ 4
528 textblock
= bpy
.data
.texts
.new(file_name
)
529 total
= sum([len(mykeys
[ctxs
]) for ctxs
in mykeys
])
530 textblock
.write('# %d Total Shortcuts\n\n' % total
)
531 textblock
.write(start_note
)
534 textblock
.write("\n[%s]\nEntries: %s\n\n" % (ctx
, len(mykeys
[ctx
])))
535 line_k
= sorted(mykeys
[ctx
])
537 add_ticks
= "-" * (max_line
- (len(keys
[0]) + len(keys
[1])))
538 entries
= "{ticks} {entry}".format(ticks
=add_ticks
, entry
=keys
[1])
539 textblock
.write("{name} {entry}\n".format(name
=keys
[0], entry
=entries
))
541 textblock
.write("\n\n")
543 # try to set the created text block to active
544 if context
.area
.type in {"TEXT_EDITOR"}:
545 bpy
.context
.space_data
.text
= bpy
.data
.texts
[file_name
]
547 self
.report({'INFO'}, "See %s textblock" % file_name
)
552 # -----------------------------------------------------
554 # ------------------------------------------------------
559 IsKeyFreeRunExportKeys
,
565 bpy
.utils
.register_class(cls
)
566 bpy
.types
.Scene
.is_keyfree
= PointerProperty(type=IskeyFreeProperties
)
571 bpy
.utils
.unregister_class(cls
)
572 del bpy
.types
.Scene
.is_keyfree