Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'rigify/rigs/biped/leg/ik.py')
-rw-r--r--rigify/rigs/biped/leg/ik.py520
1 files changed, 520 insertions, 0 deletions
diff --git a/rigify/rigs/biped/leg/ik.py b/rigify/rigs/biped/leg/ik.py
new file mode 100644
index 00000000..a04b57dc
--- /dev/null
+++ b/rigify/rigs/biped/leg/ik.py
@@ -0,0 +1,520 @@
+#====================== 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 = max(-1.0, min(1.0, bone_e.x_axis.dot(vec)))
+ 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
+
+
+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 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)
+
+ # Determine the pole offset value
+ plane = (shin_e.tail - thigh_e.head).normalize()
+ vec1 = thigh_e.x_axis.normalize()
+ vec2 = (pole_e.head - thigh_e.head).normalize()
+ 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
+
+ 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
+ con.pole_angle = pole_offset
+ 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]
+