Skip to content

Archive

Category: Blender Scripting

My current export script for character animation requires a naming convention for binding a character to it’s armature.  Also, the script only exports the currently selected mesh.

In this article, I illustrate how to examine a Blender scene to discover assets; I also show how to find the armature modifier associated with an animated character.

In outline, this is how to structure a script to export an asset library:

  • Iterate all objects in the scene
  • Retain only Mesh objects.
  • For each mesh object, check whether this object uses an armature; if so, assume the object is a kind of actor or character.
  • Export mesh objects as appropriate (You can learn how to do this from previous articles on this blog)

Interactive session

Let’s try the following:

s=Blender.Scene.GetCurrent()
s.objects
for x in s.objects:
	x.type

(After typing ‘x.type’ in the interactive console, press enter twice to effect the loop)

This will print the type of each object in the order they are stored in the scene. We only want to export Mesh objects. Pick any Mesh object using an armature. (in my case, index 1 in the object list).

object=s.objects[1]
for x in object.modifiers:
	if(x.type==Modifier.Types.ARMATURE):
		x

This will print something like:

[Modifier "Armature", Type "Armature"]

That’s all we need to identify actors and props in a Blender Scene.

Material assignments allow defining per face or per object materials. In this interactive scripting session I show how materials, material properties and material assignments can be accessed. Next time I will expose a simple solution for exporting character animations.

Per face material assignments

For the record, material assignments are accessible from [buttons window > editing tab > Link and Materials panel. There's the usual up/down arrow to select an existing material or creating a new one. Although the actual material editor is elsewhere, you can still choose a color and a name for a material created here without exiting the editing tab. Upon creating / selecting a material, the current selection is assigned this material. Or you can select vertices later and use the 'Assign' button.

Interactive Scripting Session

This may not require additional comments:

bot=Blender.Object.Get("FunBot")
mesh=bot.getData(mesh=1)
faces=mesh.faces

# this will print the material index
faces[0].mat

# to find the name of a material
# get materials as an array
materials=mesh.materials
# this prints the name of the material assigned to face zero.
materials[faces[0].mat]

# this illustrates material properties
# (in interactive mode, prints material property values)
mat=materials[0]
mat.rgbCol
mat.amb
mat.alpha
math.hard/512
mat.specCol
mat.emit

Material.Get() can be used to get all materials in a scene - for an export script, however, it may be more efficient to map materials as we export geometry to avoid exporting unused materials.

Finally, there's an *awful* lot more about Blender materials in the Material API (link to Blender docs).

Before I forget… for some reason the Blender APIDoc doesn’t just turn up whenever I google it. Here it is.

Just a while back I published a weeny export script and matching C/Objective C code that can be used to export mesh data from blender to OpenGL vertex arrays.

To export animated characters, we need to do either of two things:

  1. Export Armature key frames or frames, along with their attachment to target objects; then apply the deformation either in real time, or maybe just before we need to animate the model. The upside is that this is memory savvy. The downside is that if your game engine can’t handle your exported armatures (I don’t know how likely to happen this might be) or you don’t use a game engine, you’ll have to work out deformations yourself, starting from a fairly complex high level model.
  2. Export ready mesh key-frames or frames, with the armature effect already applied. The downside is that this requires a lot more memory.

My hunch is that how much ‘a lot more’ matters really depends how large your models are and what kind of platform you’re running on. I plan running on the iPhone/iTouch with isometric views. This means that my 3D sprites will be small – hence require less polygons. For some animations, a little compression (e.g. retaining unchanged coordinates from a previous frame) can be considered if needed, never mind sharing geometry across models.

In this article I show how to force modifiers to be applied before exporting mesh data – one way to attach an armature in blender is via modifiers. This is a good opportunity to try interactive scripting with Blender, making learning scripting easy and fun :)

Effecting the modifier stack also means that you can export complex operations applied to your object, not just armature modifiers.

Give back to Caesar what belongs to  Chicken – I borrowed this technique from the Blender to Panda3D export script – Chicken exports to Panda3D’s *.egg format,

***Believe it or not, *.egg came first, then Chicken somehow assisted the birth of this tutorial.

An Interactive Scripting Session

First I make a triangle pointing up in the z x plane, ensuring x==0 for the middle vertex. I add an armature – just one bone – aligned on the z axis and pose it to drag the top vertex to the right, about 1 unit. Finally I add the armature as modifier. I think this is important, afaik this wouldn’t work if the armature parented
the mesh. Then I type the following in the scripting console:

scene=Blender.Scene.getCurrent()
obj =scene.objects[1]
mesh=obj.getData(mesh=1)

# next line prints coord without deformation
mesh.verts[2].co.x

# this is where the magic happens
# getFromObject implicitly applies all modifiers.
tmesh.getFromObject(obj,0,1)

#next line prints coord with deformation
tmesh.verts[2].co.x

OK, this shows that we can export the deformed mesh. Now we need to get each
frame. So I insert a keyframe for the rotation of the bone (pose mode),
go 10 frames further and pose with the tip of the bone pointing right.

First, let’s repeat the first experiment,
browsing to frame 11 (left/right arrow keys)

tmesh.getFromObject(obj,0,1)
tmesh.verts[2].co.x

This shows that the script is, by default,
querying the current frame.
I found a command to change the frame:

context = scene.getRenderingContext()
# set the current frame
context.currentFrame(3)
scene.update(1)
tmesh.getFromObject(obj,0,1)
tmesh.verts[2].co.x

