Blog of roxlu, co-founder of Apollo Media. Contact info[shift+2]apollomedia.nl.

Export blender object to simple file format

Update 2015:

The following script generates a C/C++ header that you can include in your project. By default we will include the glad.h (see the glad repository on github) header to make sure we have the openGL defines/functions.

"""
 
Exports the selected object in blender to class with the same
name as the selected object. We create a vao and vbo the first time
the draw function is called. 
 
To use this script:
    - Copy and paste the contents in to a new blender script
    - Execute/Run the script with the select object.
    - Save the header file.
 
"""
 
import bpy
import math
import mathutils
import os
import bmesh
from mathutils import Vector, Matrix
 
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import Operator
 
o = bpy.context.active_object
print("="*40)
 
output = []
mat_x90 = mathutils.Matrix.Rotation(-math.pi/2, 4, 'X')
conv_mat = o.matrix_world
 
# Build the output list.
# --------------------------------------------------
def add_vertex(v, n):
    v = conv_mat * v 
    v = mat_x90 * v 
    output.append(v.x)
    output.append(v.y)
    output.append(v.z)
    output.append(n.x)
    output.append(n.y)
    output.append(n.z)
 
bm = bmesh.new()
bm.from_mesh(o.data)
bmesh.ops.triangulate(bm, faces=bm.faces)
 
for face in bm.faces: 
    for v in face.verts:
        add_vertex(v.co, face.normal)
 
bm.free()
del bm
# --------------------------------------------------
 
def create_opengl_header(context, filepath): 
    out_floats = [ '%.3f' % elem for elem in output ]
    out_floats_str = "float vertices[] = {" +",".join(out_floats) +"};"
 
    out_header = (""
    "#ifndef " +o.name.upper() +"_H\n"
    "#define " +o.name.upper() +"_H\n"
    "\n"
    "#include <glad/glad.h>\n"
    "\n"
    "class " +o.name +" {\n"
    " public: \n"
    "  void draw();\n"
    "};"
    "\n"
    "\n"
    "/* --------------------------------------------------------------- */\n"
    "\n"
    "inline void " +o.name +"::draw() {\n"
    "\n"
    "  static GLuint vao = 0;\n"
    "  static GLuint vbo = 0;\n"
    "\n"
    "  int nvertices = " +str(len(output)) +";\n"
    "  " +out_floats_str +"\n"
    "\n"
    "  if (0 == vao) {\n"
    "    glGenVertexArrays(1, &vao);\n"
    "    glBindVertexArray(vao);\n"
    "    glGenBuffers(1, &vbo);\n"
    "    glBindBuffer(GL_ARRAY_BUFFER, vbo);\n"
    "    glBufferData(GL_ARRAY_BUFFER, nvertices * sizeof(float), vertices, GL_STATIC_DRAW);\n"
    "    glEnableVertexAttribArray(0); /* positions */\n"
    "    glEnableVertexAttribArray(1); /* normals */\n"
    "    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (GLvoid*)0);\n"
    "    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (GLvoid*)12);\n"
    "  }\n"
    "\n"
    "  glBindVertexArray(vao);\n"
    "  glDrawArrays(GL_TRIANGLES, 0, " +str(len(output)) +");\n"
    "}\n"
    "\n"
    "#endif\n"
    "") 
 
    f = open(filepath, 'w', encoding='utf-8')
    f.write(out_header)
    f.close()
 
    return {'FINISHED'}
 
# This snippet is necessary to popup the file save window.
# Also adds this operator to the 'space bar menu'
class ExportSomeData(Operator, ExportHelper):
    bl_idname = "export_opengl_header.exp"  
    bl_label = "Export OpenGL Header(ROXLU)"
 
    filename_ext = ".h"
    filter_glob = StringProperty(
            default="*.h",
            options={'HIDDEN'},
            )
 
    def invoke(self, context, event):
        self.filepath = o.name +".h"
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}
 
    def execute(self, context):
        return create_opengl_header(context, self.filepath)
 
 
def menu_func_export(self, context):
    self.layout.operator(ExportSomeData.bl_idname, text="Export OpenGL Header")
 
def register():
    bpy.utils.register_class(ExportSomeData)
    bpy.types.INFO_MT_file_export.append(menu_func_export)
 
