import bpy from mathutils import Vector,Matrix from math import acos, pi #from .insert_keyframe import insert_keyframe ############################ ## Math utility functions ## ############################ def perpendicular_vector(v): """ Returns a vector that is perpendicular to the one given. The returned vector is _not_ guaranteed to be normalized. """ # Create a vector that is not aligned with v. # It doesn't matter what vector. Just any vector # that's guaranteed to not be pointing in the same # direction. if abs(v[0]) < abs(v[1]): tv = Vector((1,0,0)) else: tv = Vector((0,1,0)) # Use cross prouct to generate a vector perpendicular to # both tv and (more importantly) v. return v.cross(tv) def rotation_difference(mat1, mat2): """ Returns the shortest-path rotational difference between two matrices. """ q1 = mat1.to_quaternion() q2 = mat2.to_quaternion() angle = acos(min(1,max(-1,q1.dot(q2)))) * 2 if angle > pi: angle = -angle + (2*pi) return angle ######################################### ## "Visual Transform" helper functions ## ######################################### def get_pose_matrix_in_other_space(mat, pose_bone): """ Returns the transform matrix relative to pose_bone's current transform space. In other words, presuming that mat is in armature space, slapping the returned matrix onto pose_bone should give it the armature-space transforms of mat. TODO: try to handle cases with axis-scaled parents better. """ rest = pose_bone.bone.matrix_local.copy() rest_inv = rest.inverted() if pose_bone.parent: par_mat = pose_bone.parent.matrix.copy() par_inv = par_mat.inverted() par_rest = pose_bone.parent.bone.matrix_local.copy() else: par_mat = Matrix() par_inv = Matrix() par_rest = Matrix() # Get matrix in bone's current transform space smat = rest_inv * (par_rest * (par_inv * mat)) # Compensate for non-local location #if not pose_bone.bone.use_local_location: # loc = smat.to_translation() * (par_rest.inverted() * rest).to_quaternion() # smat.translation = loc return smat def get_local_pose_matrix(pose_bone): """ Returns the local transform matrix of the given pose bone. """ return get_pose_matrix_in_other_space(pose_bone.matrix, pose_bone) def set_pose_translation(pose_bone, mat): """ Sets the pose bone's translation to the same translation as the given matrix. Matrix should be given in bone's local space. """ if pose_bone.bone.use_local_location is True: pose_bone.location = mat.to_translation() else: loc = mat.to_translation() rest = pose_bone.bone.matrix_local.copy() if pose_bone.bone.parent: par_rest = pose_bone.bone.parent.matrix_local.copy() else: par_rest = Matrix() q = (par_rest.inverted() * rest).to_quaternion() pose_bone.location = q * loc def set_pose_rotation(pose_bone, mat): """ Sets the pose bone's rotation to the same rotation as the given matrix. Matrix should be given in bone's local space. """ q = mat.to_quaternion() if pose_bone.rotation_mode == 'QUATERNION': pose_bone.rotation_quaternion = q elif pose_bone.rotation_mode == 'AXIS_ANGLE': pose_bone.rotation_axis_angle[0] = q.angle pose_bone.rotation_axis_angle[1] = q.axis[0] pose_bone.rotation_axis_angle[2] = q.axis[1] pose_bone.rotation_axis_angle[3] = q.axis[2] else: pose_bone.rotation_euler = q.to_euler(pose_bone.rotation_mode) def set_pose_scale(pose_bone, mat): """ Sets the pose bone's scale to the same scale as the given matrix. Matrix should be given in bone's local space. """ pose_bone.scale = mat.to_scale() def match_pose_translation(pose_bone, target_bone): """ Matches pose_bone's visual translation to target_bone's visual translation. This function assumes you are in pose mode on the relevant armature. """ mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone) set_pose_translation(pose_bone, mat) bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='POSE') def match_pose_rotation(pose_bone, target_bone): """ Matches pose_bone's visual rotation to target_bone's visual rotation. This function assumes you are in pose mode on the relevant armature. """ mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone) set_pose_rotation(pose_bone, mat) bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='POSE') def match_pose_scale(pose_bone, target_bone): """ Matches pose_bone's visual scale to target_bone's visual scale. This function assumes you are in pose mode on the relevant armature. """ mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone) set_pose_scale(pose_bone, mat) bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='POSE') ############################## ## IK/FK snapping functions ## ############################## def match_pole_target(ik_first, ik_last, pole, match_bone, length): """ Places an IK chain's pole target to match ik_first's transforms to match_bone. All bones should be given as pose bones. You need to be in pose mode on the relevant armature object. ik_first: first bone in the IK chain ik_last: last bone in the IK chain pole: pole target bone for the IK chain match_bone: bone to match ik_first to (probably first bone in a matching FK chain) length: distance pole target should be placed from the chain center """ a = ik_first.matrix.to_translation() b = ik_last.matrix.to_translation() + ik_last.vector # Vector from the head of ik_first to the # tip of ik_last ikv = b - a # Get a vector perpendicular to ikv pv = perpendicular_vector(ikv).normalized() * length def set_pole(pvi): """ Set pole target's position based on a vector from the arm center line. """ # Translate pvi into armature space ploc = a + (ikv/2) + pvi # Set pole target to location mat = get_pose_matrix_in_other_space(Matrix.Translation(ploc), pole) set_pose_translation(pole, mat) bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='POSE') set_pole(pv) # Get the rotation difference between ik_first and match_bone angle = rotation_difference(ik_first.matrix, match_bone.matrix) # Try compensating for the rotation difference in both directions pv1 = Matrix.Rotation(angle, 4, ikv) * pv set_pole(pv1) ang1 = rotation_difference(ik_first.matrix, match_bone.matrix) pv2 = Matrix.Rotation(-angle, 4, ikv) * pv set_pole(pv2) ang2 = rotation_difference(ik_first.matrix, match_bone.matrix) # Do the one with the smaller angle if ang1 < ang2: set_pole(pv1)