Hi, Iv been trying to get BVH export working for 2.44 release, and its almost working!!!
Iv checked other areas of the code and have narrowed the problem down to the joint rotation that dosnt work properly…
Rotating X/Y or Z axis works but when combind it gives slightly wrong results.
Iv tried applying the rotation 6 possible ways but that dosnt help.
Look for the word FIXME to see teh area that dosnt work.
also, changing the order the rotation is exported makes a difference.
you must change the channel order also “Yposition Zposition Xrotation”
If anyone has some spare time, or a vested interest in this working, take a look and see if you can fix.
#!BPY
"""
Name: 'Motion Capture *.BVH...'
Blender: 242
Group: 'Export'
Tooltip: 'Active Armature to BVH'
"""
import Blender
import bvh_import # for the class
from Blender import *
TranslationMatrix = Mathutils.TranslationMatrix
Quaternion = Mathutils.Quaternion
import BPyMessages
RotationMatrix = Blender.Mathutils.RotationMatrix
Matrix = Blender.Mathutils.Matrix
# Change the order rotation is applied.
MATRIX_IDENTITY_3x3 = Matrix([1,0,0],[0,1,0],[0,0,1])
MATRIX_IDENTITY_4x4 = Matrix([1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1])
def eulerRotate(x,y,z, rot_order):
# Clamp all values between 0 and 360, values outside this raise an error.
mats=[RotationMatrix(x,3,'x'), RotationMatrix(y,3,'y'), RotationMatrix(z,3,'z')]
# print rot_order
# Standard BVH multiplication order, apply the rotation in the order Z,X,Y
return (mats[rot_order[2]]*(mats[rot_order[1]]* (mats[rot_order[0]]* MATRIX_IDENTITY_3x3))).toEuler()
#return (mats[rot_order[0]]*(mats[rot_order[1]]* (mats[rot_order[2]]* MATRIX_IDENTITY_3x3))).toEuler()
def bvh_export(filepath, ob, PREF_STARTFRAME, PREF_ENDFRAME, PREF_SCALE= 10.0):
Window.EditMode(0)
Blender.Window.WaitCursor(1)
file= open(filepath, 'w')
# bvh_nodes= {}
arm_data= ob.data
bones= arm_data.bones.values()
bone_locs= {}
bone_locs_rest_offset= {}
# Build a dictionary of bone children.
# None is for parentless bones
bone_children= {None:[]}
# initialize with blank lists
for bone in bones:
bone_children[bone.name] = []
for bone in bones:
parent= bone.parent
bone_name= bone.name
bone_locs[bone_name] = bone.head['ARMATURESPACE']
if parent:
bone_children[parent.name].append( bone_name )
else: # root node
bone_children[None].append( bone_name )
# sort the children, not needed but may as well
for children_list in bone_children.itervalues():
children_list.sort()
children_list.reverse()
# build a (name:bone) mapping dict
bone_dict= {}
for bone in bones: bone_dict[bone.name] = bone
# bone name list in the order that the bones are written
bones_serialized_names= []
file.write('HIERARCHY
')
def write_bones_recursive(bone_name, indent):
my_bone_children= bone_children[bone_name]
indent_str= ' '*indent # cache?
bone= bone_dict[bone_name]
loc = bone_locs[bone_name]
# make relative if we can
if bone.parent:
#loc= loc - bone_locs[bone.parent.name]
loc= loc - bone_locs[bone.parent.name]
bone_locs_rest_offset[bone_name] = loc
bones_serialized_names.append(bone_name)
if indent:
file.write('%sJOINT %s
' % (indent_str, bone_name))
else:
file.write('%sROOT %s
' % (indent_str, bone_name))
file.write('%s{
' % indent_str)
file.write('%s OFFSET %.6f %.6f %.6f
' % (indent_str, loc.x * PREF_SCALE, loc.y * PREF_SCALE, loc.z * PREF_SCALE))
file.write('%s CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
' % indent_str) # Seems best?
if my_bone_children:
# Write children
for child_bone in my_bone_children:
write_bones_recursive(child_bone, indent+1)
else:
# Write the bone end.
file.write('%s End Site
' % indent_str)
file.write('%s {
' % indent_str)
loc= bone.tail['ARMATURESPACE'] - bone_locs[bone_name]
file.write('%s OFFSET %.6f %.6f %.6f
' % (indent_str, loc.x * PREF_SCALE, loc.y * PREF_SCALE, loc.z * PREF_SCALE))
file.write('%s }
' % indent_str)
file.write('%s}
' % indent_str)
if len(bone_children[None])==1:
key= bone_children[None][0]
indent= 0
write_bones_recursive(key, indent)
else:
# Write a dummy parent node
file.write('ROOT %s
' % key)
file.write('{
')
file.write(' OFFSET 0.0 0.0 0.0
')
file.write(' CHANNELS 0
')
key= None
indent= 1
write_bones_recursive(key, indent)
file.write('}
')
# redefine bones as sorted by bones_serialized_names
# se we can write motion
pose_dict= ob.getPose().bones
class decorated_bone(object):
__slots__=(\
'name',# bone name, used as key in many places
'parent',# decorated bone parent, set in a later loop
'rest_bone',# blender armature bone
'pose_bone',# blender pose bone
'pose_mat',# blender pose matrix
'rest_arm_mat',# blender rest matrix (armature space)
'rest_local_mat',# blender rest batrix (local space)
'pose_imat',# pose_mat inverted
'rest_arm_imat',# rest_arm_mat inverted
'rest_local_imat', # rest_local_mat inverted
'rest_head_offset') # offset from parent in armature space
def __init__(self, bone_name):
self.name= bone_name
self.rest_bone= bone_dict[bone_name]
self.pose_bone= pose_dict[bone_name]
self.pose_mat= self.pose_bone.poseMatrix
mat= self.rest_bone.matrix
self.rest_arm_mat= mat['ARMATURESPACE'].copy()
self.rest_local_mat= mat['BONESPACE'].copy().resize4x4()
# inverted mats
self.pose_imat= self.pose_mat.copy().invert()
self.rest_arm_imat= self.rest_arm_mat.copy().invert()
self.rest_local_imat= self.rest_local_mat.copy().invert()
self.rest_head_offset = bone_locs_rest_offset[bone_name]
self.parent= None
def update_posedata(self):
self.pose_mat= self.pose_bone.poseMatrix
self.pose_imat= self.pose_mat.copy().invert()
def __repr__(self):
if self.parent:
return '["%s" child on "%s"]
' % (self.name, self.parent.name)
else:
return '["%s" root bone]
' % (self.name)
bones_decorated= [ decorated_bone(bone_name) for bone_name in bones_serialized_names]
# Assign parents
bones_decorated_dict= {}
for db in bones_decorated:
bones_decorated_dict[db.name] = db
for db in bones_decorated:
parent= db.rest_bone.parent
if parent:
db.parent = bones_decorated_dict[parent.name]
del bones_decorated_dict
# finish assigning parents
print bones_decorated
#print len(bones), len(pose_bones)
file.write('MOTION
')
file.write('Frames: %d
' % 100)
file.write('Frame Time: %.6f
' % 0.03)
triple= '%.6f %.6f %.6f '
for frame in xrange(PREF_STARTFRAME, PREF_ENDFRAME+1):
Blender.Set('curframe', frame)
Blender.Window.RedrawAll()
for dbone in bones_decorated:
dbone.update_posedata()
# Calculate the bone matrix, this is correct.
matrix= dbone.pose_mat
rest_matrix = dbone.rest_arm_mat
if dbone.parent:
matrix= matrix * dbone.parent.pose_imat
rest_matrix= rest_matrix * dbone.parent.rest_arm_imat
matrix= matrix * rest_matrix.copy().invert()
# Done,
# location and rotation to write, this is correct also
loc= (dbone.rest_head_offset + matrix.translationPart()) * PREF_SCALE
file.write(triple % tuple(loc))
# Rotation, Almost Correct!!!!
# FIXME
rot= matrix.toEuler()
# Why do we need to flip the X axis???
rot = rot[2], -rot[0], rot[1] # looks almost right
rot = eulerRotate(rot[0], rot[1], rot[2], [1,2,0]) # LOOKS ALMOST RIGHT
file.write(triple % tuple(rot))
file.write('
')
file.close()
# print'BVH Exported: %s frames:%d
'% (filepath, numframes-1)
Blender.Window.WaitCursor(0)
def bvh_export_ui(filepath):
# Dont overwrite
if not BPyMessages.Warning_SaveOver(filepath):
return
scn= Scene.GetCurrent()
ob_act= scn.objects.active
if not ob_act or ob_act.type != 'Armature':
BPyMessages.Error_NoArmatureActive()
arm_ob= scn.objects.active
if not arm_ob or arm_ob.type!='Armature':
Blender.Draw.PupMenu('No Armature object selected.')
return
ctx = scn.getRenderingContext()
orig_frame = Blender.Get('curframe')
PREF_STARTFRAME= Blender.Draw.Create(int(ctx.startFrame()))
PREF_ENDFRAME= Blender.Draw.Create(int(ctx.endFrame()))
block = [\
("Start Frame: ", PREF_STARTFRAME, 1, 30000, "Start Bake from what frame?: Default 1"),\
("End Frame: ", PREF_ENDFRAME, 1, 30000, "End Bake on what Frame?"),\
]
if not Blender.Draw.PupBlock("Export MDD", block):
return
PREF_STARTFRAME, PREF_ENDFRAME=\
min(PREF_STARTFRAME.val, PREF_ENDFRAME.val),\
max(PREF_STARTFRAME.val, PREF_ENDFRAME.val)
bvh_export(filepath, ob_act, PREF_STARTFRAME, PREF_ENDFRAME)
Blender.Set('curframe', orig_frame)
if __name__=='__main__':
Blender.Window.FileSelector(bvh_export_ui, 'EXPORT BVH', sys.makename(ext='.bvh'))