Rigify: finish clearing out warnings in functional code.

This commit is contained in:
Alexander Gavrilov 2022-11-21 00:01:25 +02:00
parent ad22263327
commit 3d89a38c19
14 changed files with 646 additions and 425 deletions

View File

@ -3,7 +3,7 @@
bl_info = {
"name": "Rigify",
"version": (0, 6, 6),
"author": "Nathan Vegdahl, Lucio Rossi, Ivan Cappiello, Alexander Gavrilov",
"author": "Nathan Vegdahl, Lucio Rossi, Ivan Cappiello, Alexander Gavrilov", # noqa
"blender": (3, 0, 0),
"description": "Automatic rigging from building-block components",
"location": "Armature properties, Bone properties, View3d tools panel, Armature Add menu",
@ -14,6 +14,7 @@ bl_info = {
import importlib
import sys
import bpy
import typing
# The order in which core modules of the addon are loaded and reloaded.
@ -56,6 +57,7 @@ def get_loaded_modules():
prefix = __name__ + '.'
return [name for name in sys.modules if name.startswith(prefix)]
def reload_modules():
fixed_modules = set(reload_list)
@ -66,7 +68,8 @@ def reload_modules():
for name in reload_list:
importlib.reload(sys.modules[name])
def compare_module_list(a, b):
def compare_module_list(a: list[str], b: list[str]):
# HACK: ignore the "utils" module when comparing module load orders,
# because it is inconsistent for reasons unknown.
# See rBAa918332cc3f821f5a70b1de53b65dd9ca596b093.
@ -77,19 +80,22 @@ def compare_module_list(a, b):
b_copy.remove(utils_module_name)
return a_copy == b_copy
def load_initial_modules():
load_list = [ __name__ + '.' + name for name in initial_load_order ]
for i, name in enumerate(load_list):
def load_initial_modules() -> list[str]:
names = [__name__ + '.' + name for name in initial_load_order]
for i, name in enumerate(names):
importlib.import_module(name)
module_list = get_loaded_modules()
expected_list = load_list[0 : max(11, i+1)]
expected_list = names[0: max(11, i+1)]
if not compare_module_list(module_list, expected_list):
print('!!! RIGIFY: initial load order mismatch after '+name+' - expected: \n', expected_list, '\nGot:\n', module_list)
print(f'!!! RIGIFY: initial load order mismatch after {name} - expected: \n',
expected_list, '\nGot:\n', module_list)
return names
return load_list
def load_rigs():
rig_lists.get_internal_rigs()
@ -101,18 +107,20 @@ if "reload_list" in locals():
else:
load_list = load_initial_modules()
from . import (base_rig, base_generate, rig_ui_template, feature_set_list, rig_lists, generate, ui, metarig_menu)
from . import (utils, base_rig, base_generate, rig_ui_template, feature_set_list, rig_lists,
generate, ui, metarig_menu, operators)
reload_list = reload_list_init = get_loaded_modules()
if not compare_module_list(reload_list, load_list):
print('!!! RIGIFY: initial load order mismatch - expected: \n', load_list, '\nGot:\n', reload_list)
print('!!! RIGIFY: initial load order mismatch - expected: \n',
load_list, '\nGot:\n', reload_list)
load_rigs()
from bpy.types import AddonPreferences
from bpy.props import (
from bpy.types import AddonPreferences # noqa: E402
from bpy.props import ( # noqa: E402
BoolProperty,
IntProperty,
EnumProperty,
@ -132,24 +140,26 @@ class RigifyFeatureSets(bpy.types.PropertyGroup):
name: bpy.props.StringProperty()
module_name: bpy.props.StringProperty()
def toggle_featureset(self, context):
def toggle_feature_set(self, context):
feature_set_list.call_register_function(self.module_name, self.enabled)
context.preferences.addons[__package__].preferences.update_external_rigs()
RigifyPreferences.get_instance(context).update_external_rigs()
enabled: bpy.props.BoolProperty(
name = "Enabled",
description = "Whether this feature-set is registered or not",
update = toggle_featureset,
default = True
name="Enabled",
description="Whether this feature-set is registered or not",
update=toggle_feature_set,
default=True
)
# noinspection PyPep8Naming, SpellCheckingInspection
class RIGIFY_UL_FeatureSets(bpy.types.UIList):
# noinspection PyMethodOverriding
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
# rigify_prefs: RigifyPreferences = data
# feature_sets = rigify_prefs.rigify_feature_sets
# active_set: RigifyFeatureSets = feature_sets[rigify_prefs.active_feature_set_index]
feature_set_entry: RigifyFeatureSets = item
if self.layout_type in {'DEFAULT', 'COMPACT'}:
row = layout.row()
row.prop(feature_set_entry, 'name', text="", emboss=False)
@ -160,16 +170,23 @@ class RIGIFY_UL_FeatureSets(bpy.types.UIList):
elif self.layout_type in {'GRID'}:
pass
class RigifyPreferences(AddonPreferences):
# this must match the addon name, use '__package__'
# when defining this in a submodule of a python package.
bl_idname = __name__
def register_feature_sets(self, register):
@staticmethod
def get_instance(context: bpy.types.Context = None) -> 'RigifyPreferences':
prefs = (context or bpy.context).preferences.addons[__package__].preferences
assert isinstance(prefs, RigifyPreferences)
return prefs
def register_feature_sets(self, do_register: bool):
"""Call register or unregister of external feature sets"""
self.refresh_installed_feature_sets()
for set_name in feature_set_list.get_enabled_modules_names():
feature_set_list.call_register_function(set_name, register)
feature_set_list.call_register_function(set_name, do_register)
def refresh_installed_feature_sets(self):
"""Synchronize preferences entries with what's actually in the file system."""
@ -180,7 +197,8 @@ class RigifyPreferences(AddonPreferences):
# 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 ]
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)
@ -216,8 +234,8 @@ class RigifyPreferences(AddonPreferences):
rigify_feature_sets: bpy.props.CollectionProperty(type=RigifyFeatureSets)
active_feature_set_index: IntProperty()
def draw(self, context):
layout = self.layout
def draw(self, context: bpy.types.Context):
layout: bpy.types.UILayout = self.layout
layout.label(text="Feature Sets:")
@ -231,7 +249,7 @@ class RigifyPreferences(AddonPreferences):
self, 'active_feature_set_index'
)
# Clamp active index to ensure it's in bounds.
# Clamp active index to ensure it is in bounds.
self.active_feature_set_index = max(0, min(self.active_feature_set_index, len(self.rigify_feature_sets)-1))
if len(self.rigify_feature_sets) > 0:
@ -241,10 +259,10 @@ class RigifyPreferences(AddonPreferences):
draw_feature_set_prefs(layout, context, active_fs)
def draw_feature_set_prefs(layout, context, featureset: RigifyFeatureSets):
info = feature_set_list.get_info_dict(featureset.module_name)
def draw_feature_set_prefs(layout: bpy.types.UILayout, _context, feature_set: RigifyFeatureSets):
info = feature_set_list.get_info_dict(feature_set.module_name)
description = featureset.name
description = feature_set.name
if 'description' in info:
description = info['description']
@ -255,7 +273,7 @@ def draw_feature_set_prefs(layout, context, featureset: RigifyFeatureSets):
split.label(text="Description:")
split.label(text=description)
mod = feature_set_list.get_module_safe(featureset.module_name)
mod = feature_set_list.get_module_safe(feature_set.module_name)
if mod:
split = col.row().split(factor=split_factor)
split.label(text="File:")
@ -322,7 +340,6 @@ class RigifyColorSet(bpy.types.PropertyGroup):
class RigifySelectionColors(bpy.types.PropertyGroup):
select: FloatVectorProperty(
name="object_color",
subtype='COLOR',
@ -343,10 +360,12 @@ class RigifySelectionColors(bpy.types.PropertyGroup):
class RigifyParameters(bpy.types.PropertyGroup):
name: StringProperty()
# Parameter update callback
in_update = False
def update_callback(prop_name):
from .utils.rig import get_rigify_type
@ -369,11 +388,12 @@ def update_callback(prop_name):
return callback
# Remember the initial property set
RIGIFY_PARAMETERS_BASE_DIR = set(dir(RigifyParameters))
RIGIFY_PARAMETER_TABLE = {'name': ('DEFAULT', StringProperty())}
def clear_rigify_parameters():
for name in list(dir(RigifyParameters)):
if name not in RIGIFY_PARAMETERS_BASE_DIR:
@ -390,6 +410,7 @@ def format_property_spec(spec):
class RigifyParameterValidator(object):
# noinspection GrazieInspection
"""
A wrapper around RigifyParameters that verifies properties
defined from rigs for incompatible redefinitions using a table.
@ -416,8 +437,9 @@ class RigifyParameterValidator(object):
if hasattr(RigifyParameterValidator, name):
return object.__setattr__(self, name, val_original)
if not isinstance(val_original, bpy.props._PropertyDeferred):
print("!!! RIGIFY RIG %s: INVALID DEFINITION FOR RIG PARAMETER %s: %r\n" % (self.__rig_name, name, val_original))
if not isinstance(val_original, bpy.props._PropertyDeferred): # noqa
print(f"!!! RIGIFY RIG {self.__rig_name}: "
f"INVALID DEFINITION FOR RIG PARAMETER {name}: {repr(val_original)}\n")
return
# actually defining the property modifies the dictionary with new parameters, so copy it now
@ -430,10 +452,12 @@ class RigifyParameterValidator(object):
if name in self.__prop_table:
cur_rig, cur_info = self.__prop_table[name]
if new_def != cur_info:
print("!!! RIGIFY RIG %s: REDEFINING PARAMETER %s AS:\n\n %s\n" % (self.__rig_name, name, format_property_spec(val)))
print("!!! PREVIOUS DEFINITION BY %s:\n\n %s\n" % (cur_rig, format_property_spec(cur_info)))
print(f"!!! RIGIFY RIG {self.__rig_name}: REDEFINING PARAMETER {name} AS:\n\n"
f" {format_property_spec(val)}\n"
f"!!! PREVIOUS DEFINITION BY {cur_rig}:\n\n"
f" {format_property_spec(cur_info)}\n")
# inject a generic update callback that calls the appropriate rig classmethod
# inject a generic update callback that calls the appropriate rig class method
if val[0] != bpy.props.CollectionProperty:
val[1]['update'] = update_callback(name)
@ -441,8 +465,8 @@ class RigifyParameterValidator(object):
self.__prop_table[name] = (self.__rig_name, new_def)
# noinspection SpellCheckingInspection
class RigifyArmatureLayer(bpy.types.PropertyGroup):
def get_group(self):
if 'group_prop' in self.keys():
return self['group_prop']
@ -450,20 +474,25 @@ class RigifyArmatureLayer(bpy.types.PropertyGroup):
return 0
def set_group(self, value):
arm = bpy.context.object.data
if value > len(arm.rigify_colors):
self['group_prop'] = len(arm.rigify_colors)
arm = utils.misc.verify_armature_obj(bpy.context.object).data
colors = utils.rig.get_rigify_colors(arm)
if value > len(colors):
self['group_prop'] = len(colors)
else:
self['group_prop'] = value
name: StringProperty(name="Layer Name", default=" ")
row: IntProperty(name="Layer Row", default=1, min=1, max=32, description='UI row for this layer')
selset: BoolProperty(name="Selection Set", default=False, description='Add Selection Set for this layer')
row: IntProperty(name="Layer Row", default=1, min=1, max=32,
description='UI row for this layer')
selset: BoolProperty(name="Selection Set", default=False,
description='Add Selection Set for this layer')
group: IntProperty(name="Bone Group", default=0, min=0, max=32,
get=get_group, set=set_group, description='Assign Bone Group to this layer')
get=get_group, set=set_group,
description='Assign Bone Group to this layer')
##### REGISTER #####
####################
# REGISTER
classes = (
RigifyName,
@ -477,6 +506,7 @@ classes = (
)
# noinspection PyPep8Naming
def register():
from bpy.utils import register_class
@ -532,48 +562,69 @@ def register():
), name='Theme')
IDStore = bpy.types.WindowManager
IDStore.rigify_collection = EnumProperty(items=(("All", "All", "All"),), default="All",
IDStore.rigify_collection = EnumProperty(
items=(("All", "All", "All"),), default="All",
name="Rigify Active Collection",
description="The selected rig collection")
IDStore.rigify_widgets = CollectionProperty(type=RigifyName)
IDStore.rigify_types = CollectionProperty(type=RigifyName)
IDStore.rigify_active_type = IntProperty(name="Rigify Active Type", description="The selected rig type")
IDStore.rigify_active_type = IntProperty(name="Rigify Active Type",
description="The selected rig type")
bpy.types.Armature.rigify_force_widget_update = BoolProperty(name="Overwrite Widget Meshes",
description="Forces Rigify to delete and rebuild all of the rig widget objects. By default, already existing widgets are reused as-is to facilitate manual editing",
bpy.types.Armature.rigify_force_widget_update = BoolProperty(
name="Overwrite Widget Meshes",
description="Forces Rigify to delete and rebuild all of the rig widget objects. By "
"default, already existing widgets are reused as-is to facilitate manual "
"editing",
default=False)
bpy.types.Armature.rigify_mirror_widgets = BoolProperty(name="Mirror Widgets",
description="Make widgets for left and right side bones linked duplicates with negative X scale for the right side, based on bone name symmetry",
bpy.types.Armature.rigify_mirror_widgets = BoolProperty(
name="Mirror Widgets",
description="Make widgets for left and right side bones linked duplicates with negative "
"X scale for the right side, based on bone name symmetry",
default=True)
bpy.types.Armature.rigify_widgets_collection = PointerProperty(type=bpy.types.Collection,
name="Widgets Collection",
description="Defines which collection to place widget objects in. If unset, a new one will be created based on the name of the rig")
bpy.types.Armature.rigify_rig_basename = StringProperty(name="Rigify Rig Name",
description="Optional. If specified, this name will be used for the newly generated rig, widget collection and script. Otherwise, a name is generated based on the name of the metarig object by replacing 'metarig' with 'rig', 'META' with 'RIG', or prefixing with 'RIG-'. When updating an already generated rig its name is never changed",
bpy.types.Armature.rigify_widgets_collection = PointerProperty(
type=bpy.types.Collection,
name="Widgets Collection",
description="Defines which collection to place widget objects in. If unset, a new one "
"will be created based on the name of the rig")
bpy.types.Armature.rigify_rig_basename = StringProperty(
name="Rigify Rig Name",
description="Optional. If specified, this name will be used for the newly generated rig, "
"widget collection and script. Otherwise, a name is generated based on the "
"name of the metarig object by replacing 'metarig' with 'rig', 'META' with "
"'RIG', or prefixing with 'RIG-'. When updating an already generated rig its "
"name is never changed",
default="")
bpy.types.Armature.rigify_target_rig = PointerProperty(type=bpy.types.Object,
bpy.types.Armature.rigify_target_rig = PointerProperty(
type=bpy.types.Object,
name="Rigify Target Rig",
description="Defines which rig to overwrite. If unset, a new one will be created with name based on the Rig Name option or the name of the metarig",
description="Defines which rig to overwrite. If unset, a new one will be created with "
"name based on the Rig Name option or the name of the metarig",
poll=lambda self, obj: obj.type == 'ARMATURE' and obj.data is not self)
bpy.types.Armature.rigify_rig_ui = PointerProperty(type=bpy.types.Text,
bpy.types.Armature.rigify_rig_ui = PointerProperty(
type=bpy.types.Text,
name="Rigify Target Rig UI",
description="Defines the UI to overwrite. If unset, a new one will be created and named based on the name of the rig")
description="Defines the UI to overwrite. If unset, a new one will be created and named "
"based on the name of the rig")
bpy.types.Armature.rigify_finalize_script = PointerProperty(type=bpy.types.Text,
bpy.types.Armature.rigify_finalize_script = PointerProperty(
type=bpy.types.Text,
name="Finalize Script",
description="Run this script after generation to apply user-specific changes")
IDStore.rigify_transfer_only_selected = BoolProperty(
name="Transfer Only Selected",
description="Transfer selected bones only", default=True)
bpy.context.preferences.addons[__package__].preferences.register_feature_sets(True)
bpy.context.preferences.addons[__package__].preferences.update_external_rigs()
prefs = RigifyPreferences.get_instance()
prefs.register_feature_sets(True)
prefs.update_external_rigs()
# Add rig parameters
register_rig_parameters()
@ -583,25 +634,32 @@ def register_rig_parameters():
for rig in rig_lists.rigs:
rig_module = rig_lists.rigs[rig]['module']
rig_class = rig_module.Rig
r = rig_class if hasattr(rig_class, 'add_parameters') else rig_module
rig_def = rig_class if hasattr(rig_class, 'add_parameters') else rig_module
# noinspection PyBroadException
try:
if hasattr(r, 'add_parameters'):
r.add_parameters(RigifyParameterValidator(RigifyParameters, rig, RIGIFY_PARAMETER_TABLE))
if hasattr(rig_def, 'add_parameters'):
validator = RigifyParameterValidator(RigifyParameters, rig, RIGIFY_PARAMETER_TABLE)
rig_def.add_parameters(validator)
except Exception:
import traceback
traceback.print_exc()
# noinspection PyPep8Naming
def unregister():
from bpy.utils import unregister_class
bpy.context.preferences.addons[__package__].preferences.register_feature_sets(False)
prefs = RigifyPreferences.get_instance()
prefs.register_feature_sets(False)
# Properties on PoseBones and Armature.
del bpy.types.PoseBone.rigify_type
del bpy.types.PoseBone.rigify_parameters
# Properties on PoseBones and Armature. (Annotated to suppress unknown attribute warnings.)
PoseBone: typing.Any = bpy.types.PoseBone
del PoseBone.rigify_type
del PoseBone.rigify_parameters
ArmStore: typing.Any = bpy.types.Armature
ArmStore = bpy.types.Armature
del ArmStore.rigify_layers
del ArmStore.active_feature_set
del ArmStore.rigify_colors
@ -613,7 +671,8 @@ def unregister():
del ArmStore.rigify_target_rig
del ArmStore.rigify_rig_ui
IDStore = bpy.types.WindowManager
IDStore: typing.Any = bpy.types.WindowManager
del IDStore.rigify_collection
del IDStore.rigify_types
del IDStore.rigify_active_type

View File

@ -327,9 +327,10 @@ class RigComponent(LazyRigComponent):
# Rig Stage Decorators
##############################################
# Generate @stage.<...> decorators for all valid stages.
# noinspection PyPep8Naming
@GenerateCallbackHost.stage_decorator_container
class stage:
"""Contains @stage.<...> decorators for all valid stages."""
# Declare stages for auto-completion - doesn't affect execution.
initialize: Callable
prepare_bones: Callable

View File

@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-or-later
from typing import List
from typing import TYPE_CHECKING, List, Sequence
import bpy
from bpy.props import StringProperty
@ -13,9 +13,13 @@ from shutil import rmtree
from . import feature_sets
if TYPE_CHECKING:
from . import RigifyFeatureSets
DEFAULT_NAME = 'rigify'
# noinspection PyProtectedMember
INSTALL_PATH = feature_sets._install_path()
NAME_PREFIX = feature_sets.__name__.split('.')
@ -47,35 +51,40 @@ def get_installed_modules_names() -> List[str]:
return sets
def get_prefs_feature_sets() -> Sequence['RigifyFeatureSets']:
from . import RigifyPreferences
return RigifyPreferences.get_instance().rigify_feature_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
rigify_feature_sets = get_prefs_feature_sets()
enabled_module_names = { fs.module_name for fs in rigify_feature_sets if fs.enabled }
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):
def get_module(feature_set: str):
return importlib.import_module('.'.join([*NAME_PREFIX, feature_set]))
def get_module_safe(feature_set):
def get_module_safe(feature_set: str):
# noinspection PyBroadException
try:
return get_module(feature_set)
except:
except: # noqa: E722
return None
def get_dir_path(feature_set, *extra_items):
def get_dir_path(feature_set: str, *extra_items: list[str]):
base_dir = os.path.join(INSTALL_PATH, feature_set, *extra_items)
base_path = [*NAME_PREFIX, feature_set, *extra_items]
return base_dir, base_path
def get_info_dict(feature_set):
def get_info_dict(feature_set: str):
module = get_module_safe(feature_set)
if module and hasattr(module, 'rigify_info'):
@ -86,28 +95,31 @@ def get_info_dict(feature_set):
return {}
def call_function_safe(module_name, func_name, args=[], kwargs={}):
# noinspection PyDefaultArgument
def call_function_safe(module_name: str, func_name: str, args=[], kwargs={}):
module = get_module_safe(module_name)
if module:
func = getattr(module, func_name, None)
if callable(func):
# noinspection PyBroadException
try:
return func(*args, **kwargs)
except Exception:
print(f"Rigify Error: Could not call function '{func_name}' of feature set '{module_name}': exception occurred.\n")
print(f"Rigify Error: Could not call function '{func_name}' of feature set "
f"'{module_name}': exception occurred.\n")
traceback.print_exc()
print("")
return None
def call_register_function(feature_set, register):
call_function_safe(feature_set, 'register' if register else 'unregister')
def call_register_function(feature_set: str, do_register: bool):
call_function_safe(feature_set, 'register' if do_register else 'unregister')
def get_ui_name(feature_set):
def get_ui_name(feature_set: str):
# Try to get user-defined name
info = get_info_dict(feature_set)
if 'name' in info:
@ -119,7 +131,7 @@ def get_ui_name(feature_set):
return name.title()
def feature_set_items(scene, context):
def feature_set_items(_scene, _context):
"""Get items for the Feature Set EnumProperty"""
items = [
('all', 'All', 'All installed feature sets and rigs bundled with Rigify'),
@ -157,6 +169,7 @@ def verify_feature_set_archive(zipfile):
return dirname, init_found, data_found
# noinspection PyPep8Naming
class DATA_OT_rigify_add_feature_set(bpy.types.Operator):
bl_idname = "wm.rigify_add_feature_set"
bl_label = "Add External Feature Set"
@ -175,7 +188,8 @@ class DATA_OT_rigify_add_feature_set(bpy.types.Operator):
return {'RUNNING_MODAL'}
def execute(self, context):
addon_prefs = context.preferences.addons[__package__].preferences
from . import RigifyPreferences
addon_prefs = RigifyPreferences.get_instance()
rigify_config_path = get_install_path(create=True)
@ -190,15 +204,20 @@ class DATA_OT_rigify_add_feature_set(bpy.types.Operator):
fixed_dirname = re.sub(r'[.-]', '_', base_dirname)
if not re.fullmatch(r'[a-zA-Z][a-zA-Z_0-9]*', fixed_dirname):
self.report({'ERROR'}, "The feature set archive base directory name is not a valid identifier: '%s'." % (base_dirname))
self.report({'ERROR'},
f"The feature set archive base directory name is not a valid "
f"identifier: '{base_dirname}'.")
return {'CANCELLED'}
if fixed_dirname == DEFAULT_NAME:
self.report({'ERROR'}, "The '%s' name is not allowed for feature sets." % (DEFAULT_NAME))
self.report(
{'ERROR'}, f"The '{DEFAULT_NAME}' name is not allowed for feature sets.")
return {'CANCELLED'}
if not init_found or not data_found:
self.report({'ERROR'}, "The feature set archive has no rigs or metarigs, or is missing __init__.py.")
self.report(
{'ERROR'},
"The feature set archive has no rigs or metarigs, or is missing __init__.py.")
return {'CANCELLED'}
base_dir = os.path.join(rigify_config_path, base_dirname)
@ -206,7 +225,7 @@ class DATA_OT_rigify_add_feature_set(bpy.types.Operator):
for path, name in [(base_dir, base_dirname), (fixed_dir, fixed_dirname)]:
if os.path.exists(path):
self.report({'ERROR'}, "Feature set directory already exists: '%s'." % (name))
self.report({'ERROR'}, f"Feature set directory already exists: '{name}'.")
return {'CANCELLED'}
# Unpack the validated archive and fix the directory name if necessary
@ -225,6 +244,7 @@ class DATA_OT_rigify_add_feature_set(bpy.types.Operator):
return {'FINISHED'}
# noinspection PyPep8Naming
class DATA_OT_rigify_remove_feature_set(bpy.types.Operator):
bl_idname = "wm.rigify_remove_feature_set"
bl_label = "Remove External Feature Set"
@ -238,15 +258,17 @@ class DATA_OT_rigify_remove_feature_set(bpy.types.Operator):
def invoke(self, context, event):
return context.window_manager.invoke_confirm(self, event)
# noinspection GrazieInspection
def execute(self, context):
addon_prefs = context.preferences.addons[__package__].preferences
feature_sets = addon_prefs.rigify_feature_sets
from . import RigifyPreferences
addon_prefs = RigifyPreferences.get_instance()
feature_set_list = addon_prefs.rigify_feature_sets
active_idx = addon_prefs.active_feature_set_index
active_fs = feature_sets[active_idx]
active_fs: 'RigifyFeatureSets' = feature_set_list[active_idx]
# Call the unregister callback of the set being removed.
if active_fs.enabled:
call_register_function(active_fs.module_name, register=False)
call_register_function(active_fs.module_name, do_register=False)
# Remove the feature set's folder from the file system.
rigify_config_path = get_install_path()
@ -256,7 +278,7 @@ class DATA_OT_rigify_remove_feature_set(bpy.types.Operator):
rmtree(set_path)
# Remove the feature set's entry from the addon preferences.
feature_sets.remove(active_idx)
feature_set_list.remove(active_idx)
# Remove the feature set's entries from the metarigs and rig types.
addon_prefs.update_external_rigs()
@ -271,6 +293,7 @@ def register():
bpy.utils.register_class(DATA_OT_rigify_add_feature_set)
bpy.utils.register_class(DATA_OT_rigify_remove_feature_set)
def unregister():
bpy.utils.unregister_class(DATA_OT_rigify_add_feature_set)
bpy.utils.unregister_class(DATA_OT_rigify_remove_feature_set)

View File

@ -5,6 +5,7 @@ import traceback
from string import capwords
from collections import defaultdict
from types import ModuleType
import bpy
@ -16,6 +17,8 @@ from . import feature_set_list
class ArmatureSubMenu(bpy.types.Menu):
# bl_idname = 'ARMATURE_MT_armature_class'
operators: list[tuple[str, str]]
def draw(self, context):
layout = self.layout
layout.label(text=self.bl_label)
@ -24,7 +27,10 @@ class ArmatureSubMenu(bpy.types.Menu):
layout.operator(op, icon='OUTLINER_OB_ARMATURE', text=text)
def get_metarigs(metarigs, base_dir, base_path, *, path=[], nested=False):
# noinspection PyDefaultArgument
def get_metarigs(metarig_table: dict[str, ModuleType | dict],
base_dir: str, base_path: list[str], *,
path: list[str] = [], nested=False):
""" Searches for metarig modules, and returns a list of the
imported modules.
"""
@ -32,7 +38,7 @@ def get_metarigs(metarigs, base_dir, base_path, *, path=[], nested=False):
dir_path = os.path.join(base_dir, *path)
try:
files = os.listdir(dir_path)
files: list[str] = os.listdir(dir_path)
except FileNotFoundError:
files = []
@ -45,25 +51,25 @@ def get_metarigs(metarigs, base_dir, base_path, *, path=[], nested=False):
if f[0] in [".", "_"]:
continue
if f.count(".") >= 2 or (is_dir and "." in f):
print("Warning: %r, filename contains a '.', skipping" % os.path.join(path, f))
print("Warning: %r, filename contains a '.', skipping" % os.path.join(*path, f))
continue
if is_dir: # Check directories
get_metarigs(metarigs[f], base_dir, base_path, path=[*path, f], nested=True) # "" adds a final slash
get_metarigs(metarig_table[f], base_dir, base_path, path=[*path, f], nested=True)
elif f.endswith(".py"):
# Check straight-up python files
f = f[:-3]
module = get_resource('.'.join([*base_path, *path, f]))
if nested:
metarigs[f] = module
metarig_table[f] = module
else:
metarigs[METARIG_DIR][f] = module
metarig_table[METARIG_DIR][f] = module
def make_metarig_add_execute(m):
def make_metarig_add_execute(module):
""" Create an execute method for a metarig creation operator.
"""
def execute(self, context):
def execute(_self, context):
# Add armature object
bpy.ops.object.armature_add()
obj = context.active_object
@ -76,62 +82,70 @@ def make_metarig_add_execute(m):
bones.remove(bones[0])
# Create metarig
m.create(obj)
module.create(obj)
bpy.ops.object.mode_set(mode='OBJECT')
return {'FINISHED'}
return execute
def make_metarig_menu_func(bl_idname, text):
def make_metarig_menu_func(bl_idname: str, text: str):
""" For some reason lambda's don't work for adding multiple menu
items, so we use this instead to generate the functions.
"""
def metarig_menu(self, context):
def metarig_menu(self, _context):
self.layout.operator(bl_idname, icon='OUTLINER_OB_ARMATURE', text=text)
return metarig_menu
def make_submenu_func(bl_idname, text):
def metarig_menu(self, context):
def make_submenu_func(bl_idname: str, text: str):
def metarig_menu(self, _context):
self.layout.menu(bl_idname, icon='OUTLINER_OB_ARMATURE', text=text)
return metarig_menu
# Get the metarig modules
def get_internal_metarigs():
BASE_RIGIFY_DIR = os.path.dirname(__file__)
BASE_RIGIFY_PATH = __name__.split('.')[:-1]
base_rigify_dir = os.path.dirname(__file__)
base_rigify_path = __name__.split('.')[:-1]
get_metarigs(metarigs, os.path.join(BASE_RIGIFY_DIR, METARIG_DIR), [*BASE_RIGIFY_PATH, METARIG_DIR])
get_metarigs(metarigs,
os.path.join(base_rigify_dir, METARIG_DIR),
[*base_rigify_path, METARIG_DIR])
# noinspection SpellCheckingInspection
def infinite_defaultdict():
return defaultdict(infinite_defaultdict)
metarigs = infinite_defaultdict()
metarig_ops = {}
armature_submenus = []
menu_funcs = []
# noinspection PyDefaultArgument
def create_metarig_ops(dic=metarigs):
"""Create metarig add Operators"""
for metarig_category in dic:
if metarig_category == "external":
create_metarig_ops(dic[metarig_category])
continue
if not metarig_category in metarig_ops:
if metarig_category not in metarig_ops:
metarig_ops[metarig_category] = []
for m in dic[metarig_category].values():
name = m.__name__.rsplit('.', 1)[1]
# Dynamically construct an Operator
T = type("Add_" + name + "_Metarig", (bpy.types.Operator,), {})
T.bl_idname = "object.armature_" + name + "_metarig_add"
T.bl_label = "Add " + name.replace("_", " ").capitalize() + " (metarig)"
T.bl_options = {'REGISTER', 'UNDO'}
T.execute = make_metarig_add_execute(m)
op_type = type("Add_" + name + "_Metarig", (bpy.types.Operator,), {})
op_type.bl_idname = "object.armature_" + name + "_metarig_add"
op_type.bl_label = "Add " + name.replace("_", " ").capitalize() + " (metarig)"
op_type.bl_options = {'REGISTER', 'UNDO'}
op_type.execute = make_metarig_add_execute(m)
metarig_ops[metarig_category].append((op_type, name))
metarig_ops[metarig_category].append((T, name))
def create_menu_funcs():
global menu_funcs
@ -139,6 +153,8 @@ def create_menu_funcs():
text = capwords(name.replace("_", " ")) + " (Meta-Rig)"
menu_funcs += [make_metarig_menu_func(mop.bl_idname, text)]
# noinspection PyDefaultArgument,BlIdLowercase
def create_armature_submenus(dic=metarigs):
global menu_funcs
metarig_categories = list(dic.keys())
@ -151,24 +167,29 @@ def create_armature_submenus(dic=metarigs):
if metarig_category == METARIG_DIR:
continue
armature_submenus.append(type('Class_' + metarig_category + '_submenu', (ArmatureSubMenu,), {}))
armature_submenus.append(type('Class_' + metarig_category + '_submenu',
(ArmatureSubMenu,), {}))
armature_submenus[-1].bl_label = metarig_category + ' (submenu)'
armature_submenus[-1].bl_idname = 'ARMATURE_MT_%s_class' % metarig_category
armature_submenus[-1].operators = []
menu_funcs += [make_submenu_func(armature_submenus[-1].bl_idname, metarig_category)]
for mop, name in metarig_ops[metarig_category]:
arm_sub = next((e for e in armature_submenus if e.bl_label == metarig_category + ' (submenu)'), '')
arm_sub = next((e for e in armature_submenus
if e.bl_label == metarig_category + ' (submenu)'),
'')
arm_sub.operators.append((mop.bl_idname, name,))
def init_metarig_menu():
get_internal_metarigs()
create_metarig_ops()
create_menu_funcs()
create_armature_submenus()
### Registering ###
#################
# Registering
def register():
from bpy.utils import register_class
@ -183,6 +204,7 @@ def register():
for mf in menu_funcs:
bpy.types.VIEW3D_MT_armature_add.append(mf)
def unregister():
from bpy.utils import unregister_class
@ -196,7 +218,8 @@ def unregister():
for mf in menu_funcs:
bpy.types.VIEW3D_MT_armature_add.remove(mf)
def get_external_metarigs(feature_module_names):
def get_external_metarigs(feature_module_names: list[str]):
unregister()
# Clear and fill metarigs public variables
@ -204,12 +227,14 @@ def get_external_metarigs(feature_module_names):
get_internal_metarigs()
for module_name in feature_module_names:
# noinspection PyBroadException
try:
base_dir, base_path = feature_set_list.get_dir_path(module_name, METARIG_DIR)
get_metarigs(metarigs['external'], base_dir, base_path)
except Exception:
print("Rigify Error: Could not load feature set '%s' metarigs: exception occurred.\n" % (feature_set))
print(f"Rigify Error: Could not load feature set '{module_name}' metarigs: "
f"exception occurred.\n")
traceback.print_exc()
print("")
continue

View File

@ -16,7 +16,7 @@ loaded_submodules = []
def register():
# Lazily load modules to make reloading easier. Loading this way
# hides the sub-modules and their dependencies from initial_load_order.
# hides the submodules and their dependencies from initial_load_order.
loaded_submodules[:] = [
importlib.import_module(__name__ + '.' + name) for name in submodules
]

View File

@ -15,13 +15,11 @@ from ..utils.action_layers import ActionSlotBase
def get_action_slots(arm: Armature) -> Sequence['ActionSlot']:
# noinspection PyUnresolvedReferences
return arm.rigify_action_slots
return arm.rigify_action_slots # noqa
def get_action_slots_active(arm: Armature) -> tuple[Sequence['ActionSlot'], int]:
# noinspection PyUnresolvedReferences
return arm.rigify_action_slots, arm.rigify_active_action_slot
return arm.rigify_action_slots, arm.rigify_active_action_slot # noqa
def poll_trigger_action(_self, action):
@ -176,10 +174,11 @@ def find_duplicate_slot(metarig_data: Armature, action_slot: ActionSlot) -> Opti
return None
# =============================================
# Operators
# noinspection PyPep8Naming
class RIGIFY_OT_action_create(Operator):
"""Create new Action"""
# This is needed because bpy.ops.action.new() has a poll function that blocks
@ -199,6 +198,7 @@ class RIGIFY_OT_action_create(Operator):
return {'FINISHED'}
# noinspection PyPep8Naming
class RIGIFY_OT_jump_to_action_slot(Operator):
"""Set Active Action Slot Index"""
@ -217,6 +217,7 @@ class RIGIFY_OT_jump_to_action_slot(Operator):
# =============================================
# UI Panel
# noinspection PyPep8Naming
class RIGIFY_UL_action_slots(UIList):
def draw_item(self, context: Context, layout: UILayout, data: Armature,
action_slot: ActionSlot, icon, active_data, active_propname: str,
@ -310,6 +311,7 @@ class RIGIFY_UL_action_slots(UIList):
layout.label(text="", icon_value=icon)
# noinspection PyPep8Naming
class DATA_PT_rigify_actions(Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'

View File

@ -6,13 +6,14 @@ import importlib
from ..utils.naming import Side, get_name_base_and_sides, mirror_name
from ..utils.misc import property_to_python
from ..utils.rig import get_rigify_type
from ..utils.rig import get_rigify_type, get_rigify_params
from ..rig_lists import get_rig_class
# =============================================
# Single parameter copy button
# noinspection PyPep8Naming
class POSE_OT_rigify_copy_single_parameter(bpy.types.Operator):
bl_idname = "pose.rigify_copy_single_parameter"
bl_label = "Copy Option To Selected Rigs"
@ -49,7 +50,8 @@ class POSE_OT_rigify_copy_single_parameter(bpy.types.Operator):
active_pbone = context.active_pose_bone
active_split = get_name_base_and_sides(active_pbone.name)
value = getattr(active_pbone.rigify_parameters, self.property_name)
params = get_rigify_params(active_pbone)
value = getattr(params, self.property_name)
num_copied = 0
# Copy to different bones of appropriate rig types
@ -70,8 +72,8 @@ class POSE_OT_rigify_copy_single_parameter(bpy.types.Operator):
new_value = mirror_name(value)
# Assign the final value
setattr(sel_pbone.rigify_parameters,
self.property_name, new_value)
sel_params = get_rigify_params(sel_pbone)
setattr(sel_params, self.property_name, new_value)
num_copied += 1
if num_copied:
@ -96,7 +98,7 @@ def recursive_mirror(value):
"""Mirror strings(.L/.R) in any mixed structure of dictionaries/lists."""
if isinstance(value, dict):
return { key: recursive_mirror(val) for key, val in value.items() }
return {key: recursive_mirror(val) for key, val in value.items()}
elif isinstance(value, list):
return [recursive_mirror(elem) for elem in value]
@ -108,12 +110,15 @@ def recursive_mirror(value):
return value
def copy_rigify_params(from_bone: bpy.types.PoseBone, to_bone: bpy.types.PoseBone, *, match_type=False, x_mirror=False) -> bool:
rig_type = to_bone.rigify_type
if match_type and to_bone.rigify_type != from_bone.rigify_type:
def copy_rigify_params(from_bone: bpy.types.PoseBone, to_bone: bpy.types.PoseBone, *,
match_type=False, x_mirror=False) -> bool:
rig_type = get_rigify_type(to_bone)
from_type = get_rigify_type(from_bone)
if match_type and rig_type != from_type:
return False
else:
rig_type = to_bone.rigify_type = get_rigify_type(from_bone)
rig_type = to_bone.rigify_type = from_type
from_params = from_bone.get('rigify_parameters')
if from_params and rig_type:
@ -129,6 +134,7 @@ def copy_rigify_params(from_bone: bpy.types.PoseBone, to_bone: bpy.types.PoseBon
return True
# noinspection PyPep8Naming
class POSE_OT_rigify_mirror_parameters(bpy.types.Operator):
"""Mirror Rigify type and parameters of selected bones to the opposite side. Names should end in L/R"""
@ -163,7 +169,9 @@ class POSE_OT_rigify_mirror_parameters(bpy.types.Operator):
continue
if flip_bone != pb and flip_bone.bone.select:
self.report(
{'ERROR'}, f"Bone {pb.name} selected on both sides, mirroring would be ambiguous, aborting. Only select the left or right side, not both!")
{'ERROR'},
f"Bone {pb.name} selected on both sides, mirroring would be ambiguous, "
f"aborting. Only select the left or right side, not both!")
return {'CANCELLED'}
# Then mirror the parameters.
@ -180,6 +188,7 @@ class POSE_OT_rigify_mirror_parameters(bpy.types.Operator):
return {'FINISHED'}
# noinspection PyPep8Naming
class POSE_OT_rigify_copy_parameters(bpy.types.Operator):
"""Copy Rigify type and parameters from active to selected bones"""
@ -188,9 +197,10 @@ class POSE_OT_rigify_copy_parameters(bpy.types.Operator):
bl_options = {'REGISTER', 'UNDO'}
match_type: bpy.props.BoolProperty(
name = "Match Type",
description = "Only mirror rigify parameters to selected bones which have the same rigify type as the active bone",
default = False
name="Match Type",
description="Only mirror rigify parameters to selected bones which have the same rigify "
"type as the active bone",
default=False
)
@classmethod
@ -200,7 +210,7 @@ class POSE_OT_rigify_copy_parameters(bpy.types.Operator):
return False
active = context.active_pose_bone
if not active or not active.rigify_type:
if not active or not get_rigify_type(active):
return False
select = context.selected_pose_bones
@ -218,7 +228,8 @@ class POSE_OT_rigify_copy_parameters(bpy.types.Operator):
continue
num_copied += copy_rigify_params(active_bone, pb, match_type=self.match_type)
self.report({'INFO'}, f"Copied {active_bone.rigify_type} parameters to {num_copied} bones.")
self.report({'INFO'},
f"Copied {get_rigify_type(active_bone)} parameters to {num_copied} bones.")
return {'FINISHED'}
@ -228,14 +239,15 @@ def draw_copy_mirror_ops(self, context):
if context.mode == 'POSE':
layout.separator()
op = layout.operator(POSE_OT_rigify_copy_parameters.bl_idname,
icon='DUPLICATE', text="Copy Only Parameters")
icon='DUPLICATE', text="Copy Only Parameters")
op.match_type = True
op = layout.operator(POSE_OT_rigify_copy_parameters.bl_idname,
icon='DUPLICATE', text="Copy Type & Parameters")
icon='DUPLICATE', text="Copy Type & Parameters")
op.match_type = False
layout.operator(POSE_OT_rigify_mirror_parameters.bl_idname,
icon='MOD_MIRROR', text="Mirror Type & Parameters")
# =============================================
# Registration
@ -251,11 +263,14 @@ def register():
for cls in classes:
register_class(cls)
bpy.types.VIEW3D_MT_rigify.append(draw_copy_mirror_ops)
from bpy.types import VIEW3D_MT_rigify # noqa
VIEW3D_MT_rigify.append(draw_copy_mirror_ops)
def unregister():
from bpy.utils import unregister_class
for cls in classes:
unregister_class(cls)
bpy.types.VIEW3D_MT_rigify.remove(draw_copy_mirror_ops)
from bpy.types import VIEW3D_MT_rigify # noqa
VIEW3D_MT_rigify.remove(draw_copy_mirror_ops)

View File

@ -29,6 +29,7 @@ class GenericUIListOperator(Operator):
set_context_attr(context, self.active_idx_context_path, index)
# noinspection PyPep8Naming
class UILIST_OT_entry_remove(GenericUIListOperator):
"""Remove the selected entry from the list"""
@ -48,6 +49,7 @@ class UILIST_OT_entry_remove(GenericUIListOperator):
return {'FINISHED'}
# noinspection PyPep8Naming
class UILIST_OT_entry_add(GenericUIListOperator):
"""Add an entry to the list"""
@ -67,6 +69,7 @@ class UILIST_OT_entry_add(GenericUIListOperator):
return {'FINISHED'}
# noinspection PyPep8Naming
class UILIST_OT_entry_move(GenericUIListOperator):
"""Move an entry in the list up or down"""

View File

@ -7,7 +7,6 @@ from functools import partial
from mathutils import Vector
from ..utils.errors import MetarigError
from ..utils.bones import align_bone_roll
from ..utils.rig import get_rigify_type
from ..utils.node_merger import NodeMerger
@ -184,7 +183,7 @@ def process_all(process, name_map):
def make_new_bones(obj, name_map):
eb = obj.data.edit_bones
face_bone = name_map['face']
# face_bone = name_map['face']
bone = eb.new(name='jaw_master')
bone.head = (eb['jaw.R'].head + eb['jaw.L'].head) / 2
@ -203,11 +202,11 @@ def make_new_bones(obj, name_map):
def align_bones(bones):
prev_mat = eb[bones[0]].matrix
for bone in bones[1:]:
ebone = eb[bone]
_, angle = (prev_mat.inverted() @ ebone.matrix).to_quaternion().to_swing_twist('Y')
ebone.roll -= angle
prev_mat = ebone.matrix
for bone_name in bones[1:]:
edit_bone = eb[bone_name]
_, angle = (prev_mat.inverted() @ edit_bone.matrix).to_quaternion().to_swing_twist('Y')
edit_bone.roll -= angle
prev_mat = edit_bone.matrix
align_bones(['ear.L', 'ear.L.001', 'ear.L.002', 'ear.L.003', 'ear.L.004'])
align_bones(['ear.R', 'ear.R.001', 'ear.R.002', 'ear.R.003', 'ear.R.004'])
@ -231,15 +230,15 @@ def make_new_bones(obj, name_map):
tail = getattr(eb[to_name], to_end)
return (head - tail).length < 2 * NodeMerger.epsilon
def bridge(name, from_name, from_end, to_name, to_end, roll=0):
def bridge(name, from_name, from_end, to_name, to_end, roll: str | int = 0):
if is_same_pos(from_name, from_end, to_name, to_end):
raise MetarigError(f"Locations of {from_name} {from_end} and {to_name} {to_end} overlap.")
bone = eb.new(name=name)
bone.head = getattr(eb[from_name], from_end)
bone.tail = getattr(eb[to_name], to_end)
bone.roll = (eb[from_name].roll + eb[to_name].roll) / 2 if roll == 'mix' else radians(roll)
name_map[name] = bone.name
edit_bone = eb.new(name=name)
edit_bone.head = getattr(eb[from_name], from_end)
edit_bone.tail = getattr(eb[to_name], to_end)
edit_bone.roll = (eb[from_name].roll + eb[to_name].roll) / 2 if roll == 'mix' else radians(roll)
name_map[name] = edit_bone.name
def bridge_glue(name, from_name, to_name):
bridge(name, from_name, 'head', to_name, 'head', roll=-45 if 'R' in name else 45)
@ -269,23 +268,23 @@ def make_new_bones(obj, name_map):
bridge('chin_end_glue.001', 'chin.001', 'tail', 'lip.B.L', 'head', roll=45)
def check_bone(obj, name_map, bone, **kwargs):
def check_bone(obj, name_map, bone, **_kwargs):
bone = name_map.get(bone, bone)
if bone not in obj.pose.bones:
raise MetarigError("Bone '%s' not found" % (bone))
raise MetarigError(f"Bone '{bone}' not found")
def parent_bone(obj, name_map, bone, parent=None, connect=False, **kwargs):
def parent_bone(obj, name_map, bone, parent=None, connect=False, **_kwargs):
if parent is not None:
bone = name_map.get(bone, bone)
parent = name_map.get(parent, parent)
ebone = obj.data.edit_bones[bone]
ebone.use_connect = connect
ebone.parent = obj.data.edit_bones[parent]
edit_bone = obj.data.edit_bones[bone]
edit_bone.use_connect = connect
edit_bone.parent = obj.data.edit_bones[parent]
def set_layers(obj, name_map, layer_table, bone, layer=2, pri_layer=None, sec_layer=None, **kwargs):
def set_layers(obj, name_map, layer_table, bone, layer=2, pri_layer=None, sec_layer=None, **_kwargs):
bone = name_map.get(bone, bone)
pbone = obj.pose.bones[bone]
pbone.bone.layers = layer_table[layer]
@ -306,12 +305,13 @@ connect_ends_map = {
}
# noinspection PyDefaultArgument
def set_rig(
obj, name_map, bone, rig=None,
connect_ends=None, priority=0, middle=0, sharpen=None,
falloff=None, spherical=None, falloff_length=False, scale=False,
glue_copy=None, glue_reparent=False,
params={}, **kwargs
params={}, **_kwargs
):
bone = name_map.get(bone, bone)
if rig is not None:
@ -406,12 +406,14 @@ def update_face_rig(obj):
obj.data.layers[i] = True
# noinspection PyPep8Naming
class POSE_OT_rigify_upgrade_face(bpy.types.Operator):
"""Upgrade the legacy super_face rig type to new modular face"""
bl_idname = "pose.rigify_upgrade_face"
bl_label = "Upgrade Face Rig"
bl_description = 'Upgrades the legacy super_face rig type to the new modular face. This preserves compatibility with existing weight painting, but not animation'
bl_description = 'Upgrades the legacy super_face rig type to the new modular face. This '\
'preserves compatibility with existing weight painting, but not animation'
bl_options = {'UNDO'}
@classmethod

View File

@ -3,22 +3,28 @@
import os
import traceback
import importlib
import typing
from typing import Optional
from .utils.rig import RIG_DIR
from . import feature_set_list
def get_rigs(base_dir, base_path, *, path=[], feature_set=feature_set_list.DEFAULT_NAME):
# noinspection PyDefaultArgument
def get_rigs(base_dir: str, base_path: list[str], *,
path: list[str] = [],
feature_set=feature_set_list.DEFAULT_NAME):
""" Recursively searches for rig types, and returns a list.
:param base_path: base dir where rigs are stored
:type path:str
:param path: rig path inside the base dir
:type path:str
Args:
base_dir: root directory
base_path: base dir where rigs are stored
path: rig path inside the base dir
feature_set: feature set that is being loaded
"""
rigs = {}
rig_table = {}
impl_rigs = {}
dir_path = os.path.join(base_dir, *path)
@ -43,40 +49,44 @@ def get_rigs(base_dir, base_path, *, path=[], feature_set=feature_set_list.DEFAU
if is_dir:
# Check for sub-rigs
sub_rigs, sub_impls = get_rigs(base_dir, base_path, path=[*path, f], feature_set=feature_set)
rigs.update(sub_rigs)
rig_table.update(sub_rigs)
impl_rigs.update(sub_impls)
elif f.endswith(".py"):
# Check straight-up python files
subpath = [*path, f[:-3]]
key = '.'.join(subpath)
sub_path = [*path, f[:-3]]
key = '.'.join(sub_path)
# Don't reload rig modules - it breaks isinstance
rig_module = importlib.import_module('.'.join(base_path + subpath))
rig_module = importlib.import_module('.'.join(base_path + sub_path))
if hasattr(rig_module, "Rig"):
rigs[key] = {"module": rig_module,
"feature_set": feature_set}
rig_table[key] = {"module": rig_module,
"feature_set": feature_set}
if hasattr(rig_module, 'IMPLEMENTATION') and rig_module.IMPLEMENTATION:
impl_rigs[key] = rig_module
return rigs, impl_rigs
return rig_table, impl_rigs
# Public variables
rigs = {}
implementation_rigs = {}
def get_rig_class(name):
def get_rig_class(name: str) -> Optional[typing.Type]:
try:
return rigs[name]["module"].Rig
except (KeyError, AttributeError):
return None
def get_internal_rigs():
global rigs, implementation_rigs
BASE_RIGIFY_DIR = os.path.dirname(__file__)
BASE_RIGIFY_PATH = __name__.split('.')[:-1]
base_rigify_dir = os.path.dirname(__file__)
base_rigify_path = __name__.split('.')[:-1]
rigs, implementation_rigs = get_rigs(os.path.join(base_rigify_dir, RIG_DIR),
[*base_rigify_path, RIG_DIR])
rigs, implementation_rigs = get_rigs(os.path.join(BASE_RIGIFY_DIR, RIG_DIR), [*BASE_RIGIFY_PATH, RIG_DIR])
def get_external_rigs(set_list):
# Clear and fill rigify rigs and implementation rigs public variables
@ -88,12 +98,13 @@ def get_external_rigs(set_list):
# Get external rigs
for feature_set in set_list:
# noinspection PyBroadException
try:
base_dir, base_path = feature_set_list.get_dir_path(feature_set, RIG_DIR)
external_rigs, external_impl_rigs = get_rigs(base_dir, base_path, feature_set=feature_set)
except Exception:
print("Rigify Error: Could not load feature set '%s' rigs: exception occurred.\n" % (feature_set))
print(f"Rigify Error: Could not load feature set '{feature_set}' rigs: exception occurred.\n")
traceback.print_exc()
print("")
continue

View File

@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later
'''
Quat/Euler Rotation Mode Converter v0.1
"""
Quaternion/Euler Rotation Mode Converter v0.1
This script/addon:
- Changes (pose) bone rotation mode
@ -13,19 +13,19 @@ This script/addon:
TO-DO:
- To convert object's rotation mode (already done in Mutant Bob script,
but not done in this one.
but not done in this one.)
- To understand "EnumProperty" and write it well.
- Code clean
- ...
GitHub: https://github.com/MarioMey/rotation_mode_addon/
BlenderArtist thread: http://blenderartists.org/forum/showthread.php?388197-Quat-Euler-Rotation-Mode-Converter
BlenderArtist thread: https://blenderartists.org/forum/showthread.php?388197-Quat-Euler-Rotation-Mode-Converter
Mutant Bob did the "hard code" of this script. Thanks him!
blender.stackexchange.com/questions/40711/how-to-convert-quaternions-keyframes-to-euler-ones-in-several-actions
'''
"""
# bl_info = {
# "name": "Rotation Mode Converter",
@ -56,6 +56,8 @@ def get_or_create_fcurve(action, data_path, array_index=-1, group=None):
fc.group = group
return fc
# noinspection SpellCheckingInspection
def add_keyframe_quat(action, quat, frame, bone_prefix, group):
for i in range(len(quat)):
fc = get_or_create_fcurve(action, bone_prefix + "rotation_quaternion", i, group)
@ -64,6 +66,7 @@ def add_keyframe_quat(action, quat, frame, bone_prefix, group):
fc.keyframe_points[pos].co = [frame, quat[i]]
fc.update()
def add_keyframe_euler(action, euler, frame, bone_prefix, group):
for i in range(len(euler)):
fc = get_or_create_fcurve(action, bone_prefix + "rotation_euler", i, group)
@ -72,6 +75,7 @@ def add_keyframe_euler(action, euler, frame, bone_prefix, group):
fc.keyframe_points[pos].co = [frame, euler[i]]
fc.update()
def frames_matching(action, data_path):
frames = set()
for fc in action.fcurves:
@ -80,14 +84,16 @@ def frames_matching(action, data_path):
frames.update(fri)
return frames
def group_qe(obj, action, bone, bone_prefix, order):
"""Converts only one group/bone in one action - Quat to euler."""
pose_bone = bone
def group_qe(_obj, action, bone, bone_prefix, order):
"""Converts only one group/bone in one action - Quaternion to euler."""
# pose_bone = bone
data_path = bone_prefix + "rotation_quaternion"
frames = frames_matching(action, data_path)
group = action.groups[bone.name]
for fr in frames:
# noinspection SpellCheckingInspection
quat = bone.rotation_quaternion.copy()
for fc in action.fcurves:
if fc.data_path == data_path:
@ -97,9 +103,10 @@ def group_qe(obj, action, bone, bone_prefix, order):
add_keyframe_euler(action, euler, fr, bone_prefix, group)
bone.rotation_mode = order
def group_eq(obj, action, bone, bone_prefix, order):
"""Converts only one group/bone in one action - Euler to Quat."""
pose_bone = bone
def group_eq(_obj, action, bone, bone_prefix, order):
"""Converts only one group/bone in one action - Euler to Quaternion."""
# pose_bone = bone
data_path = bone_prefix + "rotation_euler"
frames = frames_matching(action, data_path)
group = action.groups[bone.name]
@ -109,11 +116,13 @@ def group_eq(obj, action, bone, bone_prefix, order):
for fc in action.fcurves:
if fc.data_path == data_path:
euler[fc.array_index] = fc.evaluate(fr)
# noinspection SpellCheckingInspection
quat = euler.to_quaternion()
add_keyframe_quat(action, quat, fr, bone_prefix, group)
bone.rotation_mode = order
def convert_curves_of_bone_in_action(obj, action, bone, order):
"""Convert given bone's curves in given action to given rotation order."""
to_euler = False
@ -129,7 +138,7 @@ def convert_curves_of_bone_in_action(obj, action, bone, order):
bone_prefix = fcurve.data_path[:-len('rotation_quaternion')]
break
# If To-Quat conversion
# If To-Quaternion conversion
else:
if fcurve.data_path.endswith('rotation_euler'):
to_euler = True
@ -138,7 +147,7 @@ def convert_curves_of_bone_in_action(obj, action, bone, order):
# If To-Euler conversion
if to_euler and order != 'QUATERNION':
# Converts the group/bone from Quat to Euler
# Converts the group/bone from Quaternion to Euler
group_qe(obj, action, bone, bone_prefix, order)
# Removes quaternion fcurves
@ -146,9 +155,9 @@ def convert_curves_of_bone_in_action(obj, action, bone, order):
if key.data_path == 'pose.bones["' + bone.name + '"].rotation_quaternion':
action.fcurves.remove(key)
# If To-Quat conversion
# If To-Quaternion conversion
elif to_euler:
# Converts the group/bone from Euler to Quat
# Converts the group/bone from Euler to Quaternion
group_eq(obj, action, bone, bone_prefix, order)
# Removes euler fcurves
@ -159,6 +168,8 @@ def convert_curves_of_bone_in_action(obj, action, bone, order):
# Changes rotation mode to new one
bone.rotation_mode = order
# noinspection PyPep8Naming
class POSE_OT_convert_rotation(bpy.types.Operator):
bl_label = 'Convert Rotation Modes'
bl_idname = 'pose.convert_rotation'
@ -202,7 +213,7 @@ class POSE_OT_convert_rotation(bpy.types.Operator):
def invoke(self, context, event):
ob = context.object
if ob and ob.type=='ARMATURE' and ob.animation_data and ob.animation_data.action:
if ob and ob.type == 'ARMATURE' and ob.animation_data and ob.animation_data.action:
self.selected_action = context.object.animation_data.action.name
else:
self.affected_actions = 'ALL'
@ -217,7 +228,7 @@ class POSE_OT_convert_rotation(bpy.types.Operator):
layout.row().prop(self, 'affected_bones', expand=True)
layout.row().prop(self, 'affected_actions', expand=True)
if self.affected_actions=='SINGLE':
if self.affected_actions == 'SINGLE':
layout.prop_search(self, 'selected_action', bpy.data, 'actions')
layout.prop(self, 'target_rotation_mode')
@ -226,9 +237,9 @@ class POSE_OT_convert_rotation(bpy.types.Operator):
actions = [bpy.data.actions.get(self.selected_action)]
pose_bones = context.selected_pose_bones
if self.affected_bones=='ALL':
if self.affected_bones == 'ALL':
pose_bones = obj.pose.bones
if self.affected_actions=='ALL':
if self.affected_actions == 'ALL':
actions = bpy.data.actions
for action in actions:
@ -237,14 +248,17 @@ class POSE_OT_convert_rotation(bpy.types.Operator):
return {'FINISHED'}
def draw_convert_rotation(self, context):
def draw_convert_rotation(self, _context):
self.layout.separator()
self.layout.operator(POSE_OT_convert_rotation.bl_idname)
classes = [
POSE_OT_convert_rotation
]
def register():
from bpy.utils import register_class
@ -254,6 +268,7 @@ def register():
bpy.types.VIEW3D_MT_pose.append(draw_convert_rotation)
def unregister():
from bpy.utils import unregister_class

File diff suppressed because it is too large Load Diff

View File

@ -287,11 +287,9 @@ MeshObject = TypedObject[bpy.types.Mesh]
def verify_armature_obj(obj: bpy.types.Object) -> ArmatureObject:
assert obj and obj.type == 'ARMATURE'
# noinspection PyTypeChecker
return obj
return obj # noqa
def verify_mesh_obj(obj: bpy.types.Object) -> MeshObject:
assert obj and obj.type == 'MESH'
# noinspection PyTypeChecker
return obj
return obj # noqa

View File

@ -6,11 +6,10 @@ import importlib.util
import re
from itertools import count
from typing import TYPE_CHECKING, Any, Optional, Sequence
from typing import TYPE_CHECKING, Any, Optional, Sequence, Mapping
from bpy.types import bpy_struct, Constraint, Object, PoseBone, Bone, Armature
# noinspection PyUnresolvedReferences
from bpy.types import bpy_prop_array
from bpy.types import bpy_prop_array # noqa
from .misc import ArmatureObject
@ -48,23 +47,24 @@ outdated_types = {"pitchipoy.limbs.super_limb": "limbs.super_limb",
def get_rigify_type(pose_bone: PoseBone) -> str:
# noinspection PyUnresolvedReferences
return pose_bone.rigify_type.replace(" ", "")
rigify_type = pose_bone.rigify_type # noqa
return rigify_type.replace(" ", "")
def get_rigify_params(pose_bone: PoseBone) -> Any:
# noinspection PyUnresolvedReferences
return pose_bone.rigify_parameters
return pose_bone.rigify_parameters # noqa
def get_rigify_colors(arm: Armature) -> Sequence['RigifyColorSet']:
# noinspection PyUnresolvedReferences
return arm.rigify_colors
def get_rigify_colors(arm: Armature) -> Sequence['RigifyColorSet'] | Mapping[str, 'RigifyColorSet']:
return arm.rigify_colors # noqa
def get_rigify_layers(arm: Armature) -> Sequence['RigifyArmatureLayer']:
# noinspection PyUnresolvedReferences
return arm.rigify_layers
return arm.rigify_layers # noqa
def get_rigify_target_rig(arm: Armature) -> Optional[ArmatureObject]:
return arm.rigify_target_rig # noqa
def is_rig_base_bone(obj: Object, name):
@ -207,10 +207,8 @@ def _get_property_value(obj, name: str):
def _generate_properties(lines, prefix, obj: bpy_struct, base_class: type, *,
defaults: dict[str, Any] = {},
objects: dict[Any, str] = {}):
# noinspection PyUnresolvedReferences
obj_rna: bpy.types.Struct = type(obj).bl_rna
# noinspection PyUnresolvedReferences
base_rna: bpy.types.Struct = base_class.bl_rna
obj_rna: bpy.types.Struct = type(obj).bl_rna # noqa
base_rna: bpy.types.Struct = base_class.bl_rna # noqa
block_props = set(prop.identifier for prop in base_rna.properties) - set(defaults.keys())