[Bf-blender-cvs] SVN commit: /data/svn/bf-blender [31901] trunk/blender: bugfix [#23001] Addons do not unregister properly in Blender 2.5.3

Campbell Barton ideasman42 at gmail.com
Mon Sep 13 06:52:57 CEST 2010


Revision: 31901
          http://projects.blender.org/plugins/scmsvn/viewcvs.php?view=rev&root=bf-blender&revision=31901
Author:   campbellbarton
Date:     2010-09-13 06:52:56 +0200 (Mon, 13 Sep 2010)

Log Message:
-----------
bugfix [#23001] Addons do not unregister properly in Blender 2.5.3

Now reloading the user defaults also unloads/loads addons, resetting the state to the one set in the user preferences.

moved addon functions into bpy.utils
- bpy.utils.addon_enable(name, default_set=True)
- bpy.utils.addon_disable(name, default_set=True)
- bpy.utils.addon_reset_all(name, default_set=True)

the user preference operators now just wrap these.

Modified Paths:
--------------
    trunk/blender/release/scripts/modules/bpy/utils.py
    trunk/blender/release/scripts/ui/space_userpref.py
    trunk/blender/source/blender/windowmanager/intern/wm_files.c

Modified: trunk/blender/release/scripts/modules/bpy/utils.py
===================================================================
--- trunk/blender/release/scripts/modules/bpy/utils.py	2010-09-13 04:52:20 UTC (rev 31900)
+++ trunk/blender/release/scripts/modules/bpy/utils.py	2010-09-13 04:52:56 UTC (rev 31901)
@@ -54,6 +54,11 @@
     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.
@@ -108,6 +113,12 @@
         _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)
+
     def register_module_call(mod):
         _bpy_types._register_module(mod.__name__)
         register = getattr(mod, "register", None)
@@ -128,10 +139,6 @@
             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):
         # reloading this causes internal errors
         # because the classes from this module are stored internally
@@ -178,7 +185,7 @@
         for path_subdir in ("", "ui", "op", "io", "cfg", "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 +197,10 @@
                 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)
+    _bpy_types._register_immediate = True
 
-    for module_name in sorted(used_ext):
-        mod = _test_import(module_name, loaded_modules)
-        test_register(mod)
+    # deal with addons seperately
+    addon_reset_all()
 
     if reload_scripts:
         import gc
@@ -207,9 +209,7 @@
     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)
 _scripts = (_os.path.normpath(_scripts), )
@@ -261,19 +261,19 @@
 
 
 def preset_paths(subdir):
-    '''
+    """
     Returns a list of paths for a specific preset.
-    '''
+    """
 
     return (_os.path.join(_presets, subdir), )
 
 
 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 +301,11 @@
 
 
 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 +314,161 @@
         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__")
+
+    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
+
+
+    _bpy_types._register_immediate = False
+
+    def handle_error():
+        import traceback
+        traceback.print_exc()
+        _bpy_types._register_immediate = True
+
+
+    # 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:
+                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
+    try:
+        _bpy_types._register_module(module_name)
+    except:
+        handle_error()
+        del sys.modules[module_name]
+        return None
+
+    # 3) try run the modules register function
+    try:
+        mod.register()
+    except:
+        handle_error()
+        _bpy_types._unregister_module(module_name)
+        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
+    
+    _bpy_types._register_immediate = True
+
+    mod.__addon_enabled__ = True
+
+    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)
+
+    if mod is None:
+        print("addon_disable", module_name, "not loaded, nothing to do")
+        return
+
+    mod.__addon_enabled__ = False
+
+    try:
+        _bpy_types._unregister_module(module_name, free=False)  # dont free because we may want to enable again.
+        mod.unregister()
+    except:
+        traceback.print_exc()
+
+    # 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)
+    
+    print("\tbpy.utils.addon_disable", module_name)
+
+
+def addon_reset_all():
+    """
+    Sets the addon state based on the user preferences.
+    """
+
+    paths = script_paths("addons") + script_paths("addons_contrib")
+
+    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)
+            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)

Modified: trunk/blender/release/scripts/ui/space_userpref.py
===================================================================
--- trunk/blender/release/scripts/ui/space_userpref.py	2010-09-13 04:52:20 UTC (rev 31900)
+++ trunk/blender/release/scripts/ui/space_userpref.py	2010-09-13 04:52:56 UTC (rev 31901)
@@ -1049,81 +1049,19 @@
     module = StringProperty(name="Module", description="Module name of the addon to enable")
 
     def execute(self, context):
-        module_name = self.module
+        mod = bpy.utils.addon_enable(self.module)
 
-        # note, this still gets added to _bpy_types.TypeMap
-
-        import sys
-        import bpy_types as _bpy_types
-
-
-        _bpy_types._register_immediate = False
-
-        def handle_error():
-            import traceback
-            traceback.print_exc()
-            _bpy_types._register_immediate = True
-
-
-        # reload if the mtime changes
-        mod = sys.modules.get(module_name)
         if mod:
-            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...")
+            # check if add-on is written for current blender version, or raise a warning
+            info = addon_info_get(mod)
 
-                try:
-                    reload(mod)
-                except:
-                    handle_error()
-                    del sys.modules[module_name]
-                    return {'CANCELLED'}
-
-        # 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__)
-        except:
-            handle_error()
+            if info.get("blender", (0, 0, 0)) > bpy.app.version:
+                self.report("WARNING','This script was written for a newer version of Blender and might not function (correctly).\nThe script is enabled though.")
+            return {'FINISHED'}
+        else:
             return {'CANCELLED'}
 
-        # 2) try register collected modules
-        try:
-            _bpy_types._register_module(module_name)
-        except:
-            handle_error()

@@ Diff output truncated at 10240 characters. @@




More information about the Bf-blender-cvs mailing list