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", "EVT_TWEAK_L",
134 "EVT_TWEAK_M", "EVT_TWEAK_R", "A", "B", "C", "D", "E", "F", "G", "H",
136 "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "ZERO", "ONE", "TWO",
137 "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "LEFT_CTRL", "LEFT_ALT", "LEFT_SHIFT",
139 "RIGHT_CTRL", "RIGHT_SHIFT", "OSKEY", "GRLESS", "ESC", "TAB", "RET", "SPACE", "LINE_FEED",
141 "DEL", "SEMI_COLON", "PERIOD", "COMMA", "QUOTE", "ACCENT_GRAVE", "MINUS", "SLASH", "BACK_SLASH",
143 "LEFT_BRACKET", "RIGHT_BRACKET", "LEFT_ARROW", "DOWN_ARROW", "RIGHT_ARROW", "UP_ARROW", "NUMPAD_2",
144 "NUMPAD_4", "NUMPAD_6", "NUMPAD_8", "NUMPAD_1", "NUMPAD_3", "NUMPAD_5", "NUMPAD_7", "NUMPAD_9",
145 "NUMPAD_PERIOD", "NUMPAD_SLASH", "NUMPAD_ASTERIX", "NUMPAD_0", "NUMPAD_MINUS", "NUMPAD_ENTER",
147 "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15",
149 "F18", "F19", "PAUSE", "INSERT", "HOME", "PAGE_UP", "PAGE_DOWN", "END", "MEDIA_PLAY", "MEDIA_STOP",
150 "MEDIA_FIRST", "MEDIA_LAST", "TEXTINPUT", "WINDOW_DEACTIVATE", "TIMER", "TIMER0", "TIMER1", "TIMER2",
151 "TIMER_JOBS", "TIMER_AUTOSAVE", "TIMER_REPORT", "TIMERREGION", "NDOF_MOTION", "NDOF_BUTTON_MENU",
152 "NDOF_BUTTON_FIT", "NDOF_BUTTON_TOP", "NDOF_BUTTON_BOTTOM", "NDOF_BUTTON_LEFT", "NDOF_BUTTON_RIGHT",
153 "NDOF_BUTTON_FRONT", "NDOF_BUTTON_BACK", "NDOF_BUTTON_ISO1", "NDOF_BUTTON_ISO2",
154 "NDOF_BUTTON_ROLL_CW",
155 "NDOF_BUTTON_ROLL_CCW", "NDOF_BUTTON_SPIN_CW", "NDOF_BUTTON_SPIN_CCW", "NDOF_BUTTON_TILT_CW",
156 "NDOF_BUTTON_TILT_CCW", "NDOF_BUTTON_ROTATE", "NDOF_BUTTON_PANZOOM", "NDOF_BUTTON_DOMINANT",
157 "NDOF_BUTTON_PLUS", "NDOF_BUTTON_MINUS", "NDOF_BUTTON_ESC", "NDOF_BUTTON_ALT", "NDOF_BUTTON_SHIFT",
158 "NDOF_BUTTON_CTRL", "NDOF_BUTTON_1", "NDOF_BUTTON_2", "NDOF_BUTTON_3", "NDOF_BUTTON_4",
160 "NDOF_BUTTON_6", "NDOF_BUTTON_7", "NDOF_BUTTON_8", "NDOF_BUTTON_9", "NDOF_BUTTON_10",
162 "NDOF_BUTTON_B", "NDOF_BUTTON_C"
171 mychecker
= MyChecker() # Global class handler
174 # ------------------------------------------------------
175 # Button: Class for search button
176 # ------------------------------------------------------
177 class RunActionCheck(Operator
):
178 bl_idname
= "iskeyfree.action_check"
180 bl_description
= "Verify if the selected shortcut is free"
182 # noinspection PyUnusedLocal
183 def execute(self
, context
):
184 scene
= context
.scene
.is_keyfree
185 txt
= scene
.data
.upper()
187 mychecker
.check(txt
, scene
.use_crtl
, scene
.use_alt
, scene
.use_shift
,
193 # ------------------------------------------------------
195 # ------------------------------------------------------
196 class UIControlPanel(Panel
):
197 bl_idname
= "DEVISKEYFREE_PT_ui"
198 bl_space_type
= "TEXT_EDITOR"
199 bl_region_type
= "UI"
200 bl_label
= "Is Key Free"
202 bl_options
= {'DEFAULT_CLOSED'}
204 # noinspection PyUnusedLocal
205 def draw(self
, context
):
207 scene
= context
.scene
.is_keyfree
209 row
= layout
.row(align
=True)
210 row
.prop(scene
, "data")
211 row
.operator("iskeyfree.action_check", icon
="VIEWZOOM")
213 row
= layout
.row(align
=True)
214 row
.prop(scene
, "use_crtl", toggle
=True)
215 row
.prop(scene
, "use_alt", toggle
=True)
216 row
.prop(scene
, "use_shift", toggle
=True)
217 row
.prop(scene
, "use_oskey", toggle
=True)
220 row
.prop(scene
, "numpad")
222 layout
.operator("iskeyfree.run_export_keys", icon
="FILE_TEXT")
225 mylist
= mychecker
.getlist()
230 cmd
= mychecker
.getlast()
233 row
.label(text
="Current uses of " + str(cmd
), icon
="PARTICLE_DATA")
235 if oldcontext
!= e
[0]:
237 box
.label(text
=e
[0], icon
="UNPINNED")
240 row
= box
.row(align
=True)
243 cmd
= mychecker
.getlast()
246 if mychecker
.isvalidkey(mychecker
.getlastkey()) is False:
247 box
.label(text
=str(mychecker
.getlastkey()) + " looks not valid key", icon
="ERROR")
249 box
.label(text
=str(cmd
) + " is free", icon
="FILE_TICK")
252 # ------------------------------------------------------
253 # Update key (special values) event handler
254 # ------------------------------------------------------
255 # noinspection PyUnusedLocal
256 def update_data(self
, context
):
257 scene
= context
.scene
.is_keyfree
258 if scene
.numpad
!= "NONE":
259 scene
.data
= scene
.numpad
262 class IskeyFreeProperties(PropertyGroup
):
263 data
: StringProperty(
264 name
="Key", maxlen
=32,
265 description
="Shortcut to verify"
267 use_crtl
: BoolProperty(
269 description
="Ctrl key used in shortcut",
272 use_alt
: BoolProperty(
274 description
="Alt key used in shortcut",
277 use_shift
: BoolProperty(
279 description
="Shift key used in shortcut",
282 use_oskey
: BoolProperty(
284 description
="Operating system key used in shortcut",
287 numpad
: EnumProperty(
289 ('NONE', "Select key", ""),
290 ("LEFTMOUSE", "LEFTMOUSE", ""),
291 ("MIDDLEMOUSE", "MIDDLEMOUSE", ""),
292 ("RIGHTMOUSE", "RIGHTMOUSE", ""),
293 ("BUTTON4MOUSE", "BUTTON4MOUSE", ""),
294 ("BUTTON5MOUSE", "BUTTON5MOUSE", ""),
295 ("BUTTON6MOUSE", "BUTTON6MOUSE", ""),
296 ("BUTTON7MOUSE", "BUTTON7MOUSE", ""),
297 ("MOUSEMOVE", "MOUSEMOVE", ""),
298 ("INBETWEEN_MOUSEMOVE", "INBETWEEN_MOUSEMOVE", ""),
299 ("TRACKPADPAN", "TRACKPADPAN", ""),
300 ("TRACKPADZOOM", "TRACKPADZOOM", ""),
301 ("MOUSEROTATE", "MOUSEROTATE", ""),
302 ("WHEELUPMOUSE", "WHEELUPMOUSE", ""),
303 ("WHEELDOWNMOUSE", "WHEELDOWNMOUSE", ""),
304 ("WHEELINMOUSE", "WHEELINMOUSE", ""),
305 ("WHEELOUTMOUSE", "WHEELOUTMOUSE", ""),
306 ("EVT_TWEAK_L", "EVT_TWEAK_L", ""),
307 ("EVT_TWEAK_M", "EVT_TWEAK_M", ""),
308 ("EVT_TWEAK_R", "EVT_TWEAK_R", ""),
335 ("ZERO", "ZERO", ""),
338 ("THREE", "THREE", ""),
339 ("FOUR", "FOUR", ""),
340 ("FIVE", "FIVE", ""),
342 ("SEVEN", "SEVEN", ""),
343 ("EIGHT", "EIGHT", ""),
344 ("NINE", "NINE", ""),
345 ("LEFT_CTRL", "LEFT_CTRL", ""),
346 ("LEFT_ALT", "LEFT_ALT", ""),
347 ("LEFT_SHIFT", "LEFT_SHIFT", ""),
348 ("RIGHT_ALT", "RIGHT_ALT", ""),
349 ("RIGHT_CTRL", "RIGHT_CTRL", ""),
350 ("RIGHT_SHIFT", "RIGHT_SHIFT", ""),
351 ("OSKEY", "OSKEY", ""),
352 ("GRLESS", "GRLESS", ""),
356 ("SPACE", "SPACE", ""),
357 ("LINE_FEED", "LINE_FEED", ""),
358 ("BACK_SPACE", "BACK_SPACE", ""),
360 ("SEMI_COLON", "SEMI_COLON", ""),
361 ("PERIOD", "PERIOD", ""),
362 ("COMMA", "COMMA", ""),
363 ("QUOTE", "QUOTE", ""),
364 ("ACCENT_GRAVE", "ACCENT_GRAVE", ""),
365 ("MINUS", "MINUS", ""),
366 ("SLASH", "SLASH", ""),
367 ("BACK_SLASH", "BACK_SLASH", ""),
368 ("EQUAL", "EQUAL", ""),
369 ("LEFT_BRACKET", "LEFT_BRACKET", ""),
370 ("RIGHT_BRACKET", "RIGHT_BRACKET", ""),
371 ("LEFT_ARROW", "LEFT_ARROW", ""),
372 ("DOWN_ARROW", "DOWN_ARROW", ""),
373 ("RIGHT_ARROW", "RIGHT_ARROW", ""),
374 ("UP_ARROW", "UP_ARROW", ""),
375 ("NUMPAD_1", "NUMPAD_1", ""),
376 ("NUMPAD_2", "NUMPAD_2", ""),
377 ("NUMPAD_3", "NUMPAD_3", ""),
378 ("NUMPAD_4", "NUMPAD_4", ""),
379 ("NUMPAD_5", "NUMPAD_5", ""),
380 ("NUMPAD_6", "NUMPAD_6", ""),
381 ("NUMPAD_7", "NUMPAD_7", ""),
382 ("NUMPAD_8", "NUMPAD_8", ""),
383 ("NUMPAD_9", "NUMPAD_9", ""),
384 ("NUMPAD_0", "NUMPAD_0", ""),
385 ("NUMPAD_PERIOD", "NUMPAD_PERIOD", ""),
386 ("NUMPAD_SLASH", "NUMPAD_SLASH", ""),
387 ("NUMPAD_ASTERIX", "NUMPAD_ASTERIX", ""),
388 ("NUMPAD_MINUS", "NUMPAD_MINUS", ""),
389 ("NUMPAD_ENTER", "NUMPAD_ENTER", ""),
390 ("NUMPAD_PLUS", "NUMPAD_PLUS", ""),
410 ("PAUSE", "PAUSE", ""),
411 ("INSERT", "INSERT", ""),
412 ("HOME", "HOME", ""),
413 ("PAGE_UP", "PAGE_UP", ""),
414 ("PAGE_DOWN", "PAGE_DOWN", ""),
416 ("MEDIA_PLAY", "MEDIA_PLAY", ""),
417 ("MEDIA_STOP", "MEDIA_STOP", ""),
418 ("MEDIA_FIRST", "MEDIA_FIRST", ""),
419 ("MEDIA_LAST", "MEDIA_LAST", ""),
420 ("TEXTINPUT", "TEXTINPUT", ""),
421 ("WINDOW_DEACTIVATE", "WINDOW_DEACTIVATE", ""),
422 ("TIMER", "TIMER", ""),
423 ("TIMER0", "TIMER0", ""),
424 ("TIMER1", "TIMER1", ""),
425 ("TIMER2", "TIMER2", ""),
426 ("TIMER_JOBS", "TIMER_JOBS", ""),
427 ("TIMER_AUTOSAVE", "TIMER_AUTOSAVE", ""),
428 ("TIMER_REPORT", "TIMER_REPORT", ""),
429 ("TIMERREGION", "TIMERREGION", ""),
430 ("NDOF_MOTION", "NDOF_MOTION", ""),
431 ("NDOF_BUTTON_MENU", "NDOF_BUTTON_MENU", ""),
432 ("NDOF_BUTTON_FIT", "NDOF_BUTTON_FIT", ""),
433 ("NDOF_BUTTON_TOP", "NDOF_BUTTON_TOP", ""),
434 ("NDOF_BUTTON_BOTTOM", "NDOF_BUTTON_BOTTOM", ""),
435 ("NDOF_BUTTON_LEFT", "NDOF_BUTTON_LEFT", ""),
436 ("NDOF_BUTTON_RIGHT", "NDOF_BUTTON_RIGHT", ""),
437 ("NDOF_BUTTON_FRONT", "NDOF_BUTTON_FRONT", ""),
438 ("NDOF_BUTTON_BACK", "NDOF_BUTTON_BACK", ""),
439 ("NDOF_BUTTON_ISO1", "NDOF_BUTTON_ISO1", ""),
440 ("NDOF_BUTTON_ISO2", "NDOF_BUTTON_ISO2", ""),
441 ("NDOF_BUTTON_ROLL_CW", "NDOF_BUTTON_ROLL_CW", ""),
442 ("NDOF_BUTTON_ROLL_CCW", "NDOF_BUTTON_ROLL_CCW", ""),
443 ("NDOF_BUTTON_SPIN_CW", "NDOF_BUTTON_SPIN_CW", ""),
444 ("NDOF_BUTTON_SPIN_CCW", "NDOF_BUTTON_SPIN_CCW", ""),
445 ("NDOF_BUTTON_TILT_CW", "NDOF_BUTTON_TILT_CW", ""),
446 ("NDOF_BUTTON_TILT_CCW", "NDOF_BUTTON_TILT_CCW", ""),
447 ("NDOF_BUTTON_ROTATE", "NDOF_BUTTON_ROTATE", ""),
448 ("NDOF_BUTTON_PANZOOM", "NDOF_BUTTON_PANZOOM", ""),
449 ("NDOF_BUTTON_DOMINANT", "NDOF_BUTTON_DOMINANT", ""),
450 ("NDOF_BUTTON_PLUS", "NDOF_BUTTON_PLUS", ""),
451 ("NDOF_BUTTON_MINUS", "NDOF_BUTTON_MINUS", ""),
452 ("NDOF_BUTTON_ESC", "NDOF_BUTTON_ESC", ""),
453 ("NDOF_BUTTON_ALT", "NDOF_BUTTON_ALT", ""),
454 ("NDOF_BUTTON_SHIFT", "NDOF_BUTTON_SHIFT", ""),
455 ("NDOF_BUTTON_CTRL", "NDOF_BUTTON_CTRL", ""),
456 ("NDOF_BUTTON_1", "NDOF_BUTTON_1", ""),
457 ("NDOF_BUTTON_2", "NDOF_BUTTON_2", ""),
458 ("NDOF_BUTTON_3", "NDOF_BUTTON_3", ""),
459 ("NDOF_BUTTON_4", "NDOF_BUTTON_4", ""),
460 ("NDOF_BUTTON_5", "NDOF_BUTTON_5", ""),
461 ("NDOF_BUTTON_6", "NDOF_BUTTON_6", ""),
462 ("NDOF_BUTTON_7", "NDOF_BUTTON_7", ""),
463 ("NDOF_BUTTON_8", "NDOF_BUTTON_8", ""),
464 ("NDOF_BUTTON_9", "NDOF_BUTTON_9", ""),
465 ("NDOF_BUTTON_10", "NDOF_BUTTON_10", ""),
466 ("NDOF_BUTTON_A", "NDOF_BUTTON_A", ""),
467 ("NDOF_BUTTON_B", "NDOF_BUTTON_B", ""),
468 ("NDOF_BUTTON_C", "NDOF_BUTTON_C", "")
471 description
="Enter key code in find text",
476 class IsKeyFreeRunExportKeys(Operator
):
477 bl_idname
= "iskeyfree.run_export_keys"
478 bl_label
= "List all Shortcuts"
479 bl_description
= ("List all existing shortcuts in a text block\n"
480 "The newly generated list will be made active in the Text Editor\n"
481 "To access the previous ones, select them from the Header dropdown")
483 def all_shortcuts_name(self
, context
):
484 new_name
, def_name
, ext
= "", "All_Shortcuts", ".txt"
487 # first slap a simple linear count + 1 for numeric suffix, if it fails
488 # harvest for the rightmost numbers and append the max value
490 data_txt
= bpy
.data
.texts
491 list_txt
= [txt
.name
for txt
in data_txt
if txt
.name
.startswith("All_Shortcuts")]
492 new_name
= "{}_{}{}".format(def_name
, len(list_txt
) + 1, ext
)
494 if new_name
in list_txt
:
495 from re
import findall
496 test_num
= [findall(r
"\d+", words
) for words
in list_txt
]
497 suffix
+= max([int(l
[-1]) for l
in test_num
])
498 new_name
= "{}_{}{}".format(def_name
, suffix
, ext
)
503 def execute(self
, context
):
504 wm
= bpy
.context
.window_manager
505 from collections
import defaultdict
506 mykeys
= defaultdict(list)
507 file_name
= self
.all_shortcuts_name(context
) or "All_Shortcut.txt"
508 start_note
= "# Note: Some of the shortcuts entries don't have a name. Mostly Modal stuff\n"
509 col_width
, col_shortcuts
= 2, 2
511 for ctx_type
, keyboardmap
in wm
.keyconfigs
.user
.keymaps
.items():
512 for myitem
in keyboardmap
.keymap_items
:
513 padding
= len(myitem
.name
)
514 col_width
= padding
+ 2 if padding
> col_width
else col_width
516 short_type
= myitem
.type if myitem
.type else "UNKNOWN"
517 is_ctrl
= " Ctrl" if myitem
.ctrl
is True else ""
518 is_alt
= " Alt" if myitem
.alt
is True else ""
519 is_shift
= " Shift" if myitem
.shift
is True else ""
520 is_oskey
= " OsKey" if myitem
.oskey
is True else ""
521 short_cuts
= "{}{}{}{}{}".format(short_type
, is_ctrl
, is_alt
, is_shift
, is_oskey
)
524 myitem
.name
if myitem
.name
else "No Name",
527 mykeys
[ctx_type
].append(t
)
528 padding_s
= len(short_cuts
) + 2
529 col_shortcuts
= padding_s
if padding_s
> col_shortcuts
else col_shortcuts
531 max_line
= col_shortcuts
+ col_width
+ 4
532 textblock
= bpy
.data
.texts
.new(file_name
)
533 total
= sum([len(mykeys
[ctxs
]) for ctxs
in mykeys
])
534 textblock
.write('# %d Total Shortcuts\n\n' % total
)
535 textblock
.write(start_note
)
538 textblock
.write("\n[%s]\nEntries: %s\n\n" % (ctx
, len(mykeys
[ctx
])))
539 line_k
= sorted(mykeys
[ctx
])
541 add_ticks
= "-" * (max_line
- (len(keys
[0]) + len(keys
[1])))
542 entries
= "{ticks} {entry}".format(ticks
=add_ticks
, entry
=keys
[1])
543 textblock
.write("{name} {entry}\n".format(name
=keys
[0], entry
=entries
))
545 textblock
.write("\n\n")
547 # try to set the created text block to active
548 if context
.area
.type in {"TEXT_EDITOR"}:
549 bpy
.context
.space_data
.text
= bpy
.data
.texts
[file_name
]
551 self
.report({'INFO'}, "See %s textblock" % file_name
)
556 # -----------------------------------------------------
558 # ------------------------------------------------------
563 IsKeyFreeRunExportKeys
,
569 bpy
.utils
.register_class(cls
)
570 bpy
.types
.Scene
.is_keyfree
= PointerProperty(type=IskeyFreeProperties
)
575 bpy
.utils
.unregister_class(cls
)
576 del bpy
.types
.Scene
.is_keyfree