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/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_finger.py b/rigs/limbs/super_finger.py index f0c7f1e..07f4540 100644 --- a/rigs/limbs/super_finger.py +++ b/rigs/limbs/super_finger.py @@ -1,15 +1,49 @@ 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 +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' """ @@ -20,9 +54,24 @@ 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 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') @@ -62,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 ?? @@ -109,13 +158,47 @@ 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], name_base + 'tip' + name_suffix) + 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]] + + # Create IK control bone and follow socket + if self.params.generate_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 + + 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]] + 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)): @@ -133,7 +216,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 @@ -153,14 +236,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,32 +258,36 @@ 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 deform bones - con = pb[deform].constraints.new('COPY_TRANSFORMS') + # Constraining the org bones + con = pb[org].constraints.new('COPY_TRANSFORMS') con.target = self.obj con.subtarget = mch - # Constraining the mch bones - if mch_chain.index(mch) == 0: - con = pb[mch].constraints.new('COPY_LOCATION') + # Constraining the deform bones + 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 @@ -273,6 +352,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] @@ -298,6 +382,69 @@ 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 + + # 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: @@ -316,11 +463,40 @@ 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] - ) + ", " + "'" + 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): @@ -331,6 +507,40 @@ 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)]) + ) + + # 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. @@ -339,6 +549,15 @@ 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") + + 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 @@ -384,7 +603,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 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): 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) +