SVN maintenance.
[blender.git] / release / scripts / modules / bpy / utils.py
index f5144d6cdf51f2c6fd14aba7373992667d1f3d26..e00c12e3c0659b3f287d01a0ac77aaa81573e9cf 100644 (file)
@@ -23,12 +23,16 @@ This module contains utility functions specific to blender but
 not assosiated with blenders internal data.
 """
 
-import bpy as _bpy
-import os as _os
-import sys as _sys
+from _bpy import register_class
+from _bpy import unregister_class
 
 from _bpy import blend_paths
 from _bpy import script_paths as _bpy_script_paths
+from _bpy import user_resource as _user_resource
+
+import bpy as _bpy
+import os as _os
+import sys as _sys
 
 
 def _test_import(module_name, loaded_modules):
@@ -54,6 +58,11 @@ def _test_import(module_name, loaded_modules):
     return mod
 
 
+def _sys_path_ensure(path):
+    if path not in _sys.path:  # reloading would add twice
+        _sys.path.insert(0, path)
+
+
 def modules_from_path(path, loaded_modules):
     """
     Load all modules in a path and return them as a list.
@@ -94,9 +103,6 @@ def load_scripts(reload_scripts=False, refresh_scripts=False):
     import traceback
     import time
 
-    # must be set back to True on exits
-    _bpy_types._register_immediate = False
-
     t_main = time.time()
 
     loaded_modules = set()
@@ -106,10 +112,14 @@ def load_scripts(reload_scripts=False, refresh_scripts=False):
 
     if reload_scripts:
         _bpy_types.TypeMap.clear()
-        _bpy_types.PropertiesMap.clear()
+
+        # just unload, dont change user defaults, this means we can sync to reload.
+        # note that they will only actually reload of the modification time changes.
+        # this `wont` work for packages so... its not perfect.
+        for module_name in [ext.module for ext in _bpy.context.user_preferences.addons]:
+            addon_disable(module_name, default_set=False)
 
     def register_module_call(mod):
-        _bpy_types._register_module(mod.__name__)
         register = getattr(mod, "register", None)
         if register:
             try:
@@ -120,7 +130,6 @@ def load_scripts(reload_scripts=False, refresh_scripts=False):
             print("\nWarning! '%s' has no register function, this is now a requirement for registerable scripts." % mod.__file__)
 
     def unregister_module_call(mod):
-        _bpy_types._unregister_module(mod.__name__)
         unregister = getattr(mod, "unregister", None)
         if unregister:
             try:
@@ -128,11 +137,8 @@ def load_scripts(reload_scripts=False, refresh_scripts=False):
             except:
                 traceback.print_exc()
 
-    def sys_path_ensure(path):
-        if path not in _sys.path:  # reloading would add twice
-            _sys.path.insert(0, path)
-
     def test_reload(mod):
+        import imp
         # reloading this causes internal errors
         # because the classes from this module are stored internally
         # possibly to refresh internal references too but for now, best not to.
@@ -140,7 +146,7 @@ def load_scripts(reload_scripts=False, refresh_scripts=False):
             return mod
 
         try:
-            return reload(mod)
+            return imp.reload(mod)
         except:
             traceback.print_exc()
 
@@ -175,10 +181,10 @@ def load_scripts(reload_scripts=False, refresh_scripts=False):
     user_path = user_script_path()
 
     for base_path in script_paths():
-        for path_subdir in ("", "ui", "op", "io", "cfg", "keyingsets", "modules"):
+        for path_subdir in ("", "ui", "op", "io", "keyingsets", "modules"):
             path = _os.path.join(base_path, path_subdir)
             if _os.path.isdir(path):
-                sys_path_ensure(path)
+                _sys_path_ensure(path)
 
                 # only add this to sys.modules, dont run
                 if path_subdir == "modules":
@@ -190,15 +196,13 @@ def load_scripts(reload_scripts=False, refresh_scripts=False):
                 for mod in modules_from_path(path, loaded_modules):
                     test_register(mod)
 
