Extensions: support replacing missing legacy add-ons with extensions

This groups all the add-ons from 4.1 which were enabled but are no
longer distributed with Blender and provides an easy way for users to
install them. This panel is collapsed by default.

Ref !122727

Co-authored-by: Dalai Felinto <dalai@blender.org>
This commit is contained in:
Campbell Barton
2024-06-06 14:40:13 +10:00
parent 949dfbfaa8
commit 2533ff39f9
3 changed files with 252 additions and 0 deletions

View File

@@ -1901,6 +1901,14 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn):
# Only used for code-path for dropping an extension.
url: rna_prop_url
# NOTE: this can be removed once upgrading from 4.1 is no longer relevant.
# Only used when moving from previously built-in add-ons to extensions.
do_legacy_replace: BoolProperty(
name="Do Legacy Replace",
default=False,
options={'HIDDEN', 'SKIP_SAVE'}
)
@classmethod
def poll(cls, context):
if not bpy.app.online_access:
@@ -2013,6 +2021,10 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn):
_preferences_ui_redraw()
_preferences_ui_refresh_addons()
# NOTE: this can be removed once upgrading from 4.1 is no longer relevant.
if self.do_legacy_replace and (not canceled):
self._do_legacy_replace(self.pkg_id, pkg_manifest_local)
def invoke(self, context, event):
# Only for drop logic!
if self.properties.is_property_set("url"):
@@ -2080,6 +2092,30 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn):
layout.prop(self, "enable_on_install", text=rna_prop_enable_on_install_type_map[item_remote["type"]])
@staticmethod
def _do_legacy_replace(pkg_id, pkg_manifest_local):
# Disables and add-on that was replaced by an extension,
# use for upgrading 4.1 preferences or older.
# Ensure the local meta-data exists, else there may have been a problem installing,
# note that this does *not* check if the add-on could be enabled which is intentional.
# It's only important the add-on installs to justify disabling the old add-on.
# Note that there is no need to report if this was not found as failing to install will
# already have reported.
if not pkg_manifest_local.get(pkg_id):
return
from .bl_extension_ui import extensions_map_from_legacy_addons_reverse_lookup
addon_module_name = extensions_map_from_legacy_addons_reverse_lookup(pkg_id)
if not addon_module_name:
# This shouldn't happen unless someone goes out of there way
# to enable `do_legacy_replace` for a non-legacy extension.
# Use a print here as it's such a corner case and harmless.
print("Internal error, legacy lookup failed:", addon_module_name)
return
bpy.ops.preferences.addon_disable(module=addon_module_name)
class EXTENSIONS_OT_package_uninstall(Operator, _ExtCmdMixIn):
bl_idname = "extensions.package_uninstall"

View File

