4 Name: 'Scripts Config Editor'
7 Tooltip: 'View and edit available scripts configuration data'
10 __author__
= "Willian P. Germano"
11 __version__
= "0.1 2005/04/14"
12 __email__
= ('scripts', 'Author, wgermano:ig*com*br')
13 __url__
= ('blender', 'elysiun')
16 This script can be used to view and edit configuration data stored
19 Technical: this data is saved as dictionary keys with the
20 Blender.Registry module functions. It is persistent while Blender is
21 running and, if the script's author chose to, is also saved to a file
22 in the scripts config data dir.
28 To access any available key, select it from (one of) the menu(s).
36 This screen exposes the configuration data for the chosen script key. If the
37 buttons don't fit completely on the screen, you can scroll up or down with
38 arrow keys or a mouse wheel. Leave the mouse pointer over any button to get
39 a tooltip about that option.
41 Any change can be reverted -- unless you have already applied it.
43 If the key is already stored in a config file, there will be a toggle button
44 (called 'file') that controls whether the changes will be written back to
45 the file or not. If you just want to change the configuration for the current
46 session, simply unset that button. Note, though, that data from files has
47 precedence over those keys already loaded in Blender, so if you re-run this
48 config editor, unsaved changes will not be seen.
51 ESC: back to Start Screen<br>
54 ENTER: apply changes (can't be reverted, then)<br>
55 UP, DOWN Arrows and mouse wheel: scroll text up / down
59 a) Available keys are determined by which scripts you use. If the key you
60 expect isn't available (or maybe there are none or too few keys), either the
61 related script doesn't need or still doesn't support this feature or the key
62 has not been stored yet, in which case you just need to run that script once
63 to make its config data available.
65 b) There are two places where config data files can be saved: the
66 bpydata/config/ dir (1) inside the default scripts dir or (2) inside the user
67 defined Python scripts dir
68 (User Preferences window -> File Paths tab -> Python path). If available,
69 (2) is the default and also the recommended option, because then fresh Blender
70 installations won't delete your config data. To use this option, simply set a
71 dir for Python scripts at the User Preferences window and make sure this dir
72 has the subdirs bpydata/ and bpydata/config/ inside it.
74 c) The key called "General" in the "Other" menu has general config options.
75 All scripts where that data is relevant are recommended to access it and set
76 behaviors accordingly.
79 # $Id: config.py 10671 2007-05-06 15:47:07Z ianwill $
81 # --------------------------------------------------------------------------
82 # config.py version 0.1 2005/04/08
83 # --------------------------------------------------------------------------
84 # ***** BEGIN GPL LICENSE BLOCK *****
86 # Copyright (C) 2004: Willian P. Germano, wgermano _at_ ig.com.br
88 # This program is free software; you can redistribute it and/or
89 # modify it under the terms of the GNU General Public License
90 # as published by the Free Software Foundation; either version 2
91 # of the License, or (at your option) any later version.
93 # This program is distributed in the hope that it will be useful,
94 # but WITHOUT ANY WARRANTY; without even the implied warranty of
95 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
96 # GNU General Public License for more details.
98 # You should have received a copy of the GNU General Public License
99 # along with this program; if not, write to the Free Software Foundation,
100 # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
102 # ***** END GPL LICENCE BLOCK *****
103 # --------------------------------------------------------------------------
106 from Blender
import Draw
, BGL
, Registry
, Window
, sys
as bsys
107 from Blender
.Window
import Theme
108 from BPyRegistry
import LoadConfigData
, SaveConfigData
, HasConfigData
,\
111 MAX_STR_LEN
= 300 # max length for a string
112 MAX_ITEMS_NUM
= 100 # max number for each type of button
115 # The "General" configure options key is managed from this script.
117 confirm_overwrite
= True
120 'verbose': 'print script messages (info, warnings, errors) to the console',
121 'confirm_overwrite': 'scripts should always confirm before overwriting files'
124 CFG_LIST
= ['verbose', 'confirm_overwrite', 'tooltips']
127 def update_registry():
130 exec("rd['%s']=%s" % (var
, var
))
131 Registry
.SetKey(KEY_NAME
, rd
, True)
133 rd
= Registry
.GetKey('General', True)
136 for var
in CFG_LIST
[:-1]: # no need to update tooltips
137 exec("%s=rd['%s']" % (var
, var
))
138 except: update_registry()
147 GD
= {} # groups dict (includes "Other" for unmapped keys)
148 INDEX
= 0 # to pass button indices to fs callbacks
149 FREEKEY_IDX
= 0 # index of set of keys not mapped to a script name
155 DISK_UPDATE
= True # write changed data to its config file
157 ACCEPTED_TYPES
= [bool, int, float, str, unicode]
159 SCREEN
= START_SCREEN
165 BEVT_EXIT
= 0 + BEVT_START
166 BEVT_BACK
= 1 + BEVT_START
167 BEVT_DISK
= 2 + BEVT_START
168 BEVT_CANCEL
= 3 + BEVT_START
169 BEVT_APPLY
= 4 + BEVT_START
170 BEVT_HELP
= 5 + BEVT_START
171 BEVT_DEL
= 6 + BEVT_START
175 BEVT_INT
= BEVT_BOOL
+ MAX_ITEMS_NUM
176 BEVT_FLOAT
= BEVT_BOOL
+ 2*MAX_ITEMS_NUM
177 BEVT_STR
= BEVT_BOOL
+ 3*MAX_ITEMS_NUM
178 BEVT_BROWSEDIR
= BEVT_BOOL
+ 4*MAX_ITEMS_NUM
179 BEVT_BROWSEFILE
= BEVT_BOOL
+ 5*MAX_ITEMS_NUM
187 # Function definitions:
190 LoadConfigData() # loads all data from files in (u)scripts/bpydata/config/
191 return [k
for k
in Registry
.Keys() if k
[0] != "_"]
194 def show_help(script
= 'config.py'):
195 Blender
.ShowHelp(script
)
198 def fs_dir_callback(pathname
):
201 pathname
= bsys
.dirname(pathname
)
202 datatypes
= CFGKEY
.sorteddata
203 datatypes
[str][INDEX
][1] = pathname
206 def fs_file_callback(pathname
):
209 datatypes
= CFGKEY
.sorteddata
210 datatypes
[str][INDEX
][1] = pathname
213 # parse Bpymenus file to get all script filenames
214 # (used to show help for a given key)
215 def fill_scripts_dict():
216 global ALL_SCRIPTS
, ALL_GROUPS
221 home
= Blender
.Get('homedir')
224 Can't find Blender's home dir and so can't find the
225 Bpymenus file automatically stored inside it, which
226 is needed by this script. Please run the
227 Help -> System -> System Information script to get
228 information about how to fix this.
230 raise SystemError, errmsg
231 fname
= bsys
.join(home
, 'Bpymenus')
232 if not bsys
.exists(fname
): return False
234 lines
= f
.readlines()
239 ALL_GROUPS
.append(group
)
242 elif l
[0] != "'": continue
243 fields
= l
.split("'")
245 menuname
= fields
[1].replace('...','')
246 fields
= fields
[2].split()
248 fname
= fields
[1].split(sep
)[-1]
249 ALL_SCRIPTS
[fname
] = (menuname
, group_len
- 1)
253 def map_to_registered_script(name
):
256 if not name
.endswith('.py'):
257 name
= "%s.py" % name
258 if ALL_SCRIPTS
.has_key(name
):
259 return ALL_SCRIPTS
[name
] # == (menuname, group index)
264 global LABELS
, GD
, KEYMENUS
, KEYS
266 # init_data is recalled when a key is deleted, so:
273 # gather all script info, fill gui menus
275 global KEYS
, GD
, ALL_GROUPS
, ALL_SCRIPTS
, KEYMENUS
, LABELS
276 global BUT_KEYMENU
, BEVT_KEYMENU
, FREEKEY_IDX
283 res
= map_to_registered_script(k
)
285 GD
[ALL_GROUPS
[res
[1]]].append((k
, res
[0]))
286 else: GD
[None].append((k
, k
))
289 if not GD
[k
]: GD
.pop(k
)
292 GD
['Other'] = GD
[None]
296 BUT_KEYMENU
= range(len(GD
))
299 kmenu
= ['Configuration Keys: %s%%t' % k
]
302 kmenu
= "|".join(kmenu
)
303 KEYMENUS
.append(kmenu
)
307 FREEKEY_IDX
= LABELS
.index('Other')
309 length
= len(KEYMENUS
)
310 BEVT_KEYMENU
= range(1, length
+ 1)
311 BUT_KEYMENU
= range(length
)
315 def float_colors(cols
):
316 return map(lambda x
: x
/ 255.0, cols
)
322 def __init__(self
, key
, has_group
= True):
326 self
.has_group
= has_group
328 self
.fromdisk
= HasConfigData(key
) & BPY_KEY_IN_FILE
329 if not self
.fromdisk
: DISK_UPDATE
= False
330 else: DISK_UPDATE
= True
332 self
.origdata
= Registry
.GetKey(key
, True)
333 data
= self
.data
= self
.origdata
.copy()
336 Draw
.PupMenu('ERROR: couldn\'t find requested data')
345 if nd
.has_key('tooltips'):
346 ndval
= nd
['tooltips']
347 self
.tips
= data
[ndval
]
351 if nd
.has_key('limits'):
353 self
.limits
= data
[ndval
]
355 else: self
.limits
= 0
359 if not scriptname
.endswith('.py'):
360 scriptname
= "%s.py" % scriptname
361 elif nd
.has_key('script'):
363 scriptname
= data
[ndval
]
365 if not scriptname
.endswith('.py'):
366 scriptname
= "%s.py" % scriptname
367 else: scriptname
= None
369 self
.scriptname
= scriptname
374 def needs_update(self
): # check if user changed data
376 new
= self
.sorteddata
378 for vartype
in new
.keys():
379 for i
in new
[vartype
]:
380 if data
[i
[0]] != i
[1]: return 1
382 return 0 # no changes
385 def update(self
): # update original key
389 odata
= self
.origdata
390 new
= self
.sorteddata
391 for vartype
in new
.keys():
392 for i
in new
[vartype
]:
393 if data
[i
[0]] != i
[1]: data
[i
[0]] = i
[1]
394 if odata
[i
[0]] != i
[1]: odata
[i
[0]] = i
[1]
396 if DISK_UPDATE
: Registry
.SetKey(self
.key
, odata
, True)
401 delmsg
= 'OK?%t|Delete key from memory'
403 delmsg
= "%s and from disk" % delmsg
404 if Draw
.PupMenu(delmsg
) == 1:
405 Registry
.RemoveKey(self
.key
, DISK_UPDATE
)
411 def revert(self
): # revert to original key
413 new
= self
.sorteddata
414 for vartype
in new
.keys():
415 for i
in new
[vartype
]:
416 if data
[i
[0]] != i
[1]: i
[1] = data
[i
[0]]
419 def sort(self
): # create a new dict with types as keys
420 global ACCEPTED_TYPES
, BUT_TYPES
424 keys
= [k
for k
in data
.keys() if k
[0] != '_']
428 if tval
not in ACCEPTED_TYPES
: continue
429 if not datatypes
.has_key(tval
):
431 datatypes
[type(val
)].append([k
, val
])
432 if datatypes
.has_key(unicode):
433 if not datatypes
.has_key(str): datatypes
[str] = datatypes
[unicode]
435 for i
in datatypes
[unicode]: datatypes
[str].append(i
)
436 datatypes
.pop(unicode)
437 for k
in datatypes
.keys():
441 BUT_TYPES
[k
] = range(len(dk
))
442 self
.sorteddata
= datatypes
449 def gui(): # drawing the screen
451 global SCREEN
, START_SCREEN
, CONFIG_SCREEN
, KEYMENUS
, LABELS
452 global BEVT_KEYMENU
, BUT_KEYMENU
, CFGKEY
453 global BUT_TYPES
, SCROLL_DOWN
, VARS_NUM
455 WIDTH
, HEIGHT
= Window
.GetAreaSize()
457 theme
= Theme
.Get()[0]
458 tui
= theme
.get('ui')
459 ttxt
= theme
.get('text')
461 COL_BG
= float_colors(ttxt
.back
)
463 COL_TXTHI
= ttxt
.text_hi
465 BGL
.glClearColor(COL_BG
[0],COL_BG
[1],COL_BG
[2],COL_BG
[3])
466 BGL
.glClear(BGL
.GL_COLOR_BUFFER_BIT
)
467 BGL
.glColor3ub(COL_TXT
[0],COL_TXT
[1], COL_TXT
[2])
469 if SCREEN
== START_SCREEN
:
474 BGL
.glRasterPos2i(x
, y
)
475 Draw
.Text('Select a configuration key to access it. Press Q or ESC to leave.')
476 km_len
= len(KEYMENUS
)
477 km_columns
= (WIDTH
- x
) / w
478 if km_columns
== 0: km_rows
= km_len
480 km_rows
= km_len
/ km_columns
481 if (km_len
% km_columns
): km_rows
+= 1
482 if km_rows
== 0: km_rows
= 1
483 ystart
= y
+ 2*h
*km_rows
484 if ystart
> (HEIGHT
- 70): ystart
= HEIGHT
- 70
487 for i
, km
in enumerate(KEYMENUS
):
489 BGL
.glRasterPos2i(x
+ 2, y
+ h
+ 5)
491 BUT_KEYMENU
[i
] = Draw
.Menu(km
, BEVT_KEYMENU
[i
],
492 x
, y
, w
- 10, h
, 0, 'Choose a key to access its configuration data')
493 if column
> km_columns
:
501 BGL
.glColor3ub(COL_TXTHI
[0], COL_TXTHI
[1], COL_TXTHI
[2])
502 BGL
.glRasterPos2i(x
, y
)
503 Draw
.Text('Scripts Configuration Editor')
504 Draw
.PushButton('help', BEVT_HELP
, x
, 22, 45, 16,
505 'View help information about this script (hotkey: H)')
507 elif SCREEN
== CONFIG_SCREEN
:
510 data
= CFGKEY
.sorteddata
512 fromdisk
= CFGKEY
.fromdisk
513 limits
= CFGKEY
.limits
515 for k
in data
.keys():
516 VARS_NUM
+= len(data
[k
])
517 lines
= VARS_NUM
+ 5 # to account for header and footer
519 if y
> HEIGHT
- 20: y
= HEIGHT
- 20
520 BGL
.glColor3ub(COL_TXTHI
[0],COL_TXTHI
[1], COL_TXTHI
[2])
521 BGL
.glRasterPos2i(x
, y
)
522 Draw
.Text('Scripts Configuration Editor')
524 BGL
.glColor3ub(COL_TXT
[0],COL_TXT
[1], COL_TXT
[2])
527 BGL
.glRasterPos2i(10, 5)
528 txtsize
+= Draw
.Text('Arrow keys or mouse wheel to scroll, ')
529 BGL
.glRasterPos2i(txtsize
, 5)
530 Draw
.Text('Q or ESC to return.')
531 BGL
.glRasterPos2i(x
, y
)
532 Draw
.Text('Key: "%s"' % CFGKEY
.name
)
537 if CFGKEY
.scriptname
:
539 Draw
.PushButton('help', BEVT_HELP
, x
, by
, bw
, bh
,
540 'Show documentation for the script that owns this key (hotkey: H)')
541 Draw
.PushButton('back', BEVT_BACK
, x
+ (1+i
)*bw
, by
, bw
, bh
,
542 'Back to config keys selection screen (hotkey: ESC)')
543 Draw
.PushButton('exit', BEVT_EXIT
, x
+ (2+i
)*bw
, by
, bw
, bh
,
544 'Exit from Scripts Config Editor (hotkey: Q)')
545 Draw
.PushButton('revert', BEVT_CANCEL
, x
+ (3+i
)*bw
, by
, bw
, bh
,
546 'Revert data to original values (hotkey: U)')
547 Draw
.PushButton('apply', BEVT_APPLY
, x
+ (4+i
)*bw
, by
, bw
, bh
,
548 'Apply changes, if any (hotkey: ENTER)')
549 delmsg
= 'Delete this data key from memory'
550 if fromdisk
: delmsg
= "%s and from disk" % delmsg
551 Draw
.PushButton('delete', BEVT_DEL
, x
+ (5+i
)*bw
, by
, bw
, bh
,
552 '%s (hotkey: DELETE)' % delmsg
)
554 Draw
.Toggle("file", BEVT_DISK
, x
+ 3 + (6+i
)*bw
, by
, bw
, bh
, DISK_UPDATE
,
555 'Update also the file where this config key is stored')
560 if data
.has_key(bool) and y
> 0:
565 if top
< SCROLL_DOWN
: continue
569 tog
= data
[bool][i
][1]
570 if tips
and tips
.has_key(l
[0]): tooltip
= tips
[l
[0]]
571 else: tooltip
= "click to toggle"
572 BUT_TYPES
[bool][i
] = Draw
.Toggle("", BEVT_BOOL
+ i
,
573 x
, y
, w
, h
, tog
, tooltip
)
574 BGL
.glRasterPos2i(x
+ w
+ 3, y
+ 5)
575 Draw
.Text(l
[0].lower().replace('_', ' '))
578 if data
.has_key(int) and y
> 0:
584 if top
< SCROLL_DOWN
: continue
587 val
= data
[int][i
][1]
588 if limits
: min, max = limits
[l
[0]]
589 else: min, max = 0, 10
590 if tips
and tips
.has_key(l
[0]): tooltip
= tips
[l
[0]]
591 else: tooltip
= "click / drag to change"
592 BUT_TYPES
[int][i
] = Draw
.Number("", BEVT_INT
+ i
,
593 x
, y
, w
, h
, val
, min, max, tooltip
)
594 BGL
.glRasterPos2i(x
+ w
+ 3, y
+ 3)
595 Draw
.Text(l
[0].lower().replace('_', ' '))
598 if data
.has_key(float) and y
> 0:
604 if top
< SCROLL_DOWN
: continue
607 val
= data
[float][i
][1]
608 if limits
: min, max = limits
[l
[0]]
609 else: min, max = 0.0, 1.0
610 if tips
and tips
.has_key(l
[0]): tooltip
= tips
[l
[0]]
611 else: tooltip
= "click and drag to change"
612 BUT_TYPES
[float][i
] = Draw
.Number("", BEVT_FLOAT
+ i
,
613 x
, y
, w
, h
, val
, min, max, tooltip
)
614 BGL
.glRasterPos2i(x
+ w
+ 3, y
+ 3)
615 Draw
.Text(l
[0].lower().replace('_', ' '))
618 if data
.has_key(str) and y
> 0:
623 if top
< SCROLL_DOWN
: continue
627 is_dir
= is_file
= False
628 if name
.find('_dir', -4) > 0: is_dir
= True
629 elif name
.find('_file', -5) > 0: is_file
= True
632 if is_dir
and w
> wbrowse
: w
-= wbrowse
633 if tips
and tips
.has_key(l
[0]): tooltip
= tips
[l
[0]]
634 else: tooltip
= "click to write a new string"
635 name
= name
.replace('_',' ') + ': '
636 if len(l
[1]) > MAX_STR_LEN
:
637 l
[1] = l
[1][:MAX_STR_LEN
]
638 BUT_TYPES
[str][i
] = Draw
.String(name
, BEVT_STR
+ i
,
639 x
, y
, w
, h
, l
[1], MAX_STR_LEN
, tooltip
)
641 Draw
.PushButton('browse', BEVT_BROWSEDIR
+ i
, x
+w
+1, y
, wbrowse
, h
,
642 'click to open a file selector (pick any file in the desired dir)')
644 Draw
.PushButton('browse', BEVT_BROWSEFILE
+ i
, x
+ w
+ 1, y
, 50, h
,
645 'click to open a file selector')
649 global SCROLL_DOWN
, VARS_NUM
650 max = VARS_NUM
- 1 # so last item is always visible
651 if SCROLL_DOWN
> max:
653 elif SCROLL_DOWN
< 0:
657 def event(evt
, val
): # input events
659 global SCREEN
, START_SCREEN
, CONFIG_SCREEN
660 global SCROLL_DOWN
, CFGKEY
664 if evt
== Draw
.ESCKEY
:
665 if SCREEN
== START_SCREEN
: Draw
.Exit()
667 if CFGKEY
.needs_update():
668 if Draw
.PupMenu('UPDATE?%t|Data was changed') == 1:
670 SCREEN
= START_SCREEN
674 elif evt
== Draw
.QKEY
:
675 if SCREEN
== CONFIG_SCREEN
and CFGKEY
.needs_update():
676 if Draw
.PupMenu('UPDATE?%t|Data was changed') == 1:
680 elif evt
== Draw
.HKEY
:
681 if SCREEN
== START_SCREEN
: show_help()
682 elif CFGKEY
.scriptname
: show_help(CFGKEY
.scriptname
)
685 elif SCREEN
== CONFIG_SCREEN
:
686 if evt
in [Draw
.DOWNARROWKEY
, Draw
.WHEELDOWNMOUSE
]:
689 elif evt
in [Draw
.UPARROWKEY
, Draw
.WHEELUPMOUSE
]:
692 elif evt
== Draw
.UKEY
:
693 if CFGKEY
.needs_update():
695 elif evt
== Draw
.RETKEY
or evt
== Draw
.PADENTER
:
696 if CFGKEY
.needs_update():
698 elif evt
== Draw
.DELKEY
:
702 SCREEN
= START_SCREEN
708 def button_event(evt
): # gui button events
710 global SCREEN
, START_SCREEN
, CONFIG_SCREEN
, CFGKEY
, DISK_UPDATE
711 global BEVT_KEYMENU
, BUT_KEYMENU
, BUT_TYPES
, SCROLL_DOWN
, GD
, INDEX
712 global BEVT_EXIT
, BEVT_BACK
, BEVT_APPLY
, BEVT_CANCEL
, BEVT_HELP
, FREEKEY_IDX
714 if SCREEN
== START_SCREEN
:
715 for e
in BEVT_KEYMENU
:
718 k
= BUT_KEYMENU
[index
].val
- 1
719 CFGKEY
= Config(GD
[LABELS
[index
]][k
][0], index
!= FREEKEY_IDX
)
721 SCREEN
= CONFIG_SCREEN
726 elif evt
== BEVT_HELP
:
730 elif SCREEN
== CONFIG_SCREEN
:
731 datatypes
= CFGKEY
.sorteddata
732 if evt
>= BEVT_BROWSEFILE
:
733 INDEX
= evt
- BEVT_BROWSEFILE
734 Window
.FileSelector(fs_file_callback
, 'Choose file')
735 elif evt
>= BEVT_BROWSEDIR
:
736 INDEX
= evt
- BEVT_BROWSEDIR
737 Window
.FileSelector(fs_dir_callback
, 'Choose any file')
738 elif evt
>= BEVT_STR
:
739 var
= BUT_TYPES
[str][evt
- BEVT_STR
].val
740 datatypes
[str][evt
- BEVT_STR
][1] = var
741 elif evt
>= BEVT_FLOAT
:
742 var
= BUT_TYPES
[float][evt
- BEVT_FLOAT
].val
743 datatypes
[float][evt
- BEVT_FLOAT
][1] = var
744 elif evt
>= BEVT_INT
:
745 var
= BUT_TYPES
[int][evt
- BEVT_INT
].val
746 datatypes
[int][evt
- BEVT_INT
][1] = var
747 elif evt
>= BEVT_BOOL
:
748 var
= datatypes
[bool][evt
- BEVT_BOOL
][1]
749 if var
== True: var
= False
751 datatypes
[bool][evt
- BEVT_BOOL
][1] = var
753 elif evt
== BEVT_BACK
:
754 if SCREEN
== CONFIG_SCREEN
:
755 SCREEN
= START_SCREEN
758 elif evt
== BEVT_EXIT
:
759 if CFGKEY
.needs_update():
760 if Draw
.PupMenu('UPDATE?%t|Data was changed') == 1:
764 elif evt
== BEVT_APPLY
:
765 if CFGKEY
.needs_update():
767 elif evt
== BEVT_CANCEL
:
768 if CFGKEY
.needs_update():
770 elif evt
== BEVT_DEL
:
774 SCREEN
= START_SCREEN
776 elif evt
== BEVT_DISK
:
777 if DISK_UPDATE
: DISK_UPDATE
= False
778 else: DISK_UPDATE
= True
779 elif evt
== BEVT_HELP
:
780 show_help(CFGKEY
.scriptname
)
792 Draw
.PupMenu("NO DATA: please read this help screen")
793 Blender
.ShowHelp('config.py')
797 Draw
.Register(gui
, event
, button_event
)