-    # load addons
-    used_ext = {ext.module for ext in _bpy.context.user_preferences.addons}
-    paths = script_paths("addons") + script_paths("addons_contrib")
-    for path in paths:
-        sys_path_ensure(path)
+    # deal with addons seperately
+    addon_reset_all(reload_scripts)
 
-    for module_name in sorted(used_ext):
-        mod = _test_import(module_name, loaded_modules)
-        test_register(mod)
+    # run the active integration preset
+    filepath = preset_find(_bpy.context.user_preferences.inputs.active_keyconfig, "keyconfig")
+    if filepath:
+        keyconfig_set(filepath)
 
     if reload_scripts:
         import gc
@@ -207,8 +211,6 @@ def load_scripts(reload_scripts=False, refresh_scripts=False):
     if _bpy.app.debug:
         print("Python Script Load Time %.4f" % (time.time() - t_main))
 
-    _bpy_types._register_immediate = True
-
 
 # base scripts
 _scripts = _os.path.join(_os.path.dirname(__file__), _os.path.pardir, _os.path.pardir)
@@ -261,19 +263,23 @@ _presets = _os.path.join(_scripts[0], "presets")  # FIXME - multiple paths
 
 
 def preset_paths(subdir):
-    '''
+    """
     Returns a list of paths for a specific preset.
-    '''
-
-    return (_os.path.join(_presets, subdir), )
+    """
+    dirs = []
+    for path in script_paths("presets"):
+        directory = _os.path.join(path, subdir)
+        if _os.path.isdir(directory):
+            dirs.append(directory)
+    return dirs
 
 
 def smpte_from_seconds(time, fps=None):
-    '''
+    """
     Returns an SMPTE formatted string from the time in seconds: "HH:MM:SS:FF".
 
     If the *fps* is not given the current scene is used.
-    '''
+    """
     import math
 
     if fps is None:
@@ -301,11 +307,11 @@ def smpte_from_seconds(time, fps=None):
 
 
 def smpte_from_frame(frame, fps=None, fps_base=None):
-    '''
+    """
     Returns an SMPTE formatted string from the frame: "HH:MM:SS:FF".
 
     If *fps* and *fps_base* are not given the current scene is used.
-    '''
+    """
 
     if fps is None:
         fps = _bpy.context.scene.render.fps
@@ -314,3 +320,311 @@ def smpte_from_frame(frame, fps=None, fps_base=None):
         fps_base = _bpy.context.scene.render.fps_base
 
     return smpte_from_seconds((frame * fps_base) / fps, fps)
