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