Rigify: finish clearing out warnings in functional code.
This commit is contained in:
parent
ad22263327
commit
3d89a38c19
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
]
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
451
rigify/ui.py
451
rigify/ui.py
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
Loading…
Reference in New Issue