+
+
+def addon_check(module_name):
+    """
+    Returns the loaded state of the addon.
+
+    :arg module_name: The name of the addon and module.
+    :type module_name: string
+    :return: (loaded_default, loaded_state)
+    :rtype: tuple of booleans
+    """
+    loaded_default = module_name in _bpy.context.user_preferences.addons
+
+    mod = _sys.modules.get(module_name)
+    loaded_state = mod and getattr(mod, "__addon_enabled__", Ellipsis)
+
+    if loaded_state is Ellipsis:
+        print("Warning: addon-module %r found module but without"
+               " __addon_enabled__ field, possible name collision from file: %r" %
+               (module_name, getattr(mod, "__file__", "<unknown>")))
+
+        loaded_state = False
+
+    return loaded_default, loaded_state
+
+
+def addon_enable(module_name, default_set=True):
+    """
+    Enables an addon by name.
+
+    :arg module_name: The name of the addon and module.
+    :type module_name: string
+    :return: the loaded module or None on failier.
+    :rtype: module
+    """
+    # note, this still gets added to _bpy_types.TypeMap
+
+    import os
+    import sys
+    import bpy_types as _bpy_types
+    import imp
+
+    def handle_error():
+        import traceback
+        traceback.print_exc()
+
+    # reload if the mtime changes
+    mod = sys.modules.get(module_name)
+    if mod:
+        mod.__addon_enabled__ = False
+        mtime_orig = getattr(mod, "__time__", 0)
+        mtime_new = os.path.getmtime(mod.__file__)
+        if mtime_orig != mtime_new:
+            print("module changed on disk:", mod.__file__, "reloading...")
+
+            try:
+                imp.reload(mod)
+            except:
+                handle_error()
+                del sys.modules[module_name]
+                return None
+            mod.__addon_enabled__ = False
+
+    # Split registering up into 3 steps so we can undo if it fails par way through
+    # 1) try import
+    try:
+        mod = __import__(module_name)
+        mod.__time__ = os.path.getmtime(mod.__file__)
+        mod.__addon_enabled__ = False
+    except:
+        handle_error()
+        return None
+
+    # 2) try register collected modules
+    # removed, addons need to handle own registration now.
+
+    # 3) try run the modules register function
+    try:
+        mod.register()
+    except:
+        handle_error()
+        del sys.modules[module_name]
+        return None
+
+    # * OK loaded successfully! *
+    if default_set:
+        # just incase its enabled alredy
+        ext = _bpy.context.user_preferences.addons.get(module_name)
+        if not ext:
+            ext = _bpy.context.user_preferences.addons.new()
+            ext.module = module_name
+
+    mod.__addon_enabled__ = True
+
+    if _bpy.app.debug:
+        print("\tbpy.utils.addon_enable", mod.__name__)
+
+    return mod
+
+
+def addon_disable(module_name, default_set=True):
+    """
+    Disables an addon by name.
+
+    :arg module_name: The name of the addon and module.
+    :type module_name: string
+    """
+    import traceback
+    import bpy_types as _bpy_types
+
+    mod = _sys.modules.get(module_name)
+
+    # possible this addon is from a previous session and didnt load a module this time.
+    # so even if the module is not found, still disable the addon in the user prefs.
+    if mod:
+        mod.__addon_enabled__ = False
+
+        try:
+            mod.unregister()
+        except:
+            traceback.print_exc()
+    else:
+        print("addon_disable", module_name, "not loaded")
+
+    # could be in more then once, unlikely but better do this just incase.
+    addons = _bpy.context.user_preferences.addons
+
+    if default_set:
+        while module_name in addons:
+            addon = addons.get(module_name)
+            if addon:
+                addons.remove(addon)
+
+    if _bpy.app.debug:
+        print("\tbpy.utils.addon_disable", module_name)
+
+
+def addon_reset_all(reload_scripts=False):
+    """
+    Sets the addon state based on the user preferences.
+    """
+    import imp
+
+    # RELEASE SCRIPTS: official scripts distributed in Blender releases
+    paths = script_paths("addons")
+
+    # CONTRIB SCRIPTS: good for testing but not official scripts yet
+    paths += script_paths("addons_contrib")
+
+    # EXTERN SCRIPTS: external projects scripts
+    paths += script_paths("addons_extern")
+
+    for path in paths:
+        _sys_path_ensure(path)
+        for mod_name, mod_path in _bpy.path.module_names(path):
+            is_enabled, is_loaded = addon_check(mod_name)
+
+            # first check if reload is needed before changing state.
+            if reload_scripts:
+                mod = _sys.modules.get(mod_name)
+                if mod:
+                    imp.reload(mod)
+
+            if is_enabled == is_loaded:
+                pass
+            elif is_enabled:
+                addon_enable(mod_name)
+            elif is_loaded:
+                print("\taddon_reset_all unloading", mod_name)
+                addon_disable(mod_name)
+
+
+def preset_find(name, preset_path, display_name=False):
+    if not name:
+        return None
+
+    for directory in preset_paths(preset_path):
+
+        if display_name:
+            filename = ""
+            for fn in _os.listdir(directory):
+                if fn.endswith(".py") and name == _bpy.path.display_name(fn):
+                    filename = fn
+                    break
+        else:
+            filename = name + ".py"
+
+        if filename:
+            filepath = _os.path.join(directory, filename)
+            if _os.path.exists(filepath):
+                return filepath
+
+
+def keyconfig_set(filepath):
+    from os.path import basename, splitext
+
+    print("loading preset:", filepath)
+    keyconfigs = _bpy.context.window_manager.keyconfigs
+    kc_orig = keyconfigs.active
+
+    keyconfigs_old = keyconfigs[:]
+
+    try:
+        exec(compile(open(filepath).read(), filepath, 'exec'), {"__file__": filepath})
+    except:
+        import traceback
+        traceback.print_exc()
+
+    kc_new = [kc for kc in keyconfigs if kc not in keyconfigs_old][0]
+
+    kc_new.name = ""
+
+    # remove duplicates
+    name = splitext(basename(filepath))[0]
+    while True:
+        kc_dupe = keyconfigs.get(name)
+        if kc_dupe:
+            keyconfigs.remove(kc_dupe)
+        else:
+            break
+
+    kc_new.name = name
+    keyconfigs.active = kc_new
+
+
+def user_resource(type, path="", create=False):
+    """
+    Return a user resource path (normally from the users home directory).
+
+    :arg type: Resource type in ['DATAFILES', 'CONFIG', 'SCRIPTS', 'AUTOSAVE'].
+    :type type: string
+    :arg subdir: Optional subdirectory.
+    :type subdir: string
+    :arg create: Treat the path as a directory and create it if its not existing.
+    :type create: boolean
+    :return: a path.
+    :rtype: string
+    """
+
+    target_path = _user_resource(type, path)
+
+    if create:
+        # should always be true.
+        if target_path:
+            # create path if not existing.
+            if not _os.path.exists(target_path):
+                try:
+                    _os.makedirs(target_path)
+                except:
+                    import traceback
+                    traceback.print_exc()
+                    target_path = ""
+            elif not _os.path.isdir(target_path):
+                print("Path %r found but isn't a directory!" % target_path)
+                target_path = ""
+
+    return target_path
+
+
+def _bpy_module_classes(module, is_registered=False):
+    typemap_list = _bpy_types.TypeMap.get(module, ())
+    i = 0
+    while i < len(typemap_list):
+        cls_weakref, path, line = typemap_list[i]
+        cls = cls_weakref()
+
+        if cls is None:
+            del typemap_list[i]
+        else:
+            if is_registered == ("bl_rna" in cls.__dict__):
+                yield (cls, path, line)
+            i += 1
+
+
+def register_module(module, verbose=False):
+    import traceback
+    if verbose:
+        print("bpy.utils.register_module(%r): ..." % module)
+    for cls, path, line in _bpy_module_classes(module, is_registered=False):
+        if verbose:
+            print("    %s of %s:%s" % (cls, path, line))
+        try:
+            register_class(cls)
+        except:
+            print("bpy.utils.register_module(): failed to registering class '%s.%s'" % (cls.__module__, cls.__name__))
+            print("\t", path, "line", line)
+            traceback.print_exc()
+    if verbose:
+        print("done.\n")
+    if "cls" not in locals():
+        raise Exception("register_module(%r): defines no classes" % module)
+
+
+def unregister_module(module, verbose=False):
+    import traceback
+    if verbose:
+        print("bpy.utils.unregister_module(%r): ..." % module)
+    for cls, path, line in _bpy_module_classes(module, is_registered=True):
+        if verbose:
+            print("    %s of %s:%s" % (cls, path, line))
+        try:
+            unregister_class(cls)
+        except:
+            print("bpy.utils.unregister_module(): failed to unregistering class '%s.%s'" % (cls.__module__, cls.__name__))
+            print("\t", path, "line", line)
+            traceback.print_exc()
+    if verbose:
+        print("done.\n")