4
Name: 'Motion Capture (.bvh)...'
7
Tip: 'Import a (.bvh) motion capture file'
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"
15
This script imports BVH motion capture data to Blender.
17
Supported: Poser 3.01<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.
29
# $Id: bvh_import.py,v 1.6 2005/05/17 07:17:52 ianwill Exp $
32
#===============================================#
33
# BVH Import script 1.03 patched by Campbell #
34
# Small optimizations and scale input #
36
#===============================================#
38
#===============================================#
39
# BVH Import script 1.02 patched by Jm Soler #
40
# to the Poser 3.01 bvh file #
42
#===============================================#
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
#===============================================#
52
#===============================================#
54
# * Create bones when importing #
55
# * Make an IPO jitter removal script #
56
# * Work out a better naming system #
57
#===============================================#
59
# --------------------------------------------------------------------------
60
# BVH Import v0.9 by Campbell Barton (AKA Ideasman)
61
# --------------------------------------------------------------------------
62
# ***** BEGIN GPL LICENSE BLOCK *****
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.
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.
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.
78
# ***** END GPL LICENCE BLOCK *****
79
# --------------------------------------------------------------------------
85
from Blender import Window, Object, Scene, Ipo, Draw
86
from Blender.Scene import Render
89
# # PSYCO IS CRASHING ON MY SYSTEM
90
# # Attempt to load psyco, speed things up
92
# print 'using psyco to speed up BVH importing'
97
# print 'psyco is not present on this system'
105
# Get the current scene.
106
scn = Scene.GetCurrent()
107
context = scn.getRenderingContext()
109
# Here we store the Ipo curves in the order they load.
113
# We need this so we can loop through the objects and edit there IPO's
114
# Chenging there rotation to EULER rotation
118
return Draw.PupFloatInput('BVH Scale: ', 0.01, 0.001, 10.0, 0.1, 3)
122
return Blender.Mathutils.Matrix(m[0], m[1], m[2])
124
return Blender.Mathutils.Matrix(m[0], m[1], m[2], m[3])
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
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]]
146
mat3[i][k]=mat3[i][k]+mat1[i][j]*mat2[j][k]
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:
161
t=1.0-math.cos(rot4[3])
167
x=rot4[0]; y=rot4[1]; z=rot4[2]
187
eul[jj] = eul[jj] + 360.0
188
while eul[jj] >= 360.0:
189
eul[jj] = eul[jj] - 360.0
191
eul[0] = eul[0]*DEG_TO_RAD
192
eul[1] = eul[1]*DEG_TO_RAD
193
eul[2] = eul[2]*DEG_TO_RAD
195
xmat=RVAxisAngleToMat3([1,0,0,eul[0]])
196
ymat=RVAxisAngleToMat3([0,1,0,eul[1]])
197
zmat=RVAxisAngleToMat3([0,0,1,eul[2]])
199
mat=[[1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0]]
201
# Standard BVH multiplication order
202
mat=RVMatMult3 (zmat,mat)
203
mat=RVMatMult3 (xmat,mat)
204
mat=RVMatMult3 (ymat,mat)
208
# Screwy Animation Master BVH multiplcation order
209
mat=RVMatMult3 (ymat,mat)
210
mat=RVMatMult3 (xmat,mat)
211
mat=RVMatMult3 (zmat,mat)
220
return x, y, z # Returm euler roration values.
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):
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
235
ob.setLocation(offset[0]*scale, offset[1]*scale, offset[2]*scale)
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.
243
# Add Ipo's for necessary channels
244
newIpo = Ipo.New('Object', prefix + name)
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')
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')
268
objectList.append(ob)
270
# Redraw if debugging
271
if debug: Blender.Redraw()
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.
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.
288
ob.setLocation(offset[0]*scale, offset[1]*scale, offset[2]*scale)
290
# Redraw if debugging
291
if debug: Blender.Redraw()
296
#===============================================#
297
# MAIN FUNCTION - All things are done from here #
298
#===============================================#
299
def loadBVH(filename):
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()
312
# Open the file for importing
313
file = open(filename, 'r')
314
fileData = file.readlines()
315
# Make a list of lines
317
for fileLine in fileData:
318
fileLine=fileLine.replace('..','.')
319
newLine = string.split(fileLine)
331
# End file loading code
333
# Call object names with this prefix, mainly for scenes with multiple BVH's - Can imagine most partr names are the same
335
#prefix = str(len(lines)) + '_'
339
# Create Hirachy as empties
340
if lines[0][0] == 'HIERARCHY':
341
print 'Importing the BVH Hierarchy for:', filename
343
return 'ERROR: This is not a BVH file'
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)
350
#channelList [(<objectName>, [channelType1, channelType2...]), (<objectName>, [channelType1, channelType2...)]
356
lineIdx = 1 # An index for the file.
357
while lineIdx < len(lines) -1:
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]
364
# MAY NEED TO SUPPORT MULTIPLE ROOT's HERE!!!, Still unsure weather multiple roots are possible.??
366
print len(parent) * ' ' + 'node:',lines[lineIdx][1],' parent:',parent[-1]
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)
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
394
channelList.append(newChannel)
396
channels = lines[lineIdx][2:]
398
# Call funtion that uses the gatrhered data to make an empty.
399
makeJoint(name, parent, prefix, offset, channels)
401
# If we have another child then we can call ourselves a parent, else
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)
410
# Just so we can remove the Parents in a uniform way- End end never has kids
411
# so this is a placeholder
414
if lines[lineIdx] == ['}']:
415
parent = parent[:-1] # Remove the last item
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
425
#=============================================#
426
# Loop through frames, each line a frame #
427
#=============================================#
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
#=============================================#
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))
451
while lineIdx < len(lines):
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'
461
currentFrame += 1 # Incriment to next frame
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.
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)))
476
if channelList[obIdx][1] != -1:
477
VAL1=lines[lineIdx][channelList[obIdx][0]]
478
if VAL1.find('.')==-1:
479
VAL1=VAL1[:len(VAL1)-6]+'.'+VAL1[-6:]
480
objectList[obIdx].getIpo().getCurve('LocY').addBezier((currentFrame, scale * float(VAL1)))
482
if channelList[obIdx][2] != -1:
483
VAL2=lines[lineIdx][channelList[obIdx][0]]
484
if VAL2.find('.')==-1:
485
VAL2=VAL2[:len(VAL2)-6]+'.'+VAL2[-6:]
486
objectList[obIdx].getIpo().getCurve('LocZ').addBezier((currentFrame, scale * float(VAL2)))
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:]
493
VAL4=lines[lineIdx][channelList[obIdx][4]]
494
if VAL4.find('.')==-1:
495
VAL4=VAL4[:len(VAL4)-6]+'.'+VAL4[-6:]
497
VAL5=lines[lineIdx][channelList[obIdx][5]]
498
if VAL5.find('.')==-1:
499
VAL5=VAL5[:len(VAL5)-6]+'.'+VAL5[-6:]
501
x, y, z = eulerRotate(float(VAL3), float(VAL4), float(VAL5))
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))
508
# Done importing motion data #
510
# lines[lineIdx] = None # Scrap old motion data, save some memory?
512
# We have finished now
513
print currentFrame, 'done.'
515
# No point in looking further, when this loop is done
516
# There is nothine else left to do
517
print 'Imported ', currentFrame, ' frames'
522
print "bvh import time: ", Blender.sys.time() - time1
524
Blender.Window.FileSelector(loadBVH, "Import BVH")