#====================== 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 if len(self.org_bones) <= 1: raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 2 or more bones." % (strip_org(bone))) # 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 PropertyGroup """ 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