Script to replace deform action modifiers

I have been trying to write a Python script to replace the walkcycle action modifers that have been removed from 2.6x, so that a character can move along a Bezier curve. However, I am getting some confusing results. I have the source code of my Python script pasted below and a .blend file attached that I was using the script on. I successfully got the script to move the character so that its feet and back move in the correct locations along the path, but I can’t get them to rotate properly. I have a source action with a character walking in a straight line and my goal is to create a new action with it walking on a curve. The main function that does this is the one called MakeDeformedAct which starts on line 149.
I suspect that line 200 of my script is incorrect:


   new_mat = RotMat*Mathutils.Quaternion(w,x,y,z).toMatrix()

This line is meant to rotate the bones with respect to the Bezier curve. RotMat is a rotation matrix that determines how the bone must rotate to match the curve. w, x, y, and z are the quaternion values of the rotation of the bone in the source action. I have the matrix of the individual bones on lines 157-58:


  BoneMatrix = arm.bones[b].matrix['ARMATURESPACE'].rotationPart()
  BoneMatrix1 = BoneMatrix.copy().invert()

I suspect that I need to include this in line 200, but the results were even more confusing when I did that. I printed some values when the script ran from lines 206-08.


   print new_mat.toEuler()
   print (new_mat*BoneMatrix).toEuler()
   print (BoneMatrix*new_mat).toEuler()

For the bone back4 on frame 41, the results were:


[0.000000, -0.000000, -103.284904](euler)
[-161.764252, 76.715096, -0.000029](euler)
[90.000008, 0.000000, 76.715103](euler)

However, at frame 41, back4 was keyed at (58,0,0), which was different from the (0,0,-103.3) printed by the script. I think the result I want is (0,-103.3,0) but I don’t know how to change line 200 to get that result. Does anyone know why the Euler I printed does not match the rotation values I got from transform properties?
For reference, I am still using 2.49b because I use PyConstraints, but I think the .blend will still work fine without them.


import Blender
from Blender import Mathutils
from Blender import Armature
from Blender.Armature import NLA
from Blender.Armature.NLA import *
import math
from math import *
def bezier_length(co,delta):
 length = 0.0
 for i in range(len(co)-1):
  for j in range(delta):
   length += calc_dist_3d(bezier_get_pt(co,i+(j+0.0)/delta,0),bezier_get_pt(co,i+(j+1.0)/delta,0))
 return length
def bezier_get_pt(co,t,option):
 if option == 0:
  t0 = int(t)
  t1 = t-t0
  b = [0.0,0.0,0.0]
  if t >= len(co)-1.0:
   b = [co[-1][1][0],co[-1][1][1],co[-1][1][2]]
  else:
   for i in [0,1,2]:
    b[i] = (1-t1)**3*co[t0][1][i] + 3*(1-t1)**2*t1*co[t0][2][i] + 3*(1-t1)*t1**2*co[t0+1][0][i] + t1**3*co[t0+1][1][i]
  return b
def bezierPtFmLen(co,t,delta):
 length = 0.0
 i = 0
 j = 0
 while length < t:
  if j >= delta:
   j = 0
   i += 1
  length += calc_dist_3d(bezier_get_pt(co,i+(j+0.0)/delta,0),bezier_get_pt(co,i+(j+1.0)/delta,0))
  j += 1
 return bezier_get_pt(co,i+(j+0.0)/delta,0)
def member(a,b):
 for i in b:
  if i==a:
   return 1
 return 0
def lines_2d_isect(l1,l2):
 if l1[0] == "" and l2[0] == "":
  return ""
 elif l1[0] == "":
  return [l1[1],l2[0]*l1[1]+l2[1]]
 elif l2[0] == "":
  return [l2[1],l1[0]*l2[1]+l1[1]]
 else:
  if l1[0] != l2[0]:
   x = (l2[1]-l1[1])/(l1[0]-l2[0])
   return [x,l1[0]*x+l1[0]]
  else:
   return ""
def get_2d_line(c,d):
 if c[1] != d[1]:
  m = (d[1]-c[1])/(d[0]-c[0])
  b = c[1]-m*c[0]
  return [m,b]
 else:
  return ["",c[1]]
def get_2d_line_value(l,x):
 if l[0] != "":
  return l[0]*x+l[1]
 else:
  return ""
def get_2d_line_perp(l,x):
 if l[0] != "" and l[0] != 0:
  y = l[0]*x+l[1]
  return [-1/l[0],y-x/l[0]]
 elif l[0] == 0:
  return ["",x]
 else:
  return [0.0,x]
