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",
20 from bpy
.props
import (
26 from bpy
.types
import (
34 # ------------------------------------------------------
35 # Class to find keymaps
36 # ------------------------------------------------------
47 # Verify if the key is used
49 def check(cls
, findkey
, ctrl
, alt
, shift
, oskey
):
60 cls
.lastfind
= cmd
+ findkey
.upper()
61 cls
.lastkey
= findkey
.upper()
66 wm
= bpy
.context
.window_manager
69 for context
, keyboardmap
in wm
.keyconfigs
.user
.keymaps
.items():
70 for myitem
in keyboardmap
.keymap_items
:
71 if myitem
.active
and myitem
.type == findkey
:
72 if ctrl
and not myitem
.ctrl
:
74 if alt
and not myitem
.alt
:
76 if shift
and not myitem
.shift
:
78 if oskey
and not myitem
.oskey
:
82 "Ctrl" if myitem
.ctrl
else "",
83 "Alt" if myitem
.alt
else "",
84 "Shift" if myitem
.shift
else "",
85 "OsKey" if myitem
.oskey
else "",
90 sortkeys
= sorted(mykeys
, key
=lambda key
: (key
[0], key
[1], key
[2], key
[3], key
[4], key
[5]))
108 cls
.mylist
.append([e
[0], cmd
])
113 return str(bpy
.context
.screen
.name
)
125 # return result of last search
130 # verify if key is valid
132 def isvalidkey(cls
, txt
):
134 "LEFTMOUSE", "MIDDLEMOUSE", "RIGHTMOUSE", "BUTTON4MOUSE", "BUTTON5MOUSE", "BUTTON6MOUSE",
136 "MOUSEMOVE", "INBETWEEN_MOUSEMOVE", "TRACKPADPAN", "TRACKPADZOOM",
137 "MOUSEROTATE", "WHEELUPMOUSE", "WHEELDOWNMOUSE", "WHEELINMOUSE", "WHEELOUTMOUSE",
138 "A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
139 "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "ZERO", "ONE", "TWO",
140 "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "LEFT_CTRL", "LEFT_ALT", "LEFT_SHIFT",
142 "RIGHT_CTRL", "RIGHT_SHIFT", "OSKEY", "GRLESS", "ESC", "TAB", "RET", "SPACE", "LINE_FEED",
144 "DEL", "SEMI_COLON", "PERIOD", "COMMA", "QUOTE", "ACCENT_GRAVE", "MINUS", "SLASH", "BACK_SLASH",
146 "LEFT_BRACKET", "RIGHT_BRACKET", "LEFT_ARROW", "DOWN_ARROW", "RIGHT_ARROW", "UP_ARROW", "NUMPAD_2",
147 "NUMPAD_4", "NUMPAD_6", "NUMPAD_8", "NUMPAD_1", "NUMPAD_3", "NUMPAD_5", "NUMPAD_7", "NUMPAD_9",
148 "NUMPAD_PERIOD", "NUMPAD_SLASH", "NUMPAD_ASTERIX", "NUMPAD_0", "NUMPAD_MINUS", "NUMPAD_ENTER",
150 "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15",
152 "F18", "F19", "PAUSE", "INSERT", "HOME", "PAGE_UP", "PAGE_DOWN", "END", "MEDIA_PLAY", "MEDIA_STOP",
153 "MEDIA_FIRST", "MEDIA_LAST", "TEXTINPUT", "WINDOW_DEACTIVATE", "TIMER", "TIMER0", "TIMER1", "TIMER2",
154 "TIMER_JOBS", "TIMER_AUTOSAVE", "TIMER_REPORT", "TIMERREGION", "NDOF_MOTION", "NDOF_BUTTON_MENU",
155 "NDOF_BUTTON_FIT", "NDOF_BUTTON_TOP", "NDOF_BUTTON_BOTTOM", "NDOF_BUTTON_LEFT", "NDOF_BUTTON_RIGHT",
156 "NDOF_BUTTON_FRONT", "NDOF_BUTTON_BACK", "NDOF_BUTTON_ISO1", "NDOF_BUTTON_ISO2",
157 "NDOF_BUTTON_ROLL_CW",
158 "NDOF_BUTTON_ROLL_CCW", "NDOF_BUTTON_SPIN_CW", "NDOF_BUTTON_SPIN_CCW", "NDOF_BUTTON_TILT_CW",
159 "NDOF_BUTTON_TILT_CCW", "NDOF_BUTTON_ROTATE", "NDOF_BUTTON_PANZOOM", "NDOF_BUTTON_DOMINANT",
160 "NDOF_BUTTON_PLUS", "NDOF_BUTTON_MINUS", "NDOF_BUTTON_ESC", "NDOF_BUTTON_ALT", "NDOF_BUTTON_SHIFT",
161 "NDOF_BUTTON_CTRL", "NDOF_BUTTON_1", "NDOF_BUTTON_2", "NDOF_BUTTON_3", "NDOF_BUTTON_4",
163 "NDOF_BUTTON_6", "NDOF_BUTTON_7", "NDOF_BUTTON_8", "NDOF_BUTTON_9", "NDOF_BUTTON_10",
165 "NDOF_BUTTON_B", "NDOF_BUTTON_C"
174 mychecker
= MyChecker() # Global class handler
177 # ------------------------------------------------------
178 # Button: Class for search button
179 # ------------------------------------------------------
180 class RunActionCheck(Operator
):
181 bl_idname
= "iskeyfree.action_check"
183 bl_description
= "Verify if the selected shortcut is free"
185 # noinspection PyUnusedLocal
186 def execute(self
, context
):
187 scene
= context
.scene
.is_keyfree
188 txt
= scene
.data
.upper()
190 mychecker
.check(txt
, scene
.use_crtl
, scene
.use_alt
, scene
.use_shift
,
196 # ------------------------------------------------------
198 # ------------------------------------------------------
199 class UIControlPanel(Panel
):
200 bl_idname
= "DEVISKEYFREE_PT_ui"
201 bl_space_type
= "TEXT_EDITOR"
202 bl_region_type
= "UI"
203 bl_label
= "Is Key Free"
205 bl_options
= {'DEFAULT_CLOSED'}
207 # noinspection PyUnusedLocal
208 def draw(self
, context
):
210 scene
= context
.scene
.is_keyfree
212 row
= layout
.row(align
=True)
213 row
.prop(scene
, "data")
214 row
.operator("iskeyfree.action_check", icon
="VIEWZOOM")
216 row
= layout
.row(align
=True)
217 row
.prop(scene
, "use_crtl", toggle
=True)
218 row
.prop(scene
, "use_alt", toggle
=True)
219 row
.prop(scene
, "use_shift", toggle
=True)
220 row
.prop(scene
, "use_oskey", toggle
=True)
223 row
.prop(scene
, "numpad")
225 layout
.operator("iskeyfree.run_export_keys", icon
="FILE_TEXT")
228 mylist
= mychecker
.getlist()
233 cmd
= mychecker
.getlast()
236 row
.label(text
="Current uses of " + str(cmd
), icon
="PARTICLE_DATA")
238 if oldcontext
!= e
[0]:
240 box
.label(text
=e
[0], icon
="UNPINNED")
243 row
= box
.row(align
=True)
246 cmd
= mychecker
.getlast()
249 if mychecker
.isvalidkey(mychecker
.getlastkey()) is False:
250 box
.label(text
=str(mychecker
.getlastkey()) + " looks not valid key", icon
="ERROR")
252 box
.label(text
=str(cmd
) + " is free", icon
="FILE_TICK")
255 # ------------------------------------------------------
256 # Update key (special values) event handler
257 # ------------------------------------------------------
258 # noinspection PyUnusedLocal
259 def update_data(self
, context
):
260 scene
= context
.scene
.is_keyfree
261 if scene
.numpad
!= "NONE":
262 scene
.data
= scene
.numpad
265 class IskeyFreeProperties(PropertyGroup
):
266 data
: StringProperty(
267 name
="Key", maxlen
=32,
268 description
="Shortcut to verify"
270 use_crtl
: BoolProperty(
272 description
="Ctrl key used in shortcut",
275 use_alt
: BoolProperty(
277 description
="Alt key used in shortcut",
280 use_shift
: BoolProperty(
282 description
="Shift key used in shortcut",
285 use_oskey
: BoolProperty(
287 description
="Operating system key used in shortcut",
290 numpad
: EnumProperty(
292 ('NONE', "Select key", ""),
293 ("LEFTMOUSE", "LEFTMOUSE", ""),
294 ("MIDDLEMOUSE", "MIDDLEMOUSE", ""),
295 ("RIGHTMOUSE", "RIGHTMOUSE", ""),
296 ("BUTTON4MOUSE", "BUTTON4MOUSE", ""),
297 ("BUTTON5MOUSE", "BUTTON5MOUSE", ""),
298 ("BUTTON6MOUSE", "BUTTON6MOUSE", ""),
299 ("BUTTON7MOUSE", "BUTTON7MOUSE", ""),
300 ("MOUSEMOVE", "MOUSEMOVE", ""),
301 ("INBETWEEN_MOUSEMOVE", "INBETWEEN_MOUSEMOVE", ""),
302 ("TRACKPADPAN", "TRACKPADPAN", ""),
303 ("TRACKPADZOOM", "TRACKPADZOOM", ""),
304 ("MOUSEROTATE", "MOUSEROTATE", ""),
305 ("WHEELUPMOUSE", "WHEELUPMOUSE", ""),
306 ("WHEELDOWNMOUSE", "WHEELDOWNMOUSE", ""),
307 ("WHEELINMOUSE", "WHEELINMOUSE", ""),
308 ("WHEELOUTMOUSE", "WHEELOUTMOUSE", ""),
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 unicodelen(self
, string
):
506 if unicodedata
.east_asian_width(c
) in 'FWA':
512 def execute(self
, context
):
513 wm
= bpy
.context
.window_manager
514 from collections
import defaultdict
515 mykeys
= defaultdict(list)
516 file_name
= self
.all_shortcuts_name(context
) or "All_Shortcut.txt"
517 start_note
= "# Note: Some of the shortcuts entries don't have a name. Mostly Modal stuff\n"
518 col_width
, col_shortcuts
= 2, 2
520 for ctx_type
, keyboardmap
in wm
.keyconfigs
.user
.keymaps
.items():
521 for myitem
in keyboardmap
.keymap_items
:
522 padding
= len(myitem
.name
)
523 col_width
= padding
+ 2 if padding
> col_width
else col_width
525 short_type
= myitem
.type if myitem
.type else "UNKNOWN"
526 is_ctrl
= " Ctrl" if myitem
.ctrl
else ""
527 is_alt
= " Alt" if myitem
.alt
else ""
528 is_shift
= " Shift" if myitem
.shift
else ""
529 is_oskey
= " OsKey" if myitem
.oskey
else ""
530 short_cuts
= "{}{}{}{}{}".format(short_type
, is_ctrl
, is_alt
, is_shift
, is_oskey
)
533 myitem
.name
if myitem
.name
else "No Name",
536 mykeys
[ctx_type
].append(t
)
537 padding_s
= len(short_cuts
) + 2
538 col_shortcuts
= padding_s
if padding_s
> col_shortcuts
else col_shortcuts
540 max_line
= col_shortcuts
+ col_width
+ 4
541 textblock
= bpy
.data
.texts
.new(file_name
)
542 total
= sum([len(mykeys
[ctxs
]) for ctxs
in mykeys
])
543 textblock
.write('# %d Total Shortcuts\n\n' % total
)
544 textblock
.write(start_note
)
547 textblock
.write("\n[%s]\nEntries: %s\n\n" % (ctx
, len(mykeys
[ctx
])))
548 line_k
= sorted(mykeys
[ctx
])
550 add_ticks
= "-" * (max_line
- (self
.unicodelen(keys
[0]) + len(keys
[1])))
551 entries
= "{ticks} {entry}".format(ticks
=add_ticks
, entry
=keys
[1])
552 textblock
.write("{name} {entry}\n".format(name
=keys
[0], entry
=entries
))
554 textblock
.write("\n\n")
556 # try to set the created text block to active
557 if context
.area
.type in {"TEXT_EDITOR"}:
558 bpy
.context
.space_data
.text
= bpy
.data
.texts
[file_name
]
560 self
.report({'INFO'}, "See %s textblock" % file_name
)
565 # -----------------------------------------------------
567 # ------------------------------------------------------
572 IsKeyFreeRunExportKeys
,
578 bpy
.utils
.register_class(cls
)
579 bpy
.types
.Scene
.is_keyfree
= PointerProperty(type=IskeyFreeProperties
)
584 bpy
.utils
.unregister_class(cls
)
585 del bpy
.types
.Scene
.is_keyfree