4
Name: 'Motion Capture (.bvh)...'
7
Tip: 'Export a (.bvh) motion capture file'
10
__author__ = "Campbell Barton"
11
__url__ = ("blender", "elysiun")
12
__version__ = "1.0 03/30/04"
15
This script exports animation data to BVH motion capture file format.
27
# $Id: bvh_export.py,v 1.5 2004/11/07 16:31:13 ianwill Exp $
29
#===============================================#
30
# BVH Export script 1.0 by Campbell Barton #
31
# Copyright MetaVR 30/03/2004, #
32
# if you have any questions about this script #
33
# email me ideasman@linuxmail.org #
35
#===============================================#
37
# --------------------------------------------------------------------------
38
# BVH Export v0.9 by Campbell Barton (AKA Ideasman)
39
# --------------------------------------------------------------------------
40
# ***** BEGIN GPL LICENSE BLOCK *****
42
# This program is free software; you can redistribute it and/or
43
# modify it under the terms of the GNU General Public License
44
# as published by the Free Software Foundation; either version 2
45
# of the License, or (at your option) any later version.
47
# This program is distributed in the hope that it will be useful,
48
# but WITHOUT ANY WARRANTY; without even the implied warranty of
49
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
50
# GNU General Public License for more details.
52
# You should have received a copy of the GNU General Public License
53
# along with this program; if not, write to the Free Software Foundation,
54
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
56
# ***** END GPL LICENCE BLOCK *****
57
# --------------------------------------------------------------------------
60
from Blender import Scene, Object
64
# Get the current scene.
65
scn = Scene.GetCurrent()
66
context = scn.getRenderingContext()
68
frameRate = 0.3333 # 0.04 = 25fps
71
indent = ' ' # 2 space indent per object
74
# Vars used in eular rotation funtcion
75
RAD_TO_DEG = 180.0/3.14159265359
79
#====================================================#
80
# Search for children of this object and return them #
81
#====================================================#
82
def getChildren(parent):
83
children = [] # We'll assume none.
84
for child in Object.Get():
85
if child.getParent() == Object.Get(parent):
86
children.append( child.getName() )
89
#====================================================#
90
# MESSY BUT WORKS: Make a string that shows the #
91
# hierarchy as a list and then eval it #
92
#====================================================#
93
def getHierarchy(root, hierarchy):
94
hierarchy = hierarchy + '["' + root + '",'
95
for child in getChildren(root):
96
hierarchy = getHierarchy(child, hierarchy)
101
#====================================================#
102
# Strips the prefix off the name before writing #
103
#====================================================#
104
def stripName(name): # name is a string
106
# WARNING!!! Special case for a custom RIG for output
107
# for MetaVR's HPX compatable RIG.
108
print 'stripname', name[0:10]
109
if name[0:10] == 'Transform(':
111
while name[-1] != ')':
117
return name[1+name.find(prefixDelimiter): ]
120
#====================================================#
121
# Return a 6 deciaml point floating point value #
122
# as a string that dosent have any python chars #
123
#====================================================#
124
def saneFloat(float):
125
#return '%(float)b' % vars() # 6 fp as house.hqx
126
return str('%f' % float) + ' '
130
#====================================================#
131
# Recieves an object name, gets all the data for that#
132
# node from blender and returns it for formatting #
133
# and writing to a file. #
134
#====================================================#
135
def getNodeData(nodeName):
138
offset = Object.Get(nodeName).getLocation()
139
offset = (offset[0]*scale, offset[1]*scale, offset[2]*scale,)
141
#=========================#
142
# Test for X/Y/Z IPO's #
143
#=========================#
144
obipo = Object.Get(nodeName).getIpo()
146
# IF we dont have an IPO then dont check the curves.
147
# This was added to catch end nodes that never have an IPO, only an offset.
149
xloc=yloc=zloc=xrot=yrot=zrot = 0
151
else: # Do have an IPO, checkout which curves are in use.
152
# Assume the rot's/loc's exist until proven they dont
153
xloc=yloc=zloc=xrot=yrot=zrot = 1
154
if obipo.getCurve('LocX') == None:
156
if obipo.getCurve('LocY') == None:
158
if obipo.getCurve('LocZ') == None:
161
# Now for the rotations, Because of the conversion of rotation coords
162
# if there is one rotation er need to store all 3
163
if obipo.getCurve('RotX') == None and \
164
obipo.getCurve('RotY') == None and \
165
obipo.getCurve('RotZ') == None:
168
# DUMMY channels xloc, yloc, zloc, xrot, yrot, zrot
169
# [<bool>, <bool>, <bool>, <bool>, <bool>, <bool>]
170
channels = [xloc, yloc, zloc, xrot, yrot, zrot]
172
return offset, channels
175
#====================================================#
176
# Return the BVH hierarchy to a file from a list #
177
# hierarchy: is a list of the empty hierarcht #
178
# bvhHierarchy: a string, in the bvh format to write #
179
# level: how many levels we are down the tree, #
180
# ...used for indenting #
181
# Also gathers channelList , so we know the order to #
182
# write the motiondata in #
183
#====================================================#
184
def hierarchy2bvh(hierarchy, bvhHierarchy, level, channelList, nodeObjectList):
185
nodeName = hierarchy[0]
187
# Add object to nodeObjectList
188
nodeObjectList.append(Object.Get(nodeName))
193
bvhHierarchy += level * indent
195
# Add object to nodeObjectList
196
nodeObjectList.append(Object.Get(nodeName))
197
bvhHierarchy+= 'ROOT '
198
bvhHierarchy += stripName(nodeName) + '\n'
199
# If this is the last object in the list then we
200
# dont bother withwriting its real name, use "End Site" instead
201
elif len(hierarchy) == 1:
202
bvhHierarchy+= 'End Site\n'
203
# Ok This is a normal joint
205
# Add object to nodeObjectList
206
nodeObjectList.append(Object.Get(nodeName))
207
bvhHierarchy+= 'JOINT '
208
bvhHierarchy += stripName(nodeName) + '\n'
213
# Indent again, this line is just for the brackets
214
bvhHierarchy += level * indent + '{' + '\n'
219
#================================================#
220
# Data for writing to a file offset and channels #
221
#================================================#
222
offset, channels = getNodeData(nodeName)
227
bvhHierarchy += level * indent + 'OFFSET ' + saneFloat(scale * offset[0]) + ' ' + saneFloat(scale * offset[1]) + ' ' + saneFloat(scale * offset[2]) + '\n'
232
if len(hierarchy) != 1:
233
# Channels, remember who is where so when we write motiondata
234
bvhHierarchy += level * indent + 'CHANNELS '
239
bvhHierarchy += str(chCount) + ' '
241
bvhHierarchy += 'Xposition '
242
channelList.append([len(nodeObjectList)-1, 0])
244
bvhHierarchy += 'Yposition '
245
channelList.append([len(nodeObjectList)-1, 1])
247
bvhHierarchy += 'Zposition '
248
channelList.append([len(nodeObjectList)-1, 2])
250
bvhHierarchy += 'Zrotation '
251
channelList.append([len(nodeObjectList)-1, 5])
253
bvhHierarchy += 'Xrotation '
254
channelList.append([len(nodeObjectList)-1, 3])
256
bvhHierarchy += 'Yrotation '
257
channelList.append([len(nodeObjectList)-1, 4])
261
# Loop through children if any and run this function (recursively)
262
for hierarchyIdx in range(len(hierarchy)-1):
263
bvhHierarchy, level, channelList, nodeObjectList = hierarchy2bvh(hierarchy[hierarchyIdx+1], bvhHierarchy, level, channelList, nodeObjectList)
266
bvhHierarchy += level * indent + '}' + '\n'
268
return bvhHierarchy, level, channelList, nodeObjectList
270
# added by Ben Batt 30/3/2004 to make the exported rotations correct
271
def ZYXToZXY(x, y, z):
273
Converts a set of Euler rotations (x, y, z) (which are intended to be
274
applied in z, y, x order) into a set which are intended to be applied in
275
z, x, y order (the order expected by .bvh files)
283
z = atan2(-B*D*E + A*F, B*D*F + A*E)
285
# this seems to be necessary - not sure why (right/left-handed coordinates?)
287
return x*RAD_TO_DEG, y*RAD_TO_DEG, z*RAD_TO_DEG
291
def getIpoLocation(object, frame):
293
obipo = object.getIpo()
294
for i in range(object.getIpo().getNcurves()):
295
if obipo.getCurves()[i].getName() =='LocX':
296
x = object.getIpo().EvaluateCurveOn(i,frame)
297
elif obipo.getCurves()[i].getName() =='LocY':
298
y = object.getIpo().EvaluateCurveOn(i,frame)
299
elif obipo.getCurves()[i].getName() =='LocZ':
300
z = object.getIpo().EvaluateCurveOn(i,frame)
304
#====================================================#
305
# Return the BVH motion for the spesified frame #
306
# hierarchy: is a list of the empty hierarcht #
307
# bvhHierarchy: a string, in the bvh format to write #
308
# level: how many levels we are down the tree, #
309
# ...used for indenting #
310
#====================================================#
311
def motion2bvh(frame, chennelList, nodeObjectList):
313
motionData = '' # We'll append the frames to the string.
315
for chIdx in chennelList:
316
ob = nodeObjectList[chIdx[0]]
319
# Get object rotation
320
x, y, z = ob.getEuler()
322
# Convert the rotation from ZYX order to ZXY order
323
x, y, z = ZYXToZXY(x, y, z)
326
# Using regular Locations stuffs upIPO locations stuffs up
327
# Get IPO locations instead
328
xloc, yloc, zloc = getIpoLocation(ob, frame)
330
# WARNING non standard Location
331
xloc, zloc, yloc = -xloc, yloc, zloc
335
motionData += saneFloat(scale * (xloc))
337
motionData += saneFloat(scale * (yloc))
339
motionData += saneFloat(scale * (zloc))
341
motionData += saneFloat(x)
343
motionData += saneFloat(y)
345
motionData += saneFloat(z)
352
def saveBVH(filename):
354
if filename.find('.bvh', -4) <= 0: filename += '.bvh' # for safety
356
# Here we store a serialized list of blender objects as they appier
357
# in the hierarchy, this is refred to when writing motiondata
360
# In this list we store a 2 values for each node
361
# 1) An index pointing to a blender object
363
# 2) The type if channel x/y/z rot:x/y/z - Use 0-5 to indicate this
367
print 'BVH 1.0 by Campbell Barton (Ideasman) - ideasman@linuxmail.org'
369
# Get the active object and recursively traverse its kids to build
370
# the BVH hierarchy, then eval the string to make a hierarchy list.
371
hierarchy = eval(getHierarchy(Object.GetSelected()[0].getName(),''))[0] # somhow this returns a tuple with one list in it.
373
# Put all data in the file we have selected file.
374
file = open(filename, "w")
375
file.write('HIERARCHY\n') # all bvh files have this on the first line
377
# Write the whole hirarchy to a list
378
bvhHierarchy, level, chennelList, nodeObjectList = hierarchy2bvh(hierarchy, '', 0, chennelList, nodeObjectList)
379
file.write( bvhHierarchy ) # Rwite the var fileBlock to the output.
380
bvhHierarchy = None # Save a tit bit of memory
382
#====================================================#
383
# MOTION: Loop through the frames ande write out #
384
# the motion data for each #
385
#====================================================#
386
# Do some basic motion file header stuff
387
file.write('MOTION\n')
388
file.write( 'Frames: ' + str(1 + context.endFrame() - context.startFrame()) + '\n' )
389
file.write( 'Frame Time: ' + saneFloat(frameRate) + '\n' )
391
#print 'WARNING- exact frames might be stuffed up- inclusive whatever, do some tests later on.'
392
frames = range(context.startFrame(), context.endFrame()+1)
393
print 'exporting ' + str(len(frames)) + ' of motion...'
396
context.currentFrame(frame)
397
scn.update(1) # Update locations so we can write the new locations
398
#Blender.Window.RedrawAll() # causes crash
400
file.write( motion2bvh(frame, chennelList, nodeObjectList) )
402
file.write('\n') # newline
406
Blender.Window.FileSelector(saveBVH, 'Export BVH')