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