From 83ff6e0c669d4e2e2311f3f1e2f38fed85f03f00 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 17 Jun 2018 15:19:22 +0300 Subject: [PATCH 1/6] Fix the ORG bone contract in limbs.super_finger. These bones are used to attach sub-rigs, so they should also be moved in sensible way. Although it is very unlikely that fingers would have children, it's no reason to just break it. --- rigs/limbs/super_finger.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/rigs/limbs/super_finger.py b/rigs/limbs/super_finger.py index f0c7f1e..e2b682d 100644 --- a/rigs/limbs/super_finger.py +++ b/rigs/limbs/super_finger.py @@ -109,13 +109,22 @@ def generate(self): mch_chain += [mch_bone] mch_drv_chain += [mch_bone_drv] + # Creating tip control bone + tip_name = copy_bone(self.obj, org_bones[-1], temp_name) + ctrl_bone_tip = eb[tip_name] + flip_bone(self.obj, tip_name) + ctrl_bone_tip.length /= 2 + + ctrl_bone_tip.parent = eb[ctrl_chain[-1]] + # Restoring org chain parenting for bone in org_bones[1:]: eb[bone].parent = eb[org_bones[org_bones.index(bone) - 1]] + eb[bone].use_connect = True - # Parenting the master bone to the first org + # Parenting the master bone parallel to the first org ctrl_bone_master = eb[master_name] - ctrl_bone_master.parent = eb[org_bones[0]] + ctrl_bone_master.parent = eb[org_bones[0]].parent # Parenting chain bones for i in range(len(self.org_bones)): @@ -153,14 +162,6 @@ def generate(self): mch_bone_e.parent = ctrl_bone_e mch_bone_e.use_connect = False - # Creating tip control bone - tip_name = copy_bone(self.obj, org_bones[-1], temp_name) - ctrl_bone_tip = eb[tip_name] - flip_bone(self.obj, tip_name) - ctrl_bone_tip.length /= 2 - - ctrl_bone_tip.parent = eb[ctrl_chain[-1]] - bpy.ops.object.mode_set(mode='OBJECT') pb = self.obj.pose.bones @@ -183,11 +184,15 @@ def generate(self): # Pose settings for org, ctrl, deform, mch, mch_drv in zip(self.org_bones, ctrl_chain, def_chain, mch_chain, mch_drv_chain): + # Constraining the org bones + con = pb[org].constraints.new('COPY_TRANSFORMS') + con.target = self.obj + con.subtarget = mch # Constraining the deform bones con = pb[deform].constraints.new('COPY_TRANSFORMS') con.target = self.obj - con.subtarget = mch + con.subtarget = org # Constraining the mch bones if mch_chain.index(mch) == 0: From d928cee67374e77d11d5fc3e01b34e4df61761c0 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 19 Jun 2018 13:56:51 +0300 Subject: [PATCH 2/6] Support putting tweak controls on different layers for super_finger. --- rigs/limbs/super_finger.py | 26 +++++++++++++++++++++ utils.py | 47 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/rigs/limbs/super_finger.py b/rigs/limbs/super_finger.py index e2b682d..75eeb4c 100644 --- a/rigs/limbs/super_finger.py +++ b/rigs/limbs/super_finger.py @@ -3,6 +3,7 @@ from ...utils import strip_org, make_deformer_name, connected_children_names, make_mechanism_name from ...utils import create_circle_widget, create_widget from ...utils import MetarigError, align_bone_x_axis +from ...utils import layout_layer_selection_ui from rna_prop_ui import rna_idprop_ui_prop_get script = """ @@ -20,6 +21,11 @@ def __init__(self, obj, bone_name, params): self.org_bones = [bone_name] + connected_children_names(obj, bone_name) self.params = params + if params.tweak_extra_layers: + self.tweak_layers = list(params.tweak_layers) + else: + self.tweak_layers = None + if len(self.org_bones) <= 1: raise MetarigError("RIGIFY ERROR: Bone '%s': listen bro, that finger rig jusaint put tugetha rite. A little hint, use more than one bone!!" % (strip_org(bone_name))) @@ -303,6 +309,9 @@ def generate(self): # Assigning shapes to control bones create_circle_widget(self.obj, ctrl, radius=0.3, head_tail=0.5) + if self.tweak_layers: + pb[ctrl].bone.layers = self.tweak_layers + # Create ctrl master widget w = create_widget(self.obj, master_name) if w is not None: @@ -321,6 +330,9 @@ def generate(self): # Create tip control widget create_circle_widget(self.obj, tip_name, radius=0.3, head_tail=0.0) + if self.tweak_layers: + pb[tip_name].bone.layers = self.tweak_layers + # Create UI controls_string = ", ".join( ["'" + x + "'" for x in ctrl_chain] @@ -336,6 +348,19 @@ def add_parameters(params): ('-X', '-X manual', ''), ('-Y', '-Y manual', ''), ('-Z', '-Z manual', '')] params.primary_rotation_axis = bpy.props.EnumProperty(items=items, name="Primary Rotation Axis", default='automatic') + # Setting up extra tweak layers + params.tweak_extra_layers = bpy.props.BoolProperty( + name="tweak_extra_layers", + default=False, + description="" + ) + + params.tweak_layers = bpy.props.BoolVectorProperty( + size=32, + description="Layers for the tweak controls to be on", + default=tuple([i == 1 for i in range(0, 32)]) + ) + def parameters_ui(layout, params): """ Create the ui for the rig parameters. @@ -344,6 +369,7 @@ def parameters_ui(layout, params): r.label(text="Bend rotation axis:") r.prop(params, "primary_rotation_axis", text="") + layout_layer_selection_ui(layout, params, "tweak_extra_layers", "tweak_layers") def create_sample(obj): # generated by rigify.utils.write_metarig diff --git a/utils.py b/utils.py index 9b4e76d..30a81ba 100644 --- a/utils.py +++ b/utils.py @@ -1427,3 +1427,50 @@ def make_track_constraint_from_string(owner, target, subtarget, fstring): const.target_space = constraint_space[cns_props[3][0]] if bool(cns_props[3]) else "LOCAL" const.owner_space = constraint_space[cns_props[3][1]] if bool(cns_props[3]) else "LOCAL" const.head_tail = float(cns_props[4]) if bool(cns_props[4]) else 0.0 + + +#============================================= +# UI utilities +#============================================= + +def layout_layer_selection_ui(layout, params, enable_option, layers_option): + r = layout.row() + r.prop(params, enable_option) + r.active = getattr(params, enable_option) + + col = r.column(align=True) + row = col.row(align=True) + + bone_layers = bpy.context.active_pose_bone.bone.layers[:] + + for i in range(8): # Layers 0-7 + icon = "NONE" + if bone_layers[i]: + icon = "LAYER_ACTIVE" + row.prop(params, layers_option, index=i, toggle=True, text="", icon=icon) + + row = col.row(align=True) + + for i in range(16, 24): # Layers 16-23 + icon = "NONE" + if bone_layers[i]: + icon = "LAYER_ACTIVE" + row.prop(params, layers_option, index=i, toggle=True, text="", icon=icon) + + col = r.column(align=True) + row = col.row(align=True) + + for i in range(8, 16): # Layers 8-15 + icon = "NONE" + if bone_layers[i]: + icon = "LAYER_ACTIVE" + row.prop(params, layers_option, index=i, toggle=True, text="", icon=icon) + + row = col.row(align=True) + + for i in range(24, 32): # Layers 24-31 + icon = "NONE" + if bone_layers[i]: + icon = "LAYER_ACTIVE" + row.prop(params, layers_option, index=i, toggle=True, text="", icon=icon) + From add38f34f922b9d0d9d2538e6ff3ffeed2e04062 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 19 Jun 2018 12:50:01 +0300 Subject: [PATCH 3/6] Postpone twist trick of the first finger bone to DEF bones. I.e. make ORG bones follow the user-specified rotation fully, so that IK constraint sees it as-is too. --- rigs/limbs/super_finger.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/rigs/limbs/super_finger.py b/rigs/limbs/super_finger.py index 75eeb4c..bf546cc 100644 --- a/rigs/limbs/super_finger.py +++ b/rigs/limbs/super_finger.py @@ -148,7 +148,7 @@ def generate(self): def_bone_e.parent = eb[self.org_bones[i]].parent def_bone_e.use_connect = False # First mch bone - mch_bone_e.parent = eb[self.org_bones[i]].parent + mch_bone_e.parent = ctrl_bone_e mch_bone_e.use_connect = False # First mch driver bone mch_bone_drv_e.parent = eb[self.org_bones[i]].parent @@ -196,30 +196,30 @@ def generate(self): con.subtarget = mch # Constraining the deform bones - con = pb[deform].constraints.new('COPY_TRANSFORMS') - con.target = self.obj - con.subtarget = org - - # Constraining the mch bones - if mch_chain.index(mch) == 0: - con = pb[mch].constraints.new('COPY_LOCATION') + if deform == def_chain[0]: + con = pb[deform].constraints.new('COPY_LOCATION') con.target = self.obj - con.subtarget = ctrl + con.subtarget = org - con = pb[mch].constraints.new('COPY_SCALE') + con = pb[deform].constraints.new('COPY_SCALE') con.target = self.obj - con.subtarget = ctrl + con.subtarget = org - con = pb[mch].constraints.new('DAMPED_TRACK') + con = pb[deform].constraints.new('DAMPED_TRACK') con.target = self.obj - con.subtarget = ctrl_chain[ctrl_chain.index(ctrl)+1] + con.subtarget = org_bones[1] - con = pb[mch].constraints.new('STRETCH_TO') + con = pb[deform].constraints.new('STRETCH_TO') con.target = self.obj - con.subtarget = ctrl_chain[ctrl_chain.index(ctrl)+1] + con.subtarget = org_bones[1] con.volume = 'NO_VOLUME' + else: + con = pb[deform].constraints.new('COPY_TRANSFORMS') + con.target = self.obj + con.subtarget = org - elif mch_chain.index(mch) == len(mch_chain) - 1: + # Constraining the mch bones + if mch_chain.index(mch) == len(mch_chain) - 1: con = pb[mch].constraints.new('DAMPED_TRACK') con.target = self.obj con.subtarget = tip_name From 513e2085e3bc9f19988b13f9f0d5596da024bd1a Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 17 Jun 2018 18:43:09 +0300 Subject: [PATCH 4/6] Implement an IK control in limbs.super_finger with snapping. BlenRig has this feature, so Rigify was behind in this aspect. Since this is a feature for specific situations, which applies to 10 fingers if enabled, it is implemented in a lightweight way using the minimal number of new bones; specifically, IK is applied to the result of regular FK transformation, so existing FK bones can be used to tweak the shape. Another effect of that is that when IK is off, FK->IK snap simply normalizes the finger by absorbing as much of rotation as possible into the master control. This makes the operation useful even if IK is not used or even generated. For IK follow parent, it makes zero sense to follow the palm bones, so the rig has an option for explicitly specifying the parent bone. Natural choices are the hand, or a separate utility bone for something held in the hand, if the metarig has it. --- rig_ui_template.py | 165 ++++++++++++++++++++++++++++- rigs/limbs/super_finger.py | 205 +++++++++++++++++++++++++++++++++++-- 2 files changed, 360 insertions(+), 10 deletions(-) diff --git a/rig_ui_template.py b/rig_ui_template.py index 3cdda31..b50830a 100644 --- a/rig_ui_template.py +++ b/rig_ui_template.py @@ -520,6 +520,112 @@ def ik2fk_leg(obj, fk, ik): match_pole_target(thighi, shini, pole, thigh, (thighi.length + shini.length)) +def normalize_finger(obj, fk_master, fk_master_drv, fk_controls, axis): + """ Absorbs rotation from tweaks to master control bone. + obj: armature object + fk_master: master FK control + fk_master_drv: proxy bones of the master + fk_controls: list of tweak FK controls + axis: main transform axis + """ + + fk_master_drv = parse_bone_names(fk_master_drv) + fk_controls = parse_bone_names(fk_controls) + + axis_types = { + "automatic": [0, 1, 'XYZ'], + "X": [0, 1, 'XYZ'], + "-X": [0, -1, 'XYZ'], + "Y": [1, 1, 'YXZ'], + "-Y": [1, -1, 'YXZ'], + "Z": [2, 1, 'ZXY'], + "-Z": [2, -1, 'ZXY'], + } + + axis_id, axis_sign, axis_order = axis_types[axis] + + # Scan controls to collect data + ctl_matrix_list = [obj.pose.bones[name].matrix.copy() for name in fk_controls] + + rotations = [] + + for i, (drv_name, ctl_name, ctl_mat) in enumerate(zip(fk_master_drv, fk_controls, ctl_matrix_list)): + drv = obj.pose.bones[drv_name] + ctl = obj.pose.bones[ctl_name] + + drv_mat = get_pose_matrix_in_other_space(ctl_mat, drv) + + if i == 0: + # Absorb first tweak control rotation into the master control + master = obj.pose.bones[fk_master] + + master_mat = get_pose_matrix_in_other_space(ctl_mat, master) + + set_pose_translation(master, master_mat) + set_pose_rotation(master, drv_mat) + set_pose_translation(ctl, Matrix()) + set_pose_rotation(ctl, Matrix()) + + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.mode_set(mode='POSE') + else: + # Collect single axis rotations + rotations.append(drv_mat.to_quaternion().to_euler(axis_order)[axis_id]) + + # Apply average rotation to the master control scale + avg_rotation = sum(rotations) / len(rotations) + obj.pose.bones[fk_master].scale.y = 1 - axis_sign * avg_rotation / pi; + + # Correct tweak control rotations + for i, (ctl_name, ctl_mat) in enumerate(zip(fk_controls, ctl_matrix_list)): + if i > 0: + ctl = obj.pose.bones[ctl_name] + mat = get_pose_matrix_in_other_space(ctl_mat, ctl) + set_pose_translation(ctl, mat) + set_pose_rotation(ctl, mat) + + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.mode_set(mode='POSE') + + +def fk2ik_finger(obj, fk_controls, fk_chain, ik_chain): + """ Matches the fk bones in a finger rig to the ik bones. + obj: armature object + fk_controls: list of tweak FK controls + fk_chain: list of bones with final FK shape + ik_chain: list of bones with final IK shape + """ + + fk_controls = parse_bone_names(fk_controls) + fk_chain = parse_bone_names(fk_chain) + ik_chain = parse_bone_names(ik_chain) + + ik_matrix_list = [obj.pose.bones[name].matrix.copy() for name in ik_chain] + + for i, (ctl_name, fk_name, ik_matrix) in enumerate(zip(fk_controls, fk_chain, ik_matrix_list)): + fk = obj.pose.bones[fk_name] + ctl = obj.pose.bones[ctl_name] + + newmat = ik_matrix * fk.matrix.inverted() * ctl.matrix + newmat_local = get_pose_matrix_in_other_space(newmat, ctl) + set_pose_rotation(ctl, newmat_local) + + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.mode_set(mode='POSE') + + +def ik2fk_finger(obj, fk, ik): + """ Matches the ik bone in a finger rig to the fk bones. + obj: armature object + fk: FK fingertip control + ik: IK control + """ + fk_bone = obj.pose.bones[fk] + ik_bone = obj.pose.bones[ik] + + match_pose_translation(ik_bone, fk_bone) + + ################################ ## IK Rotation-Pole functions ## ################################ @@ -718,6 +824,59 @@ def execute(self, context): context.user_preferences.edit.use_global_undo = use_global_undo return {'FINISHED'} + +class Rigify_Finger_FK2IK(bpy.types.Operator): + """Snaps an FK finger to the actual shape, and normalizes the master control""" + bl_idname = "pose.rigify_finger_fk2ik_" + rig_id + bl_label = "Rigify Snap FK finger to IK" + bl_options = {'UNDO'} + + fk_master = bpy.props.StringProperty(name="FK Master Name") + fk_master_drv = bpy.props.StringProperty(name="FK Master Proxy Name") + fk_controls = bpy.props.StringProperty(name="FK Control Names") + fk_chain = bpy.props.StringProperty(name="FK Shape Bone Names") + ik_chain = bpy.props.StringProperty(name="IK Shape Bone Names") + axis = bpy.props.StringProperty(name="Main Rotation Axis") + + @classmethod + def poll(cls, context): + return (context.active_object != None and context.mode == 'POSE') + + def execute(self, context): + use_global_undo = context.user_preferences.edit.use_global_undo + context.user_preferences.edit.use_global_undo = False + try: + if self.ik_chain != '': + fk2ik_finger(context.active_object, self.fk_controls, self.fk_chain, self.ik_chain) + if self.fk_master != '': + normalize_finger(context.active_object, self.fk_master, self.fk_master_drv, self.fk_controls, self.axis) + finally: + context.user_preferences.edit.use_global_undo = use_global_undo + return {'FINISHED'} + + +class Rigify_Finger_IK2FK(bpy.types.Operator): + """Snaps the IK finger control to the FK finger""" + bl_idname = "pose.rigify_finger_ik2fk_" + rig_id + bl_label = "Rigify Snap IK finger to FK" + bl_options = {'UNDO'} + + fk_ctl = bpy.props.StringProperty(name="FK Name") + ik_ctl = bpy.props.StringProperty(name="IK Name") + + @classmethod + def poll(cls, context): + return (context.active_object != None and context.mode == 'POSE') + + def execute(self, context): + use_global_undo = context.user_preferences.edit.use_global_undo + context.user_preferences.edit.use_global_undo = False + try: + ik2fk_finger(context.active_object, self.fk_ctl, self.ik_ctl) + finally: + context.user_preferences.edit.use_global_undo = use_global_undo + return {'FINISHED'} + ########################### ## IK Rotation Pole Snap ## ########################### @@ -846,6 +1005,8 @@ def register(): bpy.utils.register_class(Rigify_Arm_IK2FK) bpy.utils.register_class(Rigify_Leg_FK2IK) bpy.utils.register_class(Rigify_Leg_IK2FK) + bpy.utils.register_class(Rigify_Finger_FK2IK) + bpy.utils.register_class(Rigify_Finger_IK2FK) bpy.utils.register_class(Rigify_Rot2PoleSwitch) bpy.utils.register_class(RigUI) bpy.utils.register_class(RigLayers) @@ -855,7 +1016,9 @@ def unregister(): bpy.utils.unregister_class(Rigify_Arm_IK2FK) bpy.utils.unregister_class(Rigify_Leg_FK2IK) bpy.utils.unregister_class(Rigify_Leg_IK2FK) - bpy.utils.register_class(Rigify_Rot2PoleSwitch) + bpy.utils.unregister_class(Rigify_Finger_FK2IK) + bpy.utils.unregister_class(Rigify_Finger_IK2FK) + bpy.utils.unregister_class(Rigify_Rot2PoleSwitch) bpy.utils.unregister_class(RigUI) bpy.utils.unregister_class(RigLayers) diff --git a/rigs/limbs/super_finger.py b/rigs/limbs/super_finger.py index bf546cc..fa6c77f 100644 --- a/rigs/limbs/super_finger.py +++ b/rigs/limbs/super_finger.py @@ -1,16 +1,48 @@ import bpy from ...utils import copy_bone, flip_bone from ...utils import strip_org, make_deformer_name, connected_children_names, make_mechanism_name -from ...utils import create_circle_widget, create_widget +from ...utils import create_circle_widget, create_widget, create_sphere_widget from ...utils import MetarigError, align_bone_x_axis from ...utils import layout_layer_selection_ui +from .limb_utils import get_bone_name +from mathutils import Vector from rna_prop_ui import rna_idprop_ui_prop_get -script = """ +script_header = """ controls = [%s] -master_name = '%s' if is_selected(controls): - layout.prop(pose_bones[master_name], '["%s"]', text="Curvature", slider=True) +""" + +script_prop = """ + layout.prop(pose_bones['%s'], '["%s"]', text="%s", slider=True) +""" + +script_prop_switch = """ + layout.prop(pose_bones['%s'], '["%s"]', text="%s", slider=False) +""" + +script_ik2fk = """ + props = layout.operator("pose.rigify_finger_ik2fk_" + rig_id, text="Snap IK->FK (%s)") + props.ik_ctl = '%s' + props.fk_ctl = '%s' +""" + +script_fk2ik = """ + props = layout.operator("pose.rigify_finger_fk2ik_" + rig_id, text="Snap FK->Act (%s)") + props.fk_master = '%s' + props.fk_master_drv = str([%s]) + props.fk_controls = str([%s]) + props.fk_chain = str([%s]) + props.ik_chain = str([%s]) + props.axis = '%s' +""" + +script_fk2ik_norm = """ + props = layout.operator("pose.rigify_finger_fk2ik_" + rig_id, text="Normalize (%s)") + props.fk_master = '%s' + props.fk_master_drv = str([%s]) + props.fk_controls = str([%s]) + props.axis = '%s' """ @@ -26,9 +58,19 @@ def __init__(self, obj, bone_name, params): else: self.tweak_layers = None + if params.ik_extra_layers: + self.ik_layers = list(params.ik_layers) + else: + self.ik_layers = None + if len(self.org_bones) <= 1: raise MetarigError("RIGIFY ERROR: Bone '%s': listen bro, that finger rig jusaint put tugetha rite. A little hint, use more than one bone!!" % (strip_org(bone_name))) + if params.generate_ik and params.ik_parent_bone != '': + if get_bone_name(params.ik_parent_bone, 'org') not in obj.data.bones: + raise MetarigError("RIGIFY ERROR: Bone '%s': IK parent '%s' not found." % (strip_org(bone_name), params.ik_parent_bone)) + + def orient_org_bones(self): bpy.ops.object.mode_set(mode='EDIT') @@ -123,6 +165,31 @@ def generate(self): ctrl_bone_tip.parent = eb[ctrl_chain[-1]] + # Create IK control bone and follow socket + if self.params.generate_ik: + ik_ctrl_name = copy_bone(self.obj, tip_name, get_bone_name(org_name, 'ctrl', 'ik')) + ik_ctrl_bone = eb[ik_ctrl_name] + ik_ctrl_bone.tail = ik_ctrl_bone.head + Vector((0, ik_ctrl_bone.length * 1.5, 0)) + ik_ctrl_bone.roll = 0 + + if eb[org_name].parent or self.params.ik_parent_bone != '': + ik_socket_name = copy_bone(self.obj, org_name, get_bone_name(org_name, 'mch', 'ik_socket')) + ik_socket_bone = eb[ik_socket_name] + ik_socket_bone.length *= 0.8; + ik_socket_bone.parent = None + + eb[ik_ctrl_name].parent = ik_socket_bone + + ik_parent_name = copy_bone(self.obj, org_name, get_bone_name(org_name, 'mch', 'ik_parent')) + ik_parent_bone = eb[ik_parent_name] + ik_parent_bone.length *= 0.7; + + if self.params.ik_parent_bone != '': + ik_parent_bone.parent = eb[get_bone_name(self.params.ik_parent_bone, 'org')] + else: + ik_socket_name = None + ik_ctrl_bone.parent = None + # Restoring org chain parenting for bone in org_bones[1:]: eb[bone].parent = eb[org_bones[org_bones.index(bone) - 1]] @@ -284,6 +351,11 @@ def generate(self): drv_var.targets[0].id = self.obj drv_var.targets[0].data_path = pb[master_name].path_from_id() + '.scale.y' + if self.params.generate_ik: + stiffness = [0.99, 0.99, 0.99] + stiffness[options[axis]["axis"]] = 0.0 + pb[org].ik_stiffness_x, pb[org].ik_stiffness_y, pb[org].ik_stiffness_z = stiffness + # Setting bone curvature setting, custom property, and drivers def_bone = self.obj.data.bones[deform] @@ -312,6 +384,66 @@ def generate(self): if self.tweak_layers: pb[ctrl].bone.layers = self.tweak_layers + # Generate IK constraints, drivers and widget + if self.params.generate_ik: + pb_ik_ctrl = pb[ik_ctrl_name] + pb_ik_ctrl.lock_scale = True, True, True + pb_ik_ctrl.lock_rotation = True, True, True + pb_ik_ctrl.lock_rotation_w = True + + pb_ik_ctrl['IK_FK'] = 1.0 + prop = rna_idprop_ui_prop_get(pb_ik_ctrl, 'IK_FK') + prop["min"] = prop["soft_min"] = 0.0 + prop["max"] = prop["soft_max"] = 1.0 + prop["description"] = 'IK/FK Switch' + + if ik_socket_name: + pb_ik_ctrl['IK_follow'] = 1 + prop = rna_idprop_ui_prop_get(pb_ik_ctrl, 'IK_follow') + prop["min"] = prop["soft_min"] = 0 + prop["max"] = prop["soft_max"] = 1 + prop["description"] = 'IK follows parent' + + # widget + create_sphere_widget(self.obj, ik_ctrl_name) + + if self.ik_layers: + pb_ik_ctrl.bone.layers = self.ik_layers + + # ik constraint + con = pb[org_bones[-1]].constraints.new('IK') + con.target = self.obj + con.subtarget = ik_ctrl_name + con.chain_count = len(org_bones) + + drv_fcu = con.driver_add("influence") + drv = drv_fcu.driver + drv.type = 'SUM' + drv_var = drv.variables.new() + drv_var.name = 'ik_fk' + drv_var.type = 'SINGLE_PROP' + drv_var.targets[0].id = self.obj + drv_var.targets[0].data_path = pb_ik_ctrl.path_from_id() + '["IK_FK"]' + + mod = drv_fcu.modifiers[0] + mod.poly_order = 1 + mod.coefficients[0] = 1.0 + mod.coefficients[1] = -1.0 + + # follow parent constraint + if ik_socket_name: + con = pb[ik_socket_name].constraints.new('COPY_TRANSFORMS') + con.target = self.obj + con.subtarget = ik_parent_name + + drv = con.driver_add("influence").driver + drv.type = 'SUM' + drv_var = drv.variables.new() + drv_var.name = 'IK_follow' + drv_var.type = 'SINGLE_PROP' + drv_var.targets[0].id = self.obj + drv_var.targets[0].data_path = pb_ik_ctrl.path_from_id() + '["IK_follow"]' + # Create ctrl master widget w = create_widget(self.obj, master_name) if w is not None: @@ -334,10 +466,36 @@ def generate(self): pb[tip_name].bone.layers = self.tweak_layers # Create UI - controls_string = ", ".join( - ["'" + x + "'" for x in ctrl_chain] - ) + ", " + "'" + master_name + "'" - return [script % (controls_string, master_name, 'finger_curve')] + controls = ctrl_chain + [tip_name, master_name] + if self.params.generate_ik: + controls.append(ik_ctrl_name) + + controls_string = ", ".join(["'" + x + "'" for x in controls]) + script = script_header % (controls_string) + script += script_prop % (master_name, 'finger_curve', 'Curvature') + + if self.params.generate_ik: + script += script_prop % (ik_ctrl_name, 'IK_FK', 'IK/FK') + if ik_socket_name: + script += script_prop_switch % (ik_ctrl_name, 'IK_follow', 'IK follow') + script += script_ik2fk % (temp_name, ik_ctrl_name, tip_name) + script += script_fk2ik % ( + temp_name, master_name, + ", ".join(["'" + x + "'" for x in mch_drv_chain]), + ", ".join(["'" + x + "'" for x in ctrl_chain]), + ", ".join(["'" + x + "'" for x in mch_chain]), + ", ".join(["'" + x + "'" for x in org_bones]), + self.params.primary_rotation_axis + ) + else: + script += script_fk2ik_norm % ( + temp_name, master_name, + ", ".join(["'" + x + "'" for x in mch_drv_chain]), + ", ".join(["'" + x + "'" for x in ctrl_chain]), + self.params.primary_rotation_axis + ) + + return [script] def add_parameters(params): @@ -361,6 +519,27 @@ def add_parameters(params): default=tuple([i == 1 for i in range(0, 32)]) ) + # IK controls + params.generate_ik = bpy.props.BoolProperty(name="Generate IK controls", default=False) + + params.ik_parent_bone = bpy.props.StringProperty( + name="IK parent bone", default="", + description="Bone to use for 'IK follow parent' (defaults to the actual parent)" + ) + + # Setting up extra ik layers + params.ik_extra_layers = bpy.props.BoolProperty( + name="ik_extra_layers", + default=False, + description="" + ) + + params.ik_layers = bpy.props.BoolVectorProperty( + size=32, + description="Layers for the ik control to be on", + default=tuple([i == 1 for i in range(0, 32)]) + ) + def parameters_ui(layout, params): """ Create the ui for the rig parameters. @@ -371,6 +550,14 @@ def parameters_ui(layout, params): layout_layer_selection_ui(layout, params, "tweak_extra_layers", "tweak_layers") + layout.prop(params, "generate_ik") + + if params.generate_ik: + layout.prop_search(params, "ik_parent_bone", bpy.context.object.pose, "bones") + + layout_layer_selection_ui(layout, params, "ik_extra_layers", "ik_layers") + + def create_sample(obj): # generated by rigify.utils.write_metarig bpy.ops.object.mode_set(mode='EDIT') @@ -415,7 +602,7 @@ def create_sample(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'YXZ' pbone = obj.pose.bones[bones['f_pinky.01.L']] - pbone.rigify_type = 'limbs.simple_tentacle' + pbone.rigify_type = 'limbs.super_finger' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False From d72edd7a0e7591ac5f022bca6443fbe5036eb05f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 19 Jun 2018 17:25:24 +0300 Subject: [PATCH 5/6] Choose better names for finger control bones in limbs.super_finger. Using names like 'f_ring.01_master.L' is somewhat silly, and 'f_ring.01.L.001' for the tip control is outright bad. This changes those to 'f_ring.master.L' etc. --- rigs/limbs/super_finger.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/rigs/limbs/super_finger.py b/rigs/limbs/super_finger.py index fa6c77f..07f4540 100644 --- a/rigs/limbs/super_finger.py +++ b/rigs/limbs/super_finger.py @@ -1,4 +1,5 @@ import bpy +import re from ...utils import copy_bone, flip_bone from ...utils import strip_org, make_deformer_name, connected_children_names, make_mechanism_name from ...utils import create_circle_widget, create_widget, create_sphere_widget @@ -110,12 +111,12 @@ def generate(self): org_name = self.org_bones[0] temp_name = strip_org(self.org_bones[0]) - if temp_name[-2:] == '.L' or temp_name[-2:] == '.R': - suffix = temp_name[-2:] - master_name = temp_name[:-2] + "_master" + suffix - else: - master_name = temp_name + "_master" - master_name = copy_bone(self.obj, org_name, master_name) + # Compute master bone name: inherit .LR suffix, but strip trailing digits + name_parts = re.match(r'^(.*?)(?:([._-])?\d+)?((?:[._-][LlRr])?)(?:\.\d+)?$', temp_name) + name_base, name_sep, name_suffix = name_parts.groups() + name_base += name_sep if name_sep else '_' + + master_name = copy_bone(self.obj, org_name, name_base + 'master' + name_suffix) ctrl_bone_master = eb[master_name] # Parenting bug fix ?? @@ -158,7 +159,7 @@ def generate(self): mch_drv_chain += [mch_bone_drv] # Creating tip control bone - tip_name = copy_bone(self.obj, org_bones[-1], temp_name) + tip_name = copy_bone(self.obj, org_bones[-1], name_base + 'tip' + name_suffix) ctrl_bone_tip = eb[tip_name] flip_bone(self.obj, tip_name) ctrl_bone_tip.length /= 2 @@ -167,7 +168,7 @@ def generate(self): # Create IK control bone and follow socket if self.params.generate_ik: - ik_ctrl_name = copy_bone(self.obj, tip_name, get_bone_name(org_name, 'ctrl', 'ik')) + ik_ctrl_name = copy_bone(self.obj, tip_name, name_base + 'ik' + name_suffix) ik_ctrl_bone = eb[ik_ctrl_name] ik_ctrl_bone.tail = ik_ctrl_bone.head + Vector((0, ik_ctrl_bone.length * 1.5, 0)) ik_ctrl_bone.roll = 0 From 665ed2acd562dc0b0fa83e2ffb276609988a593c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 24 Jun 2018 14:51:16 +0300 Subject: [PATCH 6/6] Switch existing rigs to using the new utility function for layer selection. --- rigs/limbs/arm.py | 43 +++-------------------------------- rigs/limbs/leg.py | 43 +++-------------------------------- rigs/limbs/paw.py | 43 +++-------------------------------- rigs/limbs/simple_tentacle.py | 41 ++------------------------------- rigs/limbs/super_limb.py | 43 +++-------------------------------- 5 files changed, 14 insertions(+), 199 deletions(-) diff --git a/rigs/limbs/arm.py b/rigs/limbs/arm.py index 193e203..0618388 100644 --- a/rigs/limbs/arm.py +++ b/rigs/limbs/arm.py @@ -9,6 +9,7 @@ from ...utils import MetarigError, make_mechanism_name, org from ...utils import create_limb_widget, connected_children_names from ...utils import align_bone_y_axis, align_bone_x_axis, align_bone_z_axis +from ...utils import layout_layer_selection_ui from rna_prop_ui import rna_idprop_ui_prop_get from ..widgets import create_ikarrow_widget from math import trunc, pi @@ -1151,46 +1152,8 @@ def parameters_ui(layout, params): r = layout.row() r.prop(params, "bbones") - bone_layers = bpy.context.active_pose_bone.bone.layers[:] - - for layer in ['fk', 'tweak']: - r = layout.row() - r.prop(params, layer + "_extra_layers") - r.active = params.tweak_extra_layers - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(16, 24): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8, 16): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(24, 32): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) + layout_layer_selection_ui(layout, params, "fk_extra_layers", "fk_layers") + layout_layer_selection_ui(layout, params, "tweak_extra_layers", "tweak_layers") def create_sample(obj): diff --git a/rigs/limbs/leg.py b/rigs/limbs/leg.py index 6c76dd9..059586d 100644 --- a/rigs/limbs/leg.py +++ b/rigs/limbs/leg.py @@ -10,6 +10,7 @@ from ...utils import MetarigError, make_mechanism_name, org from ...utils import create_limb_widget, connected_children_names from ...utils import align_bone_y_axis, align_bone_x_axis, align_bone_z_axis +from ...utils import layout_layer_selection_ui from rna_prop_ui import rna_idprop_ui_prop_get from ..widgets import create_ikarrow_widget from math import trunc, pi @@ -1474,46 +1475,8 @@ def parameters_ui(layout, params): r = layout.row() r.prop(params, "bbones") - bone_layers = bpy.context.active_pose_bone.bone.layers[:] - - for layer in ['fk', 'tweak']: - r = layout.row() - r.prop(params, layer + "_extra_layers") - r.active = params.tweak_extra_layers - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(16, 24): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8, 16): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(24, 32): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) + layout_layer_selection_ui(layout, params, "fk_extra_layers", "fk_layers") + layout_layer_selection_ui(layout, params, "tweak_extra_layers", "tweak_layers") def create_sample(obj): diff --git a/rigs/limbs/paw.py b/rigs/limbs/paw.py index 156c9ce..484c3c3 100644 --- a/rigs/limbs/paw.py +++ b/rigs/limbs/paw.py @@ -8,6 +8,7 @@ from ...utils import MetarigError, make_mechanism_name, org from ...utils import create_limb_widget, connected_children_names from ...utils import align_bone_y_axis, align_bone_x_axis, align_bone_z_axis +from ...utils import layout_layer_selection_ui from rna_prop_ui import rna_idprop_ui_prop_get from ..widgets import create_ikarrow_widget, create_gear_widget from ..widgets import create_foot_widget, create_ballsocket_widget @@ -1302,46 +1303,8 @@ def parameters_ui(layout, params): r = layout.row() r.prop(params, "bbones") - bone_layers = bpy.context.active_pose_bone.bone.layers[:] - - for layer in ['fk', 'tweak']: - r = layout.row() - r.prop(params, layer + "_extra_layers") - r.active = params.tweak_extra_layers - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(16, 24): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8, 16): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(24, 32): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) + layout_layer_selection_ui(layout, params, "fk_extra_layers", "fk_layers") + layout_layer_selection_ui(layout, params, "tweak_extra_layers", "tweak_layers") def create_sample(obj): diff --git a/rigs/limbs/simple_tentacle.py b/rigs/limbs/simple_tentacle.py index 5d2b9e7..224b7f8 100644 --- a/rigs/limbs/simple_tentacle.py +++ b/rigs/limbs/simple_tentacle.py @@ -4,6 +4,7 @@ from ...utils import put_bone, create_sphere_widget from ...utils import create_circle_widget, align_bone_x_axis from ...utils import MetarigError +from ...utils import layout_layer_selection_ui class Rig: @@ -277,45 +278,7 @@ def parameters_ui(layout, params): for i, axis in enumerate(['x', 'y', 'z']): row.prop(params, "copy_rotation_axes", index=i, toggle=True, text=axis) - r = layout.row() - r.prop(params, "tweak_extra_layers") - r.active = params.tweak_extra_layers - - col = r.column(align=True) - row = col.row(align=True) - - bone_layers = bpy.context.active_pose_bone.bone.layers[:] - - for i in range(8): # Layers 0-7 - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(16, 24): # Layers 16-23 - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8, 16): # Layers 8-15 - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(24, 32): # Layers 24-31 - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) + layout_layer_selection_ui(layout, params, "tweak_extra_layers", "tweak_layers") def create_sample(obj): diff --git a/rigs/limbs/super_limb.py b/rigs/limbs/super_limb.py index e567082..eb2ff49 100644 --- a/rigs/limbs/super_limb.py +++ b/rigs/limbs/super_limb.py @@ -4,6 +4,7 @@ from .leg import Rig as legRig from .paw import Rig as pawRig +from ...utils import layout_layer_selection_ui class Rig: @@ -130,46 +131,8 @@ def parameters_ui(layout, params): r = layout.row() r.prop(params, "bbones") - bone_layers = bpy.context.active_pose_bone.bone.layers[:] - - for layer in ['fk', 'tweak']: - r = layout.row() - r.prop(params, layer + "_extra_layers") - r.active = params.tweak_extra_layers - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(16,24): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8,16): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(24,32): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) + layout_layer_selection_ui(layout, params, "fk_extra_layers", "fk_layers") + layout_layer_selection_ui(layout, params, "tweak_extra_layers", "tweak_layers") def create_sample(obj):