Merge with trunk, revision 28528 - 28976.
[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, blend_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
56 def modules_from_path(path, loaded_modules):
57     """
58     Load all modules in a path and return them as a list.
59
60     :arg path: this path is scanned for scripts and packages.
61     :type path: string
62     :arg loaded_modules: alredy loaded module names, files matching these names will be ignored.
63     :type loaded_modules: set
64     :return: all loaded modules.
65     :rtype: list
66     """
67     import traceback
68     import time
69
70     modules = []
71
72     for f in sorted(_os.listdir(path)):
73         if f.endswith(".py"):
74             # python module
75             mod = _test_import(f[0:-3], loaded_modules)
76         elif ("." not in f) and (_os.path.isfile(_os.path.join(path, f, "__init__.py"))):
77             # python package
78             mod = _test_import(f, loaded_modules)
79         else:
80             mod = None
81
82         if mod:
83             modules.append(mod)
84
85     return modules
86
87 _loaded = [] # store loaded modules for reloading.
88 _bpy_types = __import__("bpy_types") # keep for comparisons, never ever reload this.
89
90
91 def load_scripts(reload_scripts=False, refresh_scripts=False):
92     """
93     Load scripts and run each modules register function.
94
95     :arg reload_scripts: Causes all scripts to have their unregister method called before loading.
96     :type reload_scripts: bool
97     :arg refresh_scripts: only load scripts which are not already loaded as modules.
98     :type refresh_scripts: bool
99     """
100     import traceback
101     import time
102
103     t_main = time.time()
104
105     loaded_modules = set()
106
107     if refresh_scripts:
108         original_modules = _sys.modules.values()
109
110     def sys_path_ensure(path):
111         if path not in _sys.path: # reloading would add twice
112             _sys.path.insert(0, path)
113
114     def test_reload(mod):
115         # reloading this causes internal errors
116         # because the classes from this module are stored internally
117         # possibly to refresh internal references too but for now, best not to.
118         if mod == _bpy_types:
119             return mod
120
121         try:
122             return reload(mod)
123         except:
124             traceback.print_exc()
125
126     def test_register(mod):
127
128         if refresh_scripts and mod in original_modules:
129             return
130
131         if reload_scripts and mod:
132             print("Reloading:", mod)
133             mod = test_reload(mod)
134
135         if mod:
136             register = getattr(mod, "register", None)
137             if register:
138                 try:
139                     register()
140                 except:
141                     traceback.print_exc()
142             else:
143                 print("\nWarning! '%s' has no register function, this is now a requirement for registerable scripts." % mod.__file__)
144             _loaded.append(mod)
145
146     if reload_scripts:
147         # reload modules that may not be directly included
148         for type_class_name in dir(_bpy.types):
149             type_class = getattr(_bpy.types, type_class_name)
150             module_name = getattr(type_class, "__module__", "")
151
152             if module_name and module_name != "bpy.types": # hard coded for C types
153                 loaded_modules.add(module_name)
154
155         # sorting isnt needed but rather it be pradictable
156         for module_name in sorted(loaded_modules):
157             print("Reloading:", module_name)
158             test_reload(_sys.modules[module_name])
159
160         # loop over and unload all scripts
161         _loaded.reverse()
162         for mod in _loaded:
163             unregister = getattr(mod, "unregister", None)
164             if unregister:
165                 try:
166                     unregister()
167                 except:
168                     traceback.print_exc()
169         _loaded[:] = []
170
171     user_path = user_script_path()
172
173     for base_path in script_paths():
174         for path_subdir in ("", "ui", "op", "io", "cfg", "keyingsets", "modules"):
175             path = _os.path.join(base_path, path_subdir)
176             if _os.path.isdir(path):
177                 sys_path_ensure(path)
178
179                 # only add this to sys.modules, dont run
180                 if path_subdir == "modules":
181                     continue
182
183                 if user_path != base_path and path_subdir == "":
184                     continue # avoid loading 2.4x scripts
185
186                 for mod in modules_from_path(path, loaded_modules):
187                     test_register(mod)
188
189     # load addons
190     used_ext = {ext.module for ext in _bpy.context.user_preferences.addons}
191     paths = script_paths("addons")
192     for path in paths:
193         sys_path_ensure(path)
194
195     for module_name in sorted(used_ext):
196         mod = _test_import(module_name, loaded_modules)
197         test_register(mod)
198
199     if reload_scripts:
200         import gc
201         print("gc.collect() -> %d" % gc.collect())
202
203     if _bpy.app.debug:
204         print("Time %.4f" % (time.time() - t_main))
205
206
207 def expandpath(path):
208     """
209     Returns the absolute path relative to the current blend file using the "//" prefix.
210     """
211     if path.startswith("//"):
212         return _os.path.join(_os.path.dirname(_bpy.data.filename), path[2:])
213
214     return path
215
216
217 def relpath(path, start=None):
218     """
219     Returns the path relative to the current blend file using the "//" prefix.
220
221     :arg start: Relative to this path, when not set the current filename is used.
222     :type start: string
223     """
224     if not path.startswith("//"):
225         if start is None:
226             start = _os.path.dirname(_bpy.data.filename)
227         return "//" + _os.path.relpath(path, start)
228
229     return path
230
231
232 _unclean_chars = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, \
233     17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, \
234     35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 58, 59, 60, 61, 62, 63, \
235     64, 91, 92, 93, 94, 96, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, \
236     133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, \
237     147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, \
238     161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, \
239     175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, \
240     189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, \
241     203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, \
242     217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, \
243     231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, \
244     245, 246, 247, 248, 249, 250, 251, 252, 253, 254]
245
246 _unclean_chars = ''.join([chr(i) for i in _unclean_chars])
247
248
249 def clean_name(name, replace="_"):
250     """
251     Returns a name with characters replaced that may cause problems under various circumstances, such as writing to a file.
252     All characters besides A-Z/a-z, 0-9 are replaced with "_"
253     or the replace argument if defined.
254     """
255     for ch in _unclean_chars:
256         name = name.replace(ch, replace)
257     return name
258
259
260 def display_name(name):
261     """
262     Creates a display string from name to be used menus and the user interface.
263     Capitalize the first letter in all lowercase names, mixed case names are kept as is.
264     Intended for use with filenames and module names.
265     """
266     name_base = _os.path.splitext(name)[0]
267
268     # string replacements
269     name_base = name_base.replace("_colon_", ":")
270
271     name_base = name_base.replace("_", " ")
272
273     if name_base.islower():
274         return name_base.capitalize()
275     else:
276         return name_base
277
278
279 # base scripts
280 _scripts = _os.path.join(_os.path.dirname(__file__), _os.path.pardir, _os.path.pardir)
281 _scripts = (_os.path.normpath(_scripts), )
282
283
284 def user_script_path():
285     path = _bpy.context.user_preferences.filepaths.python_scripts_directory
286
287     if path:
288         path = _os.path.normpath(path)
289         return path
290     else:
291         return None
292
293
294 def script_paths(subdir=None, user=True):
295     """
296     Returns a list of valid script paths from the home directory and user preferences.
297
298     Accepts any number of string arguments which are joined to make a path.
299     """
300     scripts = list(_scripts)
301
302     # add user scripts dir
303     if user:
304         user_script_path = _bpy.context.user_preferences.filepaths.python_scripts_directory
305     else:
306         user_script_path = None
307
308     for path in home_paths("scripts") + (user_script_path, ):
309         if path:
310             path = _os.path.normpath(path)
311             if path not in scripts and _os.path.isdir(path):
312                 scripts.append(path)
313
314     if not subdir:
315         return scripts
316
317     script_paths = []
318     for path in scripts:
319         path_subdir = _os.path.join(path, subdir)
320         if _os.path.isdir(path_subdir):
321             script_paths.append(path_subdir)
322
323     return script_paths
324
325
326 _presets = _os.path.join(_scripts[0], "presets") # FIXME - multiple paths
327
328
329 def preset_paths(subdir):
330     '''
331     Returns a list of paths for a spesific preset.
332     '''
333
334     return (_os.path.join(_presets, subdir), )
335
336
337 def smpte_from_seconds(time, fps=None):
338     '''
339     Returns an SMPTE formatted string from the time in seconds: "HH:MM:SS:FF".
340
341     If the fps is not given the current scene is used.
342     '''
343     import math
344
345     if fps is None:
346         fps = _bpy.context.scene.render.fps
347
348     hours = minutes = seconds = frames = 0
349
350     if time < 0:
351         time = - time
352         neg = "-"
353     else:
354         neg = ""
355
356     if time >= 3600.0: # hours
357         hours = int(time / 3600.0)
358         time = time % 3600.0
359     if time >= 60.0: # mins
360         minutes = int(time / 60.0)
361         time = time % 60.0
362
363     seconds = int(time)
364     frames= int(round(math.floor(((time - seconds) * fps))))
365
366     return "%s%02d:%02d:%02d:%02d" % (neg, hours, minutes, seconds, frames)
367
368
369 def smpte_from_frame(frame, fps=None, fps_base=None):
370     '''
371     Returns an SMPTE formatted string from the frame: "HH:MM:SS:FF".
372
373     If the fps and fps_base are not given the current scene is used.
374     '''
375
376     if fps is None:
377         fps = _bpy.context.scene.render.fps
378
379     if fps_base is None:
380         fps_base = _bpy.context.scene.render.fps_base
381
382     return smpte_from_seconds((frame * fps_base) / fps, fps)