210 lines
7.0 KiB
Python
210 lines
7.0 KiB
Python
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)
|