4
Name: 'Motion Capture (.bvh)...'
7
Tip: 'Import a (.bvh) motion capture file'
10
__author__ = "Campbell Barton"
11
__url__ = ("blender", "elysiun")
12
__version__ = "1.0.4 05/12/04"
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.9 2006/01/12 21:30:00 letterrip Exp $
32
#===============================================#
33
# BVH Import script 1.05 patched by Campbell #
34
# Modified to use Mathutils for matrix math, #
35
# Fixed possible joint naming bug, #
36
# Imports BVH's with bad EOF gracefully #
37
# Fixed duplicate joint names, make them unique #
38
# Use \r as well as \n for newlines #
39
# Added suppot for nodes with 0 motion channels #
40
# Rotation IPOs never cross more then 180d #
41
# fixes sub frame tweening and time scaling #
42
# 5x overall speedup. #
44
#===============================================#
46
#===============================================#
47
# BVH Import script 1.04 patched by jms #
48
# Small modif for blender 2.40 #
50
#===============================================#
52
#===============================================#
53
# BVH Import script 1.03 patched by Campbell #
54
# Small optimizations and scale input #
56
#===============================================#
58
#===============================================#
59
# BVH Import script 1.02 patched by Jm Soler #
60
# to the Poser 3.01 bvh file #
62
#===============================================#
64
#===============================================#
65
# BVH Import script 1.0 by Campbell Barton #
66
# 25/03/2004, euler rotation code taken from #
67
# Reevan Mckay's BVH import script v1.1 #
68
# if you have any questions about this scrip. #
69
# email me cbarton@metavr.com #
70
#===============================================#
72
#===============================================#
74
# * Create bones when importing #
75
# * Make an IPO jitter removal script #
76
# * Work out a better naming system #
77
#===============================================#
79
# --------------------------------------------------------------------------
80
# BVH Import v1.05 by Campbell Barton (AKA Ideasman)
81
# --------------------------------------------------------------------------
82
# ***** BEGIN GPL LICENSE BLOCK *****
84
# This program is free software; you can redistribute it and/or
85
# modify it under the terms of the GNU General Public License
86
# as published by the Free Software Foundation; either version 2
87
# of the License, or (at your option) any later version.
89
# This program is distributed in the hope that it will be useful,
90
# but WITHOUT ANY WARRANTY; without even the implied warranty of
91
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
92
# GNU General Public License for more details.
94
# You should have received a copy of the GNU General Public License
95
# along with this program; if not, write to the Free Software Foundation,
96
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
98
# ***** END GPL LICENCE BLOCK *****
99
# --------------------------------------------------------------------------
102
from Blender import Window, Object, Scene, Ipo, Draw
103
from Blender.Scene import Render
106
# Attempt to load psyco, speed things up
110
print 'using psyco to speed up BVH importing'
112
#print 'psyco is not present on this system'
125
return Draw.PupFloatInput('BVH Scale: ', 0.01, 0.001, 10.0, 0.1, 3)
128
#===============================================#
129
# MAIN FUNCTION - All things are done from here #
130
#===============================================#
131
def loadBVH(filename):
133
print '\nBVH Importer 1.05 by Campbell Barton (Ideasman) - cbarton@metavr.com'
135
objectCurveMapping = {}
136
objectNameMapping = {}
137
objectMotiondataMapping = {}
139
# Here we store the Ipo curves in the order they load.
143
# We need this so we can loop through the objects and edit there IPO's
144
# Chenging there rotation to EULER rotation
148
tempscale = getScale()
155
# Unique names, dont reuse any of these names.
156
uniqueObNames = [ob.name for ob in Object.Get()]
159
# FUNCTIONS ====================================#
160
def getUniqueObName(name):
162
newname = name[:min(len(name), 12)] # Concatinate to 12 chars
163
while newname in uniqueObNames:
164
newname = name + str(i)
168
# Change the order rotation is applied.
169
RotationMatrix = Blender.Mathutils.RotationMatrix
170
MATRIX_IDENTITY_3x3 = Blender.Mathutils.Matrix([1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0])
171
def eulerRotate(x,y,z):
172
x,y,z = x%360,y%360,z%360 # Clamp all values between 0 and 360, values outside this raise an error.
173
xmat = RotationMatrix(x,3,'x')
174
ymat = RotationMatrix(y,3,'y')
175
zmat = RotationMatrix(z,3,'z')
176
# Standard BVH multiplication order, apply the rotation in the order Z,X,Y
177
return (ymat*(xmat * (zmat * MATRIX_IDENTITY_3x3))).toEuler()
180
currentFrame = 1 # Set the initial frame to import all data to.
182
#===============================================#
183
# makeJoint: Here we use the node data #
184
# from the BVA file to create an empty #
185
#===============================================#
186
BVH2BLEND_TX_NAME = {'Xposition':'LocX','Yposition':'LocY','Zposition':'LocZ','Xrotation':'RotX','Yrotation':'RotY','Zrotation':'RotZ'}
187
def makeJoint(name, parent, offset, channels):
188
ob = Object.New('Empty', name) # New object, ob is shorter and nicer to use.
190
objectNameMapping[name] = ob
191
scn.link(ob) # place the object in the current scene
194
# Make me a child of another empty.
195
# Vale of None will make the empty a root node (no parent)
196
if parent[-1]: # != None
197
obParent = objectNameMapping[parent[-1]] # We use this a bit so refrence it here.
198
obParent.makeParent([ob], 1, 0) #ojbs, noninverse, 1 = not fast.
200
# Offset Empty from BVH's initial joint location.
201
ob.setLocation(offset[0]*scale, offset[1]*scale, offset[2]*scale)
203
# Add Ipo's for necessary channels
204
newIpo = Ipo.New('Object', name)
207
for channelType in channels:
208
channelType = BVH2BLEND_TX_NAME[channelType]
209
curve = newIpo.addCurve(channelType)
210
curve.setInterpolation('Linear')
211
objectCurveMapping[(obname, channelType)] = curve
214
objectList.append(ob)
216
# Redraw if debugging
217
if debug: Blender.Redraw()
220
#===============================================#
221
# makeEnd: Here we make an end node #
222
# This is needed when adding the last bone #
223
#===============================================#
224
def makeEnd(parent, offset):
225
new_name = parent[-1] + '_end'
226
ob = Object.New('Empty', new_name) # New object, ob is shorter and nicer to use.
227
objectNameMapping[new_name] = ob
231
# Dont check for a parent, an end node MUST have a parent
232
obParent = objectNameMapping[parent[-1]] # We use this a bit so refrence it here.
233
obParent.makeParent([ob], 1, 0) #ojbs, noninverse, 1 = not fast.
236
ob.setLocation(offset[0]*scale, offset[1]*scale, offset[2]*scale)
238
# Redraw if debugging
239
if debug: Blender.Redraw()
240
# END FUNCTION DEFINITIONS ====================================#
245
time1 = Blender.sys.time()
247
# Get the current scene.
248
scn = Scene.GetCurrent()
249
#context = scn.getRenderingContext()
252
for ob in scn.getChildren():
256
# Open the file for importing
257
file = open(filename, 'r')
259
# Seperate into a list of lists, each line a list of words.
260
lines = file.readlines()
261
# Non standard carrage returns?
263
lines = lines[0].split('\r')
265
# Split by whitespace.
266
lines =[ll for ll in [ [w for w in l.split() if w != '\n' ] for l in lines] if ll]
267
# End file loading code
271
# Create Hirachy as empties
272
if lines[0][0] == 'HIERARCHY':
273
print 'Importing the BVH Hierarchy for:', filename
275
return 'ERROR: This is not a BVH file'
277
# A liniar list of ancestors to keep track of a single objects heratage
278
# at any one time, this is appended and removed, dosent store tree- just a liniar list.
279
# ZERO is a place holder that means we are a root node. (no parents)
282
#channelList, sync with objectList: [[channelType1, channelType2...], [channelType1, channelType2...)]
286
lineIdx = 0 # An index for the file.
287
while lineIdx < len(lines) -1:
289
if lines[lineIdx][0] == 'ROOT' or lines[lineIdx][0] == 'JOINT':
291
# Join spaces into 1 word with underscores joining it.
292
if len(lines[lineIdx]) > 2:
293
lines[lineIdx][1] = '_'.join(lines[lineIdx][1:])
294
lines[lineIdx] = lines[lineIdx][:2]
296
# MAY NEED TO SUPPORT MULTIPLE ROOT's HERE!!!, Still unsure weather multiple roots are possible.??
298
# Make sure the names are unique- Object names will match joint names exactly and both will be unique.
299
name = getUniqueObName(lines[lineIdx][1])
300
uniqueObNames.append(name)
302
print '%snode: %s, parent: %s' % (len(parent) * ' ', name, parent[-1])
304
lineIdx += 2 # Incriment to the next line (Offset)
305
offset = ( float(lines[lineIdx][1]), float(lines[lineIdx][2]), float(lines[lineIdx][3]) )
306
lineIdx += 1 # Incriment to the next line (Channels)
308
# newChannel[Xposition, Yposition, Zposition, Xrotation, Yrotation, Zrotation]
309
# newChannel references indecies to the motiondata,
310
# if not assigned then -1 refers to the last value that will be added on loading at a value of zero, this is appended
311
# We'll add a zero value onto the end of the MotionDATA so this is always refers to a value.
312
newChannel = [-1, -1, -1, -1, -1, -1]
313
for channel in lines[lineIdx][2:]:
314
channelIndex += 1 # So the index points to the right channel
315
if channel == 'Xposition':
316
newChannel[0] = channelIndex
317
elif channel == 'Yposition':
318
newChannel[1] = channelIndex
319
elif channel == 'Zposition':
320
newChannel[2] = channelIndex
321
elif channel == 'Xrotation':
322
newChannel[3] = channelIndex
323
elif channel == 'Yrotation':
324
newChannel[4] = channelIndex
325
elif channel == 'Zrotation':
326
newChannel[5] = channelIndex
328
channelList.append(newChannel)
330
channels = lines[lineIdx][2:]
332
# Call funtion that uses the gatrhered data to make an empty.
333
makeJoint(name, parent, offset, channels)
335
# If we have another child then we can call ourselves a parent, else
338
# Account for an end node
339
if lines[lineIdx][0] == 'End' and lines[lineIdx][1] == 'Site': # There is somtimes a name after 'End Site' but we will ignore it.
340
lineIdx += 2 # Incriment to the next line (Offset)
341
offset = ( float(lines[lineIdx][1]), float(lines[lineIdx][2]), float(lines[lineIdx][3]) )
342
makeEnd(parent, offset)
344
# Just so we can remove the Parents in a uniform way- End end never has kids
345
# so this is a placeholder
348
if len(lines[lineIdx]) == 1 and lines[lineIdx][0] == '}': # == ['}']
349
parent.pop() # Remove the last item
351
#=============================================#
352
# BVH Structure loaded, Now import motion #
353
#=============================================#
354
if len(lines[lineIdx]) == 1 and lines[lineIdx][0] == 'MOTION':
355
print '\nImporting motion data'
356
lineIdx += 3 # Set the cursor to the first frame
358
#=============================================#
359
# Add a ZERO keyframe, this keeps the rig #
360
# so when we export we know where all the #
361
# joints start from #
362
#=============================================#
364
for obIdx, ob in enumerate(objectList):
366
if channelList[obIdx][0] != -1:
367
objectCurveMapping[obname, 'LocX'].addBezier((currentFrame,0))
368
objectMotiondataMapping[obname, 'LocX'] = []
369
if channelList[obIdx][1] != -1:
370
objectCurveMapping[obname, 'LocY'].addBezier((currentFrame,0))
371
objectMotiondataMapping[obname, 'LocY'] = []
372
if channelList[obIdx][2] != -1:
373
objectCurveMapping[obname, 'LocZ'].addBezier((currentFrame,0))
374
objectMotiondataMapping[obname, 'LocZ'] = []
376
channelList[obIdx][3] != -1 or\
377
channelList[obIdx][4] != -1 or\
378
channelList[obIdx][5] != -1:
379
objectMotiondataMapping[obname, 'RotX'] = []
380
objectMotiondataMapping[obname, 'RotY'] = []
381
objectMotiondataMapping[obname, 'RotZ'] = []
383
#=============================================#
384
# Loop through frames, each line a frame #
385
#=============================================#
386
MOTION_DATA_LINE_LEN = len(lines[lineIdx])
387
while lineIdx < len(lines):
388
line = lines[lineIdx]
389
if MOTION_DATA_LINE_LEN != len(line):
390
print 'ERROR: Incomplete motion data on line %i, finishing import.' % lineIdx
393
# Exit loop if we are past the motiondata.
394
# Some BVH's have extra tags like 'CONSTRAINTS and MOTIONTAGS'
395
# I dont know what they do and I dont care, they'll be ignored here.
396
if len(line) < len(objectList):
397
print '...ending on unknown tags'
401
currentFrame += 1 # Incriment to next frame
403
#=============================================#
404
# Import motion data and assign it to an IPO #
405
#=============================================#
406
line.append(0.0) # Use this as a dummy var for objects that dont have a loc/rotate channel.
408
if debug: Blender.Redraw()
409
for obIdx, ob in enumerate(objectList):
411
obChannel = channelList[obIdx]
412
if channelList[obIdx][0] != -1:
413
objectMotiondataMapping[obname, 'LocX'].append((currentFrame, scale * float( line[obChannel[0]] )))
415
if channelList[obIdx][1] != -1:
416
objectMotiondataMapping[obname, 'LocY'].append((currentFrame, scale * float( line[obChannel[1]] )))
418
if channelList[obIdx][2] != -1:
419
objectMotiondataMapping[obname, 'LocZ'].append((currentFrame, scale * float( line[obChannel[2]] )))
421
if obChannel[3] != -1 or obChannel[4] != -1 or obChannel[5] != -1:
422
x, y, z = eulerRotate(float( line[obChannel[3]] ), float( line[obChannel[4]] ), float( line[obChannel[5]] ))
423
x,y,z = x/10.0, y/10.0, z/10.0 # For IPO's 36 is 360d
424
motionMappingRotX = objectMotiondataMapping[obname, 'RotX']
425
motionMappingRotY = objectMotiondataMapping[obname, 'RotY']
426
motionMappingRotZ = objectMotiondataMapping[obname, 'RotZ']
428
# Make interpolation not cross between 180d, thjis fixes sub frame interpolation and time scaling.
429
# Will go from (355d to 365d) rather then to (355d to 5d) - inbetween these 2 there will now be a correct interpolation.
430
if len(motionMappingRotX) > 1:
431
while (motionMappingRotX[-1][1] - x) > 18: x+=36
432
while (motionMappingRotX[-1][1] - x) < -18: x-=36
434
while (motionMappingRotY[-1][1] - y) > 18: y+=36
435
while (motionMappingRotY[-1][1] - y) < -18: y-=36
437
while (motionMappingRotZ[-1][1] - z) > 18: z+=36
438
while (motionMappingRotZ[-1][1] - z) < -18: z-=36
440
motionMappingRotX.append((currentFrame, x))
441
motionMappingRotY.append((currentFrame, y))
442
motionMappingRotZ.append((currentFrame, z))
443
# Done importing motion data #
447
#=======================================#
448
# Now Write the motion to the IPO's #
449
#=======================================#
450
for key, motion_data in objectMotiondataMapping.iteritems():
452
# Strip the motion data where all the points have the same falue.
453
i = len(motion_data) -2
454
while i > 0 and len(motion_data) > 2:
455
if motion_data[i][1] == motion_data[i-1][1] == motion_data[i+1][1]:
460
obname, tx_type = key
461
curve = objectCurveMapping[obname, tx_type]
462
for point_data in motion_data:
463
curve.addBezier( point_data )
464
# Imported motion to an IPO
466
# No point in looking further, when this loop is done
467
# There is nothine else left to do
473
print 'bvh import time for %i frames: %.6f' % (currentFrame, Blender.sys.time() - time1)
477
Blender.Window.FileSelector(loadBVH, "Import BVH")
483
#loadBVH('/metavr/mocap/bvh/boxer.bvh')
484
#loadBVH('/metavr/mocap/bvh/dg-306-g.bvh') # Incompleate EOF
485
#loadBVH('/metavr/mocap/bvh/wa8lk.bvh') # duplicate joint names, \r line endings.
486
#loadBVH('/metavr/mocap/bvh/walk4.bvh') # 0 channels
489
DIR = '/metavr/mocap/bvh/'
490
for f in os.listdir(DIR):
491
if f.endswith('.bvh'):
496
if __name__ == '__main__':
b'\\ No newline at end of file'