Final merge of HEAD (bf-blender) into the orange branch.
[blender.git] / release / scripts / bvh_import.py
1 #!BPY
2
3 """
4 Name: 'Motion Capture (.bvh)...'
5 Blender: 236
6 Group: 'Import'
7 Tip: 'Import a (.bvh) motion capture file'
8 """
9
10 __author__ = "Campbell Barton"
11 __url__ = ("blender", "elysiun", "http://jmsoler.free.fr/util/blenderfile/py/bvh_import.py")
12 __version__ = "1.0.2 04/12/28"
13
14 __bpydoc__ = """\
15 This script imports BVH motion capture data to Blender.
16
17 Supported: Poser 3.01<br>
18
19 Missing:<br>
20
21 Known issues:<br>
22
23 Notes:<br>
24    Jean-Michel Soler improved importer to support Poser 3.01 files;<br>
25    Jean-Baptiste Perin wrote a script to create an armature out of the
26 Empties created by this importer, it's in the Scripts window -> Scripts -> Animation menu.
27 """
28
29 # $Id$
30 #
31
32 #===============================================#
33 # BVH Import script 1.03 patched by Campbell    #
34 # Small optimizations and scale input           #
35 # 01/01/2005,                                   #  
36 #===============================================#
37
38 #===============================================#
39 # BVH Import script 1.02 patched by Jm Soler    #
40 # to the Poser 3.01 bvh file                    # 
41 # 28/12/2004,                                   #  
42 #===============================================#
43
44 #===============================================#
45 # BVH Import script 1.0 by Campbell Barton      #
46 # 25/03/2004, euler rotation code taken from    #
47 # Reevan Mckay's BVH import script v1.1         #
48 # if you have any questions about this script   #
49 # email me ideasman@linuxmail.org               #
50 #===============================================#
51
52 #===============================================#
53 # TODO:                                         #
54 # * Create bones when importing                 #
55 # * Make an IPO jitter removal script           #
56 # * Work out a better naming system             #
57 #===============================================#
58
59 # -------------------------------------------------------------------------- 
60 # BVH Import v0.9 by Campbell Barton (AKA Ideasman) 
61 # -------------------------------------------------------------------------- 
62 # ***** BEGIN GPL LICENSE BLOCK ***** 
63
64 # This program is free software; you can redistribute it and/or 
65 # modify it under the terms of the GNU General Public License 
66 # as published by the Free Software Foundation; either version 2 
67 # of the License, or (at your option) any later version. 
68
69 # This program is distributed in the hope that it will be useful, 
70 # but WITHOUT ANY WARRANTY; without even the implied warranty of 
71 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
72 # GNU General Public License for more details. 
73
74 # You should have received a copy of the GNU General Public License 
75 # along with this program; if not, write to the Free Software Foundation, 
76 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
77
78 # ***** END GPL LICENCE BLOCK ***** 
79 # -------------------------------------------------------------------------- 
80
81
82 import string
83 import math
84 import Blender
85 from Blender import Window, Object, Scene, Ipo, Draw
86 from Blender.Scene import Render
87
88
89 # # PSYCO IS CRASHING ON MY SYSTEM
90 # # Attempt to load psyco, speed things up
91 # try:
92 #   print 'using psyco to speed up BVH importing'
93 #   import psyco
94 #   psyco.full()
95 #  
96 # except:
97 #   print 'psyco is not present on this system'
98
99 # Default scale
100 scale = 0.01
101
102 # Update as we load?
103 debug = 0
104
105 # Get the current scene.
106 scn = Scene.GetCurrent()
107 context = scn.getRenderingContext()
108
109 # Here we store the Ipo curves in the order they load.
110 channelCurves = []
111
112 # Object list
113 # We need this so we can loop through the objects and edit there IPO's 
114 # Chenging there rotation to EULER rotation
115 objectList = []
116
117 def getScale():
118         return Draw.PupFloatInput('BVH Scale: ', 0.01, 0.001, 10.0, 0.1, 3)
119
120 def MAT(m):
121         if len(m) == 3:
122                 return Blender.Mathutils.Matrix(m[0], m[1], m[2])
123         elif len(m) == 4:
124                 return Blender.Mathutils.Matrix(m[0], m[1], m[2], m[3])
125
126
127
128 #===============================================#
129 # eulerRotation: converts X, Y, Z rotation      #
130 # to eular Rotation. This entire function       #
131 # is copied from Reevan Mckay's BVH script      #
132 #===============================================#
133 # Vars used in eular rotation funtcion
134 DEG_TO_RAD = math.pi/180.0
135 RAD_TO_DEG = 180.0/math.pi
136 PI=3.14159
137
138 def eulerRotate(x,y,z): 
139   #=================================
140   def RVMatMult3 (mat1,mat2):
141   #=================================
142     mat3=[[0.0,0.0,0.0],[0.0,0.0,0.0],[0.0,0.0,0.0]]
143     for i in range(3):
144       for k in range(3):
145         for j in range(3):
146           mat3[i][k]=mat3[i][k]+mat1[i][j]*mat2[j][k]
147     return mat3
148   
149   
150   #=================================
151   def   RVAxisAngleToMat3 (rot4):
152   #     Takes a direction vector and
153   #     a rotation (in rads) and
154   #     returns the rotation matrix.
155   #     Graphics Gems I p. 466:
156   #=================================
157     mat3=[[0.0,0.0,0.0],[0.0,0.0,0.0],[0.0,0.0,0.0]]
158     if math.fabs(rot4[3])>0.01:
159       s=math.sin(rot4[3])
160       c=math.cos(rot4[3])
161       t=1.0-math.cos(rot4[3])
162     else:
163       s=rot4[3]
164       c=1.0
165       t=0.0
166
167     x=rot4[0]; y=rot4[1]; z=rot4[2]
168     
169     mat3[0][0]=t*x*x+c
170     mat3[0][1]=t*x*y+s*z
171     mat3[0][2]=t*x*z-s*y 
172     
173     mat3[1][0]=t*x*y-s*z
174     mat3[1][1]=t*y*y+c
175     mat3[1][2]=t*y*z+s*x
176     
177     mat3[2][0]=t*x*z+s*y
178     mat3[2][1]=t*y*z-s*x
179     mat3[2][2]=t*z*z+c
180     
181     return mat3
182  
183   eul = [x,y,z]
184   
185   for jj in range(3):
186     while eul[jj] < 0:
187       eul[jj] = eul[jj] + 360.0
188     while eul[jj] >= 360.0:
189       eul[jj] = eul[jj] - 360.0
190
191   eul[0] = eul[0]*DEG_TO_RAD
192   eul[1] = eul[1]*DEG_TO_RAD
193   eul[2] = eul[2]*DEG_TO_RAD
194   
195   xmat=RVAxisAngleToMat3([1,0,0,eul[0]])
196   ymat=RVAxisAngleToMat3([0,1,0,eul[1]])
197   zmat=RVAxisAngleToMat3([0,0,1,eul[2]])
198   
199   mat=[[1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0]]  
200   
201   # Standard BVH multiplication order
202   mat=RVMatMult3 (zmat,mat)
203   mat=RVMatMult3 (xmat,mat)
204   mat=RVMatMult3 (ymat,mat)
205   
206   
207   '''
208   # Screwy Animation Master BVH multiplcation order
209   mat=RVMatMult3 (ymat,mat)
210   mat=RVMatMult3 (xmat,mat)
211   mat=RVMatMult3 (zmat,mat)
212   '''
213   mat = MAT(mat)
214   
215   eul = mat.toEuler()
216   x =- eul[0]/-10
217   y =- eul[1]/-10
218   z =- eul[2]/-10
219   
220   return x, y, z # Returm euler roration values.
221
222
223
224 #===============================================#
225 # makeJoint: Here we use the node data          #
226 # from the BVA file to create an empty          #
227 #===============================================#
228 def makeJoint(name, parent, prefix, offset, channels):
229   global scale
230   # Make Empty, with the prefix in front of the name
231   ob = Object.New('Empty', prefix + name) # New object, ob is shorter and nicer to use.
232   scn.link(ob) # place the object in the current scene
233   
234   # Offset Empty
235   ob.setLocation(offset[0]*scale, offset[1]*scale, offset[2]*scale)
236
237   # Make me a child of another empty.
238   # Vale of None will make the empty a root node (no parent)
239   if parent[-1] != None:
240     obParent = Object.Get(prefix + parent[-1]) # We use this a bit so refrence it here.
241     obParent.makeParent([ob], 0, 1) #ojbs, noninverse, 1 = not fast.
242
243   # Add Ipo's for necessary channels
244   newIpo = Ipo.New('Object', prefix + name)
245   ob.setIpo(newIpo)
246   for channelType in channels:
247     if channelType == 'Xposition':
248       newIpo.addCurve('LocX')
249       newIpo.getCurve('LocX').setInterpolation('Linear')
250     if channelType == 'Yposition':
251       newIpo.addCurve('LocY')
252       newIpo.getCurve('LocY').setInterpolation('Linear')
253     if channelType == 'Zposition':
254       newIpo.addCurve('LocZ')
255       newIpo.getCurve('LocZ').setInterpolation('Linear')
256
257     if channelType == 'Zrotation':
258       newIpo.addCurve('RotZ')
259       newIpo.getCurve('RotZ').setInterpolation('Linear')
260     if channelType == 'Yrotation':
261       newIpo.addCurve('RotY')
262       newIpo.getCurve('RotY').setInterpolation('Linear')
263     if channelType == 'Xrotation':
264       newIpo.addCurve('RotX')
265       newIpo.getCurve('RotX').setInterpolation('Linear')
266
267   # Add to object list
268   objectList.append(ob)
269   
270   # Redraw if debugging
271   if debug: Blender.Redraw()
272   
273
274 #===============================================#
275 # makeEnd: Here we make an end node             #
276 # This is needed when adding the last bone      #
277 #===============================================#
278 def makeEnd(parent, prefix, offset):
279   # Make Empty, with the prefix in front of the name, end nodes have no name so call it its parents name+'_end'
280   ob = Object.New('Empty', prefix + parent[-1] + '_end') # New object, ob is shorter and nicer to use.
281   scn.link(ob)
282   
283   # Dont check for a parent, an end node MUST have a parent
284   obParent = Object.Get(prefix + parent[-1]) # We use this a bit so refrence it here.
285   obParent.makeParent([ob], 0, 1) #ojbs, noninverse, 1 = not fast.
286
287   # Offset Empty
288   ob.setLocation(offset[0]*scale, offset[1]*scale, offset[2]*scale) 
289   
290   # Redraw if debugging
291   if debug: Blender.Redraw()  
292   
293
294
295
296 #===============================================#
297 # MAIN FUNCTION - All things are done from here #
298 #===============================================#
299 def loadBVH(filename):
300   global scale
301   print ''
302   print 'BVH Importer 1.0 by Campbell Barton (Ideasman) - ideasman@linuxmail.org'
303   alpha='abcdefghijklmnopqrstuvewxyz'
304   ALPHA=alpha+alpha.upper()
305   ALPHA+=' 0123456789+-{}. '  
306   time1 = Blender.sys.time()
307   tmpScale = getScale()
308   if tmpScale != None:
309     scale = tmpScale
310   
311   # File loading stuff
312   # Open the file for importing
313   file = open(filename, 'r')  
314   fileData = file.readlines()
315   # Make a list of lines
316   lines = []
317   for fileLine in fileData:
318     fileLine=fileLine.replace('..','.')
319     newLine = string.split(fileLine)
320     if newLine != []:
321       t=[]
322       for n in newLine:
323          for n0 in n:
324            if n0 not in ALPHA:
325               n=n.replace(n0,'')  
326          t.append(n)
327       lines.append(t)
328
329     
330   del fileData
331   # End file loading code
332
333   # Call object names with this prefix, mainly for scenes with multiple BVH's - Can imagine most partr names are the same
334   # So in future
335   #prefix = str(len(lines)) + '_'
336   
337   prefix = '_'
338   
339   # Create Hirachy as empties
340   if lines[0][0] == 'HIERARCHY':
341     print 'Importing the BVH Hierarchy for:', filename
342   else:
343     return 'ERROR: This is not a BVH file'
344   
345   # A liniar list of ancestors to keep track of a single objects heratage
346   # at any one time, this is appended and removed, dosent store tree- just a liniar list.
347   # ZERO is a place holder that means we are a root node. (no parents)
348   parent = [None]  
349   
350   #channelList [(<objectName>, [channelType1, channelType2...]),  (<objectName>, [channelType1, channelType2...)]
351   channelList = []
352   channelIndex = -1
353
354   
355
356   lineIdx = 1 # An index for the file.
357   while lineIdx < len(lines) -1:
358     #...
359     if lines[lineIdx][0] == 'ROOT' or lines[lineIdx][0] == 'JOINT':
360       if lines[lineIdx][0] == 'JOINT' and len(lines[lineIdx])>2:
361          for j in range(2,len(lines[lineIdx])) :
362              lines[lineIdx][1]+='_'+lines[lineIdx][j]
363
364       # MAY NEED TO SUPPORT MULTIPLE ROOT's HERE!!!, Still unsure weather multiple roots are possible.??
365
366       print len(parent) * '  ' + 'node:',lines[lineIdx][1],' parent:',parent[-1]
367       print lineIdx
368       name = lines[lineIdx][1]
369       print name,lines[lineIdx+1],lines[lineIdx+2]
370       lineIdx += 2 # Incriment to the next line (Offset)
371       offset = ( float(lines[lineIdx][1]), float(lines[lineIdx][2]), float(lines[lineIdx][3]) )
372       lineIdx += 1 # Incriment to the next line (Channels)
373       
374       # newChannel[Xposition, Yposition, Zposition, Xrotation, Yrotation, Zrotation]
375       # newChannel has Indecies to the motiondata,
376       # -1 refers to the last value that will be added on loading at a value of zero
377       # We'll add a zero value onto the end of the MotionDATA so this is always refers to a value.
378       newChannel = [-1, -1, -1, -1, -1, -1] 
379       for channel in lines[lineIdx][2:]:
380         channelIndex += 1 # So the index points to the right channel
381         if channel == 'Xposition':
382           newChannel[0] = channelIndex
383         elif channel == 'Yposition':
384           newChannel[1] = channelIndex
385         elif channel == 'Zposition':
386           newChannel[2] = channelIndex
387         elif channel == 'Xrotation':
388           newChannel[3] = channelIndex
389         elif channel == 'Yrotation':
390           newChannel[4] = channelIndex
391         elif channel == 'Zrotation':
392           newChannel[5] = channelIndex
393       
394       channelList.append(newChannel)
395       
396       channels = lines[lineIdx][2:]
397       
398       # Call funtion that uses the gatrhered data to make an empty.
399       makeJoint(name, parent, prefix, offset, channels)
400       
401       # If we have another child then we can call ourselves a parent, else 
402       parent.append(name)
403
404     # Account for an end node
405     if lines[lineIdx][0] == 'End' and lines[lineIdx][1] == 'Site': # There is somtimes a name afetr 'End Site' but we will ignore it.
406       lineIdx += 2 # Incriment to the next line (Offset)
407       offset = ( float(lines[lineIdx][1]), float(lines[lineIdx][2]), float(lines[lineIdx][3]) )
408       makeEnd(parent, prefix, offset)
409
410       # Just so we can remove the Parents in a uniform way- End end never has kids
411       # so this is a placeholder
412       parent.append(None)
413
414     if lines[lineIdx] == ['}']:
415       parent = parent[:-1] # Remove the last item
416
417
418     #=============================================#
419     # BVH Structure loaded, Now import motion     #
420     #=============================================#    
421     if lines[lineIdx] == ['MOTION']:
422       print '\nImporting motion data'
423       lineIdx += 3 # Set the cursor to the forst frame
424       
425       #=============================================#
426       # Loop through frames, each line a frame      #
427       #=============================================#      
428       currentFrame = 1
429       print 'frames: ',
430       
431       
432       #=============================================#
433       # Add a ZERO keyframe, this keeps the rig     #
434       # so when we export we know where all the     #
435       # joints start from                           #
436       #=============================================#  
437       obIdx = 0
438       while obIdx < len(objectList) -1:
439         if channelList[obIdx][0] != -1:
440           objectList[obIdx].getIpo().getCurve('LocX').addBezier((currentFrame,0))
441         if channelList[obIdx][1] != -1:
442           objectList[obIdx].getIpo().getCurve('LocY').addBezier((currentFrame,0))
443         if channelList[obIdx][2] != -1:
444           objectList[obIdx].getIpo().getCurve('LocZ').addBezier((currentFrame,0))
445         if channelList[obIdx][3] != '-1' or channelList[obIdx][4] != '-1' or channelList[obIdx][5] != '-1':
446           objectList[obIdx].getIpo().getCurve('RotX').addBezier((currentFrame,0))
447           objectList[obIdx].getIpo().getCurve('RotY').addBezier((currentFrame,0))
448           objectList[obIdx].getIpo().getCurve('RotZ').addBezier((currentFrame,0))
449         obIdx += 1
450       
451       while lineIdx < len(lines):
452         
453         # Exit loop if we are past the motiondata.
454         # Some BVH's have extra tags like 'CONSTRAINTS and MOTIONTAGS'
455         # I dont know what they do and I dont care, they'll be ignored here.
456         if len(lines[lineIdx]) < len(objectList):
457           print '...ending on unknown tags'
458           break
459         
460         
461         currentFrame += 1 # Incriment to next frame
462                 
463         #=============================================#
464         # Import motion data and assign it to an IPO  #
465         #=============================================#
466         lines[lineIdx].append('0') # Use this as a dummy var for objects that dont have a rotate channel.
467         obIdx = 0
468         if debug: Blender.Redraw() 
469         while obIdx < len(objectList) -1:
470           if channelList[obIdx][0] != -1:
471             VAL0=lines[lineIdx][channelList[obIdx][0]]  
472             if VAL0.find('.')==-1:
473                VAL0=VAL0[:len(VAL0)-6]+'.'+VAL0[-6:] 
474             objectList[obIdx].getIpo().getCurve('LocX').addBezier((currentFrame, scale * float(VAL0)))
475
476           if channelList[obIdx][1] != -1:
477             VAL1=lines[lineIdx][channelList[obIdx][1]]  
478             if VAL1.find('.')==-1:
479                VAL1=VAL1[:len(VAL1)-6]+'.'+VAL1[-6:] 
480             objectList[obIdx].getIpo().getCurve('LocY').addBezier((currentFrame, scale * float(VAL1)))
481
482           if channelList[obIdx][2] != -1:
483             VAL2=lines[lineIdx][channelList[obIdx][2]]  
484             if VAL2.find('.')==-1:
485                VAL2=VAL2[:len(VAL2)-6]+'.'+VAL2[-6:] 
486             objectList[obIdx].getIpo().getCurve('LocZ').addBezier((currentFrame, scale * float(VAL2)))
487           
488           if channelList[obIdx][3] != '-1' or channelList[obIdx][4] != '-1' or channelList[obIdx][5] != '-1':
489             VAL3=lines[lineIdx][channelList[obIdx][3]]  
490             if VAL3.find('.')==-1:
491                VAL3=VAL3[:len(VAL3)-6]+'.'+VAL3[-6:]
492  
493             VAL4=lines[lineIdx][channelList[obIdx][4]]
494             if VAL4.find('.')==-1:
495                VAL4=VAL4[:len(VAL4)-6]+'.'+VAL4[-6:]
496
497             VAL5=lines[lineIdx][channelList[obIdx][5]] 
498             if VAL5.find('.')==-1:
499                VAL5=VAL5[:len(VAL5)-6]+'.'+VAL5[-6:]
500
501             x, y, z = eulerRotate(float(VAL3), float(VAL4), float(VAL5))
502
503             objectList[obIdx].getIpo().getCurve('RotX').addBezier((currentFrame, x))
504             objectList[obIdx].getIpo().getCurve('RotY').addBezier((currentFrame, y))
505             objectList[obIdx].getIpo().getCurve('RotZ').addBezier((currentFrame, z))
506
507           obIdx += 1
508           # Done importing motion data #
509         
510         # lines[lineIdx] = None # Scrap old motion data, save some memory?
511         lineIdx += 1
512       # We have finished now
513       print currentFrame, 'done.'
514      
515       # No point in looking further, when this loop is done
516       # There is nothine else left to do      
517       print 'Imported ', currentFrame, ' frames'
518       break
519       
520     # Main file loop
521     lineIdx += 1
522   print "bvh import time: ", Blender.sys.time() - time1
523
524 Blender.Window.FileSelector(loadBVH, "Import BVH")