[Bf-codereview] Fix for #36387, User Preferences "Addons" panel bogs down the whole interface. (issue 13271043)

lukas.toenne at gmail.com lukas.toenne at gmail.com
Tue Aug 27 09:49:39 CEST 2013


Reviewers: bf-codereview_blender.org, ideasman42,

Description:
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 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.

In order to ensure a valid list of addon modules in most cases when
script paths are changed or addons get installed manually the used
preferences now have a "scan_addons" flag which forces a one-time rescan
of the addon modules. Note that this does not cover cases where addons
get added or deleted manually outside Blender, for such cases there is
now also an explicit "Scan" operator button in the addons UI.

Please review this at https://codereview.appspot.com/13271043/

Affected files:
   intern/tools/bpy_introspect_ui.py
   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
   source/blender/blenkernel/intern/blender.c
   source/blender/editors/interface/resources.c
   source/blender/makesdna/DNA_userdef_types.h
   source/blender/makesrna/intern/rna_userdef.c


Index: intern/tools/bpy_introspect_ui.py
===================================================================
--- intern/tools/bpy_introspect_ui.py	(revision 59430)
+++ intern/tools/bpy_introspect_ui.py	(working copy)
@@ -310,7 +310,8 @@
      bpy_extras.keyconfig_utils.keyconfig_merge = lambda a, b: ()

      addon_utils = module_add("addon_utils")
-    addon_utils.modules = lambda f: ()
+    addon_utils.modules = lambda f: []
+    addon_utils.scan_modules = lambda f: None
      addon_utils.module_bl_info = lambda f: None
      addon_utils.addons_fake_modules = {}
      addon_utils.error_duplicates = ()
Index: release/scripts/modules/addon_utils.py
===================================================================
--- release/scripts/modules/addon_utils.py	(revision 59430)
+++ release/scripts/modules/addon_utils.py	(working copy)
@@ -51,7 +51,7 @@
      return addon_paths


-def modules(module_cache):
+def scan_modules(module_cache=addons_fake_modules):
      global error_duplicates
      global error_encoding
      import os
@@ -184,6 +184,11 @@
          del module_cache[mod_stale]
      del modules_stale

