diff options
Diffstat (limited to 'rigify/rigs/biped/arm')
-rw-r--r-- | rigify/rigs/biped/arm/__init__.py | 235 | ||||
-rw-r--r-- | rigify/rigs/biped/arm/deform.py | 230 | ||||
-rw-r--r-- | rigify/rigs/biped/arm/fk.py | 217 | ||||
-rw-r--r-- | rigify/rigs/biped/arm/ik.py | 339 |
4 files changed, 1021 insertions, 0 deletions
diff --git a/rigify/rigs/biped/arm/__init__.py b/rigify/rigs/biped/arm/__init__.py new file mode 100644 index 00000000..93e757f4 --- /dev/null +++ b/rigify/rigs/biped/arm/__init__.py @@ -0,0 +1,235 @@ +#====================== 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 imp +from . import fk, ik, deform +from rigify.utils import MetarigError, get_layers + +imp.reload(fk) +imp.reload(ik) +imp.reload(deform) + +script = """ +fk_arm = ["%s", "%s", "%s"] +ik_arm = ["%s", "%s", "%s", "%s"] +if is_selected(fk_arm+ik_arm): + layout.prop(pose_bones[ik_arm[2]], '["ikfk_switch"]', text="FK / IK (" + ik_arm[2] + ")", slider=True) +if is_selected(fk_arm): + try: + pose_bones[fk_arm[0]]["isolate"] + layout.prop(pose_bones[fk_arm[0]], '["isolate"]', text="Isolate Rotation (" + fk_arm[0] + ")", slider=True) + except KeyError: + pass +if is_selected(fk_arm+ik_arm): + p = layout.operator("pose.rigify_arm_fk2ik_" + rig_id, text="Snap FK->IK (" + fk_arm[0] + ")") + p.uarm_fk = fk_arm[0] + p.farm_fk = fk_arm[1] + p.hand_fk = fk_arm[2] + p.uarm_ik = ik_arm[0] + p.farm_ik = ik_arm[1] + p.hand_ik = ik_arm[2] + p = layout.operator("pose.rigify_arm_ik2fk_" + rig_id, text="Snap IK->FK (" + fk_arm[0] + ")") + p.uarm_fk = fk_arm[0] + p.farm_fk = fk_arm[1] + p.hand_fk = fk_arm[2] + p.uarm_ik = ik_arm[0] + p.farm_ik = ik_arm[1] + p.hand_ik = ik_arm[2] + p.pole = ik_arm[3] + +""" + + +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], ik_controls[2], ik_controls[3])] + + @classmethod + def add_parameters(self, group): + """ Add the parameters of this rig type to the + RigifyParameters PropertyGroup + + """ + items = [('X', 'X', ''), ('Y', 'Y', ''), ('Z', 'Z', ''), ('-X', '-X', ''), ('-Y', '-Y', ''), ('-Z', '-Z', '')] + group.primary_rotation_axis = bpy.props.EnumProperty(items=items, name="Primary Rotation Axis", default='X') + + group.bend_hint = bpy.props.BoolProperty(name="Bend Hint", default=True, description="Give IK chain a hint about which way to bend. Useful for perfectly straight chains.") + + 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="") + + r = layout.row() + r.prop(params, "bend_hint") + + 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..2c634b86 --- /dev/null +++ b/rigify/rigs/biped/arm/fk.py @@ -0,0 +1,217 @@ +#====================== 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 is 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: + hinge_p = pb[hinge] + + 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) + + # Hinge transforms are locked, for auto-ik + if self.org_parent != None: + hinge_p.lock_location = True, True, True + hinge_p.lock_rotation = True, True, True + hinge_p.lock_rotation_w = True + hinge_p.lock_scale = True, True, True + + # 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..0ecf70e7 --- /dev/null +++ b/rigify/rigs/biped/arm/ik.py @@ -0,0 +1,339 @@ +#====================== 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 +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 + + +def angle_on_plane(plane, vec1, vec2): + """ Return the angle between two vectors projected onto a plane. + """ + plane.normalize() + vec1 = vec1 - (plane * (vec1.dot(plane))) + vec2 = vec2 - (plane * (vec2.dot(plane))) + vec1.normalize() + vec2.normalize() + + # Determine the angle + angle = acos(max(-1.0, min(1.0, vec1.dot(vec2)))) + + if angle < 0.00001: # close enough to zero that sign doesn't matter + return angle + + # Determine the sign of the angle + vec3 = vec2.cross(vec1) + vec3.normalize() + sign = vec3.dot(plane) + if sign >= 0: + sign = 1 + else: + sign = -1 + + return angle * sign + + +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.bend_hint = params.bend_hint + + 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)) + + # Determine the pole offset value + plane = (farm_e.tail - uarm_e.head).normalized() + vec1 = uarm_e.x_axis.normalized() + vec2 = (pole_e.head - uarm_e.head).normalized() + pole_offset = angle_on_plane(plane, vec1, vec2) + + # 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 + + # Bend direction hint + if self.bend_hint: + con = farm_p.constraints.new('LIMIT_ROTATION') + con.name = "bend_hint" + con.owner_space = 'LOCAL' + if self.primary_rotation_axis == 'X': + con.use_limit_x = True + con.min_x = pi / 10 + con.max_x = pi / 10 + elif self.primary_rotation_axis == '-X': + con.use_limit_x = True + con.min_x = -pi / 10 + con.max_x = -pi / 10 + elif self.primary_rotation_axis == 'Y': + con.use_limit_y = True + con.min_y = pi / 10 + con.max_y = pi / 10 + elif self.primary_rotation_axis == '-Y': + con.use_limit_y = True + con.min_y = -pi / 10 + con.max_y = -pi / 10 + elif self.primary_rotation_axis == 'Z': + con.use_limit_z = True + con.min_z = pi / 10 + con.max_z = pi / 10 + elif self.primary_rotation_axis == '-Z': + con.use_limit_z = True + con.min_z = -pi / 10 + con.max_z = -pi / 10 + + # 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 + con.pole_angle = pole_offset + 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 [uarm, farm, hand, pole] + |