e98577c33100325b1685a53f6feb9ac30e0dbf61
[blender-staging.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 specific to blender but
23 not assosiated with blenders internal data.
24 """
25
26 from _bpy import register_class
27 from _bpy import unregister_class
28
29 from _bpy import blend_paths
30 from _bpy import script_paths as _bpy_script_paths
31 from _bpy import user_resource as _user_resource
32
33 import bpy as _bpy
34 import os as _os
35 import sys as _sys
36
37 import addon_utils
38
39
40 def _test_import(module_name, loaded_modules):
41     use_time = _bpy.app.debug
42
43     if module_name in loaded_modules:
44         return None
45     if "." in module_name:
46         print("Ignoring '%s', can't import files containing multiple periods." % module_name)
47         return None
48
49     if use_time:
50         import time
51         t = time.time()
52
53     try:
54         mod = __import__(module_name)
55     except:
56         import traceback
57         traceback.print_exc()
58         return None
59
60     if use_time:
61         print("time %s %.4f" % (module_name, time.time() - t))
62
63     loaded_modules.add(mod.__name__)  # should match mod.__name__ too
64     return mod
65
66
67 def _sys_path_ensure(path):
68     if path not in _sys.path:  # reloading would add twice
69         _sys.path.insert(0, path)
70
71
72 def modules_from_path(path, loaded_modules):
73     """
74     Load all modules in a path and return them as a list.
75
76     :arg path: this path is scanned for scripts and packages.
77     :type path: string
78     :arg loaded_modules: already loaded module names, files matching these names will be ignored.
79     :type loaded_modules: set
80     :return: all loaded modules.
81     :rtype: list
82     """
83     modules = []
84
85     for mod_name, mod_path in _bpy.path.module_names(path):
86         mod = _test_import(mod_name, loaded_modules)
87         if mod:
88             modules.append(mod)
89
90     return modules
91
92
93 _global_loaded_modules = []  # store loaded module names for reloading.
94 import bpy_types as _bpy_types  # keep for comparisons, never ever reload this.
95
96
97 def load_scripts(reload_scripts=False, refresh_scripts=False):
98     """
99     Load scripts and run each modules register function.
100
101     :arg reload_scripts: Causes all scripts to have their unregister method called before loading.
102     :type reload_scripts: bool
103     :arg refresh_scripts: only load scripts which are not already loaded as modules.
104     :type refresh_scripts: bool
105     """
106     use_time = _bpy.app.debug
107
108     if use_time:
109         import time
110         t_main = time.time()
111
112     loaded_modules = set()
113
114     if refresh_scripts:
115         original_modules = _sys.modules.values()
116
117     if reload_scripts:
118         _bpy_types.TypeMap.clear()
119
120         # just unload, dont change user defaults, this means we can sync to reload.
121         # note that they will only actually reload of the modification time changes.
122         # this `wont` work for packages so... its not perfect.
123         for module_name in [ext.module for ext in _bpy.context.user_preferences.addons]:
124             addon_utils.disable(module_name, default_set=False)
125
126     def register_module_call(mod):
127         register = getattr(mod, "register", None)
128         if register:
129             try:
130                 register()
131             except:
132                 import traceback
133                 traceback.print_exc()
134         else:
135             print("\nWarning! '%s' has no register function, this is now a requirement for registerable scripts." % mod.__file__)
136
137     def unregister_module_call(mod):
138         unregister = getattr(mod, "unregister", None)
139         if unregister:
140             try:
141                 unregister()
142             except:
143                 import traceback
144                 traceback.print_exc()
145
146     def test_reload(mod):
147         import imp
148         # reloading this causes internal errors
149         # because the classes from this module are stored internally
150         # possibly to refresh internal references too but for now, best not to.
151         if mod == _bpy_types:
152             return mod
153
154         try:
155             return imp.reload(mod)
156         except:
157             import traceback
158             traceback.print_exc()
159
160     def test_register(mod):
161
162         if refresh_scripts and mod in original_modules:
163             return
164
165         if reload_scripts and mod:
166             print("Reloading:", mod)
167             mod = test_reload(mod)
168
169         if mod:
170             register_module_call(mod)
171             _global_loaded_modules.append(mod.__name__)
172
173     if reload_scripts:
174
175         # module names -> modules
176         _global_loaded_modules[:] = [_sys.modules[mod_name] for mod_name in _global_loaded_modules]
177
178         # loop over and unload all scripts
179         _global_loaded_modules.reverse()
180         for mod in _global_loaded_modules:
181             unregister_module_call(mod)
182
183         for mod in _global_loaded_modules:
184             test_reload(mod)
185
186         _global_loaded_modules[:] = []
187
188     user_path = user_script_path()
189
190     for base_path in script_paths():
191         for path_subdir in ("", "ui", "op", "io", "keyingsets", "modules"):
192             path = _os.path.join(base_path, path_subdir)
193             if _os.path.isdir(path):
194                 _sys_path_ensure(path)
195
196                 # only add this to sys.modules, dont run
197                 if path_subdir == "modules":
198                     continue
199
200                 if user_path != base_path and path_subdir == "":
201                     continue  # avoid loading 2.4x scripts
202
203                 for mod in modules_from_path(path, loaded_modules):
204                     test_register(mod)
205
206     # deal with addons seperately
207     addon_utils.reset_all(reload_scripts)
208
209     # run the active integration preset
210     filepath = preset_find(_bpy.context.user_preferences.inputs.active_keyconfig, "keyconfig")
211     if filepath:
212         keyconfig_set(filepath)
213
214     if reload_scripts:
215         import gc
216         print("gc.collect() -> %d" % gc.collect())
217
218     if use_time:
219         print("Python Script Load Time %.4f" % (time.time() - t_main))
220
221
222 # base scripts
223 _scripts = _os.path.join(_os.path.dirname(__file__), _os.path.pardir, _os.path.pardir)
224 _scripts = (_os.path.normpath(_scripts), )
225
226
227 def user_script_path():
228     path = _bpy.context.user_preferences.filepaths.script_directory
229
230     if path:
231         path = _os.path.normpath(path)
232         return path
233     else:
234         return None
235
236
237 def script_paths(subdir=None, user=True):
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     if user:
247         user_script_path = _bpy.context.user_preferences.filepaths.script_directory
248     else:
249         user_script_path = None
250
251     for path in _bpy_script_paths() + (user_script_path, ):
252         if path:
253             path = _os.path.normpath(path)
254             if path not in scripts and _os.path.isdir(path):
255                 scripts.append(path)
256
257     if not subdir:
258         return scripts
259
260     script_paths = []
261     for path in scripts:
262         path_subdir = _os.path.join(path, subdir)
263         if _os.path.isdir(path_subdir):
264             script_paths.append(path_subdir)
265
266     return script_paths
267
268
269 _presets = _os.path.join(_scripts[0], "presets")  # FIXME - multiple paths
270
271
272 def preset_paths(subdir):
273     """
274     Returns a list of paths for a specific preset.
275     """
276     dirs = []
277     for path in script_paths("presets"):
278         directory = _os.path.join(path, subdir)
279         if _os.path.isdir(directory):
280             dirs.append(directory)
281     return dirs
282
283
284 def smpte_from_seconds(time, fps=None):
285     """
286     Returns an SMPTE formatted string from the time in seconds: "HH:MM:SS:FF".
287
288     If the *fps* is not given the current scene is used.
289     """
290     import math
291
292     if fps is None:
293         fps = _bpy.context.scene.render.fps
294
295     hours = minutes = seconds = frames = 0
296
297     if time < 0:
298         time = - time
299         neg = "-"
300     else:
301         neg = ""
302
303     if time >= 3600.0:  # hours
304         hours = int(time / 3600.0)
305         time = time % 3600.0
306     if time >= 60.0:  # mins
307         minutes = int(time / 60.0)
308         time = time % 60.0
309
310     seconds = int(time)
311     frames = int(round(math.floor(((time - seconds) * fps))))
312
313     return "%s%02d:%02d:%02d:%02d" % (neg, hours, minutes, seconds, frames)
314
315
316 def smpte_from_frame(frame, fps=None, fps_base=None):
317     """
318     Returns an SMPTE formatted string from the frame: "HH:MM:SS:FF".
319
320     If *fps* and *fps_base* are not given the current scene is used.
321     """
322
323     if fps is None:
324         fps = _bpy.context.scene.render.fps
325
326     if fps_base is None:
327         fps_base = _bpy.context.scene.render.fps_base
328
329     return smpte_from_seconds((frame * fps_base) / fps, fps)
330
331
332 def preset_find(name, preset_path, display_name=False):
333     if not name:
334         return None
335
336     for directory in preset_paths(preset_path):
337
338         if display_name:
339             filename = ""
340             for fn in _os.listdir(directory):
341                 if fn.endswith(".py") and name == _bpy.path.display_name(fn):
342                     filename = fn
343                     break
344         else:
345             filename = name + ".py"
346
347         if filename:
348             filepath = _os.path.join(directory, filename)
349             if _os.path.exists(filepath):
350                 return filepath
351
352
353 def keyconfig_set(filepath):
354     from os.path import basename, splitext
355
356     print("loading preset:", filepath)
357     keyconfigs = _bpy.context.window_manager.keyconfigs
358     kc_orig = keyconfigs.active
359
360     keyconfigs_old = keyconfigs[:]
361
362     try:
363         exec(compile(open(filepath).read(), filepath, 'exec'), {"__file__": filepath})
364     except:
365         import traceback
366         traceback.print_exc()
367
368     kc_new = [kc for kc in keyconfigs if kc not in keyconfigs_old][0]
369
370     kc_new.name = ""
371
372     # remove duplicates
373     name = splitext(basename(filepath))[0]
374     while True:
375         kc_dupe = keyconfigs.get(name)
376         if kc_dupe:
377             keyconfigs.remove(kc_dupe)
378         else:
379             break
380
381     kc_new.name = name
382     keyconfigs.active = kc_new
383
384
385 def user_resource(type, path="", create=False):
386     """
387     Return a user resource path (normally from the users home directory).
388
389     :arg type: Resource type in ['DATAFILES', 'CONFIG', 'SCRIPTS', 'AUTOSAVE'].
390     :type type: string
391     :arg subdir: Optional subdirectory.
392     :type subdir: string
393     :arg create: Treat the path as a directory and create it if its not existing.
394     :type create: boolean
395     :return: a path.
396     :rtype: string
397     """
398
399     target_path = _user_resource(type, path)
400
401     if create:
402         # should always be true.
403         if target_path:
404             # create path if not existing.
405             if not _os.path.exists(target_path):
406                 try:
407                     _os.makedirs(target_path)
408                 except:
409                     import traceback
410                     traceback.print_exc()
411                     target_path = ""
412             elif not _os.path.isdir(target_path):
413                 print("Path %r found but isn't a directory!" % target_path)
414                 target_path = ""
415
416     return target_path
417
418
419 def _bpy_module_classes(module, is_registered=False):
420     typemap_list = _bpy_types.TypeMap.get(module, ())
421     i = 0
422     while i < len(typemap_list):
423         cls_weakref = typemap_list[i]
424         cls = cls_weakref()
425
426         if cls is None:
427             del typemap_list[i]
428         else:
429             if is_registered == cls.is_registered:
430                 yield cls
431             i += 1
432
433
434 def register_module(module, verbose=False):
435     if verbose:
436         print("bpy.utils.register_module(%r): ..." % module)
437     for cls in _bpy_module_classes(module, is_registered=False):
438         if verbose:
439             print("    %r" % cls)
440         try:
441             register_class(cls)
442         except:
443             print("bpy.utils.register_module(): failed to registering class %r" % cls)
444             print("\t", path, "line", line)
445             import traceback
446             traceback.print_exc()
447     if verbose:
448         print("done.\n")
449     if "cls" not in locals():
450         raise Exception("register_module(%r): defines no classes" % module)
451
452
453 def unregister_module(module, verbose=False):
454     if verbose:
455         print("bpy.utils.unregister_module(%r): ..." % module)
456     for cls in _bpy_module_classes(module, is_registered=True):
457         if verbose:
458             print("    %r" % cls)
459         try:
460             unregister_class(cls)
461         except:
462             print("bpy.utils.unregister_module(): failed to unregistering class %r" % cls)
463             print("\t", path, "line", line)
464             import traceback
465             traceback.print_exc()
466     if verbose:
467         print("done.\n")