BVH import to Action [Solved]

Just wanted everyone to know that I solved the import problem and there is now a working armature/action importer for bvh. The code is on page 2

I’ve seen the scripts that load the BVH up into a set of empties and you create your armature from that. That’s cool and I recognize that a lot of work went into that, but that’s not what I need. I already have an armature built and I want to use the MOTION data from the BVH file on the bones of my prebuilt armature.

Everything’s great except a silly problem related to coordinate systems. From what I understand ‘Up’ is Z in the BVH system and ‘Y’ in the Blender system. On top of that ‘Y’ is pointed down the length of the bone that I want to animate ( plus the face that ‘X’ and ‘Z’ are now changed as well due to that fact ). Consequently a straightforward synthesis of the MOCAP data will not work.

Here’s a cleaned up version of the code I’m working with. The script depends on another file that parses the .bvh file correctly. The MOCAP data is stored in a flat list and the bone hierarchy is reinterpreted as a hierarchy of Python objects.

The algorithm:
FinalQuat = toQuaternion( EulerComposite * Inverse(BoneRestMatrix))

The code:

import Blender
from Blender import *
from Blender.Mathutils import *
import bvh_parser
import sys

translation = {
	'LeftUpLeg': 'ULeg.l',
	'RightUpLeg': 'ULeg.r',

object = Blender.Object.GetSelected()[0]
armature =
scene = Scene.GetCurrent()
context = scene.getRenderingContext()
if type(armature) != Blender.Types.ArmatureType :
	raise StandardError("Must have an object that is armature")
boneMap = {}
for bone in armature.getBones():
	print "Adding %s" %
	boneMap[] = bone

#Read the file
filename = r"C:\Documents and Settings\joeG\My Documents\Roads\Data\Foreign\BVH\Coolwalk.bvh"
fb = open(filename, 'r')
bvh = bvh_parser.analyze(bvh_parser.parse(fb))

#Now attempt to apply the data to the armature.  The python interface
#forces us to do this rather indirectly as if we were doing this via
#the user interface
action = Armature.NLA.NewAction('Cool')

data =
root = bvh.root
nodes = bvh.getNodesInOrder()

for frame in range(data.frames) :
	print "Frame: %d" % frame
	channelIndex = 0
	for node in nodes :
		bone = boneMap[ translation[] ]

		restMatrix = bone.getRestMatrix().rotationPart()

		positionUsed = False
		rotationUsed = False

		for channel in node.channels :
			value =[frame*data.numChannels + channelIndex]
			if channel == "Zrotation" :
				boneRotation = boneRotation * RotationMatrix( value, 3, 'z' )
			if channel == "Xrotation" :
				boneRotation = boneRotation * RotationMatrix( value, 3, 'x' )
			if channel == "Yrotation" :
				boneRotation = boneRotation * RotationMatrix( value, 3, 'y' )
			elif channel == "Xposition" :
				position[0] = value
			elif channel == "Yposition" :
				position[1] = value
			elif channel == "Zposition" :
				position[2] = value

			#Work with the flags
			if channel.find("rotation") >= 0 :
				rotationUsed = True
			if channel.find("position") >= 0 :
				positionUsed = True

			channelIndex += 1

		finalMatrix = boneRotation * restMatrix 
		if rotationUsed:
			bone.setQuat( finalMatrix.toQuat() )
		if positionUsed :
			bone.setLoc( position )
		flags = []
		if positionUsed: flags.append( Armature.Bone.LOC )
		if rotationUsed: flags.append( Armature.Bone.ROT )



i’m looking forward to your project’s progress.

proper, easy and flawless bvh import with some useable keyframe-reduction method is what’s lacking in blender badly (i quite like the easy-to-use bvh import and aplying imported motion data to prerigged characters in C4D - it’s a drag-drop operation in timeline - no fuss whatsoever).

as I haven’t looked at python in a year or so, I’m eagerly awaiting your final solution (why not port it to C# and make it blender-native? imo that’s where it should be!)

there were a couple of other efforts but with results that were not consistent.

Thankyou for you interest. I am so close to nailing the correctness aspect of this script. I just need to know if I’m approaching this problem from the right angle (no pun intended).

As far as removing keys, I think I might know of a method for that. It would involve identifying all t’s where f’(t) = 0 and just doing the cubic interpolation between those points (and pray that the interpolated curve matches the original as closely as possible ).

The other problem is with the script having to know which names from the file correspond to which names of the bones in the armature. Right now that is a manual process. To me it kind of looks similar to the role CSS has in an HTML document. So if you had a bunch of .bvh files from one source you can just write one file that lists the translations and never have to do anything more as long as your’re using .bvh files with the same nomenclature. I guess another way would be to create a name guesser for this. It may be as equally complicated to write…I’m not sure.

cool. some things to bear in mind:

  • frame reduction for mocaps needs (as far as i have been able to test) different method than manually keyframed or interpolated motion data - standard reduction methods usually contribute to very jerky movement.

  • i have (legit copy) of an older version of LifeForms ( ) and it has some very interesting and - they simply work well - tools for taming mocap data. get the demo if you are interested.

  • native C# bvh importer (with armature scaling etc) really should be a part of blender …

native C# bvh importer (with armature scaling etc) really should be a part of blender …


Blender is C and C++ and we use cross platform compilers, so unless gcc gets C# support on all platforms we support (Irix, OS X, Windows, Linux, BSD), I don’t see much of a chance of a patch written in that language being accepted.


oops sorry wasn’t concise and precise enough. yep, I meant blender native - whatever C it will be …

basically it was meant as a lacking feature in the package as a whole.


the best approach for key frame reduction, and other transforms on the data is through standard signal processing methods.

For python the package Scipy has tools for this, in scipy.signal

I asked on IRC and Bjornmose is knowledgable in signal processing and thus might be willing to offer some guidance.

I can post links to a few papers that might be of use as well if you like.


OK, sounds cool. I’d like to take a look at signal processing ( I studied Computer Science in college so I didn’t have to mess with signal processing ), but chances are that the project would go forward more quickly if someone with that background could offer a hand.

As an update to where I’m at I’m still getting nonsensensical bone movement. I need some better data methinks to do better testing. For instance, if I would construct a .bvh file that would rotate one bone around just one axis it would make it loads easier to debug than to have 50 something seperate channels and to try to debug from that.

since 2.37, there’s a script that quickly build an armature upon the empties created by bvh_import.
it is called BVH2ARM … and can be launched from a script windows (Scripts->Animation->BVH2ARM)

the script for 2.37 is here

in 2.40 this script is going to know a severe regression cause the setPose function is no longer available …
however a BVH2ARM for 2.40 can be found here with its online documentation
and a tutorial

I take advantage of this post to announce that a C3D Mocap Importer has recently been release and is being proposed for inclusion in 2.41 release. The documentatio can be found here and
the elysiun thread is here

Cheers …

As an illustration of my previous message …

a little animation made with BVH2ARM script :

As explained in this tutorial

And an animation made with the C3D import :

if you have any trouble with bvh_import try the one provided in this Motion Kit.

Hi joeg,

just curious how you are progressing,


Any progress? It would be great to have this long-standing Blender glitch put to bed at last :smiley:

I’m still here, but I’ve dropped the project. Sorry about that guys. I still need the functionality that I had been talking about, though.

Probably need to spend more time to understand all this-

I did BVH import in blender so I could edit BVH files. But once I tried assigning to armatures using constraints… so each bones rotatuion constrains to the empty- if there any value in this fore you?

Have never used actions in Blender. so cant comment on that.

OK, project re-opened:

This time I just took the latest and greatest, distributed with 2.41, and changed things around so that it built an armature directly. This works perfectly. My only problem is the co-ordinate system matrix math needed to apply a rotation specified in x,y,z basis to a basis formed from each bone’s rest position is driving me batty. I can confirm that, in the script’s current state, the values are being set to exactly what they were in the unmodified bvh_import script (i.e. the quaternion w,x,y,z values are the same between each bone and their ‘empty’ counterpart), so the coordinate system problem is the only thing not working. I think that the resolution of this coordinate system problem is the last obstacle to getting the script working

If any of you have had experience with setting up something like this, please let me know! I’ll post code if requested

Thanks for your time,

Sorry that I cant help you with the code. I am really rusty coding… But I would love to have this working too. Keep up the good work.

Hey all, I want to announce success with this project. By modifying the bvh_import script I was able to construct the armature and assign rotation values correctly to the bones of the armature. The most ‘difficult’ part was to find the correct sequence of matrices to multiply against the rotation matrix generated from the euler values.

In my next update I’d like to add a GUI to let the user map joint names from the BVH file to the names of bones in an existing Armature.

Oh, and I made all spaces tabs and set the tab stop to 4 instead of 8, I hope no one minds…


Name: 'Motion Capture (.bvh)...'
Blender: 239
Group: 'Import'
Tip: 'Import a (.bvh) motion capture file'

__author__ = "Campbell Barton"
__url__ = ("blender", "elysiun")
__version__ = "1.0.4 05/12/04"

__bpydoc__ = """\
This script imports BVH motion capture data to Blender.

Supported: Poser 3.01


Known issues:


	 Joe Garfield made changes to construct Armatures and Action from file
	 Jean-Michel Soler improved importer to support Poser 3.01 files;

	 Jean-Baptiste Perin wrote a script to create an armature out of the
Empties created by this importer, it's in the Scripts window -> Scripts -> Animation menu.

# $Id:,v 1.9 2006/01/12 21:30:00 letterrip Exp $

# BVH Import script 1.??                        #
# Changed loading of bvh files to load directly #
# into a new Armature.  Also creates and fills  #
# in the associated Action.                     #
# Next up is to find a way to map the joint     #
# names within the HIERARCHY to bones within an #
# existing Armature as well as improve          #
# performance.                                  #

# BVH Import script 1.05 patched by Campbell    #
# Modified to use Mathutils for matrix math,    #
# Fixed possible joint naming bug,              #
# Imports BVH's with bad EOF gracefully         #
# Fixed duplicate joint names, make them unique #
# Use \r as well as 
 for newlines             #
# Added suppot for nodes with 0 motion channels #
# Rotation IPOs never cross more then 180d      #
#    fixes sub frame tweening and time scaling  #
# 5x overall speedup.                           #
# 06/12/2005,                                   #

# BVH Import script 1.04 patched by jms         #
# Small modif for blender 2.40                  #
# 04/12/2005,                                   #

# BVH Import script 1.03 patched by Campbell    #
# Small optimizations and scale input           #
# 01/01/2005,                                   #

# BVH Import script 1.02 patched by Jm Soler    #
# to the Poser 3.01 bvh file                    #
# 28/12/2004,                                   #

# BVH Import script 1.0 by Campbell Barton      #
# 25/03/2004, euler rotation code taken from    #
# Reevan Mckay's BVH import script v1.1         #
# if you have any questions about this scrip.   #
# email me [email protected]                   #

# TODO:                                         #
# * Create GUI to handle joint->bone name       #
#   mapping among other options                 #
# * Make an IPO jitter removal script           #
# * Work out a better naming system             #

# -------------------------------------------------------------------------- 
# BVH Import v1.05 by Campbell Barton (AKA Ideasman) 
# -------------------------------------------------------------------------- 
# This program is free software; you can redistribute it and/or 
# modify it under the terms of the GNU General Public License 
# as published by the Free Software Foundation; either version 2 
# of the License, or (at your option) any later version. 
# This program is distributed in the hope that it will be useful, 
# but WITHOUT ANY WARRANTY; without even the implied warranty of 
# GNU General Public License for more details. 
# You should have received a copy of the GNU General Public License 
# along with this program; if not, write to the Free Software Foundation, 
# Inc., 59 Temple Place - Suite 330, Boston, MA	02111-1307, USA. 
# ***** END GPL LICENCE BLOCK ***** 
# -------------------------------------------------------------------------- 

import Blender
from Blender import Window, Object, Mathutils, Armature, Scene, Ipo, Draw
from Blender.Object import Pose
from Blender.Scene import Render
from Blender.Armature import NLA
import math

# Attempt to load psyco, speed things up
	import psyco
	print 'using psyco to speed up BVH importing'
	#print 'psyco is not present on this system'

def main():
	global scale
	scale = None
	# Update as we load?
	debug = 1
	def getScale():
		return Draw.PupFloatInput('BVH Scale: ', 0.01, 0.001, 10.0, 0.1, 3)
	# MAIN FUNCTION - All things are done from here #
	def loadBVH(filename):
		global scale
		print '
BVH Importer 1.?? by Campbell Barton (Ideasman) - [email protected]'
		objectCurveMapping = {}
		objectNameMapping = {}
		objectMotiondataMapping = {}
		boneChannels = {}

		armObj = Object.New('Armature', 'Armature') 
		armObj.RotX = math.pi/2.0 
		armData = Armature.Armature('Armature')
		action = NLA.NewAction("Action") 

		# Here we store the Ipo curves in the order they load.
		channelCurves = []
		# Object list
		# We need this so we can loop through the objects and edit there IPO's 
		# Chenging there rotation to EULER rotation
		boneList = []
		boneChildren = {}
		boneStack = [None]
		if scale == None:
			tempscale = getScale()
			if tempscale:
				scale = tempscale
				scale = 1.0
		# Change the order rotation is applied.
		RotationMatrix = Blender.Mathutils.RotationMatrix
		MATRIX_IDENTITY_3x3 = Blender.Mathutils.Matrix([1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0])
		def bvhRotate(x,y,z): 
			x,y,z = x%360,y%360,z%360 # Clamp all values between 0 and 360, values outside this raise an error.
			xmat = RotationMatrix(x,3,'x')
			ymat = RotationMatrix(y,3,'y')
			zmat = RotationMatrix(z,3,'z')
			# Standard BVH multiplication order, apply the rotation in the order Z,X,Y
			ret =  ymat*(xmat * (zmat * MATRIX_IDENTITY_3x3))
			return ret

		currentFrame = 1 # Set the initial frame to import all data to.
		# makeJoint: Here we use the node data	  #
		# from the BVA file to create an empty	  #
		BVH2BLEND_TX_NAME = {'Xposition':'LocX','Yposition':'LocY','Zposition':'LocZ','Xrotation':'RotX','Yrotation':'RotY','Zrotation':'RotZ'}
		def makeJoint(name, parent, offset, channels):
			bone = Armature.Editbone()
			boneChannels[name] = channels
			parentname = None
			#Default to the absolute 'offset' if there is no parent
			scaled_absolute = [a*scale for a in offset]
			if parent:
				parentname =
			if parent:
				parent = armData.bones[parentname]
				bone.parent = parent
				for i in range(3):
					scaled_absolute[i] = scale*(offset[i]+parent.head[i])
				if parentname not in boneChildren:
					boneChildren[parentname] = [bone]
					parent.tail = Mathutils.Vector(scaled_absolute[0], scaled_absolute[1], scaled_absolute[2])

			# Offset Empty from BVH's initial joint location.
			bone.head = Mathutils.Vector(scaled_absolute[0], scaled_absolute[1], scaled_absolute[2])
			# Add to bone list
			armData.bones[name] = bone
			# Redraw if debugging
			if debug: Blender.Redraw()
			return bone
		# makeEnd: Here we make an end node	     #
		# This is needed when adding the last bone      #
		def makeEnd(parent, offset):
			parentname =
			scaled_absolute = [0]*3
			for i in range(3):
				scaled_absolute[i] = scale*(offset[i]+parent.head[i])
			armData.bones[parentname].tail = Mathutils.Vector(scaled_absolute[0],scaled_absolute[1], scaled_absolute[2])
			# Redraw if debugging
			if debug: Blender.Redraw()
		# END FUNCTION DEFINITIONS ====================================#
		time1 = Blender.sys.time()
		# Get the current scene.
		scn = Scene.GetCurrent()
		#context = scn.getRenderingContext()
		# DeSelect All
		for ob in scn.getChildren():
			ob.sel = 0
		# File loading stuff
		# Open the file for importing
		file = open(filename, 'r')	
		# Seperate into a list of lists, each line a list of words.
		lines = file.readlines()
		# Non standard carrage returns?
		if len(lines) == 1:
			lines = lines[0].split('\r')
		# Split by whitespace.
		lines =[ll for ll in [ [w for w in l.split() if w != '
' ] for l in lines] if ll]
		# End file loading code
		# Create Hierarchy as empties
		if lines[0][0] == 'HIERARCHY':
			print 'Importing the BVH Hierarchy for:', filename
			return 'ERROR: This is not a BVH file'
		# A linear list of ancestors to keep track of a single objects heratage
		# at any one time, this is appended and removed, doesn't store tree- just a linear list.
		# ZERO is a place holder that means we are a root node. (no parents)
		parent = None
		#channelList, sync with objectList:  [[channelType1, channelType2...],	[channelType1, channelType2...)]
		channelList = []
		channelIndex = -1
		lineIdx = 0 # An index for the file.
		while lineIdx < len(lines) -1:
			if lines[lineIdx][0] == 'ROOT' or lines[lineIdx][0] == 'JOINT':
				# Join spaces into 1 word with underscores joining it.
				if len(lines[lineIdx]) > 2:
					lines[lineIdx][1] = '_'.join(lines[lineIdx][1:])
					lines[lineIdx] = lines[lineIdx][:2]
				# MAY NEED TO SUPPORT MULTIPLE ROOT's HERE!!!, Still unsure weather multiple roots are possible.??
				# Make sure the names are unique- Object names will match joint names exactly and both will be unique.
				name = lines[lineIdx][1]
				if debug: print 'node: %s, parent: %s' % (name,  parent)
				lineIdx += 2 # Incriment to the next line (Offset)
				offset = ( float(lines[lineIdx][1]), float(lines[lineIdx][2]), float(lines[lineIdx][3]) )
				lineIdx += 1 # Incriment to the next line (Channels)
				# newChannel[Xposition, Yposition, Zposition, Xrotation, Yrotation, Zrotation]
				# newChannel references indecies to the motiondata,
				# if not assigned then -1 refers to the last value that will be added on loading at a value of zero, this is appended 
				# We'll add a zero value onto the end of the MotionDATA so this is always refers to a value.
				newChannel = [-1, -1, -1, -1, -1, -1] 
				for channel in lines[lineIdx][2:]:
					channelIndex += 1 # So the index points to the right channel
					if channel == 'Xposition':
						newChannel[0] = channelIndex
					elif channel == 'Yposition':
						newChannel[1] = channelIndex
					elif channel == 'Zposition':
						newChannel[2] = channelIndex
					elif channel == 'Xrotation':
						newChannel[3] = channelIndex
					elif channel == 'Yrotation':
						newChannel[4] = channelIndex
					elif channel == 'Zrotation':
						newChannel[5] = channelIndex
				channels = lines[lineIdx][2:]
				if lines[lineIdx-3][0] == 'ROOT':	
					root = Armature.Editbone()
					root.head = Mathutils.Vector( offset[0]*scale, offset[1]*scale, offset[2]*scale )
					boneChannels[name] = channels
					armData.bones[name] = root
					# Call funtion that uses the gatrhered data to make an empty.
					parent = makeJoint(name, parent, offset, channels)
			# Account for an end node
			if lines[lineIdx][0] == 'End' and lines[lineIdx][1] == 'Site': # There is somtimes a name after 'End Site' but we will ignore it.
				lineIdx += 2 # Incriment to the next line (Offset)
				offset = ( float(lines[lineIdx][1]), float(lines[lineIdx][2]), float(lines[lineIdx][3]) )
				makeEnd(parent, offset)
			if len(lines[lineIdx]) == 1 and lines[lineIdx][0] == '}': # == ['}']
				name = boneStack[-1]
				if name: parent = armData.bones[name]

			# BVH Structure loaded, Now import motion     #
			if len(lines[lineIdx]) == 1 and lines[lineIdx][0] == 'MOTION':
				print '
Importing motion data'
				lineIdx += 3 # Set the cursor to the first frame
				# Add a ZERO keyframe, this keeps the rig     #
				# so when we export we know where all the     #
				# joints start from			   #
				# Loop through frames, each line a frame      #
				MOTION_DATA_LINE_LEN = len(lines[lineIdx])
				while lineIdx < len(lines):
					line = lines[lineIdx]

					if MOTION_DATA_LINE_LEN != len(line):
						print 'ERROR: Incomplete motion data on line %i, finishing import.' % lineIdx
					# Exit loop if we are past the motiondata.
					# Some BVH's have extra tags like 'CONSTRAINTS and MOTIONTAGS'
					# I dont know what they do and I dont care, they'll be ignored here.
					if len(line) < len(boneList):
						print '...ending on unknown tags'
					currentFrame += 1 # Incriment to next frame
					if currentFrame > Blender.Get("endframe"):
					# Import motion data and assign it to an IPO	#
					line.append(0.0) # Use this as a dummy var for objects that dont have a loc/rotate channel.
					if debug: Blender.Redraw() 
					poseObject = armObj.getPose()
					poseBones = poseObject.bones
					for index, bonename in enumerate(boneList):
						restBone = armData.bones[bonename]
						poseBone = poseBones[bonename]
						parentRestMatrix = Mathutils.RotationMatrix(0,4, "x")
						if restBone.parent:
							parentRestMatrix =  restBone.parent.matrix['ARMATURESPACE']

						trans = Mathutils.Vector(0.0,0.0,0.0)
						obChannel = channelList[index] 
						xformConstants = []

						if channelList[index][0] != -1:
							trans[0] = scale * float(  line[obChannel[0]]  ) 
						if channelList[index][1] != -1:
							trans[1] = scale * float(  line[obChannel[1]]  ) 

						if channelList[index][2] != -1:
							trans[2] = scale * float(  line[obChannel[2]]  ) 

						#Apply transformation
						if channelList[index][-1] != -1 or channelList[index][1] != -1 or channelList[index][2] != -1:
							poseBone.loc = trans
							xformConstants.append( Object.Pose.LOC )
						#Apply rotation as matrix
						if obChannel[3] != -1 or obChannel[4] != -1 or obChannel[5] != -1:						
							parentMat = None

							boneRest = restBone.matrix['ARMATURESPACE'].rotationPart()
							boneRestInv = Mathutils.Matrix(boneRest)

							objVec = [float(line[obChannel[i]]) for i in range(3,6)]
							accum = Mathutils.Vector(tuple(objVec))
							action = bvhRotate(accum[0],accum[1],accum[2])
							finalMatrix = boneRest*action*boneRestInv
							poseBone.quat = finalMatrix.toQuat() 
							xformConstants.append( Object.Pose.ROT )
						# Done importing motion data #
						poseBone.insertKey(armObj, currentFrame, xformConstants)
					lineIdx += 1
				# Imported motion to an IPO
				# No point in looking further, when this loop is done
				# There is nothine else left to do			
			# Main file loop
			lineIdx += 1
		print '
bvh import time for %i frames: %.6f' % (currentFrame, Blender.sys.time() - time1)
	Blender.Window.FileSelector(loadBVH, "Import BVH")
	# TESTING     #
	#loadBVH('/metavr/mocap/bvh/dg-306-g.bvh') # Incompleate EOF
	#loadBVH('/metavr/mocap/bvh/wa8lk.bvh') # duplicate joint names, \r line endings.
	#loadBVH('/metavr/mocap/bvh/walk4.bvh') # 0 channels
	scale  = 0.01
	import os
	DIR = '/metavr/mocap/bvh/'
	for f in os.listdir(DIR):
		if f.endswith('.bvh'):
			s = Scene.New(f)
			loadBVH(DIR + f)
if __name__ == '__main__':

This is excellent joeg! I look foward to your updates!

<edit> Got the 2.37 version to work perfectly!

Hi thanks joeg for this contribution.

I had a look at your script to include into Blender CVS, but I think it exposes a bug in the current blender release.

You can see below that Blenders menu somehow got to be the bone parents name.

Could you build a blender release from teh CVS, test the script and file a bug report? - if possible make a short script that just gets the names for instance and shows the corruption. That way blender/python api can get fixed and we can include this script in 2.42

  • Cam

node: LeftKnee, parent: [EditBone "|     1-Animation%x1|     2-Model %x2|     3-Material%x3|     4-Sequence%x4|     5-Scripting%x5"]
Traceback (most recent call last):
  File "&lt;string&gt;", line 357, in loadBVH
  File "&lt;string&gt;", line 222, in makeJoint
KeyError: 'bone |     1-Animation%x1|     2-Model %x2|     3-Material%x3|     4-Sequence%x4|     5-Scripting%x5 not found'