9309b76c47695a704829237028a2479c0e00a2a6
[blender.git] / release / scripts / bvh_export.py
1 #!BPY
2
3 """
4 Name: 'Motion Capture (*.bvh)'
5 Blender: 232
6 Group: 'Export'
7 Tip: 'Export a (*.bvh) motion capture file'
8 """
9
10 # $Id$
11 #
12 #===============================================#
13 # BVH Export script 1.0 by Campbell Barton      #
14 # Copyright MetaVR 30/03/2004,                  #
15 # if you have any questions about this script   #
16 # email me ideasman@linuxmail.org               #
17 #                                               #
18 #===============================================#
19
20 # -------------------------------------------------------------------------- 
21 # BVH Export v0.9 by Campbell Barton (AKA Ideasman) 
22 # -------------------------------------------------------------------------- 
23 # ***** BEGIN GPL LICENSE BLOCK ***** 
24
25 # This program is free software; you can redistribute it and/or 
26 # modify it under the terms of the GNU General Public License 
27 # as published by the Free Software Foundation; either version 2 
28 # of the License, or (at your option) any later version. 
29
30 # This program is distributed in the hope that it will be useful, 
31 # but WITHOUT ANY WARRANTY; without even the implied warranty of 
32 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
33 # GNU General Public License for more details. 
34
35 # You should have received a copy of the GNU General Public License 
36 # along with this program; if not, write to the Free Software Foundation, 
37 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
38
39 # ***** END GPL LICENCE BLOCK ***** 
40 # -------------------------------------------------------------------------- 
41
42 import Blender
43 from Blender import Scene, Object
44 import math
45 from math import *
46
47 # Get the current scene.
48 scn = Scene.GetCurrent()
49 context = scn.getRenderingContext()
50
51 frameRate = 0.3333 # 0.04 = 25fps
52 scale = 1
53
54 indent = '  ' # 2 space indent per object
55 prefixDelimiter = '_'
56
57 # Vars used in eular rotation funtcion
58 RAD_TO_DEG = 180.0/3.14159265359
59
60
61
62 #====================================================#
63 # Search for children of this object and return them #
64 #====================================================#
65 def getChildren(parent):
66   children = [] # We'll assume none.
67   for child in Object.Get():
68     if child.getParent() == Object.Get(parent):
69       children.append( child.getName() )
70   return children
71
72 #====================================================#
73 # MESSY BUT WORKS: Make a string that shows the      #
74 # hierarchy as a list and then eval it               #
75 #====================================================#
76 def getHierarchy(root, hierarchy):
77   hierarchy = hierarchy + '["' + root + '",'
78   for child in getChildren(root):
79     hierarchy = getHierarchy(child, hierarchy)
80   hierarchy += '],' 
81   return hierarchy
82
83
84 #====================================================#
85 # Strips the prefix off the name before writing      #
86 #====================================================#
87 def stripName(name): # name is a string
88   
89   # WARNING!!! Special case for a custom RIG for output
90   # for MetaVR's HPX compatable RIG.
91   print 'stripname', name[0:10]
92   if name[0:10] == 'Transform(':
93     name = name[10:]
94     while name[-1] != ')':
95       name = name[0:-1]
96       print name
97     name = name[:-1]
98   
99   
100   return name[1+name.find(prefixDelimiter): ]
101   
102
103 #====================================================#
104 # Return a 6 deciaml point floating point value      #
105 # as a string that dosent have any python chars      #
106 #====================================================#  
107 def saneFloat(float):
108   #return '%(float)b' % vars()  # 6 fp as house.hqx
109   return str('%f' % float) + ' '
110
111
112
113 #====================================================#
114 # Recieves an object name, gets all the data for that#
115 # node from blender and returns it for formatting    #
116 # and writing to a file.                             #
117 #====================================================#
118 def getNodeData(nodeName):  
119   Object.Get(nodeName)
120   # Get real location  
121   offset = Object.Get(nodeName).getLocation()
122   offset = (offset[0]*scale, offset[1]*scale, offset[2]*scale,)
123   
124   #=========================#
125   # Test for X/Y/Z IPO's    #
126   #=========================#
127   obipo = Object.Get(nodeName).getIpo()
128   
129   # IF we dont have an IPO then dont check the curves.
130   # This was added to catch end nodes that never have an IPO, only an offset.
131   if obipo == None: 
132     xloc=yloc=zloc=xrot=yrot=zrot = 0
133   
134   else: # Do have an IPO, checkout which curves are in use.
135     # Assume the rot's/loc's exist until proven they dont
136     xloc=yloc=zloc=xrot=yrot=zrot = 1
137     if obipo.getCurve('LocX') == None:
138       xloc = 0
139     if obipo.getCurve('LocY') == None:
140       yloc = 0
141     if obipo.getCurve('LocZ') == None:
142       zloc = 0
143       
144     # Now for the rotations, Because of the conversion of rotation coords
145     # if there is one rotation er need to store all 3
146     if obipo.getCurve('RotX') == None and \
147     obipo.getCurve('RotY') == None and \
148     obipo.getCurve('RotZ') == None:
149       xrot=yrot=zrot = 0
150   
151   # DUMMY channels xloc, yloc, zloc, xrot, yrot, zrot
152   # [<bool>, <bool>, <bool>, <bool>, <bool>, <bool>]
153   channels = [xloc, yloc, zloc, xrot, yrot, zrot]
154   
155   return offset, channels
156
157
158 #====================================================#
159 # Return the BVH hierarchy to a file from a list     #
160 # hierarchy: is a list of the empty hierarcht        #
161 # bvhHierarchy: a string, in the bvh format to write #
162 # level: how many levels we are down the tree,       #
163 # ...used for indenting                              #
164 # Also gathers channelList , so we know the order to #
165 # write  the motiondata in                           #
166 #====================================================#
167 def hierarchy2bvh(hierarchy, bvhHierarchy, level, channelList, nodeObjectList):
168   nodeName = hierarchy[0]
169   
170   # Add object to nodeObjectList
171   nodeObjectList.append(Object.Get(nodeName))
172   
173   #============#
174   # JOINT NAME #
175   #============# 
176   bvhHierarchy += level * indent
177   if level == 0:
178     # Add object to nodeObjectList
179     nodeObjectList.append(Object.Get(nodeName))
180     bvhHierarchy+= 'ROOT '
181     bvhHierarchy += stripName(nodeName) + '\n'
182   # If this is the last object in the list then we
183   # dont bother withwriting its real name, use "End Site" instead
184   elif len(hierarchy) == 1:
185     bvhHierarchy+= 'End Site\n'
186   # Ok This is a normal joint
187   else:
188     # Add object to nodeObjectList
189     nodeObjectList.append(Object.Get(nodeName))
190     bvhHierarchy+= 'JOINT '
191     bvhHierarchy += stripName(nodeName) + '\n'
192   #================#
193   # END JOINT NAME #
194   #================# 
195
196   # Indent again, this line is just for the brackets
197   bvhHierarchy += level * indent + '{' + '\n'
198
199   # Indent
200   level += 1   
201   
202   #================================================#
203   # Data for writing to a file offset and channels #
204   #================================================#
205   offset, channels = getNodeData(nodeName)
206   
207   #============#
208   # Offset     #
209   #============# 
210   bvhHierarchy += level * indent + 'OFFSET ' + saneFloat(scale * offset[0]) + ' '  + saneFloat(scale * offset[1]) + ' ' + saneFloat(scale * offset[2]) + '\n'
211   
212   #============#
213   # Channels   #
214   #============# 
215   if len(hierarchy) != 1:
216     # Channels, remember who is where so when we write motiondata
217     bvhHierarchy += level * indent + 'CHANNELS '
218     # Count the channels
219     chCount = 0
220     for chn in channels:
221       chCount += chn
222     bvhHierarchy += str(chCount) + ' '
223     if channels[0]:
224       bvhHierarchy += 'Xposition '
225       channelList.append([len(nodeObjectList)-1, 0])
226     if channels[1]:
227       bvhHierarchy += 'Yposition '
228       channelList.append([len(nodeObjectList)-1, 1])
229     if channels[2]:
230       bvhHierarchy += 'Zposition '
231       channelList.append([len(nodeObjectList)-1, 2])
232     if channels[5]:
233       bvhHierarchy += 'Zrotation '
234       channelList.append([len(nodeObjectList)-1, 5])
235     if channels[3]:
236       bvhHierarchy += 'Xrotation '
237       channelList.append([len(nodeObjectList)-1, 3])
238     if channels[4]:
239       bvhHierarchy += 'Yrotation '
240       channelList.append([len(nodeObjectList)-1, 4])
241     
242     bvhHierarchy += '\n'
243
244   # Loop through children if any and run this function (recursively)
245   for hierarchyIdx in range(len(hierarchy)-1):
246     bvhHierarchy, level, channelList, nodeObjectList = hierarchy2bvh(hierarchy[hierarchyIdx+1], bvhHierarchy, level, channelList, nodeObjectList)
247   # Unindent
248   level -= 1
249   bvhHierarchy += level * indent + '}' + '\n'
250   
251   return bvhHierarchy, level, channelList, nodeObjectList
252
253 # added by Ben Batt 30/3/2004 to make the exported rotations correct
254 def ZYXToZXY(x, y, z):
255   '''
256   Converts a set of Euler rotations (x, y, z) (which are intended to be
257   applied in z, y, x order) into a set which are intended to be applied in
258   z, x, y order (the order expected by .bvh files)
259   '''
260   A,B = cos(x),sin(x)
261   C,D = cos(y),sin(y)
262   E,F = cos(z),sin(z)
263
264   x = asin(-B*C)
265   y = atan2(D, A*C)
266   z = atan2(-B*D*E + A*F, B*D*F + A*E)
267
268   # this seems to be necessary - not sure why (right/left-handed coordinates?)
269   x = -x
270   return x*RAD_TO_DEG, y*RAD_TO_DEG, z*RAD_TO_DEG
271
272
273
274 def getIpoLocation(object, frame):
275   x =  y = z = 0 
276   obipo = object.getIpo()
277   for i in range(object.getIpo().getNcurves()):
278     if obipo.getCurves()[i].getName() =='LocX':
279       x = object.getIpo().EvaluateCurveOn(i,frame)
280     elif obipo.getCurves()[i].getName() =='LocY':
281       y = object.getIpo().EvaluateCurveOn(i,frame)
282     elif obipo.getCurves()[i].getName() =='LocZ':
283       z = object.getIpo().EvaluateCurveOn(i,frame)
284   return x, y, z
285
286
287 #====================================================#
288 # Return the BVH motion for the spesified frame      #
289 # hierarchy: is a list of the empty hierarcht        #
290 # bvhHierarchy: a string, in the bvh format to write #
291 # level: how many levels we are down the tree,       #
292 # ...used for indenting                              #
293 #====================================================#
294 def motion2bvh(frame, chennelList, nodeObjectList):
295   
296   motionData = '' # We'll append the frames to the string.
297   
298   for chIdx in chennelList:
299     ob = nodeObjectList[chIdx[0]]
300     chType = chIdx[1]
301     
302     # Get object rotation
303     x, y, z = ob.getEuler()
304     
305     # Convert the rotation from ZYX order to ZXY order
306     x, y, z = ZYXToZXY(x, y, z)
307      
308     
309     # Using regular Locations stuffs upIPO locations stuffs up
310     # Get IPO locations instead
311     xloc, yloc, zloc = getIpoLocation(ob, frame)
312
313     # WARNING non standard Location
314     xloc, zloc, yloc = -xloc, yloc, zloc
315     
316
317     if chType == 0:
318       motionData += saneFloat(scale * (xloc))
319     if chType == 1:
320       motionData += saneFloat(scale * (yloc))
321     if chType == 2:
322       motionData += saneFloat(scale * (zloc))      
323     if chType == 3:
324       motionData += saneFloat(x)
325     if chType == 4:
326       motionData += saneFloat(y)
327     if chType == 5:
328       motionData += saneFloat(z)
329     
330     motionData += ' '
331      
332   motionData += '\n'
333   return motionData
334
335 def saveBVH(filename):
336
337   if filename.find('.bvh', -4) <= 0: filename += '.bvh' # for safety
338
339   # Here we store a serialized list of blender objects as they appier
340   # in the hierarchy, this is refred to when writing motiondata
341   nodeObjectList = []
342   
343   # In this list we store a 2 values for each node
344   # 1) An index pointing to a blender object
345   # in objectList
346   # 2) The type if channel x/y/z rot:x/y/z - Use 0-5 to indicate this
347   chennelList = []
348   
349   print ''
350   print 'BVH  1.0 by Campbell Barton (Ideasman) - ideasman@linuxmail.org'
351   
352   # Get the active object and recursively traverse its kids to build
353   # the BVH hierarchy, then eval the string to make a hierarchy list.
354   hierarchy = eval(getHierarchy(Object.GetSelected()[0].getName(),''))[0] # somhow this returns a tuple with one list in it.
355   
356   # Put all data in the file we have selected file.
357   file = open(filename, "w")
358   file.write('HIERARCHY\n') # all bvh files have this on the first line
359   
360   # Write the whole hirarchy to a list
361   bvhHierarchy, level, chennelList, nodeObjectList = hierarchy2bvh(hierarchy, '', 0, chennelList, nodeObjectList)
362   file.write( bvhHierarchy ) # Rwite the var fileBlock to the output.
363   bvhHierarchy = None # Save a tit bit of memory
364   
365   #====================================================#
366   # MOTION: Loop through the frames ande write out     #
367   # the motion data for each                           #
368   #====================================================#
369   # Do some basic motion file header stuff
370   file.write('MOTION\n')
371   file.write( 'Frames: ' + str(1 + context.endFrame() - context.startFrame()) + '\n'  )
372   file.write( 'Frame Time: ' + saneFloat(frameRate) + '\n'  ) 
373   
374   #print 'WARNING- exact frames might be stuffed up- inclusive whatever, do some tests later on.'
375   frames = range(context.startFrame(), context.endFrame()+1)
376   print 'exporting ' + str(len(frames)) + ' of motion...'
377   
378   for frame in frames:
379     context.currentFrame(frame)
380     scn.update(1) # Update locations so we can write the new locations
381     #Blender.Window.RedrawAll() # causes crash
382     
383     file.write(  motion2bvh(frame, chennelList, nodeObjectList)  )
384      
385   file.write('\n') # newline
386   file.close()
387   print 'done'
388   
389 Blender.Window.FileSelector(saveBVH, 'SELECT NEW BVH FILE')