diff --git a/scripts/addons_core/bl_pkg/bl_extension_ui.py b/scripts/addons_core/bl_pkg/bl_extension_ui.py index 274a8e03ebb..f7d12698ee1 100644 --- a/scripts/addons_core/bl_pkg/bl_extension_ui.py +++ b/scripts/addons_core/bl_pkg/bl_extension_ui.py @@ -405,14 +405,14 @@ def addons_panel_draw_items( layout, # `bpy.types.UILayout` context, # `bpy.types.Context` *, - addon_modules, # `Dict[str, ModuleType]` + addon_modules, # `Iterable[ModuleType]` used_addon_module_name_map, # `Dict[str, bpy.types.Addon]` search_casefold, # `str` addon_tags_exclude, # `Set[str]` enabled_only, # `bool` addon_extension_manifest_map, # `Dict[str, PkgManifest_Normalized]` show_development, # `bool` -): +): # `-> Set[str]` # NOTE: this duplicates logic from `USERPREF_PT_addons` eventually this logic should be used instead. # Don't de-duplicate the logic as this is a temporary state - as long as extensions remains experimental. import addon_utils @@ -420,11 +420,15 @@ def addons_panel_draw_items( pkg_info_check_exclude_filter_ex, ) + # Build a set of module names (used to calculate missing modules). + module_names = set() + # Initialized on demand. user_addon_paths = [] for mod in addon_modules: - module_name = mod.__name__ + module_names.add(module_name := mod.__name__) + is_enabled = module_name in used_addon_module_name_map if enabled_only and (not is_enabled): continue @@ -434,8 +438,9 @@ def addons_panel_draw_items( show_expanded = bl_info["show_expanded"] if is_extension: - del bl_info if (item_local := addon_extension_manifest_map.get(module_name)) is not None: + del bl_info + item_name = item_local.name item_description = item_local.tagline item_tags = item_local.tags @@ -449,7 +454,10 @@ def addons_panel_draw_items( if USE_ADDON_IGNORE_EXTENSION_MANIFEST_HACK: item_doc_url = addon_ignore_manifest_website_hack_remote_or_default(module_name, item_doc_url) else: - item_name = module_name + # Show the name because this is used for sorting. + item_name = bl_info.get("name") or module_name + del bl_info + item_description = "" item_tags = () item_warning_legacy = "Unable to parse the manifest" @@ -550,6 +558,7 @@ def addons_panel_draw_items( if (addon_preferences := used_addon_module_name_map[module_name].preferences) is not None: box.separator(type='LINE') USERPREF_PT_addons.draw_addon_preferences(box, context, addon_preferences) + return module_names def addons_panel_draw_impl( @@ -611,13 +620,12 @@ def addons_panel_draw_impl( module_name = repo_module_prefix + pkg_id addon_extension_manifest_map[module_name] = item_local - addon_modules = addon_utils.modules(refresh=False) used_addon_module_name_map = {addon.module: addon for addon in prefs.addons} - addons_panel_draw_items( + module_names = addons_panel_draw_items( layout, context, - addon_modules=addon_modules, + addon_modules=addon_utils.modules(refresh=False), used_addon_module_name_map=used_addon_module_name_map, search_casefold=search_casefold, addon_tags_exclude=addon_tags_exclude, @@ -626,9 +634,7 @@ def addons_panel_draw_impl( show_development=show_development, ) - # Append missing scripts - # First collect scripts that are used but have no script file. - module_names = {mod.__name__ for mod in addon_modules} + # Append missing scripts. missing_modules = { addon_module_name for addon_module_name in used_addon_module_name_map if addon_module_name not in module_names @@ -2007,8 +2013,7 @@ def tags_current(wm, tags_attr): if tags_attr == "addon_tags": # Legacy add-on categories as tags. import addon_utils - addon_modules = addon_utils.modules(refresh=False) - for mod in addon_modules: + for mod in addon_utils.modules(refresh=False): module_name = mod.__name__ is_extension = addon_utils.check_extension(module_name) if is_extension: diff --git a/scripts/modules/addon_utils.py b/scripts/modules/addon_utils.py index 5d09ab8e317..d1c763d9fb5 100644 --- a/scripts/modules/addon_utils.py +++ b/scripts/modules/addon_utils.py @@ -219,14 +219,16 @@ def modules(*, module_cache=addons_fake_modules, refresh=True): modules_refresh(module_cache=module_cache) modules._is_first = False - mod_list = list(module_cache.values()) - mod_list.sort( - key=lambda mod: ( - mod.bl_info.get("category", ""), - mod.bl_info.get("name", ""), - ) - ) - return mod_list + # Dictionaries are ordered in more recent versions of Python, + # re-create the dictionary from sorted items. + # This avoids having to sort on every call to this function. + module_cache_items = list(module_cache.items()) + # Sort by name with the module name as a tie breaker. + module_cache_items.sort(key=lambda item: ((item[1].bl_info.get("name") or item[0]).casefold(), item[0])) + module_cache.clear() + module_cache.update((key, value) for key, value in module_cache_items) + + return module_cache.values() modules._is_first = True diff --git a/scripts/startup/bl_ui/space_userpref.py b/scripts/startup/bl_ui/space_userpref.py index d84bdc05f9d..6725d632db1 100644 --- a/scripts/startup/bl_ui/space_userpref.py +++ b/scripts/startup/bl_ui/space_userpref.py @@ -2453,9 +2453,6 @@ class USERPREF_PT_addons(AddOnPanel, Panel): if p ) - # Collect the categories that can be filtered on. - addon_modules = addon_utils.modules(refresh=False) - self._draw_addon_header(layout, prefs, wm) layout_topmost = layout.column() @@ -2488,12 +2485,14 @@ class USERPREF_PT_addons(AddOnPanel, Panel): search = wm.addon_search.casefold() support = wm.addon_support + module_names = set() + # initialized on demand user_addon_paths = [] - for mod in addon_modules: + for mod in addon_utils.modules(refresh=False): + module_names.add(addon_module_name := mod.__name__) bl_info = addon_utils.module_bl_info(mod) - addon_module_name = mod.__name__ is_enabled = addon_module_name in used_addon_module_name_map @@ -2628,7 +2627,6 @@ class USERPREF_PT_addons(AddOnPanel, Panel): layout_topmost.column().separator() layout_topmost.column().label(text="Missing script files") - module_names = {mod.__name__ for mod in addon_modules} for addon_module_name in sorted(missing_modules): is_enabled = addon_module_name in used_addon_module_name_map # Addon UI Code diff --git a/tests/python/bl_load_addons.py b/tests/python/bl_load_addons.py index f88996f267a..f804d4e7c42 100644 --- a/tests/python/bl_load_addons.py +++ b/tests/python/bl_load_addons.py @@ -37,9 +37,8 @@ def _init_addon_exclusions(): def addon_modules_sorted(): # Pass in an empty module cache to prevent `addon_utils` local module cache being manipulated. - modules = addon_utils.modules(module_cache={}) - modules[:] = [ - mod for mod in modules + modules = [ + mod for mod in addon_utils.modules(module_cache={}) if not (mod.__file__.startswith(EXCLUDE_DIRECTORIES)) if not (mod.__name__ in EXCLUDE_ADDONS) ]