diff options
Diffstat (limited to 'rigify/rigs/finger.py')
-rw-r--r-- | rigify/rigs/finger.py | 409 |
1 files changed, 409 insertions, 0 deletions
diff --git a/rigify/rigs/finger.py b/rigify/rigs/finger.py new file mode 100644 index 00000000..de028f36 --- /dev/null +++ b/rigify/rigs/finger.py @@ -0,0 +1,409 @@ +#====================== BEGIN GPL LICENSE BLOCK ====================== +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +#======================= END GPL LICENSE BLOCK ======================== + +import bpy +from mathutils import Vector +from rigify.utils import MetarigError +from rigify.utils import copy_bone +from rigify.utils import connected_children_names +from rigify.utils import strip_org, make_mechanism_name, make_deformer_name +from rigify.utils import get_layers +from rigify.utils import create_widget, create_line_widget, create_limb_widget +from rna_prop_ui import rna_idprop_ui_prop_get +import re + + +class Rig: + """ A finger rig. It takes a single chain of bones. + This is a control and deformation rig. + """ + def __init__(self, obj, bone, params): + """ Gather and validate data about the rig. + """ + self.obj = obj + self.org_bones = [bone] + connected_children_names(obj, bone) + self.params = params + + # Get user-specified layers, if they exist + if params.separate_extra_layers: + self.ex_layers = list(params.extra_layers) + else: + self.ex_layers = None + + # Get other rig parameters + self.primary_rotation_axis = params.primary_rotation_axis + self.use_digit_twist = params.use_digit_twist + + def deform(self): + """ Generate the deformation rig. + Just a copy of the original bones, except the first digit which is a twist bone. + """ + bpy.ops.object.mode_set(mode='EDIT') + + # Create the bones + # First bone is a twist bone + if self.use_digit_twist: + b1a = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".01"))) + b1b = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".02"))) + b1tip = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(self.org_bones[0] + ".tip"))) + else: + b1 = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0]))) + + # The rest are normal + bones = [] + for bone in self.org_bones[1:]: + bones += [copy_bone(self.obj, bone, make_deformer_name(strip_org(bone)))] + + # Position bones + eb = self.obj.data.edit_bones + if self.use_digit_twist: + b1a_e = eb[b1a] + b1b_e = eb[b1b] + b1tip_e = eb[b1tip] + + b1tip_e.use_connect = False + b1tip_e.tail += Vector((0.1, 0, 0)) + b1tip_e.head = b1b_e.tail + b1tip_e.length = b1a_e.length / 4 + + center = (b1a_e.head + b1a_e.tail) / 2 + b1a_e.tail = center + b1b_e.use_connect = False + b1b_e.head = center + + # Parenting + if self.use_digit_twist: + b1b_e.parent = eb[self.org_bones[0]] + b1tip_e.parent = eb[self.org_bones[0]] + else: + eb[b1].use_connect = False + eb[b1].parent = eb[self.org_bones[0]] + + for (ba, bb) in zip(bones, self.org_bones[1:]): + eb[ba].use_connect = False + eb[ba].parent = eb[bb] + + # Constraints + if self.use_digit_twist: + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + + b1a_p = pb[b1a] + + con = b1a_p.constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + con.subtarget = self.org_bones[0] + + con = b1a_p.constraints.new('COPY_SCALE') + con.name = "copy_scale" + con.target = self.obj + con.subtarget = self.org_bones[0] + + con = b1a_p.constraints.new('DAMPED_TRACK') + con.name = "track_to" + con.target = self.obj + con.subtarget = b1tip + + def control(self): + """ Generate the control rig. + """ + bpy.ops.object.mode_set(mode='EDIT') + + # Figure out the name for the control bone (remove the last .##) + ctrl_name = re.sub("([0-9]+\.)", "", strip_org(self.org_bones[0])[::-1], count=1)[::-1] + + # Create the bones + ctrl = copy_bone(self.obj, self.org_bones[0], ctrl_name) + + helpers = [] + bones = [] + for bone in self.org_bones: + bones += [copy_bone(self.obj, bone, strip_org(bone))] + helpers += [copy_bone(self.obj, bone, make_mechanism_name(strip_org(bone)))] + + # Position bones + eb = self.obj.data.edit_bones + + length = 0.0 + for bone in helpers: + length += eb[bone].length + eb[bone].length /= 2 + + eb[ctrl].length = length * 1.5 + + # Parent bones + prev = eb[self.org_bones[0]].parent + for (b, h) in zip(bones, helpers): + b_e = eb[b] + h_e = eb[h] + b_e.use_connect = False + h_e.use_connect = False + + b_e.parent = h_e + h_e.parent = prev + + prev = b_e + + # Transform locks and rotation mode + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + + for bone in bones[1:]: + pb[bone].lock_location = True, True, True + + if pb[self.org_bones[0]].bone.use_connect == True: + pb[bones[0]].lock_location = True, True, True + + pb[ctrl].lock_scale = True, False, True + + for bone in helpers: + pb[bone].rotation_mode = 'XYZ' + + # Drivers + i = 1 + val = 1.2 / (len(self.org_bones) - 1) + for bone in helpers: + # Add custom prop + prop_name = "bend_%02d" % i + prop = rna_idprop_ui_prop_get(pb[ctrl], prop_name, create=True) + prop["min"] = 0.0 + prop["max"] = 1.0 + prop["soft_min"] = 0.0 + prop["soft_max"] = 1.0 + if i == 1: + pb[ctrl][prop_name] = 0.0 + else: + pb[ctrl][prop_name] = val + + # Add driver + if 'X' in self.primary_rotation_axis: + fcurve = pb[bone].driver_add("rotation_euler", 0) + elif 'Y' in self.primary_rotation_axis: + fcurve = pb[bone].driver_add("rotation_euler", 1) + else: + fcurve = pb[bone].driver_add("rotation_euler", 2) + + driver = fcurve.driver + driver.type = 'SCRIPTED' + + var = driver.variables.new() + var.name = "ctrl_y" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = pb[ctrl].path_from_id() + '.scale[1]' + + var = driver.variables.new() + var.name = "bend" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = pb[ctrl].path_from_id() + '["' + prop_name + '"]' + + if '-' in self.primary_rotation_axis: + driver.expression = "-(1.0-ctrl_y) * bend * 3.14159 * 2" + else: + driver.expression = "(1.0-ctrl_y) * bend * 3.14159 * 2" + + i += 1 + + # Constraints + con = pb[helpers[0]].constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + con.subtarget = ctrl + + con = pb[helpers[0]].constraints.new('COPY_ROTATION') + con.name = "copy_rotation" + con.target = self.obj + con.subtarget = ctrl + + # Constrain org bones to the control bones + for (bone, org) in zip(bones, self.org_bones): + con = pb[org].constraints.new('COPY_TRANSFORMS') + con.name = "copy_transforms" + con.target = self.obj + con.subtarget = bone + + # Set layers for extra control bones + if self.ex_layers: + for bone in bones: + pb[bone].bone.layers = self.ex_layers + + # Create control widgets + w = create_widget(self.obj, ctrl) + if w != None: + mesh = w.data + verts = [(0, 0, 0), (0, 1, 0), (0.05, 1, 0), (0.05, 1.1, 0), (-0.05, 1.1, 0), (-0.05, 1, 0)] + if 'Z' in self.primary_rotation_axis: + # Flip x/z coordinates + temp = [] + for v in verts: + temp += [(v[2], v[1], v[0])] + verts = temp + edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 1)] + mesh.from_pydata(verts, edges, []) + mesh.update() + + for bone in bones: + create_limb_widget(self.obj, bone) + + def generate(self): + """ Generate the rig. + Do NOT modify any of the original bones, except for adding constraints. + The main armature should be selected and active before this is called. + """ + self.deform() + self.control() + + @classmethod + def add_parameters(self, group): + """ Add the parameters of this rig type to the + RigifyParameters IDPropertyGroup + """ + items = [('X', 'X', ''), ('Y', 'Y', ''), ('Z', 'Z', ''), ('-X', '-X', ''), ('-Y', '-Y', ''), ('-Z', '-Z', '')] + group.primary_rotation_axis = bpy.props.EnumProperty(items=items, name="Primary Rotation Axis", default='X') + + group.separate_extra_layers = bpy.props.BoolProperty(name="Separate Secondary Control Layers:", default=False, description="Enable putting the secondary controls on a separate layer from the primary controls.") + group.extra_layers = bpy.props.BoolVectorProperty(size=32, description="Layers for the secondary controls to be on.") + + group.use_digit_twist = bpy.props.BoolProperty(name="Digit Twist", default=True, description="Generate the dual-bone twist setup for the first finger digit.") + + @classmethod + def parameters_ui(self, layout, obj, bone): + """ Create the ui for the rig parameters. + """ + params = obj.pose.bones[bone].rigify_parameters[0] + + r = layout.row() + r.prop(params, "separate_extra_layers") + + r = layout.row() + r.active = params.separate_extra_layers + + col = r.column(align=True) + row = col.row(align=True) + row.prop(params, "extra_layers", index=0, toggle=True, text="") + row.prop(params, "extra_layers", index=1, toggle=True, text="") + row.prop(params, "extra_layers", index=2, toggle=True, text="") + row.prop(params, "extra_layers", index=3, toggle=True, text="") + row.prop(params, "extra_layers", index=4, toggle=True, text="") + row.prop(params, "extra_layers", index=5, toggle=True, text="") + row.prop(params, "extra_layers", index=6, toggle=True, text="") + row.prop(params, "extra_layers", index=7, toggle=True, text="") + row = col.row(align=True) + row.prop(params, "extra_layers", index=16, toggle=True, text="") + row.prop(params, "extra_layers", index=17, toggle=True, text="") + row.prop(params, "extra_layers", index=18, toggle=True, text="") + row.prop(params, "extra_layers", index=19, toggle=True, text="") + row.prop(params, "extra_layers", index=20, toggle=True, text="") + row.prop(params, "extra_layers", index=21, toggle=True, text="") + row.prop(params, "extra_layers", index=22, toggle=True, text="") + row.prop(params, "extra_layers", index=23, toggle=True, text="") + + col = r.column(align=True) + row = col.row(align=True) + row.prop(params, "ik_layers", index=8, toggle=True, text="") + row.prop(params, "ik_layers", index=9, toggle=True, text="") + row.prop(params, "ik_layers", index=10, toggle=True, text="") + row.prop(params, "ik_layers", index=11, toggle=True, text="") + row.prop(params, "ik_layers", index=12, toggle=True, text="") + row.prop(params, "ik_layers", index=13, toggle=True, text="") + row.prop(params, "ik_layers", index=14, toggle=True, text="") + row.prop(params, "ik_layers", index=15, toggle=True, text="") + row = col.row(align=True) + row.prop(params, "ik_layers", index=24, toggle=True, text="") + row.prop(params, "ik_layers", index=25, toggle=True, text="") + row.prop(params, "ik_layers", index=26, toggle=True, text="") + row.prop(params, "ik_layers", index=27, toggle=True, text="") + row.prop(params, "ik_layers", index=28, toggle=True, text="") + row.prop(params, "ik_layers", index=29, toggle=True, text="") + row.prop(params, "ik_layers", index=30, toggle=True, text="") + row.prop(params, "ik_layers", index=31, toggle=True, text="") + + r = layout.row() + r.label(text="Bend rotation axis:") + r.prop(params, "primary_rotation_axis", text="") + + col = layout.column() + col.prop(params, "use_digit_twist") + + @classmethod + def create_sample(self, obj): + # generated by rigify.utils.write_metarig + bpy.ops.object.mode_set(mode='EDIT') + arm = obj.data + + bones = {} + + bone = arm.edit_bones.new('finger.01') + bone.head[:] = 0.0000, 0.0000, 0.0000 + bone.tail[:] = 0.2529, 0.0000, 0.0000 + bone.roll = 3.1416 + bone.use_connect = False + bones['finger.01'] = bone.name + bone = arm.edit_bones.new('finger.02') + bone.head[:] = 0.2529, 0.0000, 0.0000 + bone.tail[:] = 0.4024, 0.0000, -0.0264 + bone.roll = -2.9671 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['finger.01']] + bones['finger.02'] = bone.name + bone = arm.edit_bones.new('finger.03') + bone.head[:] = 0.4024, 0.0000, -0.0264 + bone.tail[:] = 0.4975, -0.0000, -0.0610 + bone.roll = -2.7925 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['finger.02']] + bones['finger.03'] = bone.name + + bpy.ops.object.mode_set(mode='OBJECT') + pbone = obj.pose.bones[bones['finger.01']] + pbone.rigify_type = 'finger' + pbone.lock_location = (True, True, True) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'YZX' + pbone.rigify_parameters.add() + pbone = obj.pose.bones[bones['finger.02']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'YZX' + pbone = obj.pose.bones[bones['finger.03']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'YZX' + + bpy.ops.object.mode_set(mode='EDIT') + for bone in arm.edit_bones: + bone.select = False + bone.select_head = False + bone.select_tail = False + for b in bones: + bone = arm.edit_bones[bones[b]] + bone.select = True + bone.select_head = True + bone.select_tail = True + arm.edit_bones.active = bone + |