Exporting Armatures

I’ve written a custom Python script to export mesh vertices, materials and faces in a simple format for a game. Right now, to handle animation, I’m just using Blender.NMesh.GetRawFromObject to export a deformed mesh based on the current frame, and I just export several frames and interpolate vertex positions/normals between them. However, there are two problems with this; first, it takes more space to store all these frames, and second, I have to export every frame of every object I want to use, and I’m planning to have very many similar objects (clothes, shields and weapons for characters). Instead, I’d like to export the frames of the armature used to animate the mesh, and then deform the mesh using this information in the game.

I’ve looked at the Python documentation, but it is very sparse. Do you have any suggestions about how I could:

  • Get access to the armature as it is deformed in the current frame (something like Blender.NMesh.GetRawFromObject)
  • Find out which mesh vertices are affected by each bone of the armature (and by how much, if two bones can affect the same vertex?); or, how to assign each vertex to a bone given the armature data
  • Given the original mesh and the armature, deform the mesh using it in my game; I notice that each Bone object has a Quaternion associated with it, so I would probably need to do something like new position = (old position - old bone head) * quaternion + (new bone head). But are the quaternions relative to the bone’s parent or are they absolute? Will I need to do anything else?

Also, apparently, there used to be a bug in 2.32 with names of IPO’s that required scripts to do some weird tricks in order to get the bone locations for each frame. Is this still required in 2.33?

Anyone? I made quite a bit of progress, in that I can now meshes deformed by simple armatures, but there are weird issues. For example, each bone has its head/tail location relative to its parent, but the coordinate system in which these are given is not the same as that of its parent. If I make two horizontal bones (in the X direction that is), the second one will have tail x of 0 but a non-zero tail y. All I want to do is to reproduce the deformation as done by Blender, given the bone quaternions for a given frame.

Have you looked at the Cal3d or Md5 scripts? They had some kind of quaternion transforming thing in them.

Those scripts contain a lot of “hacks” to deal with some problems in Blender 2.32, and don’t always work properly. The MD5 script is very heavily based on the Cal3D script. I was hoping that someone knows what the new Bone objects’ fields all represent.

Hi, I’m the author of that MD5 script, and I’ll soon look into rewriting it for Blender2.33, to get rid of the ugly hacks. So as soon as I have experimented with 2.33 and have gained some insight, I’ll let you know here. Or (more probable) as soon as I run into problems myself… :wink:

  • Get access to the armature as it is deformed in the current frame (something like Blender.NMesh.GetRawFromObject)

Study the Armature and Bone submodules at the documentation:

arm = Blender.Armature.Get(‘name’) <----- ARMATURE object
bons = arm.getBones() <---- list of BONE objects
bone = bons[0] <----- for instance :slight_smile:
subbones = bone.getChildren() <---- list of BONE objects, and so on

Each bone has both, a bone.loc and a bone.tail variables, which are the coordinates of start and ending. You need to know that they’re given respect the parent bone reference, so you have to multiply matrices.

  • Find out which mesh vertices are affected by each bone of the armature (and by how much, if two bones can affect the same vertex?); or, how to assign each vertex to a bone given the armature data

Study the NMesh module. You’ll find methods to get that:
me = Blender.NMesh.Get(…)
grs = me.getVertGroupNames() <---- Groups in a mesh, i.e., the names of the bones :slight_smile:
vrts = me.getVertsFromGroup(group, weightsFlag=0/1, vertList=None) <---- makes de rest . Here you may get the influence weight for each vertex.


  • But are the quaternions relative to the bone’s parent or are they absolute? Will I need to do anything else?

I don’t know… experiment!!!

As I said, I’ve gotten as far as exporting all the data. It even takes the current frame into account, as I want to (unlike meshes, where you have to specifically ask for the “raw” object). The problem now is interpreting the data. Actually, I think that what I’m outputting is not enough, since I don’t see any unambigous way to figure out a parent’s reference frame.

Here’s the relevant part of my script:

