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