def calc_dist_3d(a,b):
 return ( (a[0]-b[0])**2 + (a[1]-b[1])**2 + (a[2]-b[2])**2 )**0.5
def bezierDer(co,dist,delta):
 length = 0.0
 i = 0
 j = 0
 while length < dist:
  if j >= delta:
   j = 0
   i += 1
  length += calc_dist_3d(bezier_get_pt(co,i+(j+0.0)/delta,0),bezier_get_pt(co,i+(j+1.0)/delta,0))
  j += 1
 d = [0.0,0.0,0.0]
 t = i+(j+1.0)/delta
 t0 = int(t)
 t1 = t-t0
 if t0 < len(co)-1:
  for k in [0,1,2]:
   d[k] = -3*(1-t1)**2*co[t0][1][k] + (-6*(1-t1)*t1+3*(1-t1)**2)*co[t0][2][k] + (-3*t1**2+6*(1-t1)*t1)*co[t0+1][0][k] + 3*t1**2*co[t0+1][1][k]
  d = normalize(d)
 else:
  print "Error in bezierDer()"
 return d
def normalize(v):
 sum = 0.0
 for i in v:
  sum += i**2
 w = []
 for i in v:
  w.append(i/sum**0.5)
 return w
def GetRotationMatrix(angle,axis):
 M = [ [0,0,0] , [0,0,0] , [0,0,0] ]
 t = angle/180.0*3.14159265
 M[0][0] = cos(t)+axis[0]**2*(1-cos(t))
 M[0][1] = axis[0]*axis[1]*(1-cos(t))-axis[2]*sin(t)
 M[0][2] = axis[1]*axis[2]*(1-cos(t))-axis[1]*sin(t)
 M[1][0] = axis[1]*axis[0]*(1-cos(t))-axis[2]*sin(t)
 M[1][1] = cos(t)+axis[1]**2*(1-cos(t))
 M[1][2] = axis[1]*axis[2]*(1-cos(t))-axis[0]*sin(t)
 M[2][0] = axis[2]*axis[0]*(1-cos(t))-axis[1]*sin(t)
 M[2][1] = axis[2]*axis[1]*(1-cos(t))-axis[2]*sin(t)
 M[2][2] = cos(t)+axis[2]**2*(1-cos(t))
 matrix = Mathutils.Matrix(M[0],M[1],M[2])
 return matrix
def deform_pt(orig,move,forth,bezier,delta):
 forthV = Mathutils.Vector(forth[0],forth[1],forth[2])
 moveOffsetV = Mathutils.Vector(move[0]-orig[0],move[1]-orig[1],move[2]-orig[2])
 dist = Mathutils.ProjectVecs(moveOffsetV,forthV).length
 CurvePivot = bezierPtFmLen(bezier,dist,delta)
 CurvePivotV = Mathutils.Vector(CurvePivot[0],CurvePivot[1],CurvePivot[2])
 bezierOriginV = Mathutils.Vector(bezier[0][1][0],bezier[0][1][1],bezier[0][1][2])
 moveV = Mathutils.Vector(move[0],move[1],move[2])
 Deform1 = moveV - dist*forthV
 der = bezierDer(bezier,dist,delta)
 derV = Mathutils.Vector(der[0],der[1],der[2])
 angle = -Mathutils.AngleBetweenVecs(derV,forthV)
 axis = forthV.cross(derV).normalize()
 RotMat = GetRotationMatrix(angle,axis)
 Deform2 = RotMat*Deform1
 return [(Deform2+CurvePivotV),derV]