def unregister():
    bpy.utils.unregister_class(ExportSomeData)
    bpy.types.INFO_MT_file_export.remove(menu_func_export)
 
 
if __name__ == "__main__":
    register()
    bpy.ops.export_opengl_header.exp('INVOKE_DEFAULT')

Scripts from 2012, 2013, 2014:

Tiny script to export a blender object to a OBJ like of file. This format is very simple to parse. I use this sometimes when working on small simulations where I want to have a 3D model.

import bpy
import math
import mathutils
from mathutils import Vector, Matrix
o = bpy.context.active_object
print("="*40)
output = []
mat_x90 = mathutils.Matrix.Rotation(-math.pi/2, 4, 'X')
conv_mat = o.matrix_world
 
def add_vert(face_index, n):
    v = o.data.vertices[face_index].co
    v = conv_mat * v 
    v = mat_x90 * v 
    output.append("v %f %f %f %f %f %f" % (v.x, v.y, v.z, n.x, n.y, n.z))
 
 
def add_face(face, norm):
    if len(face) == 4:
        add_vert(face[0], norm)
        add_vert(face[1], norm)
        add_vert(face[2], norm)
        add_vert(face[0], norm)
        add_vert(face[2], norm)
        add_vert(face[3], norm)
    elif len(face) == 3:
        add_vert(face[0], norm)
        add_vert(face[1], norm)
        add_vert(face[2], norm)
 
 
for face in o.data.polygons:
    verts = face.vertices[:]
    n = face.normal
    add_face(verts, n)
 
out_str = "\n".join(map(str, output))

Another example which lets the user select the destination path of the exported file:

import bpy
import math
import mathutils
from mathutils import Vector, Matrix
 
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import Operator
 
 
# Get the active object, create rotation matrix for Blender > GL, coordinate system
o = bpy.context.active_object
print("="*40)
output = []
mat_x90 = mathutils.Matrix.Rotation(-math.pi/2, 4, 'X')
conv_mat = o.matrix_world
 
# Collect all vertices / normals
def add_vert(face_index, n):
    v = o.data.vertices[face_index].co
    v = conv_mat * v 
    v = mat_x90 * v 
    output.append("v %f %f %f %f %f %f" % (v.x, v.y, v.z, n.x, n.y, n.z))
 
 
# Export the given face    
def add_face(face, norm):
    if len(face) == 4:
        add_vert(face[0], norm)
        add_vert(face[1], norm)
        add_vert(face[2], norm)
        add_vert(face[0], norm)
        add_vert(face[2], norm)
        add_vert(face[3], norm)
    elif len(face) == 3:
        add_vert(face[0], norm)
        add_vert(face[1], norm)
        add_vert(face[2], norm)
 
 
# Called when user selected a file, saved the file
def write_some_data(context, filepath):
    for face in o.data.polygons:
        verts = face.vertices[:]
        n = face.normal
        add_face(verts, n)
 
    out_str = "\n".join(map(str, output))
    f = open(filepath, 'w', encoding='utf-8')
    f.write(out_str)
    f.close()
    return {'FINISHED'}
 
 
# This snippet is necessary to popup the file save window.
# Also adds this operator to the 'space bar menu'
class ExportSomeData(Operator, ExportHelper):
    bl_idname = "export_simple_obj.exp"  
    bl_label = "Export Simple Object (ROXLU)"
 
    filename_ext = ".txt"
    filter_glob = StringProperty(
            default="*.txt",
            options={'HIDDEN'},
            )
    def execute(self, context):
        return write_some_data(context, self.filepath)
 
 
def menu_func_export(self, context):
    self.layout.operator(ExportSomeData.bl_idname, text="Simple Roxlu exporter")
 
def register():
    bpy.utils.register_class(ExportSomeData)
    bpy.types.INFO_MT_file_export.append(menu_func_export)
 
def unregister():
    bpy.utils.unregister_class(ExportSomeData)
    bpy.types.INFO_MT_file_export.remove(menu_func_export)
 
 
if __name__ == "__main__":
    register()
    bpy.ops.export_simple_obj.exp('INVOKE_DEFAULT')

We transform from the default Blender orientation to the default openGL orientation (z pointing into the screen).

"""
 
Exports the selected object in blender to an float array with 
vertices and normals. We do not use vertex indices but rather 
duplicate each triangle. 
 
"""
 
