diff options
author | Demeter Dzadik <demeter@blender.studio> | 2021-11-03 13:41:53 +0300 |
---|---|---|
committer | Demeter Dzadik <demeter@blender.studio> | 2021-11-03 13:43:05 +0300 |
commit | 4f2cfb266ac6a3655c24d53c08dafa46d802f77d (patch) | |
tree | 9d87fd85e73974626bc687ad4509943484db502a /rigify | |
parent | bc1449c351180d5e0ff6a8d3cc9a4b7683daee3a (diff) |
Rigify: Operators to copy and mirror parameters
This patch adds two new operators to the Rigify drop-down menu: Copy Parameters to Selected Bones and Mirror Parameters. I use both of these on a daily basis when working with Rigify, and I don't know how people live without it.
{F10171452}
To test the more unusual cases, you can grab my [feature set](https://gitlab.com/blender/CloudRig) and this file:
{F10178637}
There are two sets of symmetrical bones, with totally assymetrical parameters of all kinds of types. (CollectionProperty of PropertyGroups with just a couple strings, PointerProperty at Object datablock, BoolVectorProperty, EnumProperty, FloatVectorProperty, and the basic things like bools and ints, all seems to work!)
In a later patch if people like the idea, I would also add the Copy Rigify Parameters button to the Copy Attributes addon's Ctrl+C menu (that would only appear when Rigify is enabled, of course).
Reviewed By: sybren, angavrilov, #animation_rigging
Differential Revision: https://developer.blender.org/D11606
Diffstat (limited to 'rigify')
-rw-r--r-- | rigify/operators/copy_mirror_parameters.py | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/rigify/operators/copy_mirror_parameters.py b/rigify/operators/copy_mirror_parameters.py index 95818ba8..a844f8da 100644 --- a/rigify/operators/copy_mirror_parameters.py +++ b/rigify/operators/copy_mirror_parameters.py @@ -22,6 +22,7 @@ import bpy 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 ..rig_lists import get_rig_class @@ -109,11 +110,157 @@ def make_copy_parameter_button(layout, property_name, *, base_class, mirror_bone props.class_name = base_class.__name__ +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() } + + elif isinstance(value, list): + return [recursive_mirror(elem) for elem in value] + + elif isinstance(value, str): + return mirror_name(value) + + else: + 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: + return False + else: + rig_type = to_bone.rigify_type = get_rigify_type(from_bone) + + from_params = from_bone.get('rigify_parameters') + if from_params and rig_type: + param_dict = property_to_python(from_params) + if x_mirror: + param_dict = recursive_mirror(param_dict) + to_bone['rigify_parameters'] = param_dict + else: + try: + del to_bone['rigify_parameters'] + except KeyError: + pass + return True + + +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""" + + bl_idname = "pose.rigify_mirror_parameters" + bl_label = "Mirror Rigify Parameters" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + obj = context.object + if not obj or obj.type != 'ARMATURE' or obj.mode != 'POSE': + return False + sel_bones = context.selected_pose_bones + if not sel_bones: + return False + for pb in sel_bones: + mirrored_name = mirror_name(pb.name) + if mirrored_name != pb.name and mirrored_name in obj.pose.bones: + return True + return False + + def execute(self, context): + rig = context.object + + num_mirrored = 0 + + # First make sure that all selected bones can be mirrored unambiguously. + for pb in context.selected_pose_bones: + flip_bone = rig.pose.bones.get(mirror_name(pb.name)) + if not flip_bone: + # Bones without an opposite will just be ignored. + 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!") + return {'CANCELLED'} + + # Then mirror the parameters. + for pb in context.selected_pose_bones: + flip_bone = rig.pose.bones.get(mirror_name(pb.name)) + if flip_bone == pb or not flip_bone: + # Bones without an opposite will just be ignored. + continue + + num_mirrored += copy_rigify_params(pb, flip_bone, match_type=False, x_mirror=True) + + self.report({'INFO'}, f"Mirrored parameters of {num_mirrored} bones.") + + return {'FINISHED'} + + +class POSE_OT_rigify_copy_parameters(bpy.types.Operator): + """Copy Rigify type and parameters from active to selected bones""" + + bl_idname = "pose.rigify_copy_parameters" + bl_label = "Copy Rigify Parameters to Selected" + 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 + ) + + @classmethod + def poll(cls, context): + obj = context.object + if not obj or obj.type != 'ARMATURE' or obj.mode != 'POSE': + return False + + active = context.active_pose_bone + if not active or not active.rigify_type: + return False + + select = context.selected_pose_bones + if len(select) < 2 or active not in select: + return False + + return True + + def execute(self, context): + active_bone = context.active_pose_bone + + num_copied = 0 + for pb in context.selected_pose_bones: + if pb == active_bone: + 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.") + + return {'FINISHED'} + + +def draw_copy_mirror_ops(self, context): + layout = self.layout + if context.mode == 'POSE': + layout.separator() + op = layout.operator(POSE_OT_rigify_copy_parameters.bl_idname, + 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") + op.match_type = False + layout.operator(POSE_OT_rigify_mirror_parameters.bl_idname, + icon='MOD_MIRROR', text="Mirror Type & Parameters") + # ============================================= # Registration classes = ( POSE_OT_rigify_copy_single_parameter, + POSE_OT_rigify_mirror_parameters, + POSE_OT_rigify_copy_parameters ) @@ -122,8 +269,11 @@ def register(): for cls in classes: register_class(cls) + bpy.types.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) |