def MakeDeformedAction(SourceAct,NewAct,Armature,bones,forth,bezier,delta):
 armO = Blender.Object.Get(Armature)
 arm = armO.getData()
 act = GetActions()[SourceAct]
 a = NewAction(NewAct)
 a.setActive(armO)
 forthV = Mathutils.Vector(forth[0],forth[1],forth[2])
 for b in bones:
  BoneMatrix = arm.bones[b].matrix['ARMATURESPACE'].rotationPart()
  BoneMatrix1 = BoneMatrix.copy().invert()
  orig = arm.bones[b].head['ARMATURESPACE']
  print "orig", orig
  ipo = act.getChannelIpo(b)
  frames = getIpoFrames(ipo)
  for f in frames:
   LocX = 0
   LocY = 0
   LocZ = 0
   for i in ipo:
    if i.name == "LocX": LocX = i
    if i.name == "LocY": LocY = i
    if i.name == "LocZ": LocZ = i
   x = LocX[f]
   y = LocY[f]
   z = LocZ[f]
   move = BoneMatrix*Mathutils.Vector(x,y,z)+orig
   new_co = deform_pt(orig,move,forth,bezier,delta)
   pose = armO.getPose()
   new_co1 = BoneMatrix*(new_co[0]-orig)
   pose.bones[b].loc[:] = [new_co1[0],new_co1[1],new_co1[2]]
   pose.bones[b].insertKey(armO,int(f),Blender.Object.Pose.LOC)
   angle = -Mathutils.AngleBetweenVecs(new_co[1],forthV)
   axis = forthV.cross(new_co[1]).normalize()
   RotMat = GetRotationMatrix(angle,axis)
   RotW = 0
   RotX = 0
   RotY = 0
   RotZ = 0
   w = 0
   x = 0
   y = 0
   z = 0
   for i in ipo:
    if i.name == "QuatW": RotW = i
    if i.name == "QuatX": RotX = i
    if i.name == "QuatY": RotY = i
    if i.name == "QuatZ": RotZ = i     
   if RotW != 0: w = RotW[f]
   if RotX != 0: x = RotX[f]
   if RotY != 0: y = RotY[f]
   if RotZ != 0: z = RotZ[f]
   new_mat = RotMat*Mathutils.Quaternion(w,x,y,z).toMatrix()
   new_euler = new_mat.toEuler()
   new_quat = new_mat.toQuat()
   pose.bones[b].quat[:] = [new_quat[0],new_quat[1],new_quat[2],new_quat[3]]
   pose.bones[b].insertKey(armO,int(f),Blender.Object.Pose.ROT)
   print f,b
   print new_mat.toEuler()
   print (new_mat*BoneMatrix).toEuler()
   print (BoneMatrix*new_mat).toEuler()
def getIpoFrames(ipo):
 frames = []
 for i in ipo:
  for j in i.bezierPoints:
   if not(member(j.vec[1][0],frames)):
    frames.append(j.vec[1][0])
 frames.sort()
 return frames
b = []
b.append([[-0.1,0.0,0.0] , [0.0,0.0,0.0] , [0.1,0.0,0.0] ])
b.append([[0.9,0.0,0.0] , [1.0,0.0,0.0] , [1.1,0.0,0.0] ])
c = []
c.append([ [-1.5,-0.5,0.0] , [-1.0,0.0,0.0] , [-0.5,0.5,0.0] ])
c.append([ [0.0,0.0,0.0] , [1.0,0.0,0.0] , [2.0,0.0,0.0] ])
c.append([ [2.0,-2.4,0.0] , [1.0,-2.4,0.0] , [0.0,-2.4,0.0] ])
d = []
d.append([ [0.0,1.0,0.0] , [0.0,0.0,0.0] , [0.0,-1.0,0.0] ])
d.append([ [0.707,-2.293,0.0] , [0.0,-3.0,0.0] , [-0.707,-3.707,0.0] ])
d.append([ [-1.0,-3.0,0.0] , [-2.0,-3.0,0.0] , [-3.0,-3.0,0.0] ])
print "Length", bezier_length(d,1000)
forth = [0.0,-1.0,0.0]
bones = ["back4","FootIK.R","FootIK.L"]
MakeDeformedAction("Walk_IK4","ModAct1","Person5.0.arm",bones,forth,d,1000)
"""
orig = [0.21,0.041,0.0]
move = [0.5,-4.0,0.0]
print bezierPtFmLen(d,3.0,1000)
print deform_pt(orig,move,forth,d,1000)
"""

Attachments

curve_test_20120224.blend (255 KB)

Sorry if my last post wasn’t too clear. I wrote a small script to test the problem that I was having. I had a matrix convert into both a quaterion and to an Euler, and then I converted the quaterion into an Euler, and the two Eulers were not identical. I don’t understand why the two Eulers would be different when they originated from the same matrix.


import Blender
from Blender import Mathutils
from Blender import Armature
from Blender.Armature import NLA
from Blender.Armature.NLA import *
import math
from math import *
m = Mathutils.Matrix([-0.229793, -0.973239, 0.000000],[-0.973239, -0.229793, 0.000000],[0.000000, -0.973239, 1.000000])
new_euler = m.toEuler()
new_quat = m.toQuat()
"""
armO = Blender.Object.Get("Person5.0.arm")
arm = armO.getData()
act = GetActions()["Walk_IK4"]
a = NewAction("Test")
a.setActive(armO)
pose = armO.getPose()
pose.bones["back4"].quat[:] = [new_quat[0],new_quat[1],new_quat[2],new_quat[3]]
pose.bones["back4"].insertKey(armO,1,Blender.Object.Pose.ROT)
"""
print new_euler
print new_quat.toEuler()