Rigify: Add ability to disable installed feature sets
This patch is a continuation of D8519, was also suggested in T88711#1170152. It adds a checkbox for each feature set in the Rigify Preferences, to disable/enable that feature set without having to completely remove it from the file system. Challenges that were hopefully successfully tackled: - Keep list in sync when user manually adds or removes things through the file system instead of the "add/remove feature set" buttons in the UI. - Avoid re-building the feature set list all the time so that the checkbox states can actually be stored when the user exits Blender. - Disabling a feature set means calling its unregister function, then rebuilding the rig types and metarigs lists/menus. - Some renaming slipped in because I found the variable name "feature_set" a bit confusing. If needed, I could split this change into a separate patch or just forget about it, but I think the longer names help here. Testing would be welcome, since things turned out a bit more tricky than expected. In a follow-up patch I would like to do a code quality pass, to split the code a bit better here. There is a bit too much stuff in __init__.py in particular. I will get started on that when this gets close to being finalized. Reviewed By: angavrilov Differential Revision: https://developer.blender.org/D12260
This commit is contained in:
parent
84f5f46992
commit
e641ac2d8a
|
@ -149,6 +149,33 @@ class RigifyFeatureSets(bpy.types.PropertyGroup):
|
|||
name: bpy.props.StringProperty()
|
||||
module_name: bpy.props.StringProperty()
|
||||
|
||||
def toggle_featureset(self, context):
|
||||
feature_set_list.call_register_function(self.module_name, self.enabled)
|
||||
context.preferences.addons[__package__].preferences.update_external_rigs()
|
||||
|
||||
enabled: bpy.props.BoolProperty(
|
||||
name = "Enabled",
|
||||
description = "Whether this feature-set is registered or not",
|
||||
update = toggle_featureset,
|
||||
default = True
|
||||
)
|
||||
|
||||
|
||||
class RIGIFY_UL_FeatureSets(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
rigify_prefs = data
|
||||
feature_sets = rigify_prefs.rigify_feature_sets
|
||||
active_set = feature_sets[rigify_prefs.active_feature_set_index]
|
||||
feature_set_entry = item
|
||||
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
||||
row = layout.row()
|
||||
row.prop(feature_set_entry, 'name', text="", emboss=False)
|
||||
|
||||
icon = 'CHECKBOX_HLT' if feature_set_entry.enabled else 'CHECKBOX_DEHLT'
|
||||
row.enabled = feature_set_entry.enabled
|
||||
layout.prop(feature_set_entry, 'enabled', text="", icon=icon, emboss=False)
|
||||
elif self.layout_type in {'GRID'}:
|
||||
pass
|
||||
|
||||
class RigifyPreferences(AddonPreferences):
|
||||
# this must match the addon name, use '__package__'
|
||||
|
@ -157,32 +184,50 @@ class RigifyPreferences(AddonPreferences):
|
|||
|
||||
def register_feature_sets(self, register):
|
||||
"""Call register or unregister of external feature sets"""
|
||||
for set_name in feature_set_list.get_installed_list():
|
||||
for set_name in feature_set_list.get_enabled_modules_names():
|
||||
feature_set_list.call_register_function(set_name, register)
|
||||
|
||||
def update_external_rigs(self, force=False):
|
||||
def refresh_installed_feature_sets(self):
|
||||
"""Synchronize preferences entries with what's actually in the file system."""
|
||||
feature_set_prefs = self.rigify_feature_sets
|
||||
|
||||
module_names = feature_set_list.get_installed_modules_names()
|
||||
|
||||
# If there is a feature set preferences entry with no corresponding
|
||||
# installed module, user must've manually removed it from the filesystem,
|
||||
# so let's remove such entries.
|
||||
to_delete = [ i for i, fs in enumerate(feature_set_prefs) if fs.module_name not in module_names ]
|
||||
for i in reversed(to_delete):
|
||||
feature_set_prefs.remove(i)
|
||||
|
||||
# If there is an installed feature set in the file system but no corresponding
|
||||
# entry, user must've installed it manually. Make sure it has an entry.
|
||||
for module_name in module_names:
|
||||
for fs in feature_set_prefs:
|
||||
if module_name == fs.module_name:
|
||||
break
|
||||
else:
|
||||
fs = feature_set_prefs.add()
|
||||
fs.name = feature_set_list.get_ui_name(module_name)
|
||||
fs.module_name = module_name
|
||||
|
||||
def update_external_rigs(self):
|
||||
"""Get external feature sets"""
|
||||
set_list = feature_set_list.get_installed_list()
|
||||
|
||||
# Update feature set list
|
||||
self.rigify_feature_sets.clear()
|
||||
self.refresh_installed_feature_sets()
|
||||
|
||||
for s in set_list:
|
||||
list_entry = self.rigify_feature_sets.add()
|
||||
list_entry.name = feature_set_list.get_ui_name(s)
|
||||
list_entry.module_name = s
|
||||
set_list = feature_set_list.get_enabled_modules_names()
|
||||
|
||||
if force or len(set_list) > 0:
|
||||
# Reload rigs
|
||||
print('Reloading external rigs...')
|
||||
rig_lists.get_external_rigs(set_list)
|
||||
# Reload rigs
|
||||
print('Reloading external rigs...')
|
||||
rig_lists.get_external_rigs(set_list)
|
||||
|
||||
# Reload metarigs
|
||||
print('Reloading external metarigs...')
|
||||
metarig_menu.get_external_metarigs(set_list)
|
||||
# Reload metarigs
|
||||
print('Reloading external metarigs...')
|
||||
metarig_menu.get_external_metarigs(set_list)
|
||||
|
||||
# Re-register rig parameters
|
||||
register_rig_parameters()
|
||||
# Re-register rig parameters
|
||||
register_rig_parameters()
|
||||
|
||||
rigify_feature_sets: bpy.props.CollectionProperty(type=RigifyFeatureSets)
|
||||
active_feature_set_index: IntProperty()
|
||||
|
@ -196,8 +241,8 @@ class RigifyPreferences(AddonPreferences):
|
|||
|
||||
row = layout.row()
|
||||
row.template_list(
|
||||
"UI_UL_list",
|
||||
"rigify_feature_sets",
|
||||
'RIGIFY_UL_FeatureSets',
|
||||
'',
|
||||
self, "rigify_feature_sets",
|
||||
self, 'active_feature_set_index'
|
||||
)
|
||||
|
@ -259,8 +304,7 @@ def draw_feature_set_prefs(layout, context, featureset: RigifyFeatureSets):
|
|||
op = row.operator('wm.url_open', text="Report a Bug", icon='URL')
|
||||
op.url = info['tracker_url']
|
||||
|
||||
op = row.operator("wm.rigify_remove_feature_set", text="Remove", icon='CANCEL')
|
||||
op.featureset = featureset.module_name
|
||||
row.operator("wm.rigify_remove_feature_set", text="Remove", icon='CANCEL')
|
||||
|
||||
|
||||
class RigifyName(bpy.types.PropertyGroup):
|
||||
|
@ -443,6 +487,7 @@ classes = (
|
|||
RigifyColorSet,
|
||||
RigifySelectionColors,
|
||||
RigifyArmatureLayer,
|
||||
RIGIFY_UL_FeatureSets,
|
||||
RigifyFeatureSets,
|
||||
RigifyPreferences,
|
||||
)
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#
|
||||
#======================= END GPL LICENSE BLOCK ========================
|
||||
|
||||
from typing import List
|
||||
|
||||
import bpy
|
||||
from bpy.props import StringProperty
|
||||
import os
|
||||
|
@ -44,7 +46,8 @@ def get_install_path(*, create=False):
|
|||
return INSTALL_PATH
|
||||
|
||||
|
||||
def get_installed_list():
|
||||
def get_installed_modules_names() -> List[str]:
|
||||
"""Return a list of module names of all feature sets in the file system."""
|
||||
features_path = get_install_path()
|
||||
if not features_path:
|
||||
return []
|
||||
|
@ -60,6 +63,17 @@ def get_installed_list():
|
|||
return sets
|
||||
|
||||
|
||||
def get_enabled_modules_names() -> List[str]:
|
||||
"""Return a list of module names of all enabled feature sets."""
|
||||
rigify_prefs = bpy.context.preferences.addons[__package__].preferences
|
||||
installed_module_names = get_installed_modules_names()
|
||||
rigify_feature_sets = rigify_prefs.rigify_feature_sets
|
||||
|
||||
enabled_module_names = { fs.module_name for fs in rigify_feature_sets if fs.enabled }
|
||||
|
||||
return [name for name in installed_module_names if name in enabled_module_names]
|
||||
|
||||
|
||||
def get_module(feature_set):
|
||||
return importlib.import_module('.'.join([*NAME_PREFIX, feature_set]))
|
||||
|
||||
|
@ -88,17 +102,17 @@ def get_info_dict(feature_set):
|
|||
return {}
|
||||
|
||||
|
||||
def call_function_safe(feature_set, name, args=[], kwargs={}):
|
||||
module = get_module_safe(feature_set)
|
||||
def call_function_safe(module_name, func_name, args=[], kwargs={}):
|
||||
module = get_module_safe(module_name)
|
||||
|
||||
if module:
|
||||
func = getattr(module, name, None)
|
||||
func = getattr(module, func_name, None)
|
||||
|
||||
if callable(func):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception:
|
||||
print("Rigify Error: Could not call function '%s' of feature set '%s': exception occurred.\n" % (name, feature_set))
|
||||
print(f"Rigify Error: Could not call function '{func_name}' of feature set '{module_name}': exception occurred.\n")
|
||||
traceback.print_exc()
|
||||
print("")
|
||||
|
||||
|
@ -128,7 +142,7 @@ def feature_set_items(scene, context):
|
|||
('rigify', 'Rigify Built-in', 'Rigs bundled with Rigify'),
|
||||
]
|
||||
|
||||
for fs in get_installed_list():
|
||||
for fs in get_enabled_modules_names():
|
||||
ui_name = get_ui_name(fs)
|
||||
items.append((fs, ui_name, ui_name))
|
||||
|
||||
|
@ -222,8 +236,7 @@ class DATA_OT_rigify_add_feature_set(bpy.types.Operator):
|
|||
|
||||
addon_prefs.update_external_rigs()
|
||||
|
||||
new_index = addon_prefs.rigify_feature_sets.find(get_ui_name(fixed_dirname))
|
||||
addon_prefs.active_feature_set_index = new_index
|
||||
addon_prefs.active_feature_set_index = len(addon_prefs.rigify_feature_sets)-1
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
@ -234,8 +247,6 @@ class DATA_OT_rigify_remove_feature_set(bpy.types.Operator):
|
|||
bl_description = "Remove external feature set (rigs, metarigs, ui templates)"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
featureset: StringProperty(maxlen=1024, options={'HIDDEN', 'SKIP_SAVE'})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
@ -245,18 +256,29 @@ class DATA_OT_rigify_remove_feature_set(bpy.types.Operator):
|
|||
|
||||
def execute(self, context):
|
||||
addon_prefs = context.preferences.addons[__package__].preferences
|
||||
feature_sets = addon_prefs.rigify_feature_sets
|
||||
active_idx = addon_prefs.active_feature_set_index
|
||||
active_fs = feature_sets[active_idx]
|
||||
|
||||
# Call the unregister callback of the set being removed
|
||||
call_register_function(self.featureset, False)
|
||||
# Call the unregister callback of the set being removed.
|
||||
if active_fs.enabled:
|
||||
call_register_function(active_fs.module_name, register=False)
|
||||
|
||||
# Remove the feature set's folder from the file system.
|
||||
rigify_config_path = get_install_path()
|
||||
if rigify_config_path:
|
||||
set_path = os.path.join(rigify_config_path, self.featureset)
|
||||
set_path = os.path.join(rigify_config_path, active_fs.module_name)
|
||||
if os.path.exists(set_path):
|
||||
rmtree(set_path)
|
||||
|
||||
addon_prefs.update_external_rigs(force=True)
|
||||
addon_prefs.active_feature_set_index = 0
|
||||
# Remove the feature set's entry from the addon preferences.
|
||||
feature_sets.remove(active_idx)
|
||||
|
||||
# Remove the feature set's entries from the metarigs and rig types.
|
||||
addon_prefs.update_external_rigs()
|
||||
|
||||
# Update active index.
|
||||
addon_prefs.active_feature_set_index -= 1
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
|
|
@ -214,17 +214,16 @@ def unregister():
|
|||
for mf in menu_funcs:
|
||||
bpy.types.VIEW3D_MT_armature_add.remove(mf)
|
||||
|
||||
def get_external_metarigs(set_list):
|
||||
def get_external_metarigs(feature_module_names):
|
||||
unregister()
|
||||
|
||||
# Clear and fill metarigs public variables
|
||||
metarigs.clear()
|
||||
get_internal_metarigs()
|
||||
|
||||
|
||||
for feature_set in set_list:
|
||||
for module_name in feature_module_names:
|
||||
try:
|
||||
base_dir, base_path = feature_set_list.get_dir_path(feature_set, METARIG_DIR)
|
||||
base_dir, base_path = feature_set_list.get_dir_path(module_name, METARIG_DIR)
|
||||
|
||||
get_metarigs(metarigs['external'], base_dir, base_path)
|
||||
except Exception:
|
||||
|
|
|
@ -54,7 +54,7 @@ def build_type_list(context, rigify_types):
|
|||
|
||||
for r in sorted(rig_lists.rigs):
|
||||
if (context.object.data.active_feature_set in ('all', rig_lists.rigs[r]['feature_set'])
|
||||
or len(feature_set_list.get_installed_list()) == 0
|
||||
or len(feature_set_list.get_enabled_modules_names()) == 0
|
||||
):
|
||||
a = rigify_types.add()
|
||||
a.name = r
|
||||
|
@ -191,7 +191,7 @@ class DATA_PT_rigify_samples(bpy.types.Panel):
|
|||
id_store.rigify_active_type = 0
|
||||
|
||||
# Rig type list
|
||||
if len(feature_set_list.get_installed_list()) > 0:
|
||||
if len(feature_set_list.get_enabled_modules_names()) > 0:
|
||||
row = layout.row()
|
||||
row.prop(context.object.data, "active_feature_set")
|
||||
row = layout.row()
|
||||
|
@ -609,7 +609,7 @@ class BONE_PT_rigify_buttons(bpy.types.Panel):
|
|||
build_type_list(context, id_store.rigify_types)
|
||||
|
||||
# Rig type field
|
||||
if len(feature_set_list.get_installed_list()) > 0:
|
||||
if len(feature_set_list.get_enabled_modules_names()) > 0:
|
||||
row = layout.row()
|
||||
row.prop(context.object.data, "active_feature_set")
|
||||
row = layout.row()
|
||||
|
|
Loading…
Reference in New Issue