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