+
+def modules(module_cache=addons_fake_modules, scan=True):
+    if scan:
+        scan_modules(module_cache)
+
      mod_list = list(module_cache.values())
      mod_list.sort(key=lambda mod: (mod.bl_info["category"],
                                     mod.bl_info["name"],
Index: release/scripts/modules/bl_i18n_utils/utils.py
===================================================================
--- release/scripts/modules/bl_i18n_utils/utils.py	(revision 59430)
+++ release/scripts/modules/bl_i18n_utils/utils.py	(working copy)
@@ -172,7 +172,7 @@
      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: release/scripts/startup/bl_operators/wm.py
===================================================================
--- release/scripts/startup/bl_operators/wm.py	(revision 59430)
+++ release/scripts/startup/bl_operators/wm.py	(working copy)
@@ -1693,6 +1693,19 @@
          return {'RUNNING_MODAL'}


+class WM_OT_addon_scan(Operator):
+    "Scan addon directories for new modules"
+    bl_idname = "wm.addon_rescan"
+    bl_label = "Scan"
+
+    def execute(self, context):
+        import addon_utils
+
+        addon_utils.scan_modules()
+
+        return {'FINISHED'}
+
+
  class WM_OT_addon_install(Operator):
      "Install an addon"
      bl_idname = "wm.addon_install"
@@ -1782,7 +1795,7 @@
          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 @@
                  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 @@

          # 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(scan=False):
              if mod.__name__ in addons_new:
                  info = addon_utils.module_bl_info(mod)

@@ -1875,7 +1888,7 @@
          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: release/scripts/startup/bl_ui/__init__.py
===================================================================
--- release/scripts/startup/bl_ui/__init__.py	(revision 59430)
+++ release/scripts/startup/bl_ui/__init__.py	(working copy)
@@ -105,7 +105,9 @@

          items_unique = set()

-        for mod in addon_utils.modules(addon_utils.addons_fake_modules):
+        addons =  
addon_utils.modules(scan=context.user_preferences.scan_addons)
+        context.user_preferences.scan_addons = False
+        for mod in addons:
              info = addon_utils.module_bl_info(mod)
              items_unique.add(info["category"])

Index: release/scripts/startup/bl_ui/space_userpref.py
===================================================================
--- release/scripts/startup/bl_ui/space_userpref.py	(revision 59430)
+++ release/scripts/startup/bl_ui/space_userpref.py	(working copy)
@@ -1142,11 +1142,13 @@
          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(scan=userpref.scan_addons)]
+        userpref.scan_addons = False

          split = layout.split(percentage=0.2)
          col = split.column()
          col.prop(context.window_manager, "addon_search", text="",  
icon='VIEWZOOM')
+        col.operator("wm.addon_rescan", icon='FILE_REFRESH')

          col.label(text="Supported Level")
          col.prop(context.window_manager, "addon_support", expand=True)
@@ -1156,7 +1158,7 @@

          col = split.column()

-        # set in addon_utils.modules(...)
+        # set in addon_utils.scan_modules()
          if addon_utils.error_duplicates:
              self.draw_error(col,
                              "Multiple addons using the same name found!\n"
Index: source/blender/blenkernel/intern/blender.c
===================================================================
--- source/blender/blenkernel/intern/blender.c	(revision 59430)
+++ source/blender/blenkernel/intern/blender.c	(working copy)
@@ -136,6 +136,7 @@
  	memset(&G, 0, sizeof(Global));
  	
  	U.savetime = 1;
+	U.scan_addons = 1;

  	G.main = MEM_callocN(sizeof(Main), "initglobals");

Index: source/blender/editors/interface/resources.c
===================================================================
--- source/blender/editors/interface/resources.c	(revision 59430)
+++ source/blender/editors/interface/resources.c	(working copy)
@@ -1387,6 +1387,9 @@
  		U.savetime = 1;
  // XXX		error(STRINGIFY(BLENDER_STARTUP_FILE)" is buggy, please consider  
removing it.\n");
  	}
+	if (U.scan_addons <= 0) {
+		U.scan_addons = 1;
+	}
  	/* transform widget settings */
  	if (U.tw_hotspot == 0) {
  		U.tw_hotspot = 14;
Index: source/blender/makesdna/DNA_userdef_types.h
===================================================================
--- source/blender/makesdna/DNA_userdef_types.h	(revision 59430)
+++ source/blender/makesdna/DNA_userdef_types.h	(working copy)
@@ -482,6 +482,9 @@
  	
  	float fcu_inactive_alpha;	/* opacity of inactive F-Curves in F-Curve  
Editor */
  	float pixelsize;			/* private, set by GHOST, to multiply DPI with */
+
+	short scan_addons;
+	short pad4;
  } UserDef;

  extern UserDef U; /* from blenkernel blender.c */
Index: source/blender/makesrna/intern/rna_userdef.c
===================================================================
--- source/blender/makesrna/intern/rna_userdef.c	(revision 59430)
+++ source/blender/makesrna/intern/rna_userdef.c	(working copy)
@@ -375,6 +375,11 @@
  	RNA_POINTER_INVALIDATE(path_cmp_ptr);
  }

+static void rna_userdef_script_directory_update(Main *UNUSED(bmain), Scene  
*UNUSED(scene), PointerRNA *UNUSED(ptr))
+{
+	U.scan_addons = true;
+}
+
  static void rna_userdef_temp_update(Main *UNUSED(bmain), Scene  
*UNUSED(scene), PointerRNA *UNUSED(ptr))
  {
  	BLI_init_temporary_dir(U.tempdir);
@@ -3973,6 +3978,7 @@
  	                         "Alternate script path, matching the default  
layout with subdirs: "
  	                         "startup, addons & modules (requires restart)");
  	/* TODO, editing should reset sys.path! */
+	RNA_def_property_update(prop, 0, "rna_userdef_script_directory_update");

  	prop = RNA_def_property(srna, "i18n_branches_directory", PROP_STRING,  
PROP_DIRPATH);
  	RNA_def_property_string_sdna(prop, NULL, "i18ndir");
@@ -4138,6 +4144,11 @@
  	RNA_def_property_ui_text(prop, "Addon", "");
  	rna_def_userdef_addon_collection(brna, prop);

+	prop = RNA_def_property(srna, "scan_addons", PROP_BOOLEAN, PROP_NONE);
+	RNA_def_property_boolean_sdna(prop, NULL, "scan_addons", 1);
+	RNA_def_property_ui_text(prop, "Scan Addons", "Internal flag for  
rescanning addon modules");
+	RNA_def_property_flag(prop, PROP_HIDDEN);
+
  	prop = RNA_def_property(srna, "autoexec_paths", PROP_COLLECTION,  
PROP_NONE);
  	RNA_def_property_collection_sdna(prop, NULL, "autoexec_paths", NULL);
  	RNA_def_property_struct_type(prop, "PathCompare");




More information about the Bf-codereview mailing list