#!BPY """ Name: 'Motion Capture (.bvh)...' Blender: 232 Group: 'Export' Tip: 'Export a (.bvh) motion capture file' """ __author__ = "Campbell Barton" __url__ = ("blender", "elysiun") __version__ = "1.1 12/16/05" __bpydoc__ = """\ This script exports animation data to BVH motion capture file format. Supported:
Missing:
Known issues:
Notes:
""" # $Id$ # #===============================================# # BVH Export script 1.0 by Campbell Barton # # Copyright MetaVR 30/03/2004, # # if you have any questions about this script # # email me cbarton@metavr.com # #===============================================# # -------------------------------------------------------------------------- # BVH Export v1.1 by Campbell Barton (AKA Ideasman) # -------------------------------------------------------------------------- # ***** 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # ***** END GPL LICENCE BLOCK ***** # -------------------------------------------------------------------------- import Blender from Blender import Scene, Object import math time = Blender.sys.time from math import * # Get the current scene. scn = Scene.GetCurrent() context = scn.getRenderingContext() frameRate = 1.0/context.framesPerSec() # 0.04 = 25fps scale = 1.0 indent = '\t' # 2 space indent per object prefixDelimiter = '_' # Vars used in eular rotation funtcion RAD_TO_DEG = 180.0/3.14159265359 DEG_TO_RAD = math.pi/180.0 #====================================================# # Search for children of this object and return them # #====================================================# def getChildren(parent): children = [] # We'll assume none. for child in Object.Get(): if child.parent == parent: children.append( child ) return children #====================================================# # MESSY BUT WORKS: Make a string that shows the # # hierarchy as a list and then eval it # #====================================================# def getHierarchy(root, hierarchy): hierarchy = '%s["%s",' % (hierarchy, root.name) for child in getChildren(root): hierarchy = getHierarchy(child, hierarchy) hierarchy = '%s],' % hierarchy return hierarchy #====================================================# # Strips the prefix off the name before writing # #====================================================# def stripName(name): # name is a string # WARNING!!! Special case for a custom RIG for output # for MetaVR's HPX compatable RIG. # print 'stripname', name[0:10] if name.lower().startswith('transform('): name = name[10:].split(prefixDelimiter)[0] return name.split('_')[0] #====================================================# # Recieves an object name, gets all the data for that# # node from blender and returns it for formatting # # and writing to a file. # #====================================================# def getNodeData(nodeOb): ob = nodeOb obipo = ob.getIpo() # Get real location offset = [o*scale for o in ob.getLocation()] #=========================# # Test for X/Y/Z IPO's # #=========================# # IF we dont have an IPO then dont check the curves. # This was added to catch end nodes that never have an IPO, only an offset. # DUMMY channels xloc, yloc, zloc, xrot, yrot, zrot # [, , , , , ] channels = [0,0,0,0,0,0] # xloc,yloc,zloc,xrot,yrot,zrot if obipo != None: # Do have an IPO, checkout which curves are in use. # Assume the rot's/loc's dont exist until they proven they do. if obipo.getCurve('LocX') != None: channels[0] = 1 if obipo.getCurve('LocY') != None: channels[1] = 1 if obipo.getCurve('LocZ') != None: channels[2] = 1 # Now for the rotations, Because of the conversion of rotation coords # if there is one rotation er need to store all 3 if obipo.getCurve('RotX') != None or \ obipo.getCurve('RotY') != None or \ obipo.getCurve('RotZ') != None: channels[3] = channels[4] = channels[5] = 1 #print ob, channels return offset, channels #====================================================# # Writes the BVH hierarchy to a file # # hierarchy: is a list of the empty hierarcht # # level: how many levels we are down the tree, # # ...used for indenting # # Also gathers channelList , so we know the order to # # write the motiondata in # #====================================================# def hierarchy2bvh(file, hierarchy, level, channelList, nodeObjectList): nodeName = hierarchy[0] ob = Object.Get(nodeName) ''' obipo = ob.getIpo() if obipo != None: obcurves = obipo.getCurves() else: obcurves = None ''' #============# # JOINT NAME # #============# file.write(level * indent) if level == 0: # Add object to nodeObjectList #nodeObjectList.append( (ob, obipo, obcurves) ) nodeObjectList.append( ob ) file.write( 'ROOT %s\n' % stripName(nodeName) ) # If this is the last object in the list then we # dont bother withwriting its real name, use "End Site" instead elif len(hierarchy) == 1: file.write( 'End Site\n' ) # Ok This is a normal joint else: # Add object to nodeObjectList #nodeObjectList.append((ob, obipo, obcurves)) nodeObjectList.append( ob ) file.write( 'JOINT %s\n' % stripName(nodeName) ) #================# # END JOINT NAME # #================# # Indent again, this line is just for the brackets file.write( '%s{\n' % (level * indent) ) # Indent level += 1 #================================================# # Data for writing to a file offset and channels # #================================================# offset, channels = getNodeData(ob) #============# # Offset # #============# file.write( '%sOFFSET %.6f %.6f %.6f\n' %\ (level*indent, scale*offset[0], scale*offset[1], scale*offset[2]) ) #============# # Channels # #============# if len(hierarchy) != 1: # Channels, remember who is where so when we write motiondata file.write('%sCHANNELS %i ' % (level*indent, len([c for c in channels if c ==1]) )) if channels[0]: file.write('Xposition ') channelList.append([len(nodeObjectList)-1, 0]) if channels[1]: file.write('Yposition ') channelList.append([len(nodeObjectList)-1, 1]) if channels[2]: file.write('Zposition ') channelList.append([len(nodeObjectList)-1, 2]) if channels[5]: file.write('Zrotation ') channelList.append([len(nodeObjectList)-1, 5]) if channels[3]: file.write('Xrotation ') channelList.append([len(nodeObjectList)-1, 3]) if channels[4]: file.write('Yrotation ') channelList.append([len(nodeObjectList)-1, 4]) file.write('\n') # Loop through children if any and run this function (recursively) for hierarchyIdx in range(len(hierarchy)-1): level = hierarchy2bvh(file, hierarchy[hierarchyIdx+1], level, channelList, nodeObjectList) # Unindent level -= 1 file.write('%s}\n' % (level * indent)) return level # added by Ben Batt 30/3/2004 to make the exported rotations correct def ZYXToZXY(x, y, z): ''' Converts a set of Euler rotations (x, y, z) (which are intended to be applied in z, y, x order, into a set which are intended to be applied in z, x, y order (the order expected by .bvh files) ''' A,B = cos(x),sin(x) C,D = cos(y),sin(y) E,F = cos(z),sin(z) x = asin(-B*C) y = atan2(D, A*C) z = atan2(-B*D*E + A*F, B*D*F + A*E) # this seems to be necessary - not sure why (right/left-handed coordinates?) # x = -x # x is negative, see below. return -x*RAD_TO_DEG, y*RAD_TO_DEG, z*RAD_TO_DEG ''' # UNUSED, JUST GET OBJECT LOC/ROT def getIpoLocation(object, obipo, curves, frame): x = y = z = rx = ry = rz =0 if obipo: for i in range(obipo.getNcurves()): if curves[i].getName() =='LocX': x = obipo.EvaluateCurveOn(i,frame) elif curves[i].getName() =='LocY': y = obipo.EvaluateCurveOn(i,frame) elif curves[i].getName() =='LocZ': z = obipo.EvaluateCurveOn(i,frame) elif curves[i].getName() =='RotX': rx = obipo.EvaluateCurveOn(i,frame) elif curves[i].getName() =='RotY': ry = obipo.EvaluateCurveOn(i,frame) elif curves[i].getName() =='RotZ': rz = obipo.EvaluateCurveOn(i,frame) return x, y, z, rx*10*DEG_TO_RAD, ry*10*DEG_TO_RAD, rz*10*DEG_TO_RAD ''' #====================================================# # Return the BVH motion for the spesified frame # # hierarchy: is a list of the empty hierarcht # # level: how many levels we are down the tree, # # ...used for indenting # #====================================================# def motion2bvh(file, frame, chennelList, nodeObjectList): for chIdx in chennelList: #ob, obipo, obcurves = nodeObjectList[chIdx[0]] ob = nodeObjectList[chIdx[0]] chType = chIdx[1] # Get object rotation x, y, z = ob.getEuler() # Convert the rotation from ZYX order to ZXY order x, y, z = ZYXToZXY(x, y, z) # Location xloc, yloc, zloc = ob.matrixLocal[3][:3] # Using regular Locations stuffs upIPO locations stuffs up # Get IPO locations instead #xloc, yloc, zloc, x, y, z = getIpoLocation(ob, obipo, obcurves, frame) # Convert the rotation from ZYX order to ZXY order #x, y, z = ZYXToZXY(x, y, z) # WARNING non standard Location # xloc, zloc, yloc = -xloc, yloc, zloc if chType == 0: file.write('%.6f ' % (scale * xloc)) if chType == 1: file.write('%.6f ' % (scale * yloc)) if chType == 2: file.write('%.6f ' % (scale * zloc)) if chType == 3: file.write('%.6f ' % x) if chType == 4: file.write('%.6f ' % y) if chType == 5: file.write('%.6f ' % z) file.write('\n') def saveBVH(filename): t = time() if not filename.lower().endswith('.bvh'): filename += '.bvh' # for safety # Here we store a serialized list of blender objects as they appier # in the hierarchy, this is refred to when writing motiondata nodeObjectList = [] # In this list we store a 2 values for each node # 1) An index pointing to a blender object # in objectList # 2) The type if channel x/y/z rot:x/y/z - Use 0-5 to indicate this chennelList = [] print '\nBVH 1.1 by Campbell Barton (Ideasman) - cbarton@metavr.com' # Get the active object and recursively traverse its kids to build # the BVH hierarchy, then eval the string to make a hierarchy list. hierarchy = eval(getHierarchy(scn.getActiveObject(),''))[0] # somhow this returns a tuple with one list in it. # Put all data in the file we have selected file. file = open(filename, "w") file.write('HIERARCHY\n') # all bvh files have this on the first line # Write the whole hirarchy to a list level = 0 # Indenting level, start with no indent level = hierarchy2bvh(file, hierarchy, level, chennelList, nodeObjectList) #====================================================# # MOTION: Loop through the frames ande write out # # the motion data for each # #====================================================# # Do some basic motion file header stuff file.write( 'MOTION\n' ) file.write( 'Frames: %i\n' % ( 1 + context.endFrame() - context.startFrame() ) ) file.write( 'Frame Time: %.6f\n' % frameRate ) frames = range(context.startFrame()+1, context.endFrame()+1) print 'exporting %i of motion...' % len(frames) for frame in frames: context.currentFrame(frame) scn.update(1) # Update locations so we can write the new locations. This is the SLOW part. # Blender.Window.RedrawAll() # Debugging. motion2bvh(file, frame, chennelList, nodeObjectList) # Write the motion to a file. file.write('\n') # newline file.close() print '...Done in %.4f seconds.' % (time()-t) Blender.Window.FileSelector(saveBVH, 'Export BVH')