Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'rigify/utils')
-rw-r--r--rigify/utils/animation.py212
-rw-r--r--rigify/utils/bones.py14
-rw-r--r--rigify/utils/components.py87
-rw-r--r--rigify/utils/layers.py13
-rw-r--r--rigify/utils/misc.py8
-rw-r--r--rigify/utils/rig.py1
-rw-r--r--rigify/utils/switch_parent.py118
-rw-r--r--rigify/utils/widgets_basic.py2
8 files changed, 340 insertions, 115 deletions
diff --git a/rigify/utils/animation.py b/rigify/utils/animation.py
index 62042923..1355a0b6 100644
--- a/rigify/utils/animation.py
+++ b/rigify/utils/animation.py
@@ -377,20 +377,24 @@ TRANSFORM_PROPS_ROTATION = frozenset(['rotation_euler', 'rotation_quaternion', '
TRANSFORM_PROPS_SCALE = frozenset(['scale'])
TRANSFORM_PROPS_ALL = frozenset(TRANSFORM_PROPS_LOCATION | TRANSFORM_PROPS_ROTATION | TRANSFORM_PROPS_SCALE)
-class ActionCurveTable(object):
+def transform_props_with_locks(lock_location, lock_rotation, lock_scale):
+ props = set()
+ if not lock_location:
+ props |= TRANSFORM_PROPS_LOCATION
+ if not lock_rotation:
+ props |= TRANSFORM_PROPS_ROTATION
+ if not lock_scale:
+ props |= TRANSFORM_PROPS_SCALE
+ return props
+
+class FCurveTable(object):
"Table for efficient lookup of FCurves by properties."
- def __init__(self, action):
- from collections import defaultdict
- self.action = find_action(action)
- self.curve_map = defaultdict(dict)
- self.index_action()
+ def __init__(self):
+ self.curve_map = collections.defaultdict(dict)
- def index_action(self):
- if not self.action:
- return
-
- for curve in self.action.fcurves:
+ def index_curves(self, curves):
+ for curve in curves:
index = curve.array_index
if index < 0:
index = 0
@@ -412,6 +416,24 @@ class ActionCurveTable(object):
def get_custom_prop_curves(self, ptr, prop):
return self.get_prop_curves(ptr, rna_idprop_quote_path(prop))
+
+class ActionCurveTable(FCurveTable):
+ "Table for efficient lookup of Action FCurves by properties."
+
+ def __init__(self, action):
+ super().__init__()
+ self.action = find_action(action)
+ if self.action:
+ self.index_curves(self.action.fcurves)
+
+class DriverCurveTable(FCurveTable):
+ "Table for efficient lookup of Driver FCurves by properties."
+
+ def __init__(self, object):
+ super().__init__()
+ self.anim_data = object.animation_data
+ if self.anim_data:
+ self.index_curves(self.anim_data.drivers)
''']
exec(SCRIPT_UTILITIES_CURVES[-1])
@@ -495,7 +517,23 @@ SCRIPT_UTILITIES_BAKE = SCRIPT_UTILITIES_KEYING + SCRIPT_UTILITIES_CURVES + ['''
# Keyframe baking operator framework ##
#######################################
-class RigifyBakeKeyframesMixin:
+class RigifyOperatorMixinBase:
+ bl_options = {'UNDO', 'INTERNAL'}
+
+ def init_invoke(self, context):
+ "Override to initialize the operator before invoke."
+
+ def init_execute(self, context):
+ "Override to initialize the operator before execute."
+
+ def before_save_state(self, context, rig):
+ "Override to prepare for saving state."
+
+ def after_save_state(self, context, rig):
+ "Override to undo before_save_state."
+
+
+class RigifyBakeKeyframesMixin(RigifyOperatorMixinBase):
"""Basic framework for an operator that updates a set of keyed frames."""
# Utilities
@@ -566,6 +604,7 @@ class RigifyBakeKeyframesMixin:
self.bake_state = dict()
self.keyflags = get_keying_flags(context)
+ self.keyflags_switch = None
if context.window_manager.rigify_transfer_use_all_keys:
self.bake_add_curve_frames(self.bake_curve_table.curve_map)
@@ -604,9 +643,15 @@ class RigifyBakeKeyframesMixin:
scene = context.scene
saved_state = self.bake_state
- for frame in self.bake_frames:
- scene.frame_set(frame)
- saved_state[frame] = self.save_frame_state(context, rig)
+ try:
+ self.before_save_state(context, rig)
+
+ for frame in self.bake_frames:
+ scene.frame_set(frame)
+ saved_state[frame] = self.save_frame_state(context, rig)
+
+ finally:
+ self.after_save_state(context, rig)
def bake_clean_curves_in_range(self, context, curves):
"Deletes all keys from the given curves in the bake range."
@@ -648,10 +693,6 @@ class RigifyBakeKeyframesMixin:
"Override to execute code one time before the bake apply frame scan."
pass
- def init_execute(self, context):
- "Override to initialize the operator."
- pass
-
def execute(self, context):
self.init_execute(context)
self.bake_init(context)
@@ -661,18 +702,20 @@ class RigifyBakeKeyframesMixin:
if self.report_bake_empty():
return {'CANCELLED'}
- self.bake_save_state(context)
+ try:
+ self.bake_save_state(context)
- range, range_raw = self.bake_clean_curves_in_range(context, curves)
+ range, range_raw = self.bake_clean_curves_in_range(context, curves)
- self.execute_before_apply(context, self.bake_rig, range, range_raw)
+ self.execute_before_apply(context, self.bake_rig, range, range_raw)
- self.bake_apply_state(context)
- return {'FINISHED'}
+ self.bake_apply_state(context)
- def init_invoke(self, context):
- "Override to initialize the operator."
- pass
+ except Exception as e:
+ traceback.print_exc()
+ self.report({'ERROR'}, 'Exception: ' + str(e))
+
+ return {'FINISHED'}
def invoke(self, context, event):
self.init_invoke(context)
@@ -683,22 +726,29 @@ class RigifyBakeKeyframesMixin:
return context.window_manager.invoke_confirm(self, event)
-class RigifySingleUpdateMixin:
+class RigifySingleUpdateMixin(RigifyOperatorMixinBase):
"""Basic framework for an operator that updates only the current frame."""
- def init_execute(self, context):
- pass
-
def execute(self, context):
self.init_execute(context)
obj = context.active_object
self.keyflags = get_autokey_flags(context, ignore_keyset=True)
self.keyflags_switch = add_flags_if_set(self.keyflags, {'INSERTKEY_AVAILABLE'})
- self.apply_frame_state(context, obj, self.save_frame_state(context, obj))
- return {'FINISHED'}
- def init_invoke(self, context):
- pass
+ try:
+ try:
+ self.before_save_state(context, obj)
+ state = self.save_frame_state(context, obj)
+ finally:
+ self.after_save_state(context, obj)
+
+ self.apply_frame_state(context, obj, state)
+
+ except Exception as e:
+ traceback.print_exc()
+ self.report({'ERROR'}, 'Exception: ' + str(e))
+
+ return {'FINISHED'}
def invoke(self, context, event):
self.init_invoke(context)
@@ -773,51 +823,59 @@ def add_clear_keyframes_button(panel, *, bones=[], label='', text=''):
# Generic Snap FK to IK operator ##
###################################
-SCRIPT_REGISTER_OP_SNAP_FK_IK = ['POSE_OT_rigify_generic_fk2ik', 'POSE_OT_rigify_generic_fk2ik_bake']
+SCRIPT_REGISTER_OP_SNAP = ['POSE_OT_rigify_generic_snap', 'POSE_OT_rigify_generic_snap_bake']
-SCRIPT_UTILITIES_OP_SNAP_FK_IK = ['''
-###########################
-## Generic Snap FK to IK ##
-###########################
+SCRIPT_UTILITIES_OP_SNAP = ['''
+#############################
+## Generic Snap (FK to IK) ##
+#############################
-class RigifyGenericFk2IkBase:
- fk_bones: StringProperty(name="FK Bone Chain")
- ik_bones: StringProperty(name="IK Result Bone Chain")
- ctrl_bones: StringProperty(name="IK Controls")
+class RigifyGenericSnapBase:
+ input_bones: StringProperty(name="Input Chain")
+ output_bones: StringProperty(name="Output Chain")
+ ctrl_bones: StringProperty(name="Input Controls")
+ tooltip: StringProperty(name="Tooltip", default="FK to IK")
+ locks: bpy.props.BoolVectorProperty(name="Locked", size=3, default=[False,False,False])
undo_copy_scale: bpy.props.BoolProperty(name="Undo Copy Scale", default=False)
- keyflags = None
-
def init_execute(self, context):
- self.fk_bone_list = json.loads(self.fk_bones)
- self.ik_bone_list = json.loads(self.ik_bones)
+ self.input_bone_list = json.loads(self.input_bones)
+ self.output_bone_list = json.loads(self.output_bones)
self.ctrl_bone_list = json.loads(self.ctrl_bones)
def save_frame_state(self, context, obj):
- return get_chain_transform_matrices(obj, self.ik_bone_list)
+ return get_chain_transform_matrices(obj, self.input_bone_list)
def apply_frame_state(self, context, obj, matrices):
set_chain_transforms_from_matrices(
- context, obj, self.fk_bone_list, matrices,
- undo_copy_scale=self.undo_copy_scale, keyflags=self.keyflags
+ context, obj, self.output_bone_list, matrices,
+ undo_copy_scale=self.undo_copy_scale, keyflags=self.keyflags,
+ no_loc=self.locks[0], no_rot=self.locks[1], no_scale=self.locks[2],
)
-class POSE_OT_rigify_generic_fk2ik(RigifyGenericFk2IkBase, RigifySingleUpdateMixin, bpy.types.Operator):
- bl_idname = "pose.rigify_generic_fk2ik_" + rig_id
- bl_label = "Snap FK->IK"
- bl_options = {'UNDO', 'INTERNAL'}
- bl_description = "Snap the FK chain to IK result"
+class POSE_OT_rigify_generic_snap(RigifyGenericSnapBase, RigifySingleUpdateMixin, bpy.types.Operator):
+ bl_idname = "pose.rigify_generic_snap_" + rig_id
+ bl_label = "Snap Bones"
+ bl_description = "Snap on the current frame"
-class POSE_OT_rigify_generic_fk2ik_bake(RigifyGenericFk2IkBase, RigifyBakeKeyframesMixin, bpy.types.Operator):
- bl_idname = "pose.rigify_generic_fk2ik_bake_" + rig_id
- bl_label = "Apply Snap FK->IK To Keyframes"
- bl_options = {'UNDO', 'INTERNAL'}
- bl_description = "Snap the FK chain keyframes to IK result"
+ @classmethod
+ def description(cls, context, props):
+ return "Snap " + props.tooltip + " on the current frame"
+
+class POSE_OT_rigify_generic_snap_bake(RigifyGenericSnapBase, RigifyBakeKeyframesMixin, bpy.types.Operator):
+ bl_idname = "pose.rigify_generic_snap_bake_" + rig_id
+ bl_label = "Apply Snap To Keyframes"
+ bl_description = "Apply snap to keyframes"
+
+ @classmethod
+ def description(cls, context, props):
+ return "Apply snap " + props.tooltip + " to keyframes"
def execute_scan_curves(self, context, obj):
+ props = transform_props_with_locks(*self.locks)
self.bake_add_bone_frames(self.ctrl_bone_list, TRANSFORM_PROPS_ALL)
- return self.bake_get_all_bone_curves(self.fk_bone_list, TRANSFORM_PROPS_ALL)
+ return self.bake_get_all_bone_curves(self.output_bone_list, props)
''']
def add_fk_ik_snap_buttons(panel, op_single, op_bake, *, label=None, rig_name='', properties=None, clear_bones=None, compact=None):
@@ -840,25 +898,37 @@ def add_fk_ik_snap_buttons(panel, op_single, op_bake, *, label=None, rig_name=''
row.operator(op_bake, text='Action', icon='ACTION_TWEAK', properties=properties)
add_clear_keyframes_button(row, bones=clear_bones, text='Clear')
-def add_generic_snap_fk_to_ik(panel, *, fk_bones=[], ik_bones=[], ik_ctrl_bones=[], label='FK->IK', rig_name='', undo_copy_scale=False, compact=None, clear=True):
+def add_generic_snap(panel, *, output_bones=[], input_bones=[], input_ctrl_bones=[], label='Snap', rig_name='', undo_copy_scale=False, compact=None, clear=True, locks=None, tooltip=None):
panel.use_bake_settings()
- panel.script.add_utilities(SCRIPT_UTILITIES_OP_SNAP_FK_IK)
- panel.script.register_classes(SCRIPT_REGISTER_OP_SNAP_FK_IK)
+ panel.script.add_utilities(SCRIPT_UTILITIES_OP_SNAP)
+ panel.script.register_classes(SCRIPT_REGISTER_OP_SNAP)
op_props = {
- 'fk_bones': json.dumps(fk_bones),
- 'ik_bones': json.dumps(ik_bones),
- 'ctrl_bones': json.dumps(ik_ctrl_bones),
- 'undo_copy_scale': undo_copy_scale,
+ 'output_bones': json.dumps(output_bones),
+ 'input_bones': json.dumps(input_bones),
+ 'ctrl_bones': json.dumps(input_ctrl_bones or input_bones),
}
- clear_bones = fk_bones if clear else None
+ if undo_copy_scale:
+ op_props['undo_copy_scale'] = undo_copy_scale
+ if locks is not None:
+ op_props['locks'] = tuple(locks[0:3])
+ if tooltip is not None:
+ op_props['tooltip'] = tooltip
+
+ clear_bones = output_bones if clear else None
add_fk_ik_snap_buttons(
- panel, 'pose.rigify_generic_fk2ik_{rig_id}', 'pose.rigify_generic_fk2ik_bake_{rig_id}',
+ panel, 'pose.rigify_generic_snap_{rig_id}', 'pose.rigify_generic_snap_bake_{rig_id}',
label=label, rig_name=rig_name, properties=op_props, clear_bones=clear_bones, compact=compact,
)
+def add_generic_snap_fk_to_ik(panel, *, fk_bones=[], ik_bones=[], ik_ctrl_bones=[], label='FK->IK', rig_name='', undo_copy_scale=False, compact=None, clear=True):
+ add_generic_snap(
+ panel, output_bones=fk_bones, input_bones=ik_bones, input_ctrl_bones=ik_ctrl_bones,
+ label=label, rig_name=rig_name, undo_copy_scale=undo_copy_scale, compact=compact, clear=clear
+ )
+
###############################
# Module register/unregister ##
###############################
diff --git a/rigify/utils/bones.py b/rigify/utils/bones.py
index 6a09cee1..854d4428 100644
--- a/rigify/utils/bones.py
+++ b/rigify/utils/bones.py
@@ -657,3 +657,17 @@ def align_bone_to_axis(obj, bone_name, axis, *, length=None, roll=0, flip=False)
bone_e.tail = bone_e.head + vec
bone_e.roll = roll
+
+
+def set_bone_widget_transform(obj, bone_name, transform_bone, use_size=True, scale=1.0):
+ assert obj.mode != 'EDIT'
+
+ bone = obj.pose.bones[bone_name]
+
+ if transform_bone and transform_bone != bone_name:
+ bone.custom_shape_transform = obj.pose.bones[transform_bone]
+ else:
+ bone.custom_shape_transform = None
+
+ bone.use_custom_shape_bone_size = use_size
+ bone.custom_shape_scale = scale
diff --git a/rigify/utils/components.py b/rigify/utils/components.py
new file mode 100644
index 00000000..5c1ebcb6
--- /dev/null
+++ b/rigify/utils/components.py
@@ -0,0 +1,87 @@
+import bpy
+
+from .naming import make_derived_name
+from .bones import put_bone, copy_bone_position, align_bone_orientation
+from .widgets_basic import create_pivot_widget
+from .misc import force_lazy
+
+from ..base_rig import RigComponent, stage
+
+
+class CustomPivotControl(RigComponent):
+ """
+ A utility that generates a pivot control with a custom position.
+
+ Generates a control bone, and a MCH output bone.
+ """
+
+ def __init__(
+ self, rig, id_name, org_bone, *,
+ name=None, parent=None, position=None, matrix=None,
+ scale=1.0, scale_mch=None,
+ move_to=None, align_to=None, snap_to=None,
+ widget_axis=1.5, widget_cap=1.0, widget_square=True,
+ ):
+ super().__init__(rig)
+
+ assert rig.generator.stage == 'generate_bones'
+
+ self.bones = rig.bones
+ self.id_name = id_name
+
+ self.parent = parent
+ self.scale = scale or 1
+ self.scale_mch = scale_mch or (self.scale * 0.7)
+ self.move_to = move_to
+ self.align_to = align_to
+ self.snap_to = snap_to
+ self.widget_axis = widget_axis
+ self.widget_cap = widget_cap
+ self.widget_square = widget_square
+
+ name = name or make_derived_name(org_bone, 'ctrl', '_pivot')
+
+ self.do_make_bones(org_bone, name, position, matrix)
+
+ @property
+ def control(self):
+ return self.ctrl
+
+ @property
+ def output(self):
+ return self.mch
+
+ def do_make_bones(self, org, name, position, matrix):
+ self.bones.ctrl[self.id_name] = self.ctrl = self.copy_bone(org, name, parent=not self.parent, scale=self.scale)
+ self.bones.mch[self.id_name] = self.mch = self.copy_bone(org, make_derived_name(name, 'mch'), scale=self.scale_mch)
+
+ if position or matrix:
+ put_bone(self.obj, self.ctrl, position, matrix=matrix)
+ put_bone(self.obj, self.mch, position, matrix=matrix)
+
+ def parent_bones(self):
+ if self.snap_to:
+ bone = force_lazy(self.snap_to)
+ copy_bone_position(self.obj, bone, self.ctrl, scale=self.scale)
+ copy_bone_position(self.obj, bone, self.mch, scale=self.scale_mch)
+
+ if self.move_to:
+ pos = self.get_bone(force_lazy(self.move_to)).head
+ put_bone(self.obj, self.ctrl, pos)
+ put_bone(self.obj, self.mch, pos)
+
+ if self.align_to:
+ self.align_to = force_lazy(self.align_to)
+ align_bone_orientation(self.obj, self.ctrl, self.align_to)
+ align_bone_orientation(self.obj, self.mch, self.align_to)
+
+ if self.parent:
+ self.set_bone_parent(self.ctrl, force_lazy(self.parent))
+
+ self.set_bone_parent(self.mch, self.ctrl)
+
+ def rig_bones(self):
+ self.make_constraint(self.mch, 'COPY_LOCATION', self.ctrl, space='LOCAL', invert_xyz=(True,)*3)
+
+ def generate_widgets(self):
+ create_pivot_widget(self.obj, self.ctrl, axis_size=self.widget_axis, cap_size=self.widget_cap, square=self.widget_square)
diff --git a/rigify/utils/layers.py b/rigify/utils/layers.py
index 7a1bcef8..0cbd41f8 100644
--- a/rigify/utils/layers.py
+++ b/rigify/utils/layers.py
@@ -71,7 +71,7 @@ class ControlLayersOption:
self.toggle_option = self.name+'_layers_extra'
self.layers_option = self.name+'_layers'
- self.toggle_name = toggle_name if toggle_name else self.toggle_option
+ self.toggle_name = toggle_name if toggle_name else "Assign " + self.name.title() + " Layers"
def get(self, params):
if getattr(params, self.toggle_option):
@@ -122,10 +122,15 @@ class ControlLayersOption:
setattr(params, self.layers_option, prop_layers)
def parameters_ui(self, layout, params):
- r = layout.row()
- r.prop(params, self.toggle_option)
- r.active = getattr(params, self.toggle_option)
+ box = layout.box()
+ box.prop(params, self.toggle_option)
+ active = getattr(params, self.toggle_option)
+
+ if not active:
+ return
+
+ r = box.row()
col = r.column(align=True)
row = col.row(align=True)
diff --git a/rigify/utils/misc.py b/rigify/utils/misc.py
index 64367bb7..20fd6a08 100644
--- a/rigify/utils/misc.py
+++ b/rigify/utils/misc.py
@@ -156,6 +156,14 @@ def map_apply(func, *inputs):
# Misc
#=============================================
+
+def force_lazy(value):
+ if callable(value):
+ return value()
+ else:
+ return value
+
+
def copy_attributes(a, b):
keys = dir(a)
for key in keys:
diff --git a/rigify/utils/rig.py b/rigify/utils/rig.py
index 8c646ab5..0c07cfe6 100644
--- a/rigify/utils/rig.py
+++ b/rigify/utils/rig.py
@@ -283,6 +283,7 @@ def write_metarig(obj, layers=False, func_name="create", groups=False):
code.append(" bone.select = True")
code.append(" bone.select_head = True")
code.append(" bone.select_tail = True")
+ code.append(" bone.bbone_x = bone.bbone_z = bone.length * 0.05")
code.append(" arm.edit_bones.active = bone")
# Set appropriate layers visible
diff --git a/rigify/utils/switch_parent.py b/rigify/utils/switch_parent.py
index c26f5e74..61721266 100644
--- a/rigify/utils/switch_parent.py
+++ b/rigify/utils/switch_parent.py
@@ -8,7 +8,7 @@ import json
from .errors import MetarigError
from .naming import strip_prefix, make_derived_name
from .mechanism import MechanismUtilityMixin
-from .misc import map_list, map_apply
+from .misc import map_list, map_apply, force_lazy
from ..base_rig import *
from ..base_generate import GeneratorPlugin
@@ -16,11 +16,6 @@ from ..base_generate import GeneratorPlugin
from collections import defaultdict
from itertools import count, repeat, chain
-def _auto_call(value):
- if callable(value):
- return value()
- else:
- return value
def _rig_is_child(rig, parent):
if parent is None:
@@ -56,7 +51,7 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
##############################
# API
- def register_parent(self, rig, bone, *, name=None, is_global=False, exclude_self=False):
+ def register_parent(self, rig, bone, *, name=None, is_global=False, exclude_self=False, inject_into=None, tags=None):
"""
Registers a bone of the specified rig as a possible parent.
@@ -66,6 +61,8 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
name Name of the parent for mouse-over hint.
is_global The parent is accessible to all rigs, instead of just children of owner.
exclude_self The parent is invisible to the owner rig itself.
+ inject_into Make this parent available to children of the specified rig.
+ tags Set of tags to use for default parent selection.
Lazy creation:
The bone parameter may be a function creating the bone on demand and
@@ -74,10 +71,19 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
assert not self.frozen
assert isinstance(bone, str) or callable(bone)
+ assert callable(bone) or _rig_is_child(rig, self.generator.bone_owners[bone])
+ assert _rig_is_child(rig, inject_into)
+
+ real_rig = rig
+
+ if inject_into and inject_into is not rig:
+ rig = inject_into
+ tags = (tags or set()) | {'injected'}
entry = {
- 'rig': rig, 'bone': bone, 'name': name,
- 'is_global': is_global, 'exclude_self': exclude_self, 'used': False,
+ 'rig': rig, 'bone': bone, 'name': name, 'tags': tags,
+ 'is_global': is_global, 'exclude_self': exclude_self,
+ 'real_rig': real_rig, 'used': False,
}
if is_global:
@@ -96,9 +102,13 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
extra_parents List of bone names or (name, user_name) pairs to use as additional parents.
use_parent_mch Create an intermediate MCH bone for the constraints and parent the child to it.
select_parent Select the specified bone instead of the last one.
+ select_tags List of parent tags to try for default selection.
ignore_global Ignore the is_global flag of potential parents.
exclude_self Ignore parents registered by the rig itself.
- context_rig Rig to use for selecting parents.
+ allow_self Ignore the 'exclude_self' setting of the parent.
+ context_rig Rig to use for selecting parents; defaults to rig.
+ no_implicit Only use parents listed as extra_parents.
+ only_selected Like no_implicit, but allow the 'default' selected parent.
prop_bone Name of the bone to add the property to.
prop_id Actual name of the control property.
@@ -160,7 +170,10 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
child_option_table = {
'extra_parents': None,
'prop_bone': None, 'prop_id': None, 'prop_name': None, 'controls': None,
- 'select_parent': None, 'ignore_global': False, 'exclude_self': False, 'context_rig': None,
+ 'select_parent': None, 'ignore_global': False,
+ 'exclude_self': False, 'allow_self': False,
+ 'context_rig': None, 'select_tags': None,
+ 'no_implicit': False, 'only_selected': False,
'ctrl_bone': None,
'no_fix_location': False, 'no_fix_rotation': False, 'no_fix_scale': False,
'copy_location': None, 'copy_rotation': None, 'copy_scale': None,
@@ -199,16 +212,23 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
parents = []
for parent in self.get_rig_parent_candidates(child_rig):
+ parent_rig = parent['rig']
+
+ # Exclude injected parents
+ if parent['real_rig'] is not parent_rig:
+ if _rig_is_child(parent_rig, child_rig):
+ continue
+
if parent['rig'] is child_rig:
- if parent['exclude_self'] or child['exclude_self']:
+ if (parent['exclude_self'] and not child['allow_self']) or child['exclude_self']:
continue
elif parent['is_global'] and not child['ignore_global']:
# Can't use parents from own children, even if global (cycle risk)
- if _rig_is_child(parent['rig'], child_rig):
+ if _rig_is_child(parent_rig, child_rig):
continue
else:
# Required to be a child of the parent's rig
- if not _rig_is_child(child_rig, parent['rig']):
+ if not _rig_is_child(child_rig, parent_rig):
continue
parent['used'] = True
@@ -219,7 +239,7 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
# Call lazy creation for parents
for parent in self.parent_list:
if parent['used']:
- parent['bone'] = _auto_call(parent['bone'])
+ parent['bone'] = force_lazy(parent['bone'])
def parent_bones(self):
for child in self.child_list:
@@ -248,34 +268,68 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
# Build the final list of parent bone names
parent_map = dict()
+ parent_tags = defaultdict(set)
for parent in child['parents']:
if parent['bone'] not in parent_map:
parent_map[parent['bone']] = parent['name']
+ if parent['tags']:
+ parent_tags[parent['bone']] |= parent['tags']
last_main_parent_bone = child['parents'][-1]['bone']
- num_main_parents = len(parent_map.items())
+ extra_parents = set()
- for parent in _auto_call(child['extra_parents'] or []):
+ for parent in force_lazy(child['extra_parents'] or []):
if not isinstance(parent, tuple):
parent = (parent, None)
+ extra_parents.add(parent[0])
if parent[0] not in parent_map:
parent_map[parent[0]] = parent[1]
+ for parent in parent_map:
+ if parent in self.child_map:
+ parent_tags[parent] |= {'child'}
+
parent_bones = list(parent_map.items())
- child['parent_bones'] = parent_bones
# Find which bone to select
- select_bone = _auto_call(child['select_parent']) or last_main_parent_bone
- select_index = num_main_parents
+ select_bone = force_lazy(child['select_parent']) or last_main_parent_bone
+ select_tags = force_lazy(child['select_tags']) or []
+
+ if child['no_implicit']:
+ assert len(extra_parents) > 0
+ parent_bones = [ item for item in parent_bones if item[0] in extra_parents ]
+ if last_main_parent_bone not in extra_parents:
+ last_main_parent_bone = parent_bones[-1][0]
+
+ for tag in select_tags:
+ tag_set = tag if isinstance(tag, set) else {tag}
+ matching = [
+ bone for (bone, _) in parent_bones
+ if not tag_set.isdisjoint(parent_tags[bone])
+ ]
+ if len(matching) > 0:
+ select_bone = matching[-1]
+ break
+
+ if select_bone not in parent_map:
+ print("RIGIFY ERROR: Can't find bone '%s' to select as default parent of '%s'\n" % (select_bone, bone))
+ select_bone = last_main_parent_bone
+
+ if child['only_selected']:
+ filter_set = { select_bone, *extra_parents }
+ parent_bones = [ item for item in parent_bones if item[0] in filter_set ]
try:
select_index = 1 + next(i for i, (bone, _) in enumerate(parent_bones) if bone == select_bone)
except StopIteration:
- print("RIGIFY ERROR: Can't find bone '%s' to select as default parent of '%s'\n" % (select_bone, bone))
+ select_index = len(parent_bones)
+ print("RIGIFY ERROR: Invalid default parent '%s' of '%s'\n" % (select_bone, bone))
+
+ child['parent_bones'] = parent_bones
# Create the controlling property
- prop_bone = child['prop_bone'] = _auto_call(child['prop_bone']) or bone
+ prop_bone = child['prop_bone'] = force_lazy(child['prop_bone']) or bone
prop_name = child['prop_name'] or child['prop_id'] or 'Parent Switch'
prop_id = child['prop_id'] = child['prop_id'] or 'parent_switch'
@@ -294,12 +348,12 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
no_fix = [ child[n] for n in ['no_fix_location', 'no_fix_rotation', 'no_fix_scale'] ]
- child['copy'] = [ _auto_call(child[n]) for n in ['copy_location', 'copy_rotation', 'copy_scale'] ]
+ child['copy'] = [ force_lazy(child[n]) for n in ['copy_location', 'copy_rotation', 'copy_scale'] ]
locks = tuple(bool(nofix or copy) for nofix, copy in zip(no_fix, child['copy']))
# Create the script for the property
- controls = _auto_call(child['controls']) or set([prop_bone, bone])
+ controls = force_lazy(child['controls']) or set([prop_bone, bone])
script = self.generator.script
panel = script.panel_with_selected_check(child['rig'], controls)
@@ -377,9 +431,6 @@ class RigifySwitchParentBase:
items=lambda s,c: RigifySwitchParentBase.parent_items
)
- keyflags = None
- keyflags_switch = None
-
def save_frame_state(self, context, obj):
return get_transform_matrix(obj, self.bone, with_constraints=False)
@@ -398,16 +449,6 @@ class RigifySwitchParentBase:
no_loc=self.locks[0], no_rot=self.locks[1], no_scale=self.locks[2]
)
- def get_bone_props(self):
- props = set()
- if not self.locks[0]:
- props |= TRANSFORM_PROPS_LOCATION
- if not self.locks[1]:
- props |= TRANSFORM_PROPS_ROTATION
- if not self.locks[2]:
- props |= TRANSFORM_PROPS_SCALE
- return props
-
def init_invoke(self, context):
pose = context.active_object.pose
@@ -440,11 +481,10 @@ class POSE_OT_rigify_switch_parent(RigifySwitchParentBase, RigifySingleUpdateMix
class POSE_OT_rigify_switch_parent_bake(RigifySwitchParentBase, RigifyBakeKeyframesMixin, bpy.types.Operator):
bl_idname = "pose.rigify_switch_parent_bake_" + rig_id
bl_label = "Apply Switch Parent To Keyframes"
- bl_options = {'UNDO', 'INTERNAL'}
bl_description = "Switch parent over a frame range, adjusting keys to preserve the bone position and orientation"
def execute_scan_curves(self, context, obj):
- return self.bake_add_bone_frames(self.bone, self.get_bone_props())
+ return self.bake_add_bone_frames(self.bone, transform_props_with_locks(*self.locks))
def execute_before_apply(self, context, obj, range, range_raw):
self.bake_replace_custom_prop_keys_constant(self.prop_bone, self.prop_id, int(self.selected))
diff --git a/rigify/utils/widgets_basic.py b/rigify/utils/widgets_basic.py
index 2848e5bf..8aab5d7b 100644
--- a/rigify/utils/widgets_basic.py
+++ b/rigify/utils/widgets_basic.py
@@ -129,7 +129,7 @@ def create_bone_widget(rig, bone_name, r1=0.1, l1=0.0, r2=0.04, l2=1.0, bone_tra
mesh.update()
-def create_pivot_widget(rig, bone_name, axis_size=1.0, cap_size=1.0, square=False, bone_transform_name=None):
+def create_pivot_widget(rig, bone_name, axis_size=1.0, cap_size=1.0, square=True, bone_transform_name=None):
"""Creates a widget similar to Plain Axes empty, but with a cross or
a square on the end of each axis line.
"""