SVN maintenance.
[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
38 def _test_import(module_name, loaded_modules):
39     import traceback
40     import time
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     t = time.time()
48     try:
49         mod = __import__(module_name)
50     except:
51         traceback.print_exc()
52         return None
53
54     if _bpy.app.debug:
55         print("time %s %.4f" % (module_name, time.time() - t))
56
57     loaded_modules.add(mod.__name__)  # should match mod.__name__ too
58     return mod
59
60
61 def _sys_path_ensure(path):
62     if path not in _sys.path:  # reloading would add twice
63         _sys.path.insert(0, path)
64
65
66 def modules_from_path(path, loaded_modules):
67     """
68     Load all modules in a path and return them as a list.
69
70     :arg path: this path is scanned for scripts and packages.
71     :type path: string
72     :arg loaded_modules: already loaded module names, files matching these names will be ignored.
73     :type loaded_modules: set
74     :return: all loaded modules.
75     :rtype: list
76     """
77     import traceback
78     import time
79
80     modules = []
81
82     for mod_name, mod_path in _bpy.path.module_names(path):
83         mod = _test_import(mod_name, loaded_modules)
84         if mod:
85             modules.append(mod)
86
87     return modules
88
89
90 _global_loaded_modules = []  # store loaded module names for reloading.
91 import bpy_types as _bpy_types  # keep for comparisons, never ever reload this.
92
93
94 def load_scripts(reload_scripts=False, refresh_scripts=False):
95     """
96     Load scripts and run each modules register function.
97
98     :arg reload_scripts: Causes all scripts to have their unregister method called before loading.
99     :type reload_scripts: bool
100     :arg refresh_scripts: only load scripts which are not already loaded as modules.
101     :type refresh_scripts: bool
102     """
103     import traceback
104     import time
105
106     t_main = time.time()
107
108     loaded_modules = set()
109
110     if refresh_scripts:
111         original_modules = _sys.modules.values()
112
113     if reload_scripts:
114         _bpy_types.TypeMap.clear()
115
116         # just unload, dont change user defaults, this means we can sync to reload.
117         # note that they will only actually reload of the modification time changes.
118         # this `wont` work for packages so... its not perfect.
119         for module_name in [ext.module for ext in _bpy.context.user_preferences.addons]:
120             addon_disable(module_name, default_set=False)
121
122     def register_module_call(mod):
123         register = getattr(mod, "register", None)
124         if register:
125             try:
126                 register()
127             except:
128                 traceback.print_exc()
129         else:
130             print("\nWarning! '%s' has no register function, this is now a requirement for registerable scripts." % mod.__file__)
131
132     def unregister_module_call(mod):
133         unregister = getattr(mod, "unregister", None)
134         if unregister:
135             try:
136                 unregister()
137             except:
138                 traceback.print_exc()
139
140     def test_reload(mod):
141         import imp
142         # reloading this causes internal errors
143         # because the classes from this module are stored internally
144         # possibly to refresh internal references too but for now, best not to.
145         if mod == _bpy_types:
146             return mod
147
148         try:
149             return imp.reload(mod)
150         except:
151             traceback.print_exc()
152
153     def test_register(mod):
154
155         if refresh_scripts and mod in original_modules:
156             return
157
158         if reload_scripts and mod:
159             print("Reloading:", mod)
160             mod = test_reload(mod)
161
162         if mod:
163             register_module_call(mod)
164             _global_loaded_modules.append(mod.__name__)
165
166     if reload_scripts:
167
168         # module names -> modules
169         _global_loaded_modules[:] = [_sys.modules[mod_name] for mod_name in _global_loaded_modules]
170
171         # loop over and unload all scripts
172         _global_loaded_modules.reverse()
173         for mod in _global_loaded_modules:
174             unregister_module_call(mod)
175
176         for mod in _global_loaded_modules:
177             test_reload(mod)
178
179         _global_loaded_modules[:] = []
180
181     user_path = user_script_path()
182
183     for base_path in script_paths():
184         for path_subdir in ("", "ui", "op", "io", "keyingsets", "modules"):
185             path = _os.path.join(base_path, path_subdir)
186             if _os.path.isdir(path):
187                 _sys_path_ensure(path)
188
189                 # only add this to sys.modules, dont run
190                 if path_subdir == "modules":
191                     continue
192
193                 if user_path != base_path and path_subdir == "":
194                     continue  # avoid loading 2.4x scripts
195
196                 for mod in modules_from_path(path, loaded_modules):
197                     test_register(mod)
198
199     # deal with addons seperately
200     addon_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 _bpy.app.debug:
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=True):
231     """
232     Returns a list of valid script paths from the home directory and user preferences.
233
234     Accepts any number of string arguments which are joined to make a path.
235     """
236     scripts = list(_scripts)
237
238     # add user scripts dir
239     if user:
240         user_script_path = _bpy.context.user_preferences.filepaths.script_directory
241     else:
242         user_script_path = None
243
244     for path in _bpy_script_paths() + (user_script_path, ):
245         if path:
246             path = _os.path.normpath(path)
247             if path not in scripts and _os.path.isdir(path):
248                 scripts.append(path)
249
250     if not subdir:
251         return scripts
252
253     script_paths = []
254     for path in scripts:
255         path_subdir = _os.path.join(path, subdir)
256         if _os.path.isdir(path_subdir):
257             script_paths.append(path_subdir)
258
259     return script_paths
260
261
262 _presets = _os.path.join(_scripts[0], "presets")  # FIXME - multiple paths
263
264
265 def preset_paths(subdir):
266     """
267     Returns a list of paths for a specific preset.
268     """
269     dirs = []
270     for path in script_paths("presets"):
271         directory = _os.path.join(path, subdir)
272         if _os.path.isdir(directory):
273             dirs.append(directory)
274     return dirs
275
276
277 def smpte_from_seconds(time, fps=None):
278     """
279     Returns an SMPTE formatted string from the time in seconds: "HH:MM:SS:FF".
280
281     If the *fps* is not given the current scene is used.
282     """
283     import math
284
285     if fps is None:
286         fps = _bpy.context.scene.render.fps
287
288     hours = minutes = seconds = frames = 0
289
290     if time < 0:
291         time = - time
292         neg = "-"
293     else:
294         neg = ""
295
296     if time >= 3600.0:  # hours
297         hours = int(time / 3600.0)
298         time = time % 3600.0
299     if time >= 60.0:  # mins
300         minutes = int(time / 60.0)
301         time = time % 60.0
302
303     seconds = int(time)
304     frames = int(round(math.floor(((time - seconds) * fps))))
305
306     return "%s%02d:%02d:%02d:%02d" % (neg, hours, minutes, seconds, frames)
307
308
309 def smpte_from_frame(frame, fps=None, fps_base=None):
310     """
311     Returns an SMPTE formatted string from the frame: "HH:MM:SS:FF".
312
313     If *fps* and *fps_base* are not given the current scene is used.
314     """
315
316     if fps is None:
317         fps = _bpy.context.scene.render.fps
318
319     if fps_base is None:
320         fps_base = _bpy.context.scene.render.fps_base
321
322     return smpte_from_seconds((frame * fps_base) / fps, fps)
323
324
325 def addon_check(module_name):
326     """
327     Returns the loaded state of the addon.
328
329     :arg module_name: The name of the addon and module.
330     :type module_name: string
331     :return: (loaded_default, loaded_state)
332     :rtype: tuple of booleans
333     """
334     loaded_default = module_name in _bpy.context.user_preferences.addons
335
336     mod = _sys.modules.get(module_name)
337     loaded_state = mod and getattr(mod, "__addon_enabled__", Ellipsis)
338
339     if loaded_state is Ellipsis:
340         print("Warning: addon-module %r found module but without"
341                " __addon_enabled__ field, possible name collision from file: %r" %
342                (module_name, getattr(mod, "__file__", "<unknown>")))
343
344         loaded_state = False
345
346     return loaded_default, loaded_state
347
348
349 def addon_enable(module_name, default_set=True):
350     """
351     Enables an addon by name.
352
353     :arg module_name: The name of the addon and module.
354     :type module_name: string
355     :return: the loaded module or None on failier.
356     :rtype: module
357     """
358     # note, this still gets added to _bpy_types.TypeMap
359
360     import os
361     import sys
362     import bpy_types as _bpy_types
363     import imp
364
365     def handle_error():
366         import traceback
367         traceback.print_exc()
368
369     # reload if the mtime changes
370     mod = sys.modules.get(module_name)
371     if mod:
372         mod.__addon_enabled__ = False
373         mtime_orig = getattr(mod, "__time__", 0)
374         mtime_new = os.path.getmtime(mod.__file__)
375         if mtime_orig != mtime_new:
376             print("module changed on disk:", mod.__file__, "reloading...")
377
378             try:
379                 imp.reload(mod)
380             except:
381                 handle_error()
382                 del sys.modules[module_name]
383                 return None
384             mod.__addon_enabled__ = False
385
386     # Split registering up into 3 steps so we can undo if it fails par way through
387     # 1) try import
388     try:
389         mod = __import__(module_name)
390         mod.__time__ = os.path.getmtime(mod.__file__)
391         mod.__addon_enabled__ = False
392     except:
393         handle_error()
394         return None
395
396     # 2) try register collected modules
397     # removed, addons need to handle own registration now.
398
399     # 3) try run the modules register function
400     try:
401         mod.register()
402     except:
403         handle_error()
404         del sys.modules[module_name]
405         return None
406
407     # * OK loaded successfully! *
408     if default_set:
409         # just incase its enabled alredy
410         ext = _bpy.context.user_preferences.addons.get(module_name)
411         if not ext:
412             ext = _bpy.context.user_preferences.addons.new()
413             ext.module = module_name
414
415     mod.__addon_enabled__ = True
416
417     if _bpy.app.debug:
418         print("\tbpy.utils.addon_enable", mod.__name__)
419
420     return mod
421
422
423 def addon_disable(module_name, default_set=True):
424     """
425     Disables an addon by name.
426
427     :arg module_name: The name of the addon and module.
428     :type module_name: string
429     """
430     import traceback
431     import bpy_types as _bpy_types
432
433     mod = _sys.modules.get(module_name)
434
435     # possible this addon is from a previous session and didnt load a module this time.
436     # so even if the module is not found, still disable the addon in the user prefs.
437     if mod:
438         mod.__addon_enabled__ = False
439
440         try:
441             mod.unregister()
442         except:
443             traceback.print_exc()
444     else:
445         print("addon_disable", module_name, "not loaded")
446
447     # could be in more then once, unlikely but better do this just incase.
448     addons = _bpy.context.user_preferences.addons
449
450     if default_set:
451         while module_name in addons:
452             addon = addons.get(module_name)
453             if addon:
454                 addons.remove(addon)
455
456     if _bpy.app.debug:
457         print("\tbpy.utils.addon_disable", module_name)
458
459
460 def addon_reset_all(reload_scripts=False):
461     """
462     Sets the addon state based on the user preferences.
463     """
464     import imp
465
466     # RELEASE SCRIPTS: official scripts distributed in Blender releases
467     paths = script_paths("addons")
468
469     # CONTRIB SCRIPTS: good for testing but not official scripts yet
470     paths += script_paths("addons_contrib")
471
472     # EXTERN SCRIPTS: external projects scripts
473     paths += script_paths("addons_extern")
474
475     for path in paths:
476         _sys_path_ensure(path)
477         for mod_name, mod_path in _bpy.path.module_names(path):
478             is_enabled, is_loaded = addon_check(mod_name)
479
480             # first check if reload is needed before changing state.
481             if reload_scripts:
482                 mod = _sys.modules.get(mod_name)
483                 if mod:
484                     imp.reload(mod)
485
486             if is_enabled == is_loaded:
487                 pass
488             elif is_enabled:
489                 addon_enable(mod_name)
490             elif is_loaded:
491                 print("\taddon_reset_all unloading", mod_name)
492                 addon_disable(mod_name)
493
494
495 def preset_find(name, preset_path, display_name=False):
496     if not name:
497         return None
498
499     for directory in preset_paths(preset_path):
500
501         if display_name:
502             filename = ""
503             for fn in _os.listdir(directory):
504                 if fn.endswith(".py") and name == _bpy.path.display_name(fn):
505                     filename = fn
506                     break
507         else:
508             filename = name + ".py"
509
510         if filename:
511             filepath = _os.path.join(directory, filename)
512             if _os.path.exists(filepath):
513                 return filepath
514
515
516 def keyconfig_set(filepath):
517     from os.path import basename, splitext
518
519     print("loading preset:", filepath)
520     keyconfigs = _bpy.context.window_manager.keyconfigs
521     kc_orig = keyconfigs.active
522
523     keyconfigs_old = keyconfigs[:]
524
525     try:
526         exec(compile(open(filepath).read(), filepath, 'exec'), {"__file__": filepath})
527     except:
528         import traceback
529         traceback.print_exc()
530
531     kc_new = [kc for kc in keyconfigs if kc not in keyconfigs_old][0]
532
533     kc_new.name = ""
534
535     # remove duplicates
536     name = splitext(basename(filepath))[0]
537     while True:
538         kc_dupe = keyconfigs.get(name)
539         if kc_dupe:
540             keyconfigs.remove(kc_dupe)
541         else:
542             break
543
544     kc_new.name = name
545     keyconfigs.active = kc_new
546
547
548 def user_resource(type, path="", create=False):
549     """
550     Return a user resource path (normally from the users home directory).
551
552     :arg type: Resource type in ['DATAFILES', 'CONFIG', 'SCRIPTS', 'AUTOSAVE'].
553     :type type: string
554     :arg subdir: Optional subdirectory.
555     :type subdir: string
556     :arg create: Treat the path as a directory and create it if its not existing.
557     :type create: boolean
558     :return: a path.
559     :rtype: string
560     """
561
562     target_path = _user_resource(type, path)
563
564     if create:
565         # should always be true.
566         if target_path:
567             # create path if not existing.
568             if not _os.path.exists(target_path):
569                 try:
570                     _os.makedirs(target_path)
571                 except:
572                     import traceback
573                     traceback.print_exc()
574                     target_path = ""
575             elif not _os.path.isdir(target_path):
576                 print("Path %r found but isn't a directory!" % target_path)
577                 target_path = ""
578
579     return target_path
580
581
582 def _bpy_module_classes(module, is_registered=False):
583     typemap_list = _bpy_types.TypeMap.get(module, ())
584     i = 0
585     while i < len(typemap_list):
586         cls_weakref, path, line = typemap_list[i]
587         cls = cls_weakref()
588
589         if cls is None:
590             del typemap_list[i]
591         else:
592             if is_registered == ("bl_rna" in cls.__dict__):
593                 yield (cls, path, line)
594             i += 1
595
596
597 def register_module(module, verbose=False):
598     import traceback
599     if verbose:
600         print("bpy.utils.register_module(%r): ..." % module)
601     for cls, path, line in _bpy_module_classes(module, is_registered=False):
602         if verbose:
603             print("    %s of %s:%s" % (cls, path, line))
604         try:
605             register_class(cls)
606         except:
607             print("bpy.utils.register_module(): failed to registering class '%s.%s'" % (cls.__module__, cls.__name__))
608             print("\t", path, "line", line)
609             traceback.print_exc()
610     if verbose:
611         print("done.\n")
612     if "cls" not in locals():
613         raise Exception("register_module(%r): defines no classes" % module)
614
615
616 def unregister_module(module, verbose=False):
617     import traceback
618     if verbose:
619         print("bpy.utils.unregister_module(%r): ..." % module)
620     for cls, path, line in _bpy_module_classes(module, is_registered=True):
621         if verbose:
622             print("    %s of %s:%s" % (cls, path, line))
623         try:
624             unregister_class(cls)
625         except:
626             print("bpy.utils.unregister_module(): failed to unregistering class '%s.%s'" % (cls.__module__, cls.__name__))
627             print("\t", path, "line", line)
628             traceback.print_exc()
629     if verbose:
630         print("done.\n")