def write(filename):
	file = open(filename, "wb")
	objects = Blender.Object.GetSelected()
	arm = objects[0].getData()

	# === Header ===
	file.write("# ARM 1.0
")
	file.write("
")
	
	# === Bones ===
	file.write("# Bones: count, then name, parent name, head x,y,z, tail x,y,z, quaternion w,x,y,z for each one
")
	bones = arm.getBones()
	file.write("%d
" % len(bones))
	for bone in bones:
		file.write("%s " % bone.name)
		if bone.getParent():
			file.write("%s " % bone.getParent().name)
		else:
			file.write("none ")
		file.write("%f %f %f " % tuple(bone.head))
		file.write("%f %f %f " % tuple(bone.tail))
		file.write("%f %f %f %f
" % tuple(bone.quat))

	# === Finish Up ===
	file.close()

As you can see, it’s not several hundred lines long like the Cal3D script, so I must be doing something wrong ;).

Here’s a sample output for a very simple armature, just two bones of length 2.0 lying along the x axis, with the first one at (-2,0,0):

# ARM 1.0

# Bones: count, then name, parent name, head x,y,z, tail x,y,z, quaternion x,y,z,w for each one
2
root none -2.000000 0.000000 0.000000 0.000000 -0.000000 0.000000 1.000000 0.000000 0.000000 0.000000
leaf root 0.000000 0.000000 0.000000 0.000000 2.000000 0.000000 1.000000 0.000000 0.000000 0.00000

The leaf has head at (0,0,0) and tail at (0,2,0) with respect to the parent. But is along the X axis, so the tail should be at (2,0,0). Thus something else than translation has to be taken into account. Yet both bones have unit quaternions, so I don’t see how to figure out what it is.

My guess is that it’s because you also have to take into account the armature’s orientation, in your case objects[0].getMatrix(). The root bone’s orientation is relative to the armature, not to world-space.

Thanks for the tips. I’ve finally gotten it to work. There were several difficulties:

  • Bone.quat is only the “offset” from the rest position of the bone in the current frame, and does not contain enough information to determine the bone’s coordinate system and properly transform its children. This still has to be obtained using the tail, head and roll values, like in the Cal3D/MD5 scripts.
  • As der_ton pointed out, object coordinates are not the same as world coordinates, even if you put the cursor at 0,0,0 when you create the object. Objects must be initially placed in the top view (NUM7) to have the same coordinate system as the world. Of course you could translate everything to world coordinates in a script, but this gets complicated, especially when trying to transform quaternions or normal vectors, so I’m just taking the easy way out and ignoring it for now.
  • The order of operations required to get the real position of a bone, taking into account its deformation, is fairly involved, and took a bit of guessing to get right…

Here is Python code that correctly gets a bones’ final head/tail after applying the required transformations, in case anyone’s interested. (It should be useful for people trying to create games). It uses the math code from the MD5 and Cal3D converters (basically blender_bone2matrix and its helper functions). The actual code to export the armature is trivial (just export bone.head, bone.tail, bone.quat, and restQuat, restQuat being the result of blender_bone2matrix), so I’m only posting what is required to rebuild the final positions of the bones from this data, which can then be used for skeletal animation in-game.


# Since I haven't really looked into object-oriented features of Python, 
# I'll just use a tuple to hold the "final position" info for each bone.
# The tuple will be of the form (head, tail, mat, name).
# It's not everything you need for a game, but should be a good start.
# The "finals" map is used for memoization, to make this run in linear time.

def printFinalBones(arm):
	finals = {}
	for bone in arm.bones:
		printFinal(bone, finals)

def printFinal(final):
	print "name %s" % (final[3])
	print "head %.2f %.2f %.2f" % tuple(final[0])
	print "tail %.2f %.2f %.2f" % tuple(final[1])
	print ""

def getFinal(bone, finals):
	name = bone.name
	if name in finals:
		return finals[name]
	if bone.parent:
		parent = getFinal(bone.parent, finals)
		restMat = blender_bone2matrix(bone.head, bone.tail, bone.roll)
		defQuat = (bone.quat.x, bone.quat.y, bone.quat.z, bone.quat.w)
		defMat = quaternion2matrix(defQuat)
		parMat = parent[2]
		head = bone.head
		delta = sub(bone.tail, bone.head)
		tail = add(bone.head, vector_by_matrix(delta, defMat))
		head = vector_by_matrix(head, parMat)
		tail = vector_by_matrix(tail, parMat)
		parTail = parent[1]
		head = add(head, parTail)
		tail = add(tail, parTail)
		mat = matrix_multiply(matrix_multiply(parMat, defMat), restMat)
		final = (head, tail, mat, name)
		finals[name] = final
		return final
	else:
		restMat = blender_bone2matrix(bone.head, bone.tail, bone.roll)
		defQuat = (bone.quat.x, bone.quat.y, bone.quat.z, bone.quat.w)
		defMat = quaternion2matrix(defQuat)
		head = bone.head
		delta = sub(bone.tail, bone.head)
		tail = add(bone.head, vector_by_matrix(delta, defMat))
		mat = matrix_multiply(defMat, restMat)
		final = (head, tail, mat, name)
		finals[name] = final
		return final

# Add and subtract tuples

def add(x, y):
	r = x
	for i in range(len(r)):
		r[i] = x[i] + y[i]
	return r

def sub(x, y):
	r = x
	for i in range(len(r)):
		r[i] = x[i] - y[i]
	return r


There is a new function in the cvs called Bone.getMatrix() which willl return a worldspace or localspace matrix from a bone

-I’ didnt found it.I got the last test build too which
tolds it has implemented this function within but
it doesn’work.Can you point to this function please?

Sorry it’s bone.getRestMatrix(). :expressionless:

http://projects.blender.org/viewcvs/viewcvs.cgi/blender/source/blender/python/api2_2x/Bone.c.diff?r1=1.18&r2=1.19&cvsroot=bf-blender