1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # PEP8 compliant (https://www.python.org/dev/peps/pep-0008)
22 "name": "Is key Free",
23 "author": "Antonio Vazquez (antonioya)",
25 "blender": (2, 80, 0),
26 "location": "Text Editor > Sidebar > Dev Tab",
27 "description": "Find free shortcuts, inform about used and print a key list",
28 "wiki_url": "https://docs.blender.org/manual/en/dev/addons/"
29 "development/is_key_free.html",
30 "category": "Development"
34 from bpy
.props
import (
40 from bpy
.types
import (
47 # ------------------------------------------------------
48 # Class to find keymaps
49 # ------------------------------------------------------
60 # Verify if the key is used
62 def check(cls
, findkey
, ctrl
, alt
, shift
, oskey
):
73 cls
.lastfind
= cmd
+ findkey
.upper()
74 cls
.lastkey
= findkey
.upper()
79 wm
= bpy
.context
.window_manager
82 for context
, keyboardmap
in wm
.keyconfigs
.user
.keymaps
.items():
83 for myitem
in keyboardmap
.keymap_items
:
84 if myitem
.active
is True and myitem
.type == findkey
:
85 if ctrl
is True and myitem
.ctrl
is not True:
87 if alt
is True and myitem
.alt
is not True:
89 if shift
is True and myitem
.shift
is not True:
91 if oskey
is True and myitem
.oskey
is not True:
95 "Ctrl" if myitem
.ctrl
is True else "",
96 "Alt" if myitem
.alt
is True else "",
97 "Shift" if myitem
.shift
is True else "",
98 "OsKey" if myitem
.oskey
is True else "",
103 sortkeys
= sorted(mykeys
, key
=lambda key
: (key
[0], key
[1], key
[2], key
[3], key
[4], key
[5]))
121 cls
.mylist
.append([e
[0], cmd
])
126 return str(bpy
.context
.screen
.name
)
138 # return result of last search
143 # verify if key is valid
145 def isvalidkey(cls
, txt
):
147 "LEFTMOUSE", "MIDDLEMOUSE", "RIGHTMOUSE", "BUTTON4MOUSE", "BUTTON5MOUSE", "BUTTON6MOUSE",
149 "MOUSEMOVE", "INBETWEEN_MOUSEMOVE", "TRACKPADPAN", "TRACKPADZOOM",
150 "MOUSEROTATE", "WHEELUPMOUSE", "WHEELDOWNMOUSE", "WHEELINMOUSE", "WHEELOUTMOUSE", "EVT_TWEAK_L",
151 "EVT_TWEAK_M", "EVT_TWEAK_R", "A", "B", "C", "D", "E", "F", "G", "H",
153 "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "ZERO", "ONE", "TWO",
154 "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "LEFT_CTRL", "LEFT_ALT", "LEFT_SHIFT",
156 "RIGHT_CTRL", "RIGHT_SHIFT", "OSKEY", "GRLESS", "ESC", "TAB", "RET", "SPACE", "LINE_FEED",
158 "DEL", "SEMI_COLON", "PERIOD", "COMMA", "QUOTE", "ACCENT_GRAVE", "MINUS", "SLASH", "BACK_SLASH",
160 "LEFT_BRACKET", "RIGHT_BRACKET", "LEFT_ARROW", "DOWN_ARROW", "RIGHT_ARROW", "UP_ARROW", "NUMPAD_2",
161 "NUMPAD_4", "NUMPAD_6", "NUMPAD_8", "NUMPAD_1", "NUMPAD_3", "NUMPAD_5", "NUMPAD_7", "NUMPAD_9",
162 "NUMPAD_PERIOD", "NUMPAD_SLASH", "NUMPAD_ASTERIX", "NUMPAD_0", "NUMPAD_MINUS", "NUMPAD_ENTER",
164 "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15",
166 "F18", "F19", "PAUSE", "INSERT", "HOME", "PAGE_UP", "PAGE_DOWN", "END", "MEDIA_PLAY", "MEDIA_STOP",
167 "MEDIA_FIRST", "MEDIA_LAST", "TEXTINPUT", "WINDOW_DEACTIVATE", "TIMER", "TIMER0", "TIMER1", "TIMER2",
168 "TIMER_JOBS", "TIMER_AUTOSAVE", "TIMER_REPORT", "TIMERREGION", "NDOF_MOTION", "NDOF_BUTTON_MENU",
169 "NDOF_BUTTON_FIT", "NDOF_BUTTON_TOP", "NDOF_BUTTON_BOTTOM", "NDOF_BUTTON_LEFT", "NDOF_BUTTON_RIGHT",
170 "NDOF_BUTTON_FRONT", "NDOF_BUTTON_BACK", "NDOF_BUTTON_ISO1", "NDOF_BUTTON_ISO2",
171 "NDOF_BUTTON_ROLL_CW",
172 "NDOF_BUTTON_ROLL_CCW", "NDOF_BUTTON_SPIN_CW", "NDOF_BUTTON_SPIN_CCW", "NDOF_BUTTON_TILT_CW",
173 "NDOF_BUTTON_TILT_CCW", "NDOF_BUTTON_ROTATE", "NDOF_BUTTON_PANZOOM", "NDOF_BUTTON_DOMINANT",
174 "NDOF_BUTTON_PLUS", "NDOF_BUTTON_MINUS", "NDOF_BUTTON_ESC", "NDOF_BUTTON_ALT", "NDOF_BUTTON_SHIFT",
175 "NDOF_BUTTON_CTRL", "NDOF_BUTTON_1", "NDOF_BUTTON_2", "NDOF_BUTTON_3", "NDOF_BUTTON_4",
177 "NDOF_BUTTON_6", "NDOF_BUTTON_7", "NDOF_BUTTON_8", "NDOF_BUTTON_9", "NDOF_BUTTON_10",
179 "NDOF_BUTTON_B", "NDOF_BUTTON_C"
188 mychecker
= MyChecker() # Global class handler
191 # ------------------------------------------------------
192 # Button: Class for search button
193 # ------------------------------------------------------
194 class RunActionCheck(Operator
):
195 bl_idname
= "iskeyfree.action_check"
197 bl_description
= "Verify if the selected shortcut is free"
199 # noinspection PyUnusedLocal
200 def execute(self
, context
):
201 scene
= context
.scene
.is_keyfree
202 txt
= scene
.data
.upper()
204 mychecker
.check(txt
, scene
.use_crtl
, scene
.use_alt
, scene
.use_shift
,
210 # ------------------------------------------------------
212 # ------------------------------------------------------
213 class UIControlPanel(Panel
):
214 bl_idname
= "DEVISKEYFREE_PT_ui"
215 bl_space_type
= "TEXT_EDITOR"
216 bl_region_type
= "UI"
217 bl_label
= "Is Key Free"
219 bl_options
= {'DEFAULT_CLOSED'}
221 # noinspection PyUnusedLocal
222 def draw(self
, context
):
224 scene
= context
.scene
.is_keyfree
226 row
= layout
.row(align
=True)
227 row
.prop(scene
, "data")
228 row
.operator("iskeyfree.action_check", icon
="VIEWZOOM")
230 row
= layout
.row(align
=True)
231 row
.prop(scene
, "use_crtl", toggle
=True)
232 row
.prop(scene
, "use_alt", toggle
=True)
233 row
.prop(scene
, "use_shift", toggle
=True)
234 row
.prop(scene
, "use_oskey", toggle
=True)
237 row
.prop(scene
, "numpad")
239 layout
.operator("iskeyfree.run_export_keys", icon
="FILE_TEXT")
242 mylist
= mychecker
.getlist()
247 cmd
= mychecker
.getlast()
250 row
.label(text
="Current uses of " + str(cmd
), icon
="PARTICLE_DATA")
252 if oldcontext
!= e
[0]:
254 box
.label(text
=e
[0], icon
="UNPINNED")
257 row
= box
.row(align
=True)
260 cmd
= mychecker
.getlast()
263 if mychecker
.isvalidkey(mychecker
.getlastkey()) is False:
264 box
.label(text
=str(mychecker
.getlastkey()) + " looks not valid key", icon
="ERROR")
266 box
.label(text
=str(cmd
) + " is free", icon
="FILE_TICK")
269 # ------------------------------------------------------
270 # Update key (special values) event handler
271 # ------------------------------------------------------
272 # noinspection PyUnusedLocal
273 def update_data(self
, context
):
274 scene
= context
.scene
.is_keyfree
275 if scene
.numpad
!= "NONE":
276 scene
.data
= scene
.numpad
279 class IskeyFreeProperties(PropertyGroup
):
280 data
: StringProperty(
281 name
="Key", maxlen
=32,
282 description
="Shortcut to verify"
284 use_crtl
: BoolProperty(
286 description
="Ctrl key used in shortcut",
289 use_alt
: BoolProperty(
291 description
="Alt key used in shortcut",
294 use_shift
: BoolProperty(
296 description
="Shift key used in shortcut",
299 use_oskey
: BoolProperty(
301 description
="Operating system key used in shortcut",
304 numpad
: EnumProperty(
306 ('NONE', "Select key", ""),
307 ("LEFTMOUSE", "LEFTMOUSE", ""),
308 ("MIDDLEMOUSE", "MIDDLEMOUSE", ""),
309 ("RIGHTMOUSE", "RIGHTMOUSE", ""),
310 ("BUTTON4MOUSE", "BUTTON4MOUSE", ""),
311 ("BUTTON5MOUSE", "BUTTON5MOUSE", ""),
312 ("BUTTON6MOUSE", "BUTTON6MOUSE", ""),
313 ("BUTTON7MOUSE", "BUTTON7MOUSE", ""),
314 ("MOUSEMOVE", "MOUSEMOVE", ""),
315 ("INBETWEEN_MOUSEMOVE", "INBETWEEN_MOUSEMOVE", ""),
316 ("TRACKPADPAN", "TRACKPADPAN", ""),
317 ("TRACKPADZOOM", "TRACKPADZOOM", ""),
318 ("MOUSEROTATE", "MOUSEROTATE", ""),
319 ("WHEELUPMOUSE", "WHEELUPMOUSE", ""),
320 ("WHEELDOWNMOUSE", "WHEELDOWNMOUSE", ""),
321 ("WHEELINMOUSE", "WHEELINMOUSE", ""),
322 ("WHEELOUTMOUSE", "WHEELOUTMOUSE", ""),
323 ("EVT_TWEAK_L", "EVT_TWEAK_L", ""),
324 ("EVT_TWEAK_M", "EVT_TWEAK_M", ""),
325 ("EVT_TWEAK_R", "EVT_TWEAK_R", ""),
352 ("ZERO", "ZERO", ""),
355 ("THREE", "THREE", ""),
356 ("FOUR", "FOUR", ""),
357 ("FIVE", "FIVE", ""),
359 ("SEVEN", "SEVEN", ""),
360 ("EIGHT", "EIGHT", ""),
361 ("NINE", "NINE", ""),
362 ("LEFT_CTRL", "LEFT_CTRL", ""),
363 ("LEFT_ALT", "LEFT_ALT", ""),
364 ("LEFT_SHIFT", "LEFT_SHIFT", ""),
365 ("RIGHT_ALT", "RIGHT_ALT", ""),
366 ("RIGHT_CTRL", "RIGHT_CTRL", ""),
367 ("RIGHT_SHIFT", "RIGHT_SHIFT", ""),
368 ("OSKEY", "OSKEY", ""),
369 ("GRLESS", "GRLESS", ""),
373 ("SPACE", "SPACE", ""),
374 ("LINE_FEED", "LINE_FEED", ""),
375 ("BACK_SPACE", "BACK_SPACE", ""),
377 ("SEMI_COLON", "SEMI_COLON", ""),
378 ("PERIOD", "PERIOD", ""),
379 ("COMMA", "COMMA", ""),
380 ("QUOTE", "QUOTE", ""),
381 ("ACCENT_GRAVE", "ACCENT_GRAVE", ""),
382 ("MINUS", "MINUS", ""),
383 ("SLASH", "SLASH", ""),
384 ("BACK_SLASH", "BACK_SLASH", ""),
385 ("EQUAL", "EQUAL", ""),
386 ("LEFT_BRACKET", "LEFT_BRACKET", ""),
387 ("RIGHT_BRACKET", "RIGHT_BRACKET", ""),
388 ("LEFT_ARROW", "LEFT_ARROW", ""),
389 ("DOWN_ARROW", "DOWN_ARROW", ""),
390 ("RIGHT_ARROW", "RIGHT_ARROW", ""),
391 ("UP_ARROW", "UP_ARROW", ""),
392 ("NUMPAD_1", "NUMPAD_1", ""),
393 ("NUMPAD_2", "NUMPAD_2", ""),
394 ("NUMPAD_3", "NUMPAD_3", ""),
395 ("NUMPAD_4", "NUMPAD_4", ""),
396 ("NUMPAD_5", "NUMPAD_5", ""),
397 ("NUMPAD_6", "NUMPAD_6", ""),
398 ("NUMPAD_7", "NUMPAD_7", ""),
399 ("NUMPAD_8", "NUMPAD_8", ""),
400 ("NUMPAD_9", "NUMPAD_9", ""),
401 ("NUMPAD_0", "NUMPAD_0", ""),
402 ("NUMPAD_PERIOD", "NUMPAD_PERIOD", ""),
403 ("NUMPAD_SLASH", "NUMPAD_SLASH", ""),
404 ("NUMPAD_ASTERIX", "NUMPAD_ASTERIX", ""),
405 ("NUMPAD_MINUS", "NUMPAD_MINUS", ""),
406 ("NUMPAD_ENTER", "NUMPAD_ENTER", ""),
407 ("NUMPAD_PLUS", "NUMPAD_PLUS", ""),
427 ("PAUSE", "PAUSE", ""),
428 ("INSERT", "INSERT", ""),
429 ("HOME", "HOME", ""),
430 ("PAGE_UP", "PAGE_UP", ""),
431 ("PAGE_DOWN", "PAGE_DOWN", ""),
433 ("MEDIA_PLAY", "MEDIA_PLAY", ""),
434 ("MEDIA_STOP", "MEDIA_STOP", ""),
435 ("MEDIA_FIRST", "MEDIA_FIRST", ""),
436 ("MEDIA_LAST", "MEDIA_LAST", ""),
437 ("TEXTINPUT", "TEXTINPUT", ""),
438 ("WINDOW_DEACTIVATE", "WINDOW_DEACTIVATE", ""),
439 ("TIMER", "TIMER", ""),
440 ("TIMER0", "TIMER0", ""),
441 ("TIMER1", "TIMER1", ""),
442 ("TIMER2", "TIMER2", ""),
443 ("TIMER_JOBS", "TIMER_JOBS", ""),
444 ("TIMER_AUTOSAVE", "TIMER_AUTOSAVE", ""),
445 ("TIMER_REPORT", "TIMER_REPORT", ""),
446 ("TIMERREGION", "TIMERREGION", ""),
447 ("NDOF_MOTION", "NDOF_MOTION", ""),
448 ("NDOF_BUTTON_MENU", "NDOF_BUTTON_MENU", ""),
449 ("NDOF_BUTTON_FIT", "NDOF_BUTTON_FIT", ""),
450 ("NDOF_BUTTON_TOP", "NDOF_BUTTON_TOP", ""),
451 ("NDOF_BUTTON_BOTTOM", "NDOF_BUTTON_BOTTOM", ""),
452 ("NDOF_BUTTON_LEFT", "NDOF_BUTTON_LEFT", ""),
453 ("NDOF_BUTTON_RIGHT", "NDOF_BUTTON_RIGHT", ""),
454 ("NDOF_BUTTON_FRONT", "NDOF_BUTTON_FRONT", ""),
455 ("NDOF_BUTTON_BACK", "NDOF_BUTTON_BACK", ""),
456 ("NDOF_BUTTON_ISO1", "NDOF_BUTTON_ISO1", ""),
457 ("NDOF_BUTTON_ISO2", "NDOF_BUTTON_ISO2", ""),
458 ("NDOF_BUTTON_ROLL_CW", "NDOF_BUTTON_ROLL_CW", ""),
459 ("NDOF_BUTTON_ROLL_CCW", "NDOF_BUTTON_ROLL_CCW", ""),
460 ("NDOF_BUTTON_SPIN_CW", "NDOF_BUTTON_SPIN_CW", ""),
461 ("NDOF_BUTTON_SPIN_CCW", "NDOF_BUTTON_SPIN_CCW", ""),
462 ("NDOF_BUTTON_TILT_CW", "NDOF_BUTTON_TILT_CW", ""),
463 ("NDOF_BUTTON_TILT_CCW", "NDOF_BUTTON_TILT_CCW", ""),
464 ("NDOF_BUTTON_ROTATE", "NDOF_BUTTON_ROTATE", ""),
465 ("NDOF_BUTTON_PANZOOM", "NDOF_BUTTON_PANZOOM", ""),
466 ("NDOF_BUTTON_DOMINANT", "NDOF_BUTTON_DOMINANT", ""),
467 ("NDOF_BUTTON_PLUS", "NDOF_BUTTON_PLUS", ""),
468 ("NDOF_BUTTON_MINUS", "NDOF_BUTTON_MINUS", ""),
469 ("NDOF_BUTTON_ESC", "NDOF_BUTTON_ESC", ""),
470 ("NDOF_BUTTON_ALT", "NDOF_BUTTON_ALT", ""),
471 ("NDOF_BUTTON_SHIFT", "NDOF_BUTTON_SHIFT", ""),
472 ("NDOF_BUTTON_CTRL", "NDOF_BUTTON_CTRL", ""),
473 ("NDOF_BUTTON_1", "NDOF_BUTTON_1", ""),
474 ("NDOF_BUTTON_2", "NDOF_BUTTON_2", ""),
475 ("NDOF_BUTTON_3", "NDOF_BUTTON_3", ""),
476 ("NDOF_BUTTON_4", "NDOF_BUTTON_4", ""),
477 ("NDOF_BUTTON_5", "NDOF_BUTTON_5", ""),
478 ("NDOF_BUTTON_6", "NDOF_BUTTON_6", ""),
479 ("NDOF_BUTTON_7", "NDOF_BUTTON_7", ""),
480 ("NDOF_BUTTON_8", "NDOF_BUTTON_8", ""),
481 ("NDOF_BUTTON_9", "NDOF_BUTTON_9", ""),
482 ("NDOF_BUTTON_10", "NDOF_BUTTON_10", ""),
483 ("NDOF_BUTTON_A", "NDOF_BUTTON_A", ""),
484 ("NDOF_BUTTON_B", "NDOF_BUTTON_B", ""),
485 ("NDOF_BUTTON_C", "NDOF_BUTTON_C", "")
488 description
="Enter key code in find text",
493 class IsKeyFreeRunExportKeys(Operator
):
494 bl_idname
= "iskeyfree.run_export_keys"
495 bl_label
= "List all Shortcuts"
496 bl_description
= ("List all existing shortcuts in a text block\n"
497 "The newly generated list will be made active in the Text Editor\n"
498 "To access the previous ones, select them from the Header dropdown")
500 def all_shortcuts_name(self
, context
):
501 new_name
, def_name
, ext
= "", "All_Shortcuts", ".txt"
504 # first slap a simple linear count + 1 for numeric suffix, if it fails
505 # harvest for the rightmost numbers and append the max value
507 data_txt
= bpy
.data
.texts
508 list_txt
= [txt
.name
for txt
in data_txt
if txt
.name
.startswith("All_Shortcuts")]
509 new_name
= "{}_{}{}".format(def_name
, len(list_txt
) + 1, ext
)
511 if new_name
in list_txt
:
512 from re
import findall
513 test_num
= [findall("\d+", words
) for words
in list_txt
]
514 suffix
+= max([int(l
[-1]) for l
in test_num
])
515 new_name
= "{}_{}{}".format(def_name
, suffix
, ext
)
520 def execute(self
, context
):
521 wm
= bpy
.context
.window_manager
522 from collections
import defaultdict
523 mykeys
= defaultdict(list)
524 file_name
= self
.all_shortcuts_name(context
) or "All_Shortcut.txt"
525 start_note
= "# Note: Some of the shortcuts entries don't have a name. Mostly Modal stuff\n"
526 col_width
, col_shortcuts
= 2, 2
528 for ctx_type
, keyboardmap
in wm
.keyconfigs
.user
.keymaps
.items():
529 for myitem
in keyboardmap
.keymap_items
:
530 padding
= len(myitem
.name
)
531 col_width
= padding
+ 2 if padding
> col_width
else col_width
533 short_type
= myitem
.type if myitem
.type else "UNKNOWN"
534 is_ctrl
= " Ctrl" if myitem
.ctrl
is True else ""
535 is_alt
= " Alt" if myitem
.alt
is True else ""
536 is_shift
= " Shift" if myitem
.shift
is True else ""
537 is_oskey
= " OsKey" if myitem
.oskey
is True else ""
538 short_cuts
= "{}{}{}{}{}".format(short_type
, is_ctrl
, is_alt
, is_shift
, is_oskey
)
541 myitem
.name
if myitem
.name
else "No Name",
544 mykeys
[ctx_type
].append(t
)
545 padding_s
= len(short_cuts
) + 2
546 col_shortcuts
= padding_s
if padding_s
> col_shortcuts
else col_shortcuts
548 max_line
= col_shortcuts
+ col_width
+ 4
549 textblock
= bpy
.data
.texts
.new(file_name
)
550 total
= sum([len(mykeys
[ctxs
]) for ctxs
in mykeys
])
551 textblock
.write('# %d Total Shortcuts\n\n' % total
)
552 textblock
.write(start_note
)
555 textblock
.write("\n[%s]\nEntries: %s\n\n" % (ctx
, len(mykeys
[ctx
])))
556 line_k
= sorted(mykeys
[ctx
])
558 add_ticks
= "-" * (max_line
- (len(keys
[0]) + len(keys
[1])))
559 entries
= "{ticks} {entry}".format(ticks
=add_ticks
, entry
=keys
[1])
560 textblock
.write("{name} {entry}\n".format(name
=keys
[0], entry
=entries
))
562 textblock
.write("\n\n")
564 # try to set the created text block to active
565 if context
.area
.type in {"TEXT_EDITOR"}:
566 bpy
.context
.space_data
.text
= bpy
.data
.texts
[file_name
]
568 self
.report({'INFO'}, "See %s textblock" % file_name
)
573 # -----------------------------------------------------
575 # ------------------------------------------------------
580 IsKeyFreeRunExportKeys
,
586 bpy
.utils
.register_class(cls
)
587 bpy
.types
.Scene
.is_keyfree
= PointerProperty(type=IskeyFreeProperties
)
592 bpy
.utils
.unregister_class(cls
)
593 del bpy
.types
.Scene
.is_keyfree