If you repeat this operation using the frame value, you will see that the vertex coordinate moves at every frame.

Next time I’ll look into exporting several actions for a given character.

If you are using Blender for your 3D models, then you’ll want to export them to a format that OpenGL ES can understand. Maybe you just want to extract the data from Blender and load it into another format than just ‘raw vertex arrays’ – either way at the moment this is a little bit of a pain because there’s nothing like a standard script (see this article for roundabout solutions).

I’m working on a nice and nicer script for doing that. Today I’m just posting the code for a very simple script – after playing a little bit with this, maybe you’ll want to write your own export script (link to blender wiki-book), not because it’s easy with a little know-how, but because that will give you flexibility at low cost.

Outline

  • I export binary data from Blender using Python. It’s easy to write scripts that do that, especially because Blender has an interactive Python console and a data browser, so we can explore the structure underlying a scene dynamically and pick what we need. Other solutions tend to rely on parsing an *.obj or *.vrml file generated by a standard export script. This gives no control over what gets exported, and I believe parsing is harder than just exporting ad-hoc binary data and reading it back into your iPhone app.
  • I import the binary data using trivial stdio C functions. That is also easy – just check the code.
  • Writing/reading binary data may require running the Python script on your Apple Mac. I believe the ‘struct’ python class/module just writes using the system’s default binary types. So for example if you work with a graphic designer and for some reason they run Blender on a PC, you might get garbled output.

Here’s my python script – that needs to go in the blender scripts folder (see below):

#!BPY
# my_export_script.py
# this writes out the number of triangles in the
# model as an int, then writes out each coordinate for each vertex
# for each triangle. Indexed data is readily available from Blender
# and would be way more efficient.
""" Registration info for Blender menus:
Name: 'Test for simple mesh export2'
Blender: 249
Group: 'Export'
Tip: 'Export selected mesh for a test'
"""

import Blender
import bpy
import struct

def write_face_vertices(face,out):
	k=face.verts
	write_vertex(k[0],out)
	write_vertex(k[1],out)
	write_vertex(k[2],out)
	write_vertex(k[2],out)
	write_vertex(k[3],out)
	write_vertex(k[0],out)

def write_vertex(k,out):
	out.write(struct.pack('f',k.co.x))
	out.write(struct.pack('f',k.co.y))
	out.write(struct.pack('f',k.co.z))	

def write_obj(filepath):
	out = open(filepath, 'wb')
	sce = bpy.data.scenes.active
	ob = sce.objects.active
	mesh = ob.getData(mesh=1)
	out.write(struct.pack('i',len(mesh.faces)*2))
	#out.write('face_count %i\n' % (len(mesh.faces)))
	for face in mesh.faces:
		write_face_vertices(face,out)
	out.close()

Blender.Window.FileSelector(write_obj, "Export")

Now, here’s the class that reads the data – that goes directly into your project and requires nothing – well yes, you’d have to write your own header file for it :).

// RawMesh.m loads raw mesh data from the main resource bundle.

// This is where files end up when you build after

// adding them to your XCode project
// triangleCount is an int; vertexArray is a float*
// You could probably make vertexArray a float[] (a C array, not NSArray)
// and avoid the malloc/free worry doll.

#import “RawMesh.h”

#define VERTICES_PER_TRIANGLE 9

@implementation RawMesh

@synthesize triangleCount,vertexArray;

-(id)initFromResourceFile:(NSString*) fname{

if (self=[super init]) {

// get the file path from main resource bundle

NSBundle *mainBundle = [NSBundle mainBundle];

// rglmd=raw gl mesh data

NSString* path = [mainBundle pathForResource:fname ofType:@"rglmd"];

FILE * fh = fopen([path UTF8String], “rglmd”);

// determine the number of faces

fread(&triangleCount, sizeof(int), 1, fh);

NSLog(@”FACES:%i”,triangleCount);

vertexArray= malloc(sizeof(float)*triangleCount*VERTICES_PER_TRIANGLE);

fread(vertexArray, sizeof(float),triangleCount*VERTICES_PER_TRIANGLE, fh);

}return self;

}

-(void)dealloc{

[super dealloc];

free(vertexArray);

}

@end

Finally, here’s the code that renders this using GL-ES:

RawMesh* mesh=[[RawMesh alloc]initFromResourceFile:filename];
glVertexPointer(3, GL_FLOAT, 0, mesh.vertexArray);
// let the gl know that we want to use the vertex array.
glEnableClientState(GL_VERTEX_ARRAY);
// since the script didn't import color or material, just use anything
glColor4f(1.0f,0.6f,0.0f,0.0f);
// does this need an explanation?
glDrawArrays(GL_TRIANGLES, 0, mesh.triangleCount*3);

I hope this can help you. I’d like to comment more on the above (and please keep in mind it’s just a basic script to get started – I know there’s no animation, no materials, not even normals).

One more thing: on Mac OSx, you need to dig blender.app and look inside it to find Blender’s script folder. Because this is further buried down in a (hidden) dot file this can be a bit of a pain. But the blender wiki book (see my previous article) explains how to do it.

Here you are. I’ll improve this article. I didn’t want to delay publishing this code because many iPhone devs, like me, come from other languages and other platforms, so Python and C stdio can seem a little intimidating – collecting the information to write this misery of a scriptlet took me hours, now here it is.