Fix for #36387, User Preferences "Addons" panel bogs down the whole interface.
authorLukas Toenne <lukas.toenne@googlemail.com>
Wed, 28 Aug 2013 06:36:54 +0000 (06:36 +0000)
committerLukas Toenne <lukas.toenne@googlemail.com>
Wed, 28 Aug 2013 06:36:54 +0000 (06:36 +0000)
The addons panel draw function calls addon_utils.modules() which in turn retrieves a list of fake modules from the script paths every time. This can become costly when network paths are included for
addons. Solution is to put the scanning process into a dedicated "refresh" function and disable it in frequently called draw and filter functions, i.e. in these cases the cached addons_fake_modules list
will be used instead.

Note that this may lead to invalid addon lists if script paths are changed (which is not working 100% without restart anyway according to Campbell). For this there is now a "Refresh" operator button in
the addons preferences. If necessary and feasible such forced refreshes can be added later too.

release/scripts/modules/addon_utils.py
release/scripts/modules/bl_i18n_utils/utils.py
release/scripts/startup/bl_operators/wm.py
release/scripts/startup/bl_ui/__init__.py
release/scripts/startup/bl_ui/space_userpref.py

index 3aa4eef63928d1486cea8ca7282c06bd3e6da3b4..2ac7d4c85aa94fcb107038e97c6f4e1a01509cae 100644 (file)
@@ -35,7 +35,6 @@ error_duplicates = False
 error_encoding = False
 addons_fake_modules = {}
 
-
 def paths():
     # RELEASE SCRIPTS: official scripts distributed in Blender releases
     addon_paths = _bpy.utils.script_paths("addons")
@@ -51,7 +50,7 @@ def paths():
     return addon_paths
 
 
-def modules(module_cache):
+def modules_refresh(module_cache=addons_fake_modules):
     global error_duplicates
     global error_encoding
     import os
@@ -184,6 +183,11 @@ def modules(module_cache):
         del module_cache[mod_stale]
     del modules_stale
 
+
+def modules(module_cache=addons_fake_modules, refresh=True):
+    if refresh:
+        modules_refresh(module_cache)
+
     mod_list = list(module_cache.values())
     mod_list.sort(key=lambda mod: (mod.bl_info["category"],
                                    mod.bl_info["name"],
@@ -370,6 +374,9 @@ def reset_all(reload_scripts=False):
     """
     import sys
 
+    # initializes addons_fake_modules
+    modules_refresh()
+
     # RELEASE SCRIPTS: official scripts distributed in Blender releases
     paths_list = paths()
 
index 636e9a4a148f077f7843651f06f0e2dd7ac3375a..1b6d65d89d05939e176f7a5484135d278c6038aa 100644 (file)
@@ -172,7 +172,7 @@ def enable_addons(addons={}, support={}, disable=False, check_only=False):
     userpref = bpy.context.user_preferences
     used_ext = {ext.module for ext in userpref.addons}
 
-    ret = [mod for mod in addon_utils.modules(addon_utils.addons_fake_modules)
+    ret = [mod for mod in addon_utils.modules()
                if ((addons and mod.__name__ in addons) or
                    (not addons and addon_utils.module_bl_info(mod)["support"] in support))]
 
index 5095bfdd9d26a398267281c464d65c81962c2f10..c575b6dfdafe86ad805f674831670314cf8f4cc0 100644 (file)
@@ -1693,6 +1693,19 @@ class WM_OT_theme_install(Operator):
         return {'RUNNING_MODAL'}
 
 
+class WM_OT_addon_refresh(Operator):
+    "Scan addon directories for new modules"
+    bl_idname = "wm.addon_refresh"
+    bl_label = "Refresh"
+
+    def execute(self, context):
+        import addon_utils
+        
+        addon_utils.modules_refresh()
+        
+        return {'FINISHED'}
+
+
 class WM_OT_addon_install(Operator):
     "Install an addon"
     bl_idname = "wm.addon_install"
@@ -1782,7 +1795,7 @@ class WM_OT_addon_install(Operator):
         del pyfile_dir
         # done checking for exceptional case
 
-        addons_old = {mod.__name__ for mod in addon_utils.modules(addon_utils.addons_fake_modules)}
+        addons_old = {mod.__name__ for mod in addon_utils.modules()}
 
         #check to see if the file is in compressed format (.zip)
         if zipfile.is_zipfile(pyfile):
@@ -1825,7 +1838,7 @@ class WM_OT_addon_install(Operator):
                 traceback.print_exc()
                 return {'CANCELLED'}
 
-        addons_new = {mod.__name__ for mod in addon_utils.modules(addon_utils.addons_fake_modules)} - addons_old
+        addons_new = {mod.__name__ for mod in addon_utils.modules()} - addons_old
         addons_new.discard("modules")
 
         # disable any addons we may have enabled previously and removed.
@@ -1835,7 +1848,7 @@ class WM_OT_addon_install(Operator):
 
         # possible the zip contains multiple addons, we could disallow this
         # but for now just use the first
-        for mod in addon_utils.modules(addon_utils.addons_fake_modules):
+        for mod in addon_utils.modules(refresh=False):
             if mod.__name__ in addons_new:
                 info = addon_utils.module_bl_info(mod)
 
@@ -1875,7 +1888,7 @@ class WM_OT_addon_remove(Operator):
         import os
         import addon_utils
 
-        for mod in addon_utils.modules(addon_utils.addons_fake_modules):
+        for mod in addon_utils.modules():
             if mod.__name__ == module:
                 filepath = mod.__file__
                 if os.path.exists(filepath):
index b9f2e8406c6163ceba5931ae622badf5cfdb9612..df6247f65f57c8049ae78581c1e430bb1481ac83 100644 (file)
@@ -105,7 +105,7 @@ def register():
 
         items_unique = set()
 
-        for mod in addon_utils.modules(addon_utils.addons_fake_modules):
+        for mod in addon_utils.modules(refresh=False):
             info = addon_utils.module_bl_info(mod)
             items_unique.add(info["category"])
 
index c2825d0641e9fe622dfcd51bfa8b3543ffb0d6cf..0418434c92b2ba49ca417129efec34df33a586e1 100644 (file)
@@ -1142,11 +1142,12 @@ class USERPREF_PT_addons(Panel):
         scripts_addons_folder = bpy.utils.user_resource('SCRIPTS', "addons")
 
         # collect the categories that can be filtered on
-        addons = [(mod, addon_utils.module_bl_info(mod)) for mod in addon_utils.modules(addon_utils.addons_fake_modules)]
+        addons = [(mod, addon_utils.module_bl_info(mod)) for mod in addon_utils.modules(refresh=False)]
 
         split = layout.split(percentage=0.2)
         col = split.column()
         col.prop(context.window_manager, "addon_search", text="", icon='VIEWZOOM')
+        col.operator("wm.addon_refresh", icon='FILE_REFRESH')
 
         col.label(text="Supported Level")
         col.prop(context.window_manager, "addon_support", expand=True)
@@ -1156,7 +1157,7 @@ class USERPREF_PT_addons(Panel):
 
         col = split.column()
 
-        # set in addon_utils.modules(...)
+        # set in addon_utils.modules_refresh()
         if addon_utils.error_duplicates:
             self.draw_error(col,
                             "Multiple addons using the same name found!\n"