From 471be493e60fd1562fa2d8147a09fd22efdafef3 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Wed, 17 Nov 2010 06:05:16 +0000 Subject: Adding the Rigify addon to svn. --- rigify/rigs/__init__.py | 0 rigify/rigs/biped/__init__.py | 0 rigify/rigs/biped/arm/__init__.py | 209 ++++++++++++++++ rigify/rigs/biped/arm/deform.py | 230 ++++++++++++++++++ rigify/rigs/biped/arm/fk.py | 208 ++++++++++++++++ rigify/rigs/biped/arm/ik.py | 281 +++++++++++++++++++++ rigify/rigs/biped/leg/__init__.py | 237 ++++++++++++++++++ rigify/rigs/biped/leg/deform.py | 264 ++++++++++++++++++++ rigify/rigs/biped/leg/fk.py | 246 +++++++++++++++++++ rigify/rigs/biped/leg/ik.py | 499 ++++++++++++++++++++++++++++++++++++++ rigify/rigs/copy.py | 114 +++++++++ rigify/rigs/finger.py | 409 +++++++++++++++++++++++++++++++ rigify/rigs/misc/__init__.py | 0 rigify/rigs/misc/delta.py | 161 ++++++++++++ rigify/rigs/neck_short.py | 385 +++++++++++++++++++++++++++++ rigify/rigs/palm.py | 273 +++++++++++++++++++++ rigify/rigs/spine.py | 484 ++++++++++++++++++++++++++++++++++++ 17 files changed, 4000 insertions(+) create mode 100644 rigify/rigs/__init__.py create mode 100644 rigify/rigs/biped/__init__.py create mode 100644 rigify/rigs/biped/arm/__init__.py create mode 100644 rigify/rigs/biped/arm/deform.py create mode 100644 rigify/rigs/biped/arm/fk.py create mode 100644 rigify/rigs/biped/arm/ik.py create mode 100644 rigify/rigs/biped/leg/__init__.py create mode 100644 rigify/rigs/biped/leg/deform.py create mode 100644 rigify/rigs/biped/leg/fk.py create mode 100644 rigify/rigs/biped/leg/ik.py create mode 100644 rigify/rigs/copy.py create mode 100644 rigify/rigs/finger.py create mode 100644 rigify/rigs/misc/__init__.py create mode 100644 rigify/rigs/misc/delta.py create mode 100644 rigify/rigs/neck_short.py create mode 100644 rigify/rigs/palm.py create mode 100644 rigify/rigs/spine.py (limited to 'rigify/rigs') diff --git a/rigify/rigs/__init__.py b/rigify/rigs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/rigify/rigs/biped/__init__.py b/rigify/rigs/biped/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/rigify/rigs/biped/arm/__init__.py b/rigify/rigs/biped/arm/__init__.py new file mode 100644 index 00000000..4198345b --- /dev/null +++ b/rigify/rigs/biped/arm/__init__.py @@ -0,0 +1,209 @@ +#====================== 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 imp import reload +from . import fk, ik, deform +from rigify.utils import MetarigError, get_layers + +reload(fk) +reload(ik) +reload(deform) + +script = """ +fk_arm = ["%s", "%s", "%s"] +ik_arm = ["%s", "%s"] +if is_selected(fk_arm+ik_arm): + layout.prop(pose_bones[ik_arm[0]], '["ikfk_switch"]', text="FK / IK (" + ik_arm[0] + ")", slider=True) +if is_selected(fk_arm): + layout.prop(pose_bones[fk_arm[0]], '["isolate"]', text="Isolate Rotation (" + fk_arm[0] + ")", slider=True) +""" + + +class Rig: + """ An arm rig, with IK/FK switching and hinge switch. + + """ + def __init__(self, obj, bone, params): + """ Gather and validate data about the rig. + Store any data or references to data that will be needed later on. + In particular, store names of bones that will be needed. + Do NOT change any data in the scene. This is a gathering phase only. + + """ + # Gather deform rig + self.deform_rig = deform.Rig(obj, bone, params) + + # Gather FK rig + self.fk_rig = fk.Rig(obj, bone, params) + + # Gather IK rig + self.ik_rig = ik.Rig(obj, bone, params, ikfk_switch=True) + + 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_rig.generate() + fk_controls = self.fk_rig.generate() + ik_controls = self.ik_rig.generate() + return [script % (fk_controls[0], fk_controls[1], fk_controls[2], ik_controls[0], ik_controls[1])] + + @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_ik_layers = bpy.props.BoolProperty(name="Separate IK Control Layers:", default=False, description="Enable putting the ik controls on a separate layer from the fk controls.") + group.ik_layers = bpy.props.BoolVectorProperty(size=32, description="Layers for the ik controls to be on.") + + group.use_upper_arm_twist = bpy.props.BoolProperty(name="Upper Arm Twist", default=True, description="Generate the dual-bone twist setup for the upper arm.") + group.use_forearm_twist = bpy.props.BoolProperty(name="Forearm Twist", default=True, description="Generate the dual-bone twist setup for the forearm.") + + @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_ik_layers") + + r = layout.row() + r.active = params.separate_ik_layers + + col = r.column(align=True) + row = col.row(align=True) + row.prop(params, "ik_layers", index=0, toggle=True, text="") + row.prop(params, "ik_layers", index=1, toggle=True, text="") + row.prop(params, "ik_layers", index=2, toggle=True, text="") + row.prop(params, "ik_layers", index=3, toggle=True, text="") + row.prop(params, "ik_layers", index=4, toggle=True, text="") + row.prop(params, "ik_layers", index=5, toggle=True, text="") + row.prop(params, "ik_layers", index=6, toggle=True, text="") + row.prop(params, "ik_layers", index=7, toggle=True, text="") + row = col.row(align=True) + row.prop(params, "ik_layers", index=16, toggle=True, text="") + row.prop(params, "ik_layers", index=17, toggle=True, text="") + row.prop(params, "ik_layers", index=18, toggle=True, text="") + row.prop(params, "ik_layers", index=19, toggle=True, text="") + row.prop(params, "ik_layers", index=20, toggle=True, text="") + row.prop(params, "ik_layers", index=21, toggle=True, text="") + row.prop(params, "ik_layers", index=22, toggle=True, text="") + row.prop(params, "ik_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="Elbow rotation axis:") + r.prop(params, "primary_rotation_axis", text="") + + col = layout.column() + col.prop(params, "use_upper_arm_twist") + col.prop(params, "use_forearm_twist") + + @classmethod + def create_sample(self, obj): + # generated by rigify.utils.write_meta_rig + bpy.ops.object.mode_set(mode='EDIT') + arm = obj.data + + bones = {} + + bone = arm.edit_bones.new('upper_arm') + bone.head[:] = 0.0000, 0.0000, 0.0000 + bone.tail[:] = 0.3000, 0.0300, 0.0000 + bone.roll = 1.5708 + bone.use_connect = False + bones['upper_arm'] = bone.name + bone = arm.edit_bones.new('forearm') + bone.head[:] = 0.3000, 0.0300, 0.0000 + bone.tail[:] = 0.6000, 0.0000, 0.0000 + bone.roll = 1.5708 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['upper_arm']] + bones['forearm'] = bone.name + bone = arm.edit_bones.new('hand') + bone.head[:] = 0.6000, 0.0000, 0.0000 + bone.tail[:] = 0.7000, 0.0000, 0.0000 + bone.roll = 3.1416 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['forearm']] + bones['hand'] = bone.name + + bpy.ops.object.mode_set(mode='OBJECT') + pbone = obj.pose.bones[bones['upper_arm']] + pbone.rigify_type = 'biped.arm' + 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 = 'QUATERNION' + pbone.rigify_parameters.add() + pbone = obj.pose.bones[bones['forearm']] + 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 = 'QUATERNION' + pbone = obj.pose.bones[bones['hand']] + 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 = 'QUATERNION' + + 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 + diff --git a/rigify/rigs/biped/arm/deform.py b/rigify/rigs/biped/arm/deform.py new file mode 100644 index 00000000..2a7b3109 --- /dev/null +++ b/rigify/rigs/biped/arm/deform.py @@ -0,0 +1,230 @@ +#====================== 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 math import acos, degrees +from mathutils import Vector, Matrix +from rigify.utils import MetarigError +from rigify.utils import copy_bone, flip_bone, put_bone +from rigify.utils import connected_children_names +from rigify.utils import strip_org, make_mechanism_name, make_deformer_name + + +def align_roll(obj, bone1, bone2): + bone1_e = obj.data.edit_bones[bone1] + bone2_e = obj.data.edit_bones[bone2] + + bone1_e.roll = 0.0 + + # Get the directions the bones are pointing in, as vectors + y1 = bone1_e.y_axis + x1 = bone1_e.x_axis + y2 = bone2_e.y_axis + x2 = bone2_e.x_axis + + # Get the shortest axis to rotate bone1 on to point in the same direction as bone2 + axis = y1.cross(y2) + axis.normalize() + + # Angle to rotate on that shortest axis + angle = y1.angle(y2) + + # Create rotation matrix to make bone1 point in the same direction as bone2 + rot_mat = Matrix.Rotation(angle, 3, axis) + + # Roll factor + x3 = x1 * rot_mat + dot = x2 * x3 + if dot > 1.0: + dot = 1.0 + elif dot < -1.0: + dot = -1.0 + roll = acos(dot) + + # Set the roll + bone1_e.roll = roll + + # Check if we rolled in the right direction + x3 = bone1_e.x_axis * rot_mat + check = x2 * x3 + + # If not, reverse + if check < 0.9999: + bone1_e.roll = -roll + + +class Rig: + """ An FK arm rig, with hinge switch. + + """ + def __init__(self, obj, bone, params): + """ Gather and validate data about the rig. + Store any data or references to data that will be needed later on. + In particular, store references to bones that will be needed, and + store names of bones that will be needed. + Do NOT change any data in the scene. This is a gathering phase only. + + """ + self.obj = obj + self.params = params + + # Get the chain of 3 connected bones + self.org_bones = [bone] + connected_children_names(self.obj, bone)[:2] + + if len(self.org_bones) != 3: + raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 3 bones." % (strip_org(bone))) + + # Get rig parameters + self.use_upper_arm_twist = params.use_upper_arm_twist + self.use_forearm_twist = params.use_forearm_twist + + 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. + + """ + bpy.ops.object.mode_set(mode='EDIT') + + # Create upper arm bones + if self.use_upper_arm_twist: + uarm1 = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".01"))) + uarm2 = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".02"))) + utip = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(self.org_bones[0] + ".tip"))) + else: + uarm = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0]))) + + # Create forearm bones + if self.use_forearm_twist: + farm1 = copy_bone(self.obj, self.org_bones[1], make_deformer_name(strip_org(self.org_bones[1] + ".01"))) + farm2 = copy_bone(self.obj, self.org_bones[1], make_deformer_name(strip_org(self.org_bones[1] + ".02"))) + ftip = copy_bone(self.obj, self.org_bones[1], make_mechanism_name(strip_org(self.org_bones[1] + ".tip"))) + else: + farm = copy_bone(self.obj, self.org_bones[1], make_deformer_name(strip_org(self.org_bones[1]))) + + # Create hand bone + hand = copy_bone(self.obj, self.org_bones[2], make_deformer_name(strip_org(self.org_bones[2]))) + + # Get edit bones + eb = self.obj.data.edit_bones + + org_uarm_e = eb[self.org_bones[0]] + if self.use_upper_arm_twist: + uarm1_e = eb[uarm1] + uarm2_e = eb[uarm2] + utip_e = eb[utip] + else: + uarm_e = eb[uarm] + + org_farm_e = eb[self.org_bones[1]] + if self.use_forearm_twist: + farm1_e = eb[farm1] + farm2_e = eb[farm2] + ftip_e = eb[ftip] + else: + farm_e = eb[farm] + + org_hand_e = eb[self.org_bones[2]] + hand_e = eb[hand] + + # Parent and position upper arm bones + if self.use_upper_arm_twist: + uarm1_e.use_connect = False + uarm2_e.use_connect = False + utip_e.use_connect = False + + uarm1_e.parent = org_uarm_e.parent + uarm2_e.parent = org_uarm_e + utip_e.parent = org_uarm_e + + center = Vector((org_uarm_e.head + org_uarm_e.tail) / 2) + + uarm1_e.tail = center + uarm2_e.head = center + put_bone(self.obj, utip, org_uarm_e.tail) + utip_e.length = org_uarm_e.length / 8 + else: + uarm_e.use_connect = False + uarm_e.parent = org_uarm_e + + # Parent and position forearm bones + if self.use_forearm_twist: + farm1_e.use_connect = False + farm2_e.use_connect = False + ftip_e.use_connect = False + + farm1_e.parent = org_farm_e + farm2_e.parent = org_farm_e + ftip_e.parent = org_farm_e + + center = Vector((org_farm_e.head + org_farm_e.tail) / 2) + + farm1_e.tail = center + farm2_e.head = center + put_bone(self.obj, ftip, org_farm_e.tail) + ftip_e.length = org_farm_e.length / 8 + + # Align roll of farm2 with hand + align_roll(self.obj, farm2, hand) + else: + farm_e.use_connect = False + farm_e.parent = org_farm_e + + # Parent hand + hand_e.use_connect = False + hand_e.parent = org_hand_e + + # Object mode, get pose bones + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + + if self.use_upper_arm_twist: + uarm1_p = pb[uarm1] + if self.use_forearm_twist: + farm2_p = pb[farm2] + hand_p = pb[hand] + + # Upper arm constraints + if self.use_upper_arm_twist: + con = uarm1_p.constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + con.subtarget = self.org_bones[0] + + con = uarm1_p.constraints.new('COPY_SCALE') + con.name = "copy_scale" + con.target = self.obj + con.subtarget = self.org_bones[0] + + con = uarm1_p.constraints.new('DAMPED_TRACK') + con.name = "track_to" + con.target = self.obj + con.subtarget = utip + + # Forearm constraints + if self.use_forearm_twist: + con = farm2_p.constraints.new('COPY_ROTATION') + con.name = "copy_rotation" + con.target = self.obj + con.subtarget = hand + + con = farm2_p.constraints.new('DAMPED_TRACK') + con.name = "track_to" + con.target = self.obj + con.subtarget = ftip + diff --git a/rigify/rigs/biped/arm/fk.py b/rigify/rigs/biped/arm/fk.py new file mode 100644 index 00000000..20ba89f2 --- /dev/null +++ b/rigify/rigs/biped/arm/fk.py @@ -0,0 +1,208 @@ +#====================== 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 +import math +from rigify.utils import MetarigError +from rigify.utils import copy_bone, flip_bone, put_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_limb_widget +from rna_prop_ui import rna_idprop_ui_prop_get + + +class Rig: + """ An FK arm rig, with hinge switch. + + """ + def __init__(self, obj, bone, params): + """ Gather and validate data about the rig. + Store any data or references to data that will be needed later on. + In particular, store references to bones that will be needed, and + store names of bones that will be needed. + Do NOT change any data in the scene. This is a gathering phase only. + + """ + self.obj = obj + self.params = params + + # Get the chain of 3 connected bones + self.org_bones = [bone] + connected_children_names(self.obj, bone)[:2] + + if len(self.org_bones) != 3: + raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 3 bones." % (strip_org(bone))) + + # Get (optional) parent + if self.obj.data.bones[bone].parent == None: + self.org_parent = None + else: + self.org_parent = self.obj.data.bones[bone].parent.name + + # Get the rig parameters + if "layers" in params: + self.layers = get_layers(params["layers"]) + else: + self.layers = None + + self.primary_rotation_axis = params.primary_rotation_axis + + 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. + + """ + bpy.ops.object.mode_set(mode='EDIT') + + # Create the control bones + uarm = copy_bone(self.obj, self.org_bones[0], strip_org(self.org_bones[0])) + farm = copy_bone(self.obj, self.org_bones[1], strip_org(self.org_bones[1])) + hand = copy_bone(self.obj, self.org_bones[2], strip_org(self.org_bones[2])) + + # Create the hinge bones + if self.org_parent != None: + hinge = copy_bone(self.obj, self.org_parent, make_mechanism_name(uarm + ".hinge")) + socket1 = copy_bone(self.obj, uarm, make_mechanism_name(uarm + ".socket1")) + socket2 = copy_bone(self.obj, uarm, make_mechanism_name(uarm + ".socket2")) + + # Get edit bones + eb = self.obj.data.edit_bones + + uarm_e = eb[uarm] + farm_e = eb[farm] + hand_e = eb[hand] + + if self.org_parent != None: + hinge_e = eb[hinge] + socket1_e = eb[socket1] + socket2_e = eb[socket2] + + # Parenting + farm_e.parent = uarm_e + hand_e.parent = farm_e + + if self.org_parent != None: + hinge_e.use_connect = False + socket1_e.use_connect = False + socket2_e.use_connect = False + + uarm_e.parent = hinge_e + hinge_e.parent = socket2_e + socket2_e.parent = None + + # Positioning + if self.org_parent != None: + center = (hinge_e.head + hinge_e.tail) / 2 + hinge_e.head = center + socket1_e.length /= 4 + socket2_e.length /= 3 + + # Object mode, get pose bones + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + + uarm_p = pb[uarm] + farm_p = pb[farm] + hand_p = pb[hand] + + if self.org_parent != None: + socket1_p = pb[socket1] + socket2_p = pb[socket2] + + # Set the elbow to only bend on the x-axis. + farm_p.rotation_mode = 'XYZ' + if 'X' in self.primary_rotation_axis: + farm_p.lock_rotation = (False, True, True) + elif 'Y' in self.primary_rotation_axis: + farm_p.lock_rotation = (True, False, True) + else: + farm_p.lock_rotation = (True, True, False) + + # Set up custom properties + if self.org_parent != None: + prop = rna_idprop_ui_prop_get(uarm_p, "isolate", create=True) + uarm_p["isolate"] = 0.0 + prop["soft_min"] = prop["min"] = 0.0 + prop["soft_max"] = prop["max"] = 1.0 + + # Hinge constraints / drivers + if self.org_parent != None: + con = socket2_p.constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + con.subtarget = socket1 + + con = socket2_p.constraints.new('COPY_TRANSFORMS') + con.name = "isolate_off" + con.target = self.obj + con.subtarget = socket1 + + # Driver + fcurve = con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = "var" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = uarm_p.path_from_id() + '["isolate"]' + mod = fcurve.modifiers[0] + mod.poly_order = 1 + mod.coefficients[0] = 1.0 + mod.coefficients[1] = -1.0 + + # Constrain org bones to controls + con = pb[self.org_bones[0]].constraints.new('COPY_TRANSFORMS') + con.name = "fk" + con.target = self.obj + con.subtarget = uarm + + con = pb[self.org_bones[1]].constraints.new('COPY_TRANSFORMS') + con.name = "fk" + con.target = self.obj + con.subtarget = farm + + con = pb[self.org_bones[2]].constraints.new('COPY_TRANSFORMS') + con.name = "fk" + con.target = self.obj + con.subtarget = hand + + # Set layers if specified + if self.layers: + uarm_p.bone.layers = self.layers + farm_p.bone.layers = self.layers + hand_p.bone.layers = self.layers + + # Create control widgets + create_limb_widget(self.obj, uarm) + create_limb_widget(self.obj, farm) + + ob = create_widget(self.obj, hand) + if ob != None: + verts = [(0.7, 1.5, 0.0), (0.7, -0.25, 0.0), (-0.7, -0.25, 0.0), (-0.7, 1.5, 0.0), (0.7, 0.723, 0.0), (-0.7, 0.723, 0.0), (0.7, 0.0, 0.0), (-0.7, 0.0, 0.0)] + edges = [(1, 2), (0, 3), (0, 4), (3, 5), (4, 6), (1, 6), (5, 7), (2, 7)] + mesh = ob.data + mesh.from_pydata(verts, edges, []) + mesh.update() + + mod = ob.modifiers.new("subsurf", 'SUBSURF') + mod.levels = 2 + + return [uarm, farm, hand] + diff --git a/rigify/rigs/biped/arm/ik.py b/rigify/rigs/biped/arm/ik.py new file mode 100644 index 00000000..c341ede6 --- /dev/null +++ b/rigify/rigs/biped/arm/ik.py @@ -0,0 +1,281 @@ +#====================== 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 math import pi +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, insert_before_lr +from rigify.utils import get_layers +from rigify.utils import create_widget, create_line_widget, create_sphere_widget +from rna_prop_ui import rna_idprop_ui_prop_get + + +class Rig: + """ An IK arm rig, with an optional ik/fk switch. + + """ + def __init__(self, obj, bone, params, ikfk_switch=False): + """ Gather and validate data about the rig. + Store any data or references to data that will be needed later on. + In particular, store references to bones that will be needed, and + store names of bones that will be needed. + Do NOT change any data in the scene. This is a gathering phase only. + + ikfk_switch: if True, create an ik/fk switch slider + """ + self.obj = obj + self.params = params + self.switch = ikfk_switch + + # Get the chain of 3 connected bones + self.org_bones = [bone] + connected_children_names(self.obj, bone)[:2] + + if len(self.org_bones) != 3: + raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 3 bones." % (strip_org(bone))) + + # Get the rig parameters + if params.separate_ik_layers: + self.layers = list(params.ik_layers) + else: + self.layers = None + + self.primary_rotation_axis = params.primary_rotation_axis + + 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. + + """ + bpy.ops.object.mode_set(mode='EDIT') + + # Create the bones + uarm = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(insert_before_lr(self.org_bones[0], "_ik")))) + farm = copy_bone(self.obj, self.org_bones[1], make_mechanism_name(strip_org(insert_before_lr(self.org_bones[1], "_ik")))) + + hand = copy_bone(self.obj, self.org_bones[2], strip_org(insert_before_lr(self.org_bones[2], "_ik"))) + pole = copy_bone(self.obj, self.org_bones[0], strip_org(insert_before_lr(self.org_bones[0], "_pole"))) + + vishand = copy_bone(self.obj, self.org_bones[2], "VIS-" + strip_org(insert_before_lr(self.org_bones[2], "_ik"))) + vispole = copy_bone(self.obj, self.org_bones[1], "VIS-" + strip_org(insert_before_lr(self.org_bones[0], "_pole"))) + + # Get edit bones + eb = self.obj.data.edit_bones + + uarm_e = eb[uarm] + farm_e = eb[farm] + hand_e = eb[hand] + pole_e = eb[pole] + vishand_e = eb[vishand] + vispole_e = eb[vispole] + + # Parenting + farm_e.parent = uarm_e + + hand_e.use_connect = False + hand_e.parent = None + + pole_e.use_connect = False + + vishand_e.use_connect = False + vishand_e.parent = None + + vispole_e.use_connect = False + vispole_e.parent = None + + # Misc + hand_e.use_local_location = False + + vishand_e.hide_select = True + vispole_e.hide_select = True + + # Positioning + v1 = farm_e.tail - uarm_e.head + if 'X' in self.primary_rotation_axis or 'Y' in self.primary_rotation_axis: + v2 = v1.cross(farm_e.x_axis) + if (v2 * farm_e.z_axis) > 0.0: + v2 *= -1.0 + else: + v2 = v1.cross(farm_e.z_axis) + if (v2 * farm_e.x_axis) < 0.0: + v2 *= -1.0 + v2.normalize() + v2 *= v1.length + + if '-' in self.primary_rotation_axis: + v2 *= -1 + + pole_e.head = farm_e.head + v2 + pole_e.tail = pole_e.head + (Vector((0, 1, 0)) * (v1.length / 8)) + pole_e.roll = 0.0 + + vishand_e.tail = vishand_e.head + Vector((0, 0, v1.length / 32)) + vispole_e.tail = vispole_e.head + Vector((0, 0, v1.length / 32)) + + # Object mode, get pose bones + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + + uarm_p = pb[uarm] + farm_p = pb[farm] + hand_p = pb[hand] + pole_p = pb[pole] + vishand_p = pb[vishand] + vispole_p = pb[vispole] + + # Set the elbow to only bend on the primary axis + if 'X' in self.primary_rotation_axis: + farm_p.lock_ik_y = True + farm_p.lock_ik_z = True + elif 'Y' in self.primary_rotation_axis: + farm_p.lock_ik_x = True + farm_p.lock_ik_z = True + else: + farm_p.lock_ik_x = True + farm_p.lock_ik_y = True + + # Pole target only translates + pole_p.lock_location = False, False, False + pole_p.lock_rotation = True, True, True + pole_p.lock_rotation_w = True + pole_p.lock_scale = True, True, True + + # Set up custom properties + if self.switch == True: + prop = rna_idprop_ui_prop_get(hand_p, "ikfk_switch", create=True) + hand_p["ikfk_switch"] = 0.0 + prop["soft_min"] = prop["min"] = 0.0 + prop["soft_max"] = prop["max"] = 1.0 + + # IK Constraint + con = farm_p.constraints.new('IK') + con.name = "ik" + con.target = self.obj + con.subtarget = hand + con.pole_target = self.obj + con.pole_subtarget = pole + if self.primary_rotation_axis == 'X' or self.primary_rotation_axis == 'Y': + con.pole_angle = -pi / 2 + elif self.primary_rotation_axis == '-X' or self.primary_rotation_axis == '-Y': + con.pole_angle = pi / 2 + elif self.primary_rotation_axis == 'Z': + con.pole_angle = 0.0 + elif self.primary_rotation_axis == '-Z': + con.pole_angle = pi + con.chain_count = 2 + + # Constrain org bones to controls + con = pb[self.org_bones[0]].constraints.new('COPY_TRANSFORMS') + con.name = "ik" + con.target = self.obj + con.subtarget = uarm + if self.switch == True: + # IK/FK switch driver + fcurve = con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = "var" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = hand_p.path_from_id() + '["ikfk_switch"]' + + con = pb[self.org_bones[1]].constraints.new('COPY_TRANSFORMS') + con.name = "ik" + con.target = self.obj + con.subtarget = farm + if self.switch == True: + # IK/FK switch driver + fcurve = con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = "var" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = hand_p.path_from_id() + '["ikfk_switch"]' + + con = pb[self.org_bones[2]].constraints.new('COPY_TRANSFORMS') + con.name = "ik" + con.target = self.obj + con.subtarget = hand + if self.switch == True: + # IK/FK switch driver + fcurve = con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = "var" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = hand_p.path_from_id() + '["ikfk_switch"]' + + # VIS hand constraints + con = vishand_p.constraints.new('COPY_LOCATION') + con.name = "copy_loc" + con.target = self.obj + con.subtarget = self.org_bones[2] + + con = vishand_p.constraints.new('STRETCH_TO') + con.name = "stretch_to" + con.target = self.obj + con.subtarget = hand + con.volume = 'NO_VOLUME' + con.rest_length = vishand_p.length + + # VIS pole constraints + con = vispole_p.constraints.new('COPY_LOCATION') + con.name = "copy_loc" + con.target = self.obj + con.subtarget = self.org_bones[1] + + con = vispole_p.constraints.new('STRETCH_TO') + con.name = "stretch_to" + con.target = self.obj + con.subtarget = pole + con.volume = 'NO_VOLUME' + con.rest_length = vispole_p.length + + # Set layers if specified + if self.layers: + hand_p.bone.layers = self.layers + pole_p.bone.layers = self.layers + vishand_p.bone.layers = self.layers + vispole_p.bone.layers = self.layers + + # Create widgets + create_line_widget(self.obj, vispole) + create_line_widget(self.obj, vishand) + create_sphere_widget(self.obj, pole) + + ob = create_widget(self.obj, hand) + if ob != None: + verts = [(0.7, 1.5, 0.0), (0.7, -0.25, 0.0), (-0.7, -0.25, 0.0), (-0.7, 1.5, 0.0), (0.7, 0.723, 0.0), (-0.7, 0.723, 0.0), (0.7, 0.0, 0.0), (-0.7, 0.0, 0.0)] + edges = [(1, 2), (0, 3), (0, 4), (3, 5), (4, 6), (1, 6), (5, 7), (2, 7)] + mesh = ob.data + mesh.from_pydata(verts, edges, []) + mesh.update() + + mod = ob.modifiers.new("subsurf", 'SUBSURF') + mod.levels = 2 + + return [hand, pole] + diff --git a/rigify/rigs/biped/leg/__init__.py b/rigify/rigs/biped/leg/__init__.py new file mode 100644 index 00000000..ac8913f5 --- /dev/null +++ b/rigify/rigs/biped/leg/__init__.py @@ -0,0 +1,237 @@ +#====================== 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 imp import reload +from . import fk, ik, deform +from rigify.utils import MetarigError, get_layers + +reload(fk) +reload(ik) +reload(deform) + +script = """ +fk_leg = ["%s", "%s", "%s"] +ik_leg = ["%s", "%s", "%s"] +if is_selected(fk_leg+ik_leg): + layout.prop(pose_bones[ik_leg[0]], '["ikfk_switch"]', text="FK / IK (" + ik_leg[0] + ")", slider=True) +if is_selected(fk_leg): + layout.prop(pose_bones[fk_leg[0]], '["isolate"]', text="Isolate Rotation (" + fk_leg[0] + ")", slider=True) +""" + + +class Rig: + """ A leg rig, with IK/FK switching, a hinge switch, and foot roll. + + """ + def __init__(self, obj, bone, params): + """ Gather and validate data about the rig. + Store any data or references to data that will be needed later on. + In particular, store names of bones that will be needed. + Do NOT change any data in the scene. This is a gathering phase only. + + """ + # Gather deform rig + self.deform_rig = deform.Rig(obj, bone, params) + + # Gather FK rig + self.fk_rig = fk.Rig(obj, bone, params) + + # Gather IK rig + self.ik_rig = ik.Rig(obj, bone, params, ikfk_switch=True) + + 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_rig.generate() + fk_controls = self.fk_rig.generate() + ik_controls = self.ik_rig.generate() + return [script % (fk_controls[0], fk_controls[1], fk_controls[2], ik_controls[0], ik_controls[1], ik_controls[2])] + + @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_ik_layers = bpy.props.BoolProperty(name="Separate IK Control Layers:", default=False, description="Enable putting the ik controls on a separate layer from the fk controls.") + group.ik_layers = bpy.props.BoolVectorProperty(size=32, description="Layers for the ik controls to be on.") + + group.use_thigh_twist = bpy.props.BoolProperty(name="Thigh Twist", default=True, description="Generate the dual-bone twist setup for the thigh.") + group.use_shin_twist = bpy.props.BoolProperty(name="Shin Twist", default=True, description="Generate the dual-bone twist setup for the shin.") + + @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_ik_layers") + + r = layout.row() + r.active = params.separate_ik_layers + + col = r.column(align=True) + row = col.row(align=True) + row.prop(params, "ik_layers", index=0, toggle=True, text="") + row.prop(params, "ik_layers", index=1, toggle=True, text="") + row.prop(params, "ik_layers", index=2, toggle=True, text="") + row.prop(params, "ik_layers", index=3, toggle=True, text="") + row.prop(params, "ik_layers", index=4, toggle=True, text="") + row.prop(params, "ik_layers", index=5, toggle=True, text="") + row.prop(params, "ik_layers", index=6, toggle=True, text="") + row.prop(params, "ik_layers", index=7, toggle=True, text="") + row = col.row(align=True) + row.prop(params, "ik_layers", index=16, toggle=True, text="") + row.prop(params, "ik_layers", index=17, toggle=True, text="") + row.prop(params, "ik_layers", index=18, toggle=True, text="") + row.prop(params, "ik_layers", index=19, toggle=True, text="") + row.prop(params, "ik_layers", index=20, toggle=True, text="") + row.prop(params, "ik_layers", index=21, toggle=True, text="") + row.prop(params, "ik_layers", index=22, toggle=True, text="") + row.prop(params, "ik_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="Knee rotation axis:") + r.prop(params, "primary_rotation_axis", text="") + + col = layout.column() + col.prop(params, "use_thigh_twist") + col.prop(params, "use_shin_twist") + + @classmethod + def create_sample(self, obj): + # generated by rigify.utils.write_meta_rig + bpy.ops.object.mode_set(mode='EDIT') + arm = obj.data + + bones = {} + + bone = arm.edit_bones.new('thigh') + bone.head[:] = -0.0000, 0.0000, 1.0000 + bone.tail[:] = -0.0000, -0.0500, 0.5000 + bone.roll = -0.0000 + bone.use_connect = False + bones['thigh'] = bone.name + bone = arm.edit_bones.new('shin') + bone.head[:] = -0.0000, -0.0500, 0.5000 + bone.tail[:] = -0.0000, 0.0000, 0.1000 + bone.roll = -0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['thigh']] + bones['shin'] = bone.name + bone = arm.edit_bones.new('foot') + bone.head[:] = -0.0000, 0.0000, 0.1000 + bone.tail[:] = 0.0000, -0.1200, 0.0300 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['shin']] + bones['foot'] = bone.name + bone = arm.edit_bones.new('heel') + bone.head[:] = -0.0000, 0.0000, 0.1000 + bone.tail[:] = -0.0000, 0.0600, 0.0000 + bone.roll = -0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['shin']] + bones['heel'] = bone.name + bone = arm.edit_bones.new('toe') + bone.head[:] = 0.0000, -0.1200, 0.0300 + bone.tail[:] = 0.0000, -0.2000, 0.0300 + bone.roll = 3.1416 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['foot']] + bones['toe'] = bone.name + + bpy.ops.object.mode_set(mode='OBJECT') + pbone = obj.pose.bones[bones['thigh']] + pbone.rigify_type = 'biped.leg' + 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 = 'QUATERNION' + pbone.rigify_parameters.add() + pbone = obj.pose.bones[bones['shin']] + 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 = 'QUATERNION' + pbone = obj.pose.bones[bones['foot']] + 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 = 'QUATERNION' + pbone = obj.pose.bones[bones['heel']] + 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 = 'QUATERNION' + pbone = obj.pose.bones[bones['toe']] + 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 = 'QUATERNION' + + 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 + diff --git a/rigify/rigs/biped/leg/deform.py b/rigify/rigs/biped/leg/deform.py new file mode 100644 index 00000000..df6c6a60 --- /dev/null +++ b/rigify/rigs/biped/leg/deform.py @@ -0,0 +1,264 @@ +#====================== 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 math import acos, degrees +from mathutils import Vector, Matrix +from rigify.utils import MetarigError +from rigify.utils import copy_bone, flip_bone, put_bone +from rigify.utils import connected_children_names +from rigify.utils import strip_org, make_mechanism_name, make_deformer_name + + +def align_roll(obj, bone1, bone2): + bone1_e = obj.data.edit_bones[bone1] + bone2_e = obj.data.edit_bones[bone2] + + bone1_e.roll = 0.0 + + # Get the directions the bones are pointing in, as vectors + y1 = bone1_e.y_axis + x1 = bone1_e.x_axis + y2 = bone2_e.y_axis + x2 = bone2_e.x_axis + + # Get the shortest axis to rotate bone1 on to point in the same direction as bone2 + axis = y1.cross(y2) + axis.normalize() + + # Angle to rotate on that shortest axis + angle = y1.angle(y2) + + # Create rotation matrix to make bone1 point in the same direction as bone2 + rot_mat = Matrix.Rotation(angle, 3, axis) + + # Roll factor + x3 = x1 * rot_mat + dot = x2 * x3 + if dot > 1.0: + dot = 1.0 + elif dot < -1.0: + dot = -1.0 + roll = acos(dot) + + # Set the roll + bone1_e.roll = roll + + # Check if we rolled in the right direction + x3 = bone1_e.x_axis * rot_mat + check = x2 * x3 + + # If not, reverse + if check < 0.9999: + bone1_e.roll = -roll + + +class Rig: + """ A leg deform-bone setup. + + """ + def __init__(self, obj, bone, params): + """ Gather and validate data about the rig. + Store any data or references to data that will be needed later on. + In particular, store references to bones that will be needed, and + store names of bones that will be needed. + Do NOT change any data in the scene. This is a gathering phase only. + + """ + self.obj = obj + self.params = params + + # Get the chain of 2 connected bones + leg_bones = [bone] + connected_children_names(self.obj, bone)[:2] + + if len(leg_bones) != 2: + raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone))) + + # Get the foot and heel + foot = None + heel = None + for b in self.obj.data.bones[leg_bones[1]].children: + if b.use_connect == True: + if len(b.children) == 0: + heel = b.name + else: + foot = b.name + + if foot == None or heel == None: + raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone))) + + # Get the toe + toe = None + for b in self.obj.data.bones[foot].children: + if b.use_connect == True: + toe = b.name + + if toe == None: + raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone))) + + self.org_bones = leg_bones + [foot, toe, heel] + + # Get rig parameters + self.use_thigh_twist = params.use_thigh_twist + self.use_shin_twist = params.use_shin_twist + + 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. + + """ + bpy.ops.object.mode_set(mode='EDIT') + + # Create upper arm bones + if self.use_thigh_twist: + thigh1 = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".01"))) + thigh2 = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".02"))) + utip = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(self.org_bones[0] + ".tip"))) + else: + thigh = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0]))) + + # Create forearm bones + if self.use_shin_twist: + shin1 = copy_bone(self.obj, self.org_bones[1], make_deformer_name(strip_org(self.org_bones[1] + ".01"))) + shin2 = copy_bone(self.obj, self.org_bones[1], make_deformer_name(strip_org(self.org_bones[1] + ".02"))) + stip = copy_bone(self.obj, self.org_bones[1], make_mechanism_name(strip_org(self.org_bones[1] + ".tip"))) + else: + shin = copy_bone(self.obj, self.org_bones[1], make_deformer_name(strip_org(self.org_bones[1]))) + + # Create foot bone + foot = copy_bone(self.obj, self.org_bones[2], make_deformer_name(strip_org(self.org_bones[2]))) + + # Create toe bone + toe = copy_bone(self.obj, self.org_bones[3], make_deformer_name(strip_org(self.org_bones[3]))) + + # Get edit bones + eb = self.obj.data.edit_bones + + org_thigh_e = eb[self.org_bones[0]] + if self.use_thigh_twist: + thigh1_e = eb[thigh1] + thigh2_e = eb[thigh2] + utip_e = eb[utip] + else: + thigh_e = eb[thigh] + + org_shin_e = eb[self.org_bones[1]] + if self.use_shin_twist: + shin1_e = eb[shin1] + shin2_e = eb[shin2] + stip_e = eb[stip] + else: + shin_e = eb[shin] + + org_foot_e = eb[self.org_bones[2]] + foot_e = eb[foot] + + org_toe_e = eb[self.org_bones[3]] + toe_e = eb[toe] + + # Parent and position thigh bones + if self.use_thigh_twist: + thigh1_e.use_connect = False + thigh2_e.use_connect = False + utip_e.use_connect = False + + thigh1_e.parent = org_thigh_e.parent + thigh2_e.parent = org_thigh_e + utip_e.parent = org_thigh_e + + center = Vector((org_thigh_e.head + org_thigh_e.tail) / 2) + + thigh1_e.tail = center + thigh2_e.head = center + put_bone(self.obj, utip, org_thigh_e.tail) + utip_e.length = org_thigh_e.length / 8 + else: + thigh_e.use_connect = False + thigh_e.parent = org_thigh_e + + # Parent and position shin bones + if self.use_shin_twist: + shin1_e.use_connect = False + shin2_e.use_connect = False + stip_e.use_connect = False + + shin1_e.parent = org_shin_e + shin2_e.parent = org_shin_e + stip_e.parent = org_shin_e + + center = Vector((org_shin_e.head + org_shin_e.tail) / 2) + + shin1_e.tail = center + shin2_e.head = center + put_bone(self.obj, stip, org_shin_e.tail) + stip_e.length = org_shin_e.length / 8 + + # Align roll of shin2 with foot + align_roll(self.obj, shin2, foot) + else: + shin_e.use_connect = False + shin_e.parent = org_shin_e + + # Parent foot + foot_e.use_connect = False + foot_e.parent = org_foot_e + + # Parent toe + toe_e.use_connect = False + toe_e.parent = org_toe_e + + # Object mode, get pose bones + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + + if self.use_thigh_twist: + thigh1_p = pb[thigh1] + if self.use_shin_twist: + shin2_p = pb[shin2] + foot_p = pb[foot] + + # Thigh constraints + if self.use_thigh_twist: + con = thigh1_p.constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + con.subtarget = self.org_bones[0] + + con = thigh1_p.constraints.new('COPY_SCALE') + con.name = "copy_scale" + con.target = self.obj + con.subtarget = self.org_bones[0] + + con = thigh1_p.constraints.new('DAMPED_TRACK') + con.name = "track_to" + con.target = self.obj + con.subtarget = utip + + # Shin constraints + if self.use_shin_twist: + con = shin2_p.constraints.new('COPY_ROTATION') + con.name = "copy_rotation" + con.target = self.obj + con.subtarget = foot + + con = shin2_p.constraints.new('DAMPED_TRACK') + con.name = "track_to" + con.target = self.obj + con.subtarget = stip + diff --git a/rigify/rigs/biped/leg/fk.py b/rigify/rigs/biped/leg/fk.py new file mode 100644 index 00000000..a212d445 --- /dev/null +++ b/rigify/rigs/biped/leg/fk.py @@ -0,0 +1,246 @@ +#====================== 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 +import math +from mathutils import Vector +from rigify.utils import MetarigError +from rigify.utils import copy_bone, flip_bone, put_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_limb_widget +from rna_prop_ui import rna_idprop_ui_prop_get + + +class Rig: + """ An FK leg rig, with hinge switch. + + """ + def __init__(self, obj, bone, params): + """ Gather and validate data about the rig. + Store any data or references to data that will be needed later on. + In particular, store references to bones that will be needed, and + store names of bones that will be needed. + Do NOT change any data in the scene. This is a gathering phase only. + + """ + self.obj = obj + self.params = params + + # Get the chain of 2 connected bones + leg_bones = [bone] + connected_children_names(self.obj, bone)[:2] + + if len(leg_bones) != 2: + raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone))) + + # Get the foot and heel + foot = None + heel = None + for b in self.obj.data.bones[leg_bones[1]].children: + if b.use_connect == True: + if len(b.children) == 0: + heel = b.name + else: + foot = b.name + + if foot == None or heel == None: + raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone))) + + # Get the toe + toe = None + for b in self.obj.data.bones[foot].children: + if b.use_connect == True: + toe = b.name + + # Get the toe + if toe == None: + raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone))) + + self.org_bones = leg_bones + [foot, toe, heel] + + # Get (optional) parent + if self.obj.data.bones[bone].parent == None: + self.org_parent = None + else: + self.org_parent = self.obj.data.bones[bone].parent.name + + # Get rig parameters + if "layers" in params: + self.layers = get_layers(params["layers"]) + else: + self.layers = None + + self.primary_rotation_axis = params.primary_rotation_axis + + 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. + + """ + bpy.ops.object.mode_set(mode='EDIT') + + # Create the control bones + thigh = copy_bone(self.obj, self.org_bones[0], strip_org(self.org_bones[0])) + shin = copy_bone(self.obj, self.org_bones[1], strip_org(self.org_bones[1])) + foot = copy_bone(self.obj, self.org_bones[2], strip_org(self.org_bones[2])) + + # Create the foot mechanism bone + foot_mch = copy_bone(self.obj, self.org_bones[2], make_mechanism_name(strip_org(self.org_bones[2]))) + + # Create the hinge bones + if self.org_parent != None: + hinge = copy_bone(self.obj, self.org_parent, make_mechanism_name(thigh + ".hinge")) + socket1 = copy_bone(self.obj, thigh, make_mechanism_name(thigh + ".socket1")) + socket2 = copy_bone(self.obj, thigh, make_mechanism_name(thigh + ".socket2")) + + # Get edit bones + eb = self.obj.data.edit_bones + + thigh_e = eb[thigh] + shin_e = eb[shin] + foot_e = eb[foot] + foot_mch_e = eb[foot_mch] + + if self.org_parent != None: + hinge_e = eb[hinge] + socket1_e = eb[socket1] + socket2_e = eb[socket2] + + # Parenting + shin_e.parent = thigh_e + foot_e.parent = shin_e + + foot_mch_e.use_connect = False + foot_mch_e.parent = foot_e + + if self.org_parent != None: + hinge_e.use_connect = False + socket1_e.use_connect = False + socket2_e.use_connect = False + + thigh_e.parent = hinge_e + hinge_e.parent = socket2_e + socket2_e.parent = None + + # Positioning + vec = Vector(eb[self.org_bones[3]].vector) + vec = vec.normalize() + foot_e.tail = foot_e.head + (vec * foot_e.length) + foot_e.roll = eb[self.org_bones[3]].roll + + if self.org_parent != None: + center = (hinge_e.head + hinge_e.tail) / 2 + hinge_e.head = center + socket1_e.length /= 4 + socket2_e.length /= 3 + + # Object mode, get pose bones + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + + thigh_p = pb[thigh] + shin_p = pb[shin] + foot_p = pb[foot] + + if self.org_parent != None: + socket1_p = pb[socket1] + socket2_p = pb[socket2] + + # Set the elbow to only bend on the x-axis. + shin_p.rotation_mode = 'XYZ' + if 'X' in self.primary_rotation_axis: + shin_p.lock_rotation = (False, True, True) + elif 'Y' in self.primary_rotation_axis: + shin_p.lock_rotation = (True, False, True) + else: + shin_p.lock_rotation = (True, True, False) + + # Set up custom properties + if self.org_parent != None: + prop = rna_idprop_ui_prop_get(thigh_p, "isolate", create=True) + thigh_p["isolate"] = 0.0 + prop["soft_min"] = prop["min"] = 0.0 + prop["soft_max"] = prop["max"] = 1.0 + + # Hinge constraints / drivers + if self.org_parent != None: + con = socket2_p.constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + con.subtarget = socket1 + + con = socket2_p.constraints.new('COPY_TRANSFORMS') + con.name = "isolate_off" + con.target = self.obj + con.subtarget = socket1 + + # Driver + fcurve = con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = "var" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = thigh_p.path_from_id() + '["isolate"]' + mod = fcurve.modifiers[0] + mod.poly_order = 1 + mod.coefficients[0] = 1.0 + mod.coefficients[1] = -1.0 + + # Constrain org bones to controls + con = pb[self.org_bones[0]].constraints.new('COPY_TRANSFORMS') + con.name = "fk" + con.target = self.obj + con.subtarget = thigh + + con = pb[self.org_bones[1]].constraints.new('COPY_TRANSFORMS') + con.name = "fk" + con.target = self.obj + con.subtarget = shin + + con = pb[self.org_bones[2]].constraints.new('COPY_TRANSFORMS') + con.name = "fk" + con.target = self.obj + con.subtarget = foot_mch + + # Set layers if specified + if self.layers: + thigh_p.bone.layers = self.layers + shin_p.bone.layers = self.layers + foot_p.bone.layers = self.layers + + # Create control widgets + create_limb_widget(self.obj, thigh) + create_limb_widget(self.obj, shin) + + ob = create_widget(self.obj, foot) + if ob != None: + verts = [(0.7, 1.5, 0.0), (0.7, -0.25, 0.0), (-0.7, -0.25, 0.0), (-0.7, 1.5, 0.0), (0.7, 0.723, 0.0), (-0.7, 0.723, 0.0), (0.7, 0.0, 0.0), (-0.7, 0.0, 0.0)] + edges = [(1, 2), (0, 3), (0, 4), (3, 5), (4, 6), (1, 6), (5, 7), (2, 7)] + mesh = ob.data + mesh.from_pydata(verts, edges, []) + mesh.update() + + mod = ob.modifiers.new("subsurf", 'SUBSURF') + mod.levels = 2 + + return [thigh, shin, foot] + diff --git a/rigify/rigs/biped/leg/ik.py b/rigify/rigs/biped/leg/ik.py new file mode 100644 index 00000000..5697a2b6 --- /dev/null +++ b/rigify/rigs/biped/leg/ik.py @@ -0,0 +1,499 @@ +#====================== 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 math import pi, acos +from rigify.utils import MetarigError +from rigify.utils import copy_bone, flip_bone, put_bone +from rigify.utils import connected_children_names +from rigify.utils import strip_org, make_mechanism_name, insert_before_lr +from rigify.utils import get_layers +from rigify.utils import create_widget, create_line_widget, create_sphere_widget, create_circle_widget +from rna_prop_ui import rna_idprop_ui_prop_get + + +def align_x_axis(obj, bone, vec): + """ Aligns the x-axis of a bone to the given vector. This only works if it + can be an exact match. + Must be in edit mode. + + """ + vec.normalize() + bone_e = obj.data.edit_bones[bone] + dot = bone_e.x_axis.dot(vec) + if dot < -1: + dot = -1 + elif dot > 1: + dot = 1 + + angle = acos(dot) + + bone_e.roll += angle + + dot1 = bone_e.x_axis.dot(vec) + + bone_e.roll -= angle * 2 + + dot2 = bone_e.x_axis.dot(vec) + + if dot1 > dot2: + bone_e.roll += angle * 2 + + +class Rig: + """ An IK leg rig, with an optional ik/fk switch. + + """ + def __init__(self, obj, bone, params, ikfk_switch=False): + """ Gather and validate data about the rig. + Store any data or references to data that will be needed later on. + In particular, store references to bones that will be needed, and + store names of bones that will be needed. + Do NOT change any data in the scene. This is a gathering phase only. + """ + self.obj = obj + self.params = params + self.switch = ikfk_switch + + # Get the chain of 2 connected bones + leg_bones = [bone] + connected_children_names(self.obj, bone)[:2] + + if len(leg_bones) != 2: + raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone))) + + # Get the foot and heel + foot = None + heel = None + for b in self.obj.data.bones[leg_bones[1]].children: + if b.use_connect == True: + if len(b.children) == 0: + heel = b.name + else: + foot = b.name + + if foot == None or heel == None: + raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone))) + + # Get the toe + toe = None + for b in self.obj.data.bones[foot].children: + if b.use_connect == True: + toe = b.name + + # Get toe + if toe == None: + raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone))) + + self.org_bones = leg_bones + [foot, toe, heel] + + # Get rig parameters + if params.separate_ik_layers: + self.layers = list(params.ik_layers) + else: + self.layers = None + + self.primary_rotation_axis = params.primary_rotation_axis + + 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. + + """ + bpy.ops.object.mode_set(mode='EDIT') + + # Create the bones + thigh = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(insert_before_lr(self.org_bones[0], "_ik")))) + shin = copy_bone(self.obj, self.org_bones[1], make_mechanism_name(strip_org(insert_before_lr(self.org_bones[1], "_ik")))) + + foot = copy_bone(self.obj, self.org_bones[2], strip_org(insert_before_lr(self.org_bones[2], "_ik"))) + foot_ik_target = copy_bone(self.obj, self.org_bones[2], make_mechanism_name(strip_org(insert_before_lr(self.org_bones[2], "_ik_target")))) + pole = copy_bone(self.obj, self.org_bones[0], strip_org(insert_before_lr(self.org_bones[0], "_pole"))) + + toe = copy_bone(self.obj, self.org_bones[3], strip_org(self.org_bones[3])) + toe_parent = copy_bone(self.obj, self.org_bones[2], make_mechanism_name(strip_org(self.org_bones[3] + ".parent"))) + toe_parent_socket1 = copy_bone(self.obj, self.org_bones[2], make_mechanism_name(strip_org(self.org_bones[3] + ".socket1"))) + toe_parent_socket2 = copy_bone(self.obj, self.org_bones[2], make_mechanism_name(strip_org(self.org_bones[3] + ".socket2"))) + + foot_roll = copy_bone(self.obj, self.org_bones[4], strip_org(insert_before_lr(self.org_bones[2], "_roll"))) + roll1 = copy_bone(self.obj, self.org_bones[4], make_mechanism_name(strip_org(self.org_bones[2] + ".roll"))) + roll2 = copy_bone(self.obj, self.org_bones[4], make_mechanism_name(strip_org(self.org_bones[2] + ".roll"))) + + visfoot = copy_bone(self.obj, self.org_bones[2], "VIS-" + strip_org(insert_before_lr(self.org_bones[2], "_ik"))) + vispole = copy_bone(self.obj, self.org_bones[1], "VIS-" + strip_org(insert_before_lr(self.org_bones[0], "_pole"))) + + # Get edit bones + eb = self.obj.data.edit_bones + + org_foot_e = eb[self.org_bones[2]] + thigh_e = eb[thigh] + shin_e = eb[shin] + foot_e = eb[foot] + foot_ik_target_e = eb[foot_ik_target] + pole_e = eb[pole] + toe_e = eb[toe] + toe_parent_e = eb[toe_parent] + toe_parent_socket1_e = eb[toe_parent_socket1] + toe_parent_socket2_e = eb[toe_parent_socket2] + foot_roll_e = eb[foot_roll] + roll1_e = eb[roll1] + roll2_e = eb[roll2] + visfoot_e = eb[visfoot] + vispole_e = eb[vispole] + + # Parenting + shin_e.parent = thigh_e + + foot_e.use_connect = False + foot_e.parent = None + foot_ik_target_e.use_connect = False + foot_ik_target_e.parent = roll2_e + + pole_e.use_connect = False + pole_e.parent = foot_e + + toe_e.parent = toe_parent_e + toe_parent_e.use_connect = False + toe_parent_e.parent = toe_parent_socket1_e + toe_parent_socket1_e.use_connect = False + toe_parent_socket1_e.parent = roll1_e + toe_parent_socket2_e.use_connect = False + toe_parent_socket2_e.parent = eb[self.org_bones[2]] + + foot_roll_e.use_connect = False + foot_roll_e.parent = foot_e + + roll1_e.use_connect = False + roll1_e.parent = foot_e + + roll2_e.use_connect = False + roll2_e.parent = roll1_e + + visfoot_e.use_connect = False + visfoot_e.parent = None + + vispole_e.use_connect = False + vispole_e.parent = None + + # Misc + foot_e.use_local_location = False + + visfoot_e.hide_select = True + vispole_e.hide_select = True + + # Positioning + vec = Vector(toe_e.vector) + vec = vec.normalize() + foot_e.tail = foot_e.head + (vec * foot_e.length) + foot_e.roll = toe_e.roll + + v1 = shin_e.tail - thigh_e.head + + if 'X' in self.primary_rotation_axis or 'Y' in self.primary_rotation_axis: + v2 = v1.cross(shin_e.x_axis) + if (v2 * shin_e.z_axis) > 0.0: + v2 *= -1.0 + else: + v2 = v1.cross(shin_e.z_axis) + if (v2 * shin_e.x_axis) < 0.0: + v2 *= -1.0 + v2.normalize() + v2 *= v1.length + + if '-' in self.primary_rotation_axis: + v2 *= -1 + + pole_e.head = shin_e.head + v2 + pole_e.tail = pole_e.head + (Vector((0, 1, 0)) * (v1.length / 8)) + pole_e.roll = 0.0 + + flip_bone(self.obj, toe_parent_socket1) + flip_bone(self.obj, toe_parent_socket2) + toe_parent_socket1_e.head = Vector(org_foot_e.tail) + toe_parent_socket2_e.head = Vector(org_foot_e.tail) + toe_parent_socket1_e.tail = Vector(org_foot_e.tail) + (Vector((0, 0, 1)) * foot_e.length / 2) + toe_parent_socket2_e.tail = Vector(org_foot_e.tail) + (Vector((0, 0, 1)) * foot_e.length / 3) + toe_parent_socket2_e.roll = toe_parent_socket1_e.roll + + tail = Vector(roll1_e.tail) + roll1_e.tail = Vector(org_foot_e.tail) + roll1_e.tail = Vector(org_foot_e.tail) + roll1_e.head = tail + roll2_e.head = Vector(org_foot_e.tail) + foot_roll_e.head = Vector(org_foot_e.tail) + put_bone(self.obj, foot_roll, roll1_e.head) + foot_roll_e.length /= 2 + + roll_axis = roll1_e.vector.cross(org_foot_e.vector) + align_x_axis(self.obj, roll1, roll_axis) + align_x_axis(self.obj, roll2, roll_axis) + foot_roll_e.roll = roll2_e.roll + + visfoot_e.tail = visfoot_e.head + Vector((0, 0, v1.length / 32)) + vispole_e.tail = vispole_e.head + Vector((0, 0, v1.length / 32)) + + # Weird alignment issues. Fix. + toe_parent_e.head = Vector(org_foot_e.head) + toe_parent_e.tail = Vector(org_foot_e.tail) + toe_parent_e.roll = org_foot_e.roll + + foot_e.head = Vector(org_foot_e.head) + + foot_ik_target_e.head = Vector(org_foot_e.head) + foot_ik_target_e.tail = Vector(org_foot_e.tail) + + # Object mode, get pose bones + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + + thigh_p = pb[thigh] + shin_p = pb[shin] + foot_p = pb[foot] + pole_p = pb[pole] + foot_roll_p = pb[foot_roll] + roll1_p = pb[roll1] + roll2_p = pb[roll2] + toe_p = pb[toe] + toe_parent_p = pb[toe_parent] + toe_parent_socket1_p = pb[toe_parent_socket1] + visfoot_p = pb[visfoot] + vispole_p = pb[vispole] + + # Set the knee to only bend on the primary axis. + if 'X' in self.primary_rotation_axis: + shin_p.lock_ik_y = True + shin_p.lock_ik_z = True + elif 'Y' in self.primary_rotation_axis: + shin_p.lock_ik_x = True + shin_p.lock_ik_z = True + else: + shin_p.lock_ik_x = True + shin_p.lock_ik_y = True + + # Foot roll control only rotates on x-axis. + foot_roll_p.rotation_mode = 'XYZ' + foot_roll_p.lock_rotation = False, True, True + foot_roll_p.lock_location = True, True, True + foot_roll_p.lock_scale = True, True, True + + # Pole target only translates + pole_p.lock_location = False, False, False + pole_p.lock_rotation = True, True, True + pole_p.lock_rotation_w = True + pole_p.lock_scale = True, True, True + + # Set up custom properties + if self.switch == True: + prop = rna_idprop_ui_prop_get(foot_p, "ikfk_switch", create=True) + foot_p["ikfk_switch"] = 0.0 + prop["soft_min"] = prop["min"] = 0.0 + prop["soft_max"] = prop["max"] = 1.0 + + # IK Constraint + con = shin_p.constraints.new('IK') + con.name = "ik" + con.target = self.obj + con.subtarget = foot_ik_target + con.pole_target = self.obj + con.pole_subtarget = pole + if self.primary_rotation_axis == 'X' or self.primary_rotation_axis == 'Y': + con.pole_angle = -pi / 2 + elif self.primary_rotation_axis == '-X' or self.primary_rotation_axis == '-Y': + con.pole_angle = pi / 2 + elif self.primary_rotation_axis == 'Z': + con.pole_angle = 0.0 + elif self.primary_rotation_axis == '-Z': + con.pole_angle = pi + con.chain_count = 2 + + # toe_parent constraint + con = toe_parent_socket1_p.constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + con.subtarget = toe_parent_socket2 + + con = toe_parent_socket1_p.constraints.new('COPY_SCALE') + con.name = "copy_scale" + con.target = self.obj + con.subtarget = toe_parent_socket2 + + con = toe_parent_socket1_p.constraints.new('COPY_TRANSFORMS') # drive with IK switch + con.name = "fk" + con.target = self.obj + con.subtarget = toe_parent_socket2 + + fcurve = con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = "var" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = foot_p.path_from_id() + '["ikfk_switch"]' + mod = fcurve.modifiers[0] + mod.poly_order = 1 + mod.coefficients[0] = 1.0 + mod.coefficients[1] = -1.0 + + # Foot roll constraints + con = roll1_p.constraints.new('COPY_ROTATION') + con.name = "roll" + con.target = self.obj + con.subtarget = foot_roll + con.target_space = 'LOCAL' + con.owner_space = 'LOCAL' + + con = roll1_p.constraints.new('LIMIT_ROTATION') + con.name = "limit_roll" + con.use_limit_x = True + con.min_x = -180 + con.max_x = 0 + con.owner_space = 'LOCAL' + + con = roll2_p.constraints.new('COPY_ROTATION') + con.name = "roll" + con.target = self.obj + con.subtarget = foot_roll + con.target_space = 'LOCAL' + con.owner_space = 'LOCAL' + + con = roll2_p.constraints.new('LIMIT_ROTATION') + con.name = "limit_roll" + con.use_limit_x = True + con.min_x = 0 + con.max_x = 180 + con.owner_space = 'LOCAL' + + # Constrain org bones to controls + con = pb[self.org_bones[0]].constraints.new('COPY_TRANSFORMS') + con.name = "ik" + con.target = self.obj + con.subtarget = thigh + if self.switch == True: + # IK/FK switch driver + fcurve = con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = "var" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = foot_p.path_from_id() + '["ikfk_switch"]' + + con = pb[self.org_bones[1]].constraints.new('COPY_TRANSFORMS') + con.name = "ik" + con.target = self.obj + con.subtarget = shin + if self.switch == True: + # IK/FK switch driver + fcurve = con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = "var" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = foot_p.path_from_id() + '["ikfk_switch"]' + + con = pb[self.org_bones[2]].constraints.new('COPY_TRANSFORMS') + con.name = "ik" + con.target = self.obj + con.subtarget = foot_ik_target + if self.switch == True: + # IK/FK switch driver + fcurve = con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = "var" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = foot_p.path_from_id() + '["ikfk_switch"]' + + con = pb[self.org_bones[3]].constraints.new('COPY_TRANSFORMS') + con.name = "copy_transforms" + con.target = self.obj + con.subtarget = toe + + # VIS foot constraints + con = visfoot_p.constraints.new('COPY_LOCATION') + con.name = "copy_loc" + con.target = self.obj + con.subtarget = self.org_bones[2] + + con = visfoot_p.constraints.new('STRETCH_TO') + con.name = "stretch_to" + con.target = self.obj + con.subtarget = foot + con.volume = 'NO_VOLUME' + con.rest_length = visfoot_p.length + + # VIS pole constraints + con = vispole_p.constraints.new('COPY_LOCATION') + con.name = "copy_loc" + con.target = self.obj + con.subtarget = self.org_bones[1] + + con = vispole_p.constraints.new('STRETCH_TO') + con.name = "stretch_to" + con.target = self.obj + con.subtarget = pole + con.volume = 'NO_VOLUME' + con.rest_length = vispole_p.length + + # Set layers if specified + if self.layers: + foot_p.bone.layers = self.layers + pole_p.bone.layers = self.layers + foot_roll_p.bone.layers = self.layers + visfoot_p.bone.layers = self.layers + vispole_p.bone.layers = self.layers + + toe_p.bone.layers = [(i[0] or i[1]) for i in zip(toe_p.bone.layers, self.layers)] # Both FK and IK layers + + # Create widgets + create_line_widget(self.obj, vispole) + create_line_widget(self.obj, visfoot) + create_sphere_widget(self.obj, pole) + create_circle_widget(self.obj, toe, radius=0.7, head_tail=0.5) + + ob = create_widget(self.obj, foot) + if ob != None: + verts = [(0.7, 1.5, 0.0), (0.7, -0.25, 0.0), (-0.7, -0.25, 0.0), (-0.7, 1.5, 0.0), (0.7, 0.723, 0.0), (-0.7, 0.723, 0.0), (0.7, 0.0, 0.0), (-0.7, 0.0, 0.0)] + edges = [(1, 2), (0, 3), (0, 4), (3, 5), (4, 6), (1, 6), (5, 7), (2, 7)] + mesh = ob.data + mesh.from_pydata(verts, edges, []) + mesh.update() + + mod = ob.modifiers.new("subsurf", 'SUBSURF') + mod.levels = 2 + + ob = create_widget(self.obj, foot_roll) + if ob != None: + verts = [(0.3999999761581421, 0.766044557094574, 0.6427875757217407), (0.17668449878692627, 3.823702598992895e-08, 3.2084670920085046e-08), (-0.17668461799621582, 9.874240447516058e-08, 8.285470443070153e-08), (-0.39999961853027344, 0.7660449147224426, 0.6427879333496094), (0.3562471270561218, 0.6159579753875732, 0.5168500542640686), (-0.35624682903289795, 0.6159582138061523, 0.5168502926826477), (0.20492683351039886, 0.09688037633895874, 0.0812922865152359), (-0.20492687821388245, 0.0968804731965065, 0.08129236847162247)] + edges = [(1, 2), (0, 3), (0, 4), (3, 5), (1, 6), (4, 6), (2, 7), (5, 7)] + mesh = ob.data + mesh.from_pydata(verts, edges, []) + mesh.update() + + mod = ob.modifiers.new("subsurf", 'SUBSURF') + mod.levels = 2 + + return [foot, pole, foot_roll] + diff --git a/rigify/rigs/copy.py b/rigify/rigs/copy.py new file mode 100644 index 00000000..61c4cc99 --- /dev/null +++ b/rigify/rigs/copy.py @@ -0,0 +1,114 @@ +#====================== 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 rigify.utils import MetarigError +from rigify.utils import copy_bone +from rigify.utils import strip_org, make_deformer_name +from rigify.utils import create_bone_widget + + +class Rig: + """ A "copy" rig. All it does is duplicate the original bone and + constrain it. + 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_bone = bone + self.org_name = strip_org(bone) + self.params = params + + 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. + + """ + bpy.ops.object.mode_set(mode='EDIT') + + # Make a control bone (copy of original). + bone = copy_bone(self.obj, self.org_bone, self.org_name) + + # Make a deformation bone (copy of original, child of original). + def_bone = copy_bone(self.obj, self.org_bone, make_deformer_name(self.org_name)) + + # Get edit bones + eb = self.obj.data.edit_bones + bone_e = eb[bone] + def_bone_e = eb[def_bone] + + # Parent + def_bone_e.use_connect = False + def_bone_e.parent = eb[self.org_bone] + + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + + # Constrain the original bone. + con = pb[self.org_bone].constraints.new('COPY_TRANSFORMS') + con.name = "copy_loc" + con.target = self.obj + con.subtarget = bone + + # Create control widget + create_bone_widget(self.obj, bone) + + @classmethod + def create_sample(self, obj): + """ Create a sample metarig for this rig type. + + """ + # generated by rigify.utils.write_metarig + bpy.ops.object.mode_set(mode='EDIT') + arm = obj.data + + bones = {} + + bone = arm.edit_bones.new('Bone') + bone.head[:] = 0.0000, 0.0000, 0.0000 + bone.tail[:] = 0.0000, 0.0000, 0.2000 + bone.roll = 0.0000 + bone.use_connect = False + bones['Bone'] = bone.name + + bpy.ops.object.mode_set(mode='OBJECT') + pbone = obj.pose.bones[bones['Bone']] + pbone.rigify_type = 'copy' + 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 = 'QUATERNION' + pbone.rigify_parameters.add() + + 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 + 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 + diff --git a/rigify/rigs/misc/__init__.py b/rigify/rigs/misc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/rigify/rigs/misc/delta.py b/rigify/rigs/misc/delta.py new file mode 100644 index 00000000..2157970a --- /dev/null +++ b/rigify/rigs/misc/delta.py @@ -0,0 +1,161 @@ +#====================== 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 math import acos +from rigify.utils import MetarigError +from rigify.utils import copy_bone +from rigify.utils import org_name, make_mechanism_name + + +class Rig: + """ A delta rig. + Creates a setup that will place its child at its position in pose mode, + but will not modifying its child's position in edit mode. + This is a mechanism-only rig (no control or deformation bones). + + """ + def __init__(self, obj, bone, params): + """ Gather and validate data about the rig. + Store any data or references to data that will be needed later on. + In particular, store references to bones that will be needed. + Do NOT change any data in the scene. This is a gathering phase only. + + """ + bb = obj.data.bones + + if bb[bone].children == None: + raise MetarigError("RIGIFY ERROR: bone '%s': rig type requires one child." % org_name(bone.name)) + if bb[bone].use_connect == True: + raise MetarigError("RIGIFY ERROR: bone '%s': rig type cannot be connected to parent." % org_name(bone.name)) + + self.obj = obj + self.org_bones = {"delta": bone, "child": bb[bone].children[0].name} + self.org_names = [org_name(bone), org_name(bb[bone].children[0].name)] + + 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. + + """ + bpy.ops.object.mode_set(mode='EDIT') + eb = self.obj.data.edit_bones + + org_delta = self.org_bones["delta"] + org_delta_e = eb[self.org_bones["delta"]] + org_child = self.org_bones["child"] + org_child_e = eb[self.org_bones["child"]] + + # Calculate the matrix for achieving the delta + child_mat = org_delta_e.matrix.invert() * org_child_e.matrix + mat = org_delta_e.matrix * child_mat.invert() + + # Create the delta bones. + delta_e = eb[copy_bone(self.obj, self.org_bones["delta"])] + delta_e.name = make_mechanism_name(self.org_names[0]) + delta = delta_e.name + + # Set the delta to the matrix's transforms + set_mat(self.obj, delta, mat) + + bpy.ops.object.mode_set(mode='OBJECT') + + # Constrain org_delta to delta + con = self.obj.pose.bones[org_delta].constraints.new('COPY_TRANSFORMS') + con.name = "delta" + con.target = self.obj + con.subtarget = delta + + @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('delta') + bone.head[:] = 0.0000, -0.1198, 0.1253 + bone.tail[:] = -0.0000, -0.2483, 0.2785 + bone.roll = -0.0000 + bone.use_connect = False + bones['delta'] = bone.name + bone = arm.edit_bones.new('Bone') + bone.head[:] = -0.0000, 0.0000, 0.0000 + bone.tail[:] = -0.0000, 0.0000, 0.2000 + bone.roll = 0.0000 + bone.use_connect = False + bone.parent = arm.edit_bones[bones['delta']] + bones['Bone'] = bone.name + + bpy.ops.object.mode_set(mode='OBJECT') + pbone = obj.pose.bones[bones['delta']] + pbone.rigify_type = 'misc.delta' + 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 = 'QUATERNION' + pbone.rigify_parameters.add() + pbone = obj.pose.bones[bones['Bone']] + 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 = 'QUATERNION' + + 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 + + +def set_mat(obj, bone_name, matrix): + """ Sets the bone to have the given transform matrix. + """ + a = obj.data.edit_bones[bone_name] + + a.head = (0, 0, 0) + a.tail = (0, 1, 0) + + a.transform(matrix) + + d = acos(a.matrix.to_quat().dot(matrix.to_quat())) * 2 + + roll_1 = a.roll + d + roll_2 = a.roll - d + + a.roll = roll_1 + d1 = a.matrix.to_quat().dot(matrix.to_quat()) + a.roll = roll_2 + d2 = a.matrix.to_quat().dot(matrix.to_quat()) + + if d1 > d2: + a.roll = roll_1 + else: + a.roll = roll_2 + diff --git a/rigify/rigs/neck_short.py b/rigify/rigs/neck_short.py new file mode 100644 index 00000000..3c34fbda --- /dev/null +++ b/rigify/rigs/neck_short.py @@ -0,0 +1,385 @@ +#====================== 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, flip_bone, put_bone +from rigify.utils import connected_children_names +from rigify.utils import strip_org, make_mechanism_name, make_deformer_name +from rigify.utils import obj_to_bone, create_circle_widget +from rna_prop_ui import rna_idprop_ui_prop_get + +script1 = """ +head_neck = ["%s", "%s"] +""" + +script2 = """ +if is_selected(head_neck[0]): + layout.prop(pose_bones[head_neck[0]], '["isolate"]', text="Isolate (" + head_neck[0] + ")", slider=True) +""" + +script3 = """ +if is_selected(head_neck): + layout.prop(pose_bones[head_neck[0]], '["neck_follow"]', text="Neck Follow Head (" + head_neck[0] + ")", slider=True) +""" + + +class Rig: + """ A "spine" rig. It turns a chain of bones into a rig with two controls: + One for the hips, and one for the rib cage. + + """ + def __init__(self, obj, bone_name, params): + """ Gather and validate data about the rig. + + """ + self.obj = obj + self.org_bones = [bone_name] + connected_children_names(obj, bone_name) + 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))) + + self.isolate = False + if self.obj.data.bones[bone_name].parent: + self.isolate = True + + def gen_deform(self): + """ Generate the deformation rig. + + """ + for name in self.org_bones: + bpy.ops.object.mode_set(mode='EDIT') + eb = self.obj.data.edit_bones + + # Create deform bone + bone_e = eb[copy_bone(self.obj, name)] + + # Change its name + bone_e.name = make_deformer_name(strip_org(name)) + bone_name = bone_e.name + + # Leave edit mode + bpy.ops.object.mode_set(mode='OBJECT') + + # Get the pose bone + bone = self.obj.pose.bones[bone_name] + + # Constrain to the original bone + con = bone.constraints.new('COPY_TRANSFORMS') + con.name = "copy_transforms" + con.target = self.obj + con.subtarget = name + + def gen_control(self): + """ Generate the control rig. + + """ + #--------------------------------- + # Create the neck and head controls + bpy.ops.object.mode_set(mode='EDIT') + + # Create bones + neck_ctrl = copy_bone(self.obj, self.org_bones[0], strip_org(self.org_bones[0])) + neck_follow = copy_bone(self.obj, self.org_bones[-1], make_mechanism_name(strip_org(self.org_bones[0] + ".follow"))) + + head_ctrl = copy_bone(self.obj, self.org_bones[-1], strip_org(self.org_bones[-1])) + head_mch = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(self.org_bones[-1]))) + if self.isolate: + head_socket1 = copy_bone(self.obj, self.org_bones[-1], make_mechanism_name(strip_org(self.org_bones[-1] + ".socket1"))) + head_socket2 = copy_bone(self.obj, self.org_bones[-1], make_mechanism_name(strip_org(self.org_bones[-1] + ".socket2"))) + + # Create neck chain bones + neck = [] + helpers = [] + for name in self.org_bones: + neck += [copy_bone(self.obj, name, make_mechanism_name(strip_org(name)))] + helpers += [copy_bone(self.obj, neck_ctrl, make_mechanism_name(strip_org(name + ".02")))] + + # Fetch edit bones + eb = self.obj.data.edit_bones + + neck_ctrl_e = eb[neck_ctrl] + neck_follow_e = eb[neck_follow] + head_ctrl_e = eb[head_ctrl] + head_mch_e = eb[head_mch] + if self.isolate: + head_socket1_e = eb[head_socket1] + head_socket2_e = eb[head_socket2] + + # Parenting + head_ctrl_e.use_connect = False + head_ctrl_e.parent = neck_ctrl_e.parent + head_mch_e.use_connect = False + head_mch_e.parent = head_ctrl_e + + if self.isolate: + head_socket1_e.use_connect = False + head_socket1_e.parent = neck_ctrl_e.parent + + head_socket2_e.use_connect = False + head_socket2_e.parent = None + + head_ctrl_e.parent = head_socket2_e + + for (name1, name2) in zip(neck, helpers): + eb[name1].use_connect = False + eb[name1].parent = eb[name2] + eb[name2].use_connect = False + eb[name2].parent = neck_ctrl_e.parent + + neck_follow_e.use_connect = False + neck_follow_e.parent = neck_ctrl_e.parent + neck_ctrl_e.parent = neck_follow_e + + # Position + put_bone(self.obj, neck_follow, neck_ctrl_e.head) + put_bone(self.obj, head_ctrl, neck_ctrl_e.head) + put_bone(self.obj, head_mch, neck_ctrl_e.head) + head_mch_e.length /= 2 + + if self.isolate: + put_bone(self.obj, head_socket1, neck_ctrl_e.head) + head_mch_e.length /= 2 + + put_bone(self.obj, head_socket2, neck_ctrl_e.head) + head_mch_e.length /= 3 + + for (name1, name2) in zip(neck, helpers): + put_bone(self.obj, name2, eb[name1].head) + eb[name2].length /= 3 + + # Switch to object mode + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + neck_ctrl_p = pb[neck_ctrl] + neck_follow_p = pb[neck_follow] + head_ctrl_p = pb[head_ctrl] + if self.isolate: + head_socket1_p = pb[head_socket1] + head_socket2_p = pb[head_socket2] + + # Custom bone appearance + neck_ctrl_p.custom_shape_transform = pb[self.org_bones[(len(self.org_bones) - 1) // 2]] + head_ctrl_p.custom_shape_transform = pb[self.org_bones[-1]] + + # Custom properties + prop = rna_idprop_ui_prop_get(head_ctrl_p, "inf_extent", create=True) + head_ctrl_p["inf_extent"] = 0.5 + prop["min"] = 0.0 + prop["max"] = 1.0 + prop["soft_min"] = 0.0 + prop["soft_max"] = 1.0 + + prop = rna_idprop_ui_prop_get(head_ctrl_p, "neck_follow", create=True) + head_ctrl_p["neck_follow"] = 1.0 + prop["min"] = 0.0 + prop["max"] = 2.0 + prop["soft_min"] = 0.0 + prop["soft_max"] = 1.0 + + if self.isolate: + prop = rna_idprop_ui_prop_get(head_ctrl_p, "isolate", create=True) + head_ctrl_p["isolate"] = 0.0 + prop["min"] = 0.0 + prop["max"] = 1.0 + prop["soft_min"] = 0.0 + prop["soft_max"] = 1.0 + + # Constraints + + # Neck follow + con = neck_follow_p.constraints.new('COPY_ROTATION') + con.name = "copy_rotation" + con.target = self.obj + con.subtarget = head_ctrl + + fcurve = con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'SCRIPTED' + var.name = "follow" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = head_ctrl_p.path_from_id() + '["neck_follow"]' + driver.expression = "follow / 2" + + # Isolate + if self.isolate: + con = head_socket2_p.constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + con.subtarget = head_socket1 + + con = head_socket2_p.constraints.new('COPY_TRANSFORMS') + con.name = "copy_transforms" + con.target = self.obj + con.subtarget = head_socket1 + + fcurve = con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'SCRIPTED' + var.name = "isolate" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = head_ctrl_p.path_from_id() + '["isolate"]' + driver.expression = "1.0 - isolate" + + # Neck chain + first = True + prev = None + i = 0 + l = len(neck) + for (name1, name2, org_name) in zip(neck, helpers, self.org_bones): + con = pb[org_name].constraints.new('COPY_TRANSFORMS') + con.name = "copy_transforms" + con.target = self.obj + con.subtarget = name1 + + n_con = pb[name2].constraints.new('COPY_TRANSFORMS') + n_con.name = "neck" + n_con.target = self.obj + n_con.subtarget = neck_ctrl + + h_con = pb[name2].constraints.new('COPY_TRANSFORMS') + h_con.name = "head" + h_con.target = self.obj + h_con.subtarget = head_mch + + con = pb[name2].constraints.new('COPY_LOCATION') + con.name = "anchor" + con.target = self.obj + if first: + con.subtarget = neck_ctrl + else: + con.subtarget = prev + con.head_tail = 1.0 + + # Drivers + n = (i + 1) / l + + # Neck influence + fcurve = n_con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'SCRIPTED' + var.name = "ext" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = head_ctrl_p.path_from_id() + '["inf_extent"]' + driver.expression = "1.0 if (%.4f > (1.0-ext) or (1.0-ext) == 0.0) else (%.4f / (1.0-ext))" % (n, n) + + # Head influence + if (i + 1) == l: + h_con.influence = 1.0 + else: + fcurve = h_con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'SCRIPTED' + var.name = "ext" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = head_ctrl_p.path_from_id() + '["inf_extent"]' + driver.expression = "0.0 if (%.4f <= (1.0-ext)) else ((%.4f - (1.0-ext)) / ext)" % (n, n) + + first = False + prev = name1 + i += 1 + + # Create control widgets + w1 = create_circle_widget(self.obj, neck_ctrl, radius=1.0, head_tail=0.5) + w2 = create_circle_widget(self.obj, head_ctrl, radius=1.0, head_tail=0.5) + + if w1 != None: + obj_to_bone(w1, self.obj, self.org_bones[(len(self.org_bones) - 1) // 2]) + if w2 != None: + obj_to_bone(w2, self.obj, self.org_bones[-1]) + + # Return control bones + return (head_ctrl, neck_ctrl) + + 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.gen_deform() + (head, neck) = self.gen_control() + + script = script1 % (head, neck) + if self.isolate: + script += script2 + script += script3 + + return [script] + + @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('neck') + bone.head[:] = 0.0000, 0.0000, 0.0000 + bone.tail[:] = 0.0000, -0.0500, 0.1500 + bone.roll = 0.0000 + bone.use_connect = False + bones['neck'] = bone.name + bone = arm.edit_bones.new('head') + bone.head[:] = 0.0000, -0.0500, 0.1500 + bone.tail[:] = 0.0000, -0.0500, 0.4000 + bone.roll = 3.1416 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['neck']] + bones['head'] = bone.name + + bpy.ops.object.mode_set(mode='OBJECT') + pbone = obj.pose.bones[bones['neck']] + pbone.rigify_type = 'neck_short' + 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 = 'QUATERNION' + pbone.rigify_parameters.add() + pbone = obj.pose.bones[bones['head']] + 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 = 'QUATERNION' + + 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 + diff --git a/rigify/rigs/palm.py b/rigify/rigs/palm.py new file mode 100644 index 00000000..5a0fa773 --- /dev/null +++ b/rigify/rigs/palm.py @@ -0,0 +1,273 @@ +#====================== 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 math import sin, cos, pi +from rigify.utils import MetarigError +from rigify.utils import copy_bone +from rigify.utils import strip_org, deformer, mch +from rigify.utils import create_widget +import re + + +def bone_siblings(obj, bone): + """ Returns a list of the siblings of the given bone. + This requires that the bones has a parent. + + """ + parent = obj.data.bones[bone].parent + + if parent == None: + return [] + + bones = [] + + for b in parent.children: + if b.name != bone: + bones += [b.name] + + return bones + + +def bone_distance(obj, bone1, bone2): + """ Returns the distance between two bones. + + """ + vec = obj.data.bones[bone1].head - obj.data.bones[bone2].head + return vec.length + + +class Rig: + """ A "palm" rig. A set of sibling bones that bend with each other. + This is a control and deformation rig. + + """ + def __init__(self, obj, bone, params): + """ Gather and validate data about the rig. + """ + self.obj = obj + self.params = params + + siblings = bone_siblings(obj, bone) + + if len(siblings) == 0: + raise MetarigError("RIGIFY ERROR: Bone '%s': must have a parent and at least one sibling." % (strip_org(bone))) + + # Sort list by name and distance + siblings.sort() + siblings.sort(key=lambda b: bone_distance(obj, bone, b)) + + self.org_bones = [bone] + siblings + + # Get rig parameters + self.palm_rotation_axis = params.palm_rotation_axis + + 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. + + """ + bpy.ops.object.mode_set(mode='EDIT') + + # Figure out the name for the control bone (remove the last .##) + last_bone = self.org_bones[-1:][0] + ctrl_name = re.sub("([0-9]+\.)", "", strip_org(last_bone)[::-1], count=1)[::-1] + + # Make control bone + ctrl = copy_bone(self.obj, last_bone, ctrl_name) + + # Make deformation bones + def_bones = [] + for bone in self.org_bones: + b = copy_bone(self.obj, bone, deformer(strip_org(bone))) + def_bones += [b] + + # Parenting + eb = self.obj.data.edit_bones + + for d, b in zip(def_bones, self.org_bones): + eb[d].use_connect = False + eb[d].parent = eb[b] + + # Constraints + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + + i = 0 + div = len(self.org_bones) - 1 + for b in self.org_bones: + con = pb[b].constraints.new('COPY_TRANSFORMS') + con.name = "copy_transforms" + con.target = self.obj + con.subtarget = ctrl + con.target_space = 'LOCAL' + con.owner_space = 'LOCAL' + con.influence = i / div + + con = pb[b].constraints.new('COPY_ROTATION') + con.name = "copy_rotation" + con.target = self.obj + con.subtarget = ctrl + con.target_space = 'LOCAL' + con.owner_space = 'LOCAL' + if 'X' in self.palm_rotation_axis: + con.invert_x = True + con.use_x = True + con.use_z = False + else: + con.invert_z = True + con.use_x = False + con.use_z = True + con.use_y = False + + con.influence = (i / div) - (1 - cos((i * pi / 2) / div)) + + i += 1 + + # Create control widget + w = create_widget(self.obj, ctrl) + if w != None: + mesh = w.data + verts = [(0.15780271589756012, 2.086162567138672e-07, -0.30000004172325134), (0.15780259668827057, 1.0, -0.2000001072883606), (-0.15780280530452728, 0.9999999403953552, -0.20000004768371582), (-0.15780259668827057, -2.086162567138672e-07, -0.29999998211860657), (-0.15780256688594818, -2.7089754439657554e-07, 0.30000004172325134), (-0.1578027755022049, 0.9999998807907104, 0.19999995827674866), (0.15780262649059296, 0.9999999403953552, 0.19999989867210388), (0.1578027456998825, 1.4633496903115883e-07, 0.29999998211860657), (0.15780268609523773, 0.2500001788139343, -0.27500003576278687), (-0.15780264139175415, 0.24999985098838806, -0.2749999761581421), (0.15780262649059296, 0.7500000596046448, -0.22500008344650269), (-0.1578027606010437, 0.7499998807907104, -0.2250000238418579), (0.15780265629291534, 0.75, 0.22499991953372955), (0.15780271589756012, 0.2500000596046448, 0.2749999761581421), (-0.15780261158943176, 0.2499997615814209, 0.27500003576278687), (-0.1578027307987213, 0.7499998807907104, 0.22499997913837433)] + if 'Z' in self.palm_rotation_axis: + # Flip x/z coordinates + temp = [] + for v in verts: + temp += [(v[2], v[1], v[0])] + verts = temp + edges = [(1, 2), (0, 3), (4, 7), (5, 6), (8, 0), (9, 3), (10, 1), (11, 2), (12, 6), (13, 7), (4, 14), (15, 5), (10, 8), (11, 9), (15, 14), (12, 13)] + mesh.from_pydata(verts, edges, []) + mesh.update() + + mod = w.modifiers.new("subsurf", 'SUBSURF') + mod.levels = 2 + + @classmethod + def add_parameters(self, group): + """ Add the parameters of this rig type to the + RigifyParameters IDPropertyGroup + + """ + items = [('X', 'X', ''), ('Z', 'Z', '')] + group.palm_rotation_axis = bpy.props.EnumProperty(items=items, name="Palm Rotation Axis", default='X') + + @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.label(text="Primary rotation axis:") + r.prop(params, "palm_rotation_axis", text="") + + @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('palm.parent') + bone.head[:] = 0.0000, 0.0000, 0.0000 + bone.tail[:] = 0.0577, 0.0000, -0.0000 + bone.roll = 3.1416 + bone.use_connect = False + bones['palm.parent'] = bone.name + bone = arm.edit_bones.new('palm.04') + bone.head[:] = 0.0577, 0.0315, -0.0000 + bone.tail[:] = 0.1627, 0.0315, -0.0000 + bone.roll = 3.1416 + bone.use_connect = False + bone.parent = arm.edit_bones[bones['palm.parent']] + bones['palm.04'] = bone.name + bone = arm.edit_bones.new('palm.03') + bone.head[:] = 0.0577, 0.0105, -0.0000 + bone.tail[:] = 0.1627, 0.0105, -0.0000 + bone.roll = 3.1416 + bone.use_connect = False + bone.parent = arm.edit_bones[bones['palm.parent']] + bones['palm.03'] = bone.name + bone = arm.edit_bones.new('palm.02') + bone.head[:] = 0.0577, -0.0105, -0.0000 + bone.tail[:] = 0.1627, -0.0105, -0.0000 + bone.roll = 3.1416 + bone.use_connect = False + bone.parent = arm.edit_bones[bones['palm.parent']] + bones['palm.02'] = bone.name + bone = arm.edit_bones.new('palm.01') + bone.head[:] = 0.0577, -0.0315, -0.0000 + bone.tail[:] = 0.1627, -0.0315, -0.0000 + bone.roll = 3.1416 + bone.use_connect = False + bone.parent = arm.edit_bones[bones['palm.parent']] + bones['palm.01'] = bone.name + + bpy.ops.object.mode_set(mode='OBJECT') + pbone = obj.pose.bones[bones['palm.parent']] + 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 = 'QUATERNION' + pbone = obj.pose.bones[bones['palm.04']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, True, True) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'YXZ' + pbone = obj.pose.bones[bones['palm.03']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, True, True) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'YXZ' + pbone = obj.pose.bones[bones['palm.02']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, True, True) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'YXZ' + pbone = obj.pose.bones[bones['palm.01']] + pbone.rigify_type = 'palm' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, True, True) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'YXZ' + pbone.rigify_parameters.add() + + 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 + diff --git a/rigify/rigs/spine.py b/rigify/rigs/spine.py new file mode 100644 index 00000000..2318b3b9 --- /dev/null +++ b/rigify/rigs/spine.py @@ -0,0 +1,484 @@ +#====================== 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, flip_bone, put_bone +from rigify.utils import connected_children_names +from rigify.utils import strip_org, make_mechanism_name, make_deformer_name +from rigify.utils import obj_to_bone, create_circle_widget +from rna_prop_ui import rna_idprop_ui_prop_get + + +script = """ +hips = "%s" +ribs = "%s" +if is_selected([hips, ribs]): + layout.prop(pose_bones[ribs], '["pivot_slide"]', text="Pivot Slide (" + ribs + ")", slider=True) +if is_selected(ribs): + layout.prop(pose_bones[ribs], '["isolate"]', text="Isolate Rotation (" + ribs + ")", slider=True) +""" + + +class Rig: + """ A "spine" rig. It turns a chain of bones into a rig with two controls: + One for the hips, and one for the rib cage. + + """ + def __init__(self, obj, bone_name, params): + """ Gather and validate data about the rig. + + """ + self.obj = obj + self.org_bones = [bone_name] + connected_children_names(obj, bone_name) + 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))) + + def gen_deform(self): + """ Generate the deformation rig. + + """ + for name in self.org_bones: + bpy.ops.object.mode_set(mode='EDIT') + eb = self.obj.data.edit_bones + + # Create deform bone + bone_e = eb[copy_bone(self.obj, name)] + + # Change its name + bone_e.name = make_deformer_name(strip_org(name)) + bone_name = bone_e.name + + # Leave edit mode + bpy.ops.object.mode_set(mode='OBJECT') + + # Get the pose bone + bone = self.obj.pose.bones[bone_name] + + # Constrain to the original bone + con = bone.constraints.new('COPY_TRANSFORMS') + con.name = "copy_transforms" + con.target = self.obj + con.subtarget = name + + def gen_control(self): + """ Generate the control rig. + + """ + #--------------------------------- + # Create the hip and rib controls + bpy.ops.object.mode_set(mode='EDIT') + + # Copy org bones + hip_control = copy_bone(self.obj, self.org_bones[0], strip_org(self.org_bones[0])) + rib_control = copy_bone(self.obj, self.org_bones[-1], strip_org(self.org_bones[-1])) + rib_mch = copy_bone(self.obj, self.org_bones[-1], make_mechanism_name(strip_org(self.org_bones[-1] + ".follow"))) + hinge = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(self.org_bones[-1]) + ".hinge")) + + eb = self.obj.data.edit_bones + + hip_control_e = eb[hip_control] + rib_control_e = eb[rib_control] + rib_mch_e = eb[rib_mch] + hinge_e = eb[hinge] + + # Parenting + hip_control_e.use_connect = False + rib_control_e.use_connect = False + rib_mch_e.use_connect = False + hinge_e.use_connect = False + + hinge_e.parent = None + rib_control_e.parent = hinge_e + rib_mch_e.parent = rib_control_e + + # Position + flip_bone(self.obj, hip_control) + flip_bone(self.obj, hinge) + + hinge_e.length /= 2 + rib_mch_e.length /= 2 + + put_bone(self.obj, rib_control, hip_control_e.head) + put_bone(self.obj, rib_mch, hip_control_e.head) + + bpy.ops.object.mode_set(mode='POSE') + bpy.ops.object.mode_set(mode='EDIT') + eb = self.obj.data.edit_bones + + # Switch to object mode + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + hip_control_p = pb[hip_control] + rib_control_p = pb[rib_control] + hinge_p = pb[hinge] + + # No translation on rib control + rib_control_p.lock_location = [True, True, True] + + # Hip does not use local location + hip_control_p.bone.use_local_location = False + + # Custom hinge property + prop = rna_idprop_ui_prop_get(rib_control_p, "isolate", create=True) + rib_control_p["isolate"] = 1.0 + prop["soft_min"] = prop["min"] = 0.0 + prop["soft_max"] = prop["max"] = 1.0 + + # Constraints + con = hinge_p.constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + con.subtarget = hip_control + + con1 = hinge_p.constraints.new('COPY_ROTATION') + con1.name = "isolate_off.01" + con1.target = self.obj + con1.subtarget = hip_control + + con2 = rib_control_p.constraints.new('COPY_SCALE') + con2.name = "isolate_off.02" + con2.target = self.obj + con2.subtarget = hip_control + con2.use_offset = True + con2.target_space = 'LOCAL' + con2.owner_space = 'LOCAL' + + # Drivers for "isolate_off" + fcurve = con1.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = "var" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = rib_control_p.path_from_id() + '["isolate"]' + mod = fcurve.modifiers[0] + mod.poly_order = 1 + mod.coefficients[0] = 1.0 + mod.coefficients[1] = -1.0 + + fcurve = con2.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = "var" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = rib_control_p.path_from_id() + '["isolate"]' + mod = fcurve.modifiers[0] + mod.poly_order = 1 + mod.coefficients[0] = 1.0 + mod.coefficients[1] = -1.0 + + # Appearence + hip_control_p.custom_shape_transform = pb[self.org_bones[0]] + rib_control_p.custom_shape_transform = pb[self.org_bones[-1]] + + #------------------------- + # Create flex spine chain + + # Create bones/parenting/positiong + bpy.ops.object.mode_set(mode='EDIT') + flex_bones = [] + flex_helpers = [] + prev_bone = None + for b in self.org_bones: + # Create bones + bone = copy_bone(self.obj, b, make_mechanism_name(strip_org(b) + ".flex")) + helper = copy_bone(self.obj, rib_mch, make_mechanism_name(strip_org(b) + ".flex_h")) + flex_bones += [bone] + flex_helpers += [helper] + + eb = self.obj.data.edit_bones + bone_e = eb[bone] + helper_e = eb[helper] + + # Parenting + bone_e.use_connect = False + helper_e.use_connect = False + if prev_bone == None: + helper_e.parent = eb[hip_control] + bone_e.parent = helper_e + + # Position + put_bone(self.obj, helper, bone_e.head) + helper_e.length /= 4 + + prev_bone = bone + + # Constraints + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + rib_control_p = pb[rib_control] + rib_mch_p = pb[rib_mch] + + inc = 1.0 / (len(flex_helpers) - 1) + inf = 1.0 / (len(flex_helpers) - 1) + for b in zip(flex_helpers[1:], flex_bones[:-1], self.org_bones[1:]): + bone_p = pb[b[0]] + + # Scale constraints + con = bone_p.constraints.new('COPY_SCALE') + con.name = "copy_scale1" + con.target = self.obj + con.subtarget = flex_helpers[0] + con.influence = 1.0 + + con = bone_p.constraints.new('COPY_SCALE') + con.name = "copy_scale2" + con.target = self.obj + con.subtarget = rib_mch + con.influence = inf + + # Bend constraints + con = bone_p.constraints.new('COPY_ROTATION') + con.name = "bend1" + con.target = self.obj + con.subtarget = flex_helpers[0] + con.influence = 1.0 + + con = bone_p.constraints.new('COPY_ROTATION') + con.name = "bend2" + con.target = self.obj + con.subtarget = rib_mch + con.influence = inf + + # If not the rib control + if b[0] != flex_helpers[-1]: + # Custom bend property + prop_name = "bend_" + strip_org(b[2]) + prop = rna_idprop_ui_prop_get(rib_control_p, prop_name, create=True) + rib_control_p[prop_name] = inf + prop["min"] = 0.0 + prop["max"] = 1.0 + prop["soft_min"] = 0.0 + prop["soft_max"] = 1.0 + + # Bend driver + fcurve = con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = prop_name + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = rib_control_p.path_from_id() + '["' + prop_name + '"]' + + # Location constraint + con = bone_p.constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + con.subtarget = b[1] + con.head_tail = 1.0 + + inf += inc + + #---------------------------- + # Create reverse spine chain + + # Create bones/parenting/positioning + bpy.ops.object.mode_set(mode='EDIT') + rev_bones = [] + prev_bone = None + for b in zip(flex_bones, self.org_bones): + # Create bones + bone = copy_bone(self.obj, b[1], make_mechanism_name(strip_org(b[1]) + ".reverse")) + rev_bones += [bone] + eb = self.obj.data.edit_bones + bone_e = eb[bone] + + # Parenting + bone_e.use_connect = False + bone_e.parent = eb[b[0]] + + # Position + flip_bone(self.obj, bone) + bone_e.tail = Vector(eb[b[0]].head) + #bone_e.head = Vector(eb[b[0]].tail) + if prev_bone == None: + pass # Position base bone wherever you want, for now do nothing (i.e. position at hips) + else: + put_bone(self.obj, bone, eb[prev_bone].tail) + + prev_bone = bone + + # Constraints + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + prev_bone = None + for bone in rev_bones: + bone_p = pb[bone] + + con = bone_p.constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + if prev_bone == None: + con.subtarget = hip_control # Position base bone wherever you want, for now hips + else: + con.subtarget = prev_bone + con.head_tail = 1.0 + prev_bone = bone + + #--------------------------------------------- + # Constrain org bones to flex bone's rotation + pb = self.obj.pose.bones + for b in zip(self.org_bones, flex_bones): + con = pb[b[0]].constraints.new('COPY_TRANSFORMS') + con.name = "copy_rotation" + con.target = self.obj + con.subtarget = b[1] + + #--------------------------- + # Create pivot slide system + pb = self.obj.pose.bones + bone_p = pb[self.org_bones[0]] + rib_control_p = pb[rib_control] + + # Custom pivot_slide property + prop = rna_idprop_ui_prop_get(rib_control_p, "pivot_slide", create=True) + rib_control_p["pivot_slide"] = 1.0 / len(self.org_bones) + prop["min"] = 0.0 + prop["max"] = 1.0 + prop["soft_min"] = 1.0 / len(self.org_bones) + prop["soft_max"] = 1.0 - (1.0 / len(self.org_bones)) + + # Anchor constraint + con = bone_p.constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + con.subtarget = rev_bones[0] + + # Slide constraints + i = 1 + tot = len(rev_bones) + for rb in rev_bones: + con = bone_p.constraints.new('COPY_LOCATION') + con.name = "slide." + str(i) + con.target = self.obj + con.subtarget = rb + con.head_tail = 1.0 + + # Driver + fcurve = con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = "slide" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = rib_control_p.path_from_id() + '["pivot_slide"]' + mod = fcurve.modifiers[0] + mod.poly_order = 1 + mod.coefficients[0] = 1 - i + mod.coefficients[1] = tot + + i += 1 + + # Create control widgets + w1 = create_circle_widget(self.obj, hip_control, radius=1.0, head_tail=1.0) + w2 = create_circle_widget(self.obj, rib_control, radius=1.0, head_tail=0.0) + + if w1 != None: + obj_to_bone(w1, self.obj, self.org_bones[0]) + if w2 != None: + obj_to_bone(w2, self.obj, self.org_bones[-1]) + + # Return control names + return hip_control, rib_control + + 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.gen_deform() + hips, ribs = self.gen_control() + + return [script % (hips, ribs)] + + @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('hips') + bone.head[:] = 0.0000, 0.0000, 0.0000 + bone.tail[:] = -0.0000, -0.0590, 0.2804 + bone.roll = -0.0000 + bone.use_connect = False + bones['hips'] = bone.name + bone = arm.edit_bones.new('spine') + bone.head[:] = -0.0000, -0.0590, 0.2804 + bone.tail[:] = 0.0000, 0.0291, 0.5324 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['hips']] + bones['spine'] = bone.name + bone = arm.edit_bones.new('ribs') + bone.head[:] = 0.0000, 0.0291, 0.5324 + bone.tail[:] = -0.0000, 0.0000, 1.0000 + bone.roll = -0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine']] + bones['ribs'] = bone.name + + bpy.ops.object.mode_set(mode='OBJECT') + pbone = obj.pose.bones[bones['hips']] + pbone.rigify_type = 'spine' + 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 = 'QUATERNION' + pbone = obj.pose.bones[bones['spine']] + 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 = 'QUATERNION' + pbone = obj.pose.bones[bones['ribs']] + 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 = 'QUATERNION' + pbone = obj.pose.bones[bones['hips']] + pbone['rigify_type'] = 'spine' + + 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 + -- cgit v1.2.3