@@ -386,6 +386,103 @@ def extensions_panel_draw_online_extensions_request_impl(
row.operator("extensions.userpref_allow_online", text="Allow Online Access", icon='CHECKMARK')
# NOTE: this can be removed once upgrading from 4.1 is no longer relevant.
def extensions_map_from_legacy_addons_ensure():
import os
global extensions_map_from_legacy_addons
global extensions_map_from_legacy_addons_url
if extensions_map_from_legacy_addons is not None:
return
filepath = os.path.join(os.path.dirname(__file__), "extensions_map_from_legacy_addons.py")
with open(filepath, "rb") as fh:
data = eval(compile(fh.read(), filepath, "eval"), {})
extensions_map_from_legacy_addons = data["extensions"]
extensions_map_from_legacy_addons_url = data["remote_url"]
def extensions_map_from_legacy_addons_reverse_lookup(pkg_id):
# Return the old name from the package ID.
extensions_map_from_legacy_addons_ensure()
for key_addon_module_name, (value_pkg_id, _) in extensions_map_from_legacy_addons.items():
if pkg_id == value_pkg_id:
return key_addon_module_name
return ""
extensions_map_from_legacy_addons = None
extensions_map_from_legacy_addons_url = None
# NOTE: this can be removed once upgrading from 4.1 is no longer relevant.
def extensions_panel_draw_missing_with_extension_impl(
*,
context,
layout,
missing_modules,
):
layout_header, layout_panel = layout.panel("builtin_addons", default_closed=True)
layout_header.label(text="Missing Built-in Add-ons", icon='ERROR')
if layout_panel is None:
return
prefs = context.preferences
extensions_map_from_legacy_addons_ensure()
repo_index = -1
repo = None
for repo_test_index, repo_test in enumerate(prefs.extensions.repos):
if (
repo_test.use_remote_url and
(repo_test.remote_url.rstrip("/") == extensions_map_from_legacy_addons_url)
):
repo_index = repo_test_index
repo = repo_test
break
box = layout_panel.box()
box.label(text="Add-ons previously shipped with Blender are now available from extensions.blender.org.")
can_install = True
if repo is None:
# Most likely the user manually removed this.
box.label(text="Blender's extension repository not found!", icon='ERROR')
elif not repo.enabled:
box.label(text="Blender's extension repository must be enabled to install extensions!", icon='ERROR')
repo_index = -1
del repo
for addon_module_name in sorted(missing_modules):
addon_pkg_id, addon_name = extensions_map_from_legacy_addons[addon_module_name]
boxsub = box.column().box()
colsub = boxsub.column()
row = colsub.row()
row_left = row.row()
row_left.alignment = 'LEFT'
row_left.label(text=addon_name, translate=False)
row_right = row.row()
row_right.alignment = 'RIGHT'
if repo_index != -1:
# NOTE: it's possible this extension is already installed.
# the user could have installed it manually, then opened this popup.
# This is enough of a corner case that it's not especially worth detecting
# and communicating this particular state of affairs to the user.
# Worst case, they install and it will re-install an already installed extension.
props = row_right.operator("extensions.package_install", text="Install")
props.repo_index = repo_index
props.pkg_id = addon_pkg_id
props.do_legacy_replace = True
del props
row_right.operator("preferences.addon_disable", text="", icon="X", emboss=False).module = addon_module_name
def extensions_panel_draw_missing_impl(
*,
layout,
@@ -792,6 +889,30 @@ def extensions_panel_draw_impl(
if addon_module_name not in module_names
}
# NOTE: this can be removed once upgrading from 4.1 is no longer relevant.
if missing_modules:
# Split the missing modules into two groups, ones which can be upgraded and ones that can't.
extensions_map_from_legacy_addons_ensure()
missing_modules_with_extension = set()
missing_modules_without_extension = set()
for addon_module_name in missing_modules:
if addon_module_name in extensions_map_from_legacy_addons:
missing_modules_with_extension.add(addon_module_name)
else:
missing_modules_without_extension.add(addon_module_name)
if missing_modules_with_extension:
extensions_panel_draw_missing_with_extension_impl(
context=context,
layout=layout_topmost,
missing_modules=missing_modules_with_extension,
)
# Pretend none of these shenanigans ever occurred (to simplify removal).
missing_modules = missing_modules_without_extension
# End code-path for 4.1x migration.
if missing_modules:
extensions_panel_draw_missing_impl(
layout=layout_topmost,

View File

@@ -0,0 +1,95 @@
# This is a data file that is evaluated directly (not imported).
# NOTE: this can be removed once upgrading from 4.1 is no longer relevant.
{
"remote_url": "https://extensions.blender.org/api/v1/extensions",
"extensions": {
# The first value is the extension ID, the second is it's name.
"add_camera_rigs": ("add_camera_rigs", "Add Camera Rigs"),
"add_curve_extra_objects": ("extra_curve_objectes", "Extra Curve Objectes"),
"add_curve_ivygen": ("ivygen", "IvyGen"),
"add_curve_sapling": ("sapling_tree_gen", "Sapling Tree Gen"),
"add_mesh_BoltFactory": ("boltfactory", "BoltFactory"),
"add_mesh_discombobulator": ("discombobulator", "Discombobulator"),
"add_mesh_extra_objects": ("extra_mesh_objects", "Extra Mesh Objects"),
"add_mesh_geodesic_domes": ("geodesic_domes", "Geodesic Domes"),
"amaranth": ("amaranth", "Amaranth Toolset"),
"animation_add_corrective_shape_key": ("corrective_shape_keys", "Corrective Shape Keys"),
"animation_animall": ("animall", "AnimAll"),
"ant_landscape": ("antlandscape", "A.N.T.Landscape"),
"archimesh": ("archimesh", "Archimesh"),
"blender_id": ("blender_id_authentication", "Blender ID authentication"),
"bone_selection_sets": ("bone_selection_sets", "Bone Selection Sets"),
"btrace": ("btracer", "BTracer"),
"camera_turnaround": ("turnaround_camera", "Turnaround Camera"),
"curve_assign_shapekey": ("assign_shape_keys", "Assign Shape Keys"),
"curve_simplify": ("simplify_curves_plus", "Simplify Curves+"),
"curve_tools": ("curve_tools", "Curve Tools"),
"development_edit_operator": ("edit_operator_source", "Edit Operator Source"),
"development_icon_get": ("icon_viewer", "Icon Viewer"),
"development_iskeyfree": ("is_key_free", "Is key Free"),
"greasepencil_tools": ("grease_pencil_tools", "Grease Pencil Tools"),
"io_anim_camera": ("export_camera_animation", "Export Camera Animation"),
"io_anim_nuke_chan": ("nuke_animation_format_chan", "Nuke Animation Format (.chan)"),
"io_coat3D": ("coat_applink", "3D-Coat Applink"),
"io_export_dxf": ("export_autocad_dxf_format_dxf", "Export Autocad DXF Format (.dxf)"),
"io_export_paper_model": ("export_paper_model", "Export Paper Model"),
"io_export_pc2": ("export_pointcache_formatpc2", "Export Pointcache Format(.pc2)"),
"io_import_BrushSet": ("import_brushset", "Import BrushSet"),
"io_import_dxf": ("import_autocad_dxf_format_dxf", "Import AutoCAD DXF Format (.dxf)"),
"io_import_palette": ("import_palettes", "Import Palettes"),
"io_mesh_atomic": ("atomic_blender_pdb_xyz", "Atomic Blender PDB/XYZ"),
"io_scene_3ds": ("autodesk_3ds_format", "Autodesk 3DS format"),
"io_shape_mdd": ("newtek_mdd_format", "NewTek MDD format"),
"lighting_dynamic_sky": ("dynamic_sky", "Dynamic Sky"),
"lighting_tri_lights": ("tri_lighting", "Tri-lighting"),
"magic_uv": ("magic_uv", "Magic UV"),
"materials_library_vx": ("material_library", "Material Library"),
"materials_utils": ("material_utilities", "Material Utilities"),
"measureit": ("measureit", "MeasureIt"),
"mesh_auto_mirror": ("auto_mirror", "Auto Mirror"),
"mesh_bsurfaces": ("bsurfaces_gpl_edition", "Bsurfaces GPL Edition"),
"mesh_f2": ("f2", "F2"),
"mesh_inset": ("inset_straight_skeleton", "Inset Straight Skeleton"),
"mesh_looptools": ("looptools", "LoopTools"),
"mesh_snap_utilities_line": ("snap_utilities_line", "Snap_Utilities_Line"),
"mesh_tiny_cad": ("tinycad_mesh_tools", "tinyCAD Mesh tools"),
"mesh_tissue": ("tissue", "Tissue"),
"mesh_tools": ("edit_mesh_tools", "Edit Mesh Tools"),
"node_arrange": ("node_arrange", "Node Arrange"),
"node_presets": ("node_presets", "Node Presets"),
"object_boolean_tools": ("bool_tool", "Bool Tool"),
"object_carver": ("carver", "Carver"),
"object_collection_manager": ("collection_manager", "Collection Manager"),
"object_color_rules": ("object_color_rules", "Object Color Rules"),
"object_edit_linked": ("edit_linked_library", "Edit Linked Library"),
"object_fracture_cell": ("cell_fracture", "Cell Fracture"),
"object_print3d_utils": ("print3d_toolbox", "3D-Print Toolbox"),
"object_scatter": ("scatter_objects", "Scatter Objects"),
"object_skinify": ("skinify_rig", "Skinify Rig"),
"paint_palette": ("paint_palettes", "Paint Palettes"),
"power_sequencer": ("power_sequencer", "Power Sequencer"),
"precision_drawing_tools": ("precision_drawing_tools_pdt", "Precision Drawing Tools (PDT)"),
"real_snow": ("real_snow", "Real Snow"),
"render_copy_settings": ("copy_render_settings", "Copy Render Settings"),
"render_freestyle_svg": ("freestyle_svg_exporter", "Freestyle SVG Exporter"),
"render_povray": ("pov_at_ble", "POV@Ble"),
"render_ui_animation_render": ("ui_animation_render", "UI Animation Render"),
"rigify": ("rigify", "Rigify"),
"space_clip_editor_refine_solution": ("refine_tracking_solution", "Refine tracking solution"),
"space_view3d_3d_navigation": ("navigation", "3D Navigation"),
"space_view3d_align_tools": ("align_tools", "Align Tools"),
"space_view3d_brush_menus": ("dynamic_brush_menus", "Dynamic Brush Menus"),
"space_view3d_copy_attributes": ("copy_attributes_menu", "Copy Attributes Menu"),
"space_view3d_math_vis": ("math_vis_console", "Math Vis (Console)"),
"space_view3d_modifier_tools": ("modifier_tools", "Modifier Tools"),
"space_view3d_pie_menus": ("viewport_pie_menus", "3D Viewport Pie Menus"),
"space_view3d_spacebar_menu": ("dynamic_context_menu", "Dynamic Context Menu"),
"space_view3d_stored_views": ("stored_views", "Stored Views"),
"storypencil": ("storypencil_storyboard_tools", "Storypencil - Storyboard Tools"),
"sun_position": ("sun_position", "Sun Position"),
"system_blend_info": ("scene_information", "Scene Information"),
"system_demo_mode": ("demo_mode", "Demo Mode"),
"system_property_chart": ("property_chart", "Property Chart"),
"vdm_brush_baker": ("vdm_brush_baker", "VDM Brush Baker"),
},
}