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