make it so an error registering/unregestering wont stop loading scripts, just print...
[blender.git] / release / scripts / modules / bpy / utils.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
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.
7 #
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.
12 #
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.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 """
22 This module contains utility functions spesific to blender but
23 not assosiated with blenders internal data.
24 """
25
26 import bpy as _bpy
27 import os as _os
28 import sys as _sys
29
30 from _bpy import home_paths
31
32
33 def _test_import(module_name, loaded_modules):
34     import traceback
35     import time
36     if module_name in loaded_modules:
37         return None
38     if "." in module_name:
39         print("Ignoring '%s', can't import files containing multiple periods." % module_name)
40         return None
41
42     try:
43         t = time.time()
44         ret = __import__(module_name)
45         if _bpy.app.debug:
46             print("time %s %.4f" % (module_name, time.time() - t))
47         return ret
48     except:
49         traceback.print_exc()
50         return None
51
52
53 def modules_from_path(path, loaded_modules):
54     """
55     Load all modules in a path and return them as a list.
56     """
57     import traceback
58     import time
59     
60     modules = []
61     
62     for f in sorted(_os.listdir(path)):
63         if f.endswith(".py"):
64             # python module
65             mod = _test_import(f[0:-3], loaded_modules)
66         elif ("." not in f) and (_os.path.isfile(_os.path.join(path, f, "__init__.py"))):
67             # python package
68             mod = _test_import(f, loaded_modules)
69         else:
70             mod = None
71         
72         if mod:
73             modules.append(mod)
74     
75     return modules
76
77 _loaded = [] # store loaded modules for reloading.
78 _bpy_types = __import__("bpy_types") # keep for comparisons, never ever reload this.
79
80
81 def load_scripts(reload_scripts=False, refresh_scripts=False):
82     import traceback
83     import time
84
85     t_main = time.time()
86
87     loaded_modules = set()
88     
89     if refresh_scripts:
90         original_modules = _sys.modules.values()
91
92     def sys_path_ensure(path):
93         if path not in _sys.path: # reloading would add twice
94             _sys.path.insert(0, path)
95
96     def test_reload(mod):
97         # reloading this causes internal errors
98         # because the classes from this module are stored internally
99         # possibly to refresh internal references too but for now, best not to.
100         if mod == _bpy_types:
101             return mod
102
103         try:
104             return reload(mod)
105         except:
106             traceback.print_exc()
107     
108     def test_register(mod):
109
110         if refresh_scripts and mod in original_modules:
111             return
112
113         if reload_scripts and mod:
114             print("Reloading:", mod)
115             mod = test_reload(mod)
116
117         if mod:
118             register = getattr(mod, "register", None)
119             if register:
120                 try:
121                     register()
122                 except:
123                     traceback.print_exc()
124             else:
125                 print("\nWarning! '%s' has no register function, this is now a requirement for registerable scripts." % mod.__file__)
126             _loaded.append(mod)
127     
128     if reload_scripts:
129         # reload modules that may not be directly included
130         for type_class_name in dir(_bpy.types):
131             type_class = getattr(_bpy.types, type_class_name)
132             module_name = getattr(type_class, "__module__", "")
133
134             if module_name and module_name != "bpy.types": # hard coded for C types
135                 loaded_modules.add(module_name)
136
137         for module_name in loaded_modules:
138             print("Reloading:", module_name)
139             test_reload(_sys.modules[module_name])
140             
141         # loop over and unload all scripts
142         _loaded.reverse()
143         for mod in _loaded:
144             unregister = getattr(mod, "unregister", None)
145             if unregister:
146                 try:
147                     unregister()
148                 except:
149                     traceback.print_exc()
150         _loaded[:] = []
151
152
153     for base_path in script_paths():
154         for path_subdir in ("", "ui", "op", "io", "cfg"):
155             path = _os.path.join(base_path, path_subdir)
156             if _os.path.isdir(path):
157                 sys_path_ensure(path)
158
159                 for mod in modules_from_path(path, loaded_modules):
160                     test_register(mod)
161
162
163     # load extensions
164     used_ext = {ext.module for ext in _bpy.context.user_preferences.extensions}    
165     paths = script_paths("extensions")
166     for path in paths:
167         sys_path_ensure(path)
168     
169     for module_name in sorted(used_ext):
170         mod = _test_import(module_name, loaded_modules)
171         test_register(mod)
172
173
174     if _bpy.app.debug:
175         print("Time %.4f" % (time.time() - t_main))
176
177
178 def expandpath(path):
179     if path.startswith("//"):
180         return _os.path.join(_os.path.dirname(_bpy.data.filename), path[2:])
181
182     return path
183
184
185 _unclean_chars = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, \
186     17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, \
187     35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 58, 59, 60, 61, 62, 63, \
188     64, 91, 92, 93, 94, 96, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, \
189     133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, \
190     147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, \
191     161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, \
192     175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, \
193     189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, \
194     203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, \
195     217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, \
196     231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, \
197     245, 246, 247, 248, 249, 250, 251, 252, 253, 254]
198
199 _unclean_chars = ''.join([chr(i) for i in _unclean_chars])
200
201
202 def clean_name(name, replace="_"):
203     """
204     Returns a name with characters replaced that may cause problems under various circumstances, such as writing to a file.
205     All characters besides A-Z/a-z, 0-9 are replaced with "_"
206     or the replace argumet if defined.
207     """
208     for ch in _unclean_chars:
209         name = name.replace(ch, replace)
210     return name
211
212
213 def display_name(name):
214     """
215     Creates a display string from name to be used menus and the user interface.
216     Capitalize the first letter in all lowercase names, mixed case names are kept as is.
217     Intended for use with filenames and module names.
218     """
219     name_base = _os.path.splitext(name)[0]
220
221     # string replacements
222     name_base = name_base.replace("_colon_", ":")
223
224     name_base = name_base.replace("_", " ")
225
226     if name_base.islower():
227         return name_base.capitalize()
228     else:
229         return name_base
230
231
232 # base scripts
233 _scripts = _os.path.join(_os.path.dirname(__file__), _os.path.pardir, _os.path.pardir)
234 _scripts = (_os.path.normpath(_scripts), )
235
236
237 def script_paths(*args):
238     """
239     Returns a list of valid script paths from the home directory and user preferences.
240
241     Accepts any number of string arguments which are joined to make a path.
242     """
243     scripts = list(_scripts)
244
245     # add user scripts dir
246     user_script_path = _bpy.context.user_preferences.filepaths.python_scripts_directory
247     
248     for path in home_paths("scripts") + (user_script_path, ):
249         if path:
250             path = _os.path.normpath(path)
251             if path not in scripts and _os.path.isdir(path):
252                 scripts.append(path)
253
254     if not args:
255         return scripts
256
257     subdir = _os.path.join(*args)
258     script_paths = []
259     for path in scripts:
260         path_subdir = _os.path.join(path, subdir)
261         if _os.path.isdir(path_subdir):
262             script_paths.append(path_subdir)
263
264     return script_paths
265
266
267 _presets = _os.path.join(_scripts[0], "presets") # FIXME - multiple paths
268
269
270 def preset_paths(subdir):
271     '''
272     Returns a list of paths for a spesific preset.
273     '''
274
275     return (_os.path.join(_presets, subdir), )