diff options
Diffstat (limited to 'rigify/utils')
-rw-r--r-- | rigify/utils/animation.py | 212 | ||||
-rw-r--r-- | rigify/utils/bones.py | 14 | ||||
-rw-r--r-- | rigify/utils/components.py | 87 | ||||
-rw-r--r-- | rigify/utils/layers.py | 13 | ||||
-rw-r--r-- | rigify/utils/misc.py | 8 | ||||
-rw-r--r-- | rigify/utils/rig.py | 1 | ||||
-rw-r--r-- | rigify/utils/switch_parent.py | 118 | ||||
-rw-r--r-- | rigify/utils/widgets_basic.py | 2 |
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. """ |