Qt blender addon browser
A add-on browser widget, to quickly open Blender add-on folders in explorer.
To help speed up add-on development.
https://github.com/hannesdelbeke/qt-addon-browser
Features¶
Main priority¶
- no option to right click, open install directory
copy default blender addon manager features¶
- enable disable
- list all addons
- search addons by name
- show explorer path (but no copy)
- docs button
- bug report button
- preferences
- addon install
Stretch goals¶
fix minor issues with the default addon browser
- shows all addons, no option to filter out default addons
- no option to filter by directory
- no easy option to customize.
- search bar doesn’t stay at top when scrolling down
Wishlist¶
- can we have some kind of view mode to see what data is saved to blender cloud?
unsure how this works atm - support display (e.g. as red) even if addon not correctly installed. (e.g. when a folder with files is in the addon folder without a correct python script setup)
- show dependencies
- python libs based on requirements?
- other dependencies, e.g. blend files?
- editable install (point to local addon working repo or project)
Dev notes¶
Wireframe MVP¶
🔎 search _______________ |
|
---|---|
☑️ addon name | 📄 📂 |
☑️ addon name | 📄 📂 |
☑️ addon name | 📄 📂 |
📄docs
📂install folder
search filters addon by name
- a button to open the preferences addon tab
- a search text box
- rows of (1 for each addon)
- a checkbox (addon enable toggle)
- label (addon name)
- button (to open documentation)
- button (to open install folder)
Code reference¶
Code
bpy.utils.user_resource('SCRIPTS', path="addons")
'C:\\Users\\hanne\\AppData\\Roaming\\Blender Foundation\\Blender\\3.2\\scripts\\addons'
bpy.utils.script_path_user()
'C:\\Users\\hanne\\AppData\\Roaming\\Blender Foundation\\Blender\\3.2\\scripts'
script_paths = [Path(x) for x in bpy.utils.script_paths]
['C:\\Program Files\\Blender Foundation\\Blender 3.2\\3.2\\scripts\\modules', 'C:\\Program Files\\Blender Foundation\\Blender 3.2\\3.2\\scripts', 'C:\\Users\\hanne\\AppData\\Roaming\\Blender Foundation\\Blender\\3.2\\scripts', 'C:\\Users\\hanne\\OneDrive\\Documents\\repos\\_Blender\\BlenderTools']
addon_paths = [x / "addons" for x in script_paths if x.exists()]
Code
default addon manager code space_userpref.py, def draw(self, context), line 1836
import os
import addon_utils
layout = self.layout
wm = context.window_manager
prefs = context.preferences
used_ext = {ext.module for ext in prefs.addons}
addon_user_dirs = tuple(
p for p in (
os.path.join(prefs.filepaths.script_directory, "addons"),
bpy.utils.user_resource('SCRIPTS', path="addons"),
)
if p
)
# collect the categories that can be filtered on
addons = [
(mod, addon_utils.module_bl_info(mod))
for mod in addon_utils.modules(refresh=False)
]
split = layout.split(factor=0.6)
row = split.row()
row.prop(wm, "addon_support", expand=True)
row = split.row(align=True)
row.operator("preferences.addon_install", icon='IMPORT', text="Install...")
row.operator("preferences.addon_refresh", icon='FILE_REFRESH', text="Refresh")
row = layout.row()
row.prop(prefs.view, "show_addons_enabled_only")
row.prop(wm, "addon_filter", text="")
row.prop(wm, "addon_search", text="", icon='VIEWZOOM')
col = layout.column()
# set in addon_utils.modules_refresh()
if addon_utils.error_duplicates:
box = col.box()
row = box.row()
row.label(text="Multiple add-ons with the same name found!")
row.label(icon='ERROR')
box.label(text="Delete one of each pair to resolve:")
for (addon_name, addon_file, addon_path) in addon_utils.error_duplicates:
box.separator()
sub_col = box.column(align=True)
sub_col.label(text=addon_name + ":")
sub_col.label(text=" " + addon_file)
sub_col.label(text=" " + addon_path)
if addon_utils.error_encoding:
self.draw_error(
col,
"One or more addons do not have UTF-8 encoding\n"
"(see console for details)",
)
show_enabled_only = prefs.view.show_addons_enabled_only
filter = wm.addon_filter
search = wm.addon_search.lower()
support = wm.addon_support
# initialized on demand
user_addon_paths = []
for mod, info in addons:
module_name = mod.__name__
is_enabled = module_name in used_ext
if info["support"] not in support:
continue
# check if addon should be visible with current filters
is_visible = (
(filter == "All") or
(filter == info["category"]) or
(filter == "User" and (mod.__file__.startswith(addon_user_dirs)))
)
if show_enabled_only:
is_visible = is_visible and is_enabled
if is_visible:
if search and not (
(search in info["name"].lower()) or
(info["author"] and (search in info["author"].lower())) or
((filter == "All") and (search in info["category"].lower()))
):
continue
# Addon UI Code
col_box = col.column()
box = col_box.box()
colsub = box.column()
row = colsub.row(align=True)
row.operator(
"preferences.addon_expand",
icon='DISCLOSURE_TRI_DOWN' if info["show_expanded"] else 'DISCLOSURE_TRI_RIGHT',
emboss=False,
).module = module_name
row.operator(
"preferences.addon_disable" if is_enabled else "preferences.addon_enable",
icon='CHECKBOX_HLT' if is_enabled else 'CHECKBOX_DEHLT', text="",
emboss=False,
).module = module_name
sub = row.row()
sub.active = is_enabled
sub.label(text="%s: %s" % (info["category"], info["name"]))
if info["warning"]:
sub.label(icon='ERROR')
# icon showing support level.
sub.label(icon=self._support_icon_mapping.get(info["support"], 'QUESTION'))
# Expanded UI (only if additional info is available)
if info["show_expanded"]:
if info["description"]:
split = colsub.row().split(factor=0.15)
split.label(text="Description:")
split.label(text=info["description"])
if info["location"]:
split = colsub.row().split(factor=0.15)
split.label(text="Location:")
split.label(text=info["location"])
if mod:
split = colsub.row().split(factor=0.15)
split.label(text="File:")
split.label(text=mod.__file__, translate=False)
if info["author"]:
split = colsub.row().split(factor=0.15)
split.label(text="Author:")
split.label(text=info["author"], translate=False)
if info["version"]:
split = colsub.row().split(factor=0.15)
split.label(text="Version:")
split.label(text=".".join(str(x) for x in info["version"]), translate=False)
if info["warning"]:
split = colsub.row().split(factor=0.15)
split.label(text="Warning:")
split.label(text=" " + info["warning"], icon='ERROR')
user_addon = USERPREF_PT_addons.is_user_addon(mod, user_addon_paths)
tot_row = bool(info["doc_url"]) + bool(user_addon)
if tot_row:
split = colsub.row().split(factor=0.15)
split.label(text="Internet:")
sub = split.row()
if info["doc_url"]:
sub.operator(
"wm.url_open", text="Documentation", icon='HELP',
).url = info["doc_url"]
# Only add "Report a Bug" button if tracker_url is set
# or the add-on is bundled (use official tracker then).
if info.get("tracker_url"):
sub.operator(
"wm.url_open", text="Report a Bug", icon='URL',
).url = info["tracker_url"]
elif not user_addon:
addon_info = (
"Name: %s %s\n"
"Author: %s\n"
) % (info["name"], str(info["version"]), info["author"])
props = sub.operator(
"wm.url_open_preset", text="Report a Bug", icon='URL',
)
props.type = 'BUG_ADDON'
props.id = addon_info
if user_addon:
sub.operator(
"preferences.addon_remove", text="Remove", icon='CANCEL',
).module = mod.__name__
# Show addon user preferences
if is_enabled:
addon_preferences = prefs.addons[module_name].preferences
if addon_preferences is not None:
draw = getattr(addon_preferences, "draw", None)
if draw is not None:
addon_preferences_class = type(addon_preferences)
box_prefs = col_box.box()
box_prefs.label(text="Preferences:")
addon_preferences_class.layout = box_prefs
try:
draw(context)
except:
import traceback
traceback.print_exc()
box_prefs.label(text="Error (see console)", icon='ERROR')
del addon_preferences_class.layout
# Append missing scripts
# First collect scripts that are used but have no script file.
module_names = {mod.__name__ for mod, info in addons}
missing_modules = {ext for ext in used_ext if ext not in module_names}
if missing_modules and filter in {"All", "Enabled"}:
col.column().separator()
col.column().label(text="Missing script files")
module_names = {mod.__name__ for mod, info in addons}
for module_name in sorted(missing_modules):
is_enabled = module_name in used_ext
# Addon UI Code
box = col.column().box()
colsub = box.column()
row = colsub.row(align=True)
row.label(text="", icon='ERROR')
if is_enabled:
row.operator(
"preferences.addon_disable", icon='CHECKBOX_HLT', text="", emboss=False,
).module = module_name
row.label(text=module_name, translate=False)
blender addon extension¶
make a Blender addon to extend the addon preferences.
add a folder button to open the location
File: C:/addon-location
📂
see space_userprefs.py
reference repo that edits the add-on UI
qt blender addon browser
Blender addon
Backlinks¶
- qt blender addon browser
- thumbnails scroll window
- qt blender addon browser no icons but scroll list of addons