import bpy
import math
import mathutils
from mathutils import Vector, Matrix
o = bpy.context.active_object
print("="*40)
output = []
mat_x90 = mathutils.Matrix.Rotation(-math.pi/2, 4, 'X')
conv_mat = o.matrix_world
 
def add_vert(face_index, n):
    v = o.data.vertices[face_index].co
    v = conv_mat * v 
    v = mat_x90 * v 
    output.append(v.x)
    output.append(v.y)
    output.append(v.z)
    output.append(n.x)
    output.append(n.y)
    output.append(n.z)
 
 
def add_face(face, norm):
    if len(face) == 4:
        add_vert(face[0], norm)
        add_vert(face[1], norm)
        add_vert(face[2], norm)
        add_vert(face[0], norm)
        add_vert(face[2], norm)
        add_vert(face[3], norm)
    elif len(face) == 3:
        add_vert(face[0], norm)
        add_vert(face[1], norm)
        add_vert(face[2], norm)
 
 
for face in o.data.polygons:
    verts = face.vertices[:]
    n = face.normal
    add_face(verts, n)
 
out_str = "int nvertices = " +str(len(output)) +";\n"
out_floats = [ '%.3f' % elem for elem in output ]
out_str += "float vertices[] = {" +",".join(out_floats) +"};"
 
dest = "path_to_your_header_file/bone.h"
f = open(dest, 'w+')
f.write(out_str)
f.close()
print("Created.")

And another updated (May 2017) version which exports the vertices into a .h and .cpp file in the same directory as the .blend file and correctly sets some external variables.

"""
 
Exports the selected object in blender to an float array with 
vertices and normals. The vertices are rotated into the OpenGL
axis system. We do not use vertex indices but rather 
duplicate each triangle. 
 
We create a .h and .cpp file called "Model[ObjectName].h/cpp" 
and save it into the same location as the .blend file.
 
"""
 
import bpy
import math
import mathutils
from mathutils import Vector, Matrix
o = bpy.context.active_object
print("="*40)
output = []
mat_x90 = mathutils.Matrix.Rotation(-math.pi/2, 4, 'X')
conv_mat = o.matrix_world
 
def add_vert(face_index, n):
    v = o.data.vertices[face_index].co
    v = conv_mat * v 
    v = mat_x90 * v 
    output.append(v.x)
    output.append(v.y)
    output.append(v.z)
    output.append(n.x)
    output.append(n.y)
    output.append(n.z)
 
 
def add_face(face, norm):
    if len(face) == 4:
        add_vert(face[0], norm)
        add_vert(face[1], norm)
        add_vert(face[2], norm)
        add_vert(face[0], norm)
        add_vert(face[2], norm)
        add_vert(face[3], norm)
    elif len(face) == 3:
        add_vert(face[0], norm)
        add_vert(face[1], norm)
        add_vert(face[2], norm)
 
 
for face in o.data.polygons:
    verts = face.vertices[:]
    n = face.normal
    add_face(verts, n)
 
model_name = o.name;
 
out_header_str = "" \
 +"#ifndef MODEL_" +model_name.upper() +"_H\n" \
 +"#define MODEL_" +model_name.upper() +"_H\n" \
 +"extern int nfloats_" +model_name.lower() +";\n" \
 +"extern int nvertices_" +model_name.lower() +";\n" \
 +"extern float vertices_" +model_name.lower() +"[];\n" \
 +"#endif"
 
out_cpp_str = "" \
 +"#include \"Model" +model_name +".h\"\n" \
 +"int nfloats_" +model_name.lower() +" = " +str(len(output)) +";\n" \
 +"int nvertices_" +model_name.lower() +" = " +str((int(len(output) / 6))) +";\n" 
out_cpp_floats = [ '%.3f' % elem for elem in output ]
out_cpp_str += "float vertices_" +model_name.lower() +"[] = {" +",".join(out_cpp_floats) +"};"
 
header_filepath = bpy.path.abspath("//") +"Model" +model_name +".h"
cpp_filepath = bpy.path.abspath("//") +"Model" +model_name +".cpp"
 
f = open(cpp_filepath, 'w+')
f.write(out_cpp_str)
f.close()
 
f = open(header_filepath, "w+")
f.write(out_header_str)
f.close()
 
print("Created.")