Jump to content
  • Advertisement

Project: NubDevice

Blender Mesh Export Script

Sign in to follow this  


This vacation week was nice for me. Gained some knowledge from the community so tonight I'm giving back. 

When I'm playing with my toy 3D frameworks, I like to have mesh data in an easy to read format. When I load, all I want to see are two integers at the start for how many vertices to read and how many face indices tightly follow, and do the bulk read. Easy fast data in engine side.

Generating that data I use Blender as my modeler of choice and this export file format has worked well. I'd like to share my custom export script supporting vertex position, texture coordinates and face indices as an export add-on. It's a nice jumping off point for your own custom export requirements. Currently it's sensitive to the object mode selection, triangulates mesh data, flips or not the v texture coordinate and writes optionally a binary or ascii format mesh file with a .geo file extension.. 

Use as you wish. 



bl_info = {
    "name": "Geo Format Exporter",
    "description": "Writes geometry format to disk",
    "author": "Mark Kughler (GoliathForgeOnline)",
    "version": (1, 0),
    "blender": (2, 79, 0),
    "location": "File > Export > GEO",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "support": 'COMMUNITY',
    "category": "Import-Export"

import bpy
import bmesh
import struct #https://docs.python.org/3/library/struct.html
from bpy import context

def triangulateObject(obj):
    me = obj.data
    bm = bmesh.new()
    bmesh.ops.triangulate(bm, faces=bm.faces[:], quad_method=0, ngon_method=0)
def writeObject(self, context):
    ob = context.object
    uv_layer = ob.data.uv_layers.active.data
    vertBuff = []
    uvBuff   = []
    faceBuff = []
    #rebuild vertex, uv and face indices excluding duplicates
    for poly in ob.data.polygons:
        for index in poly.loop_indices:
            thisVertex = ob.data.vertices[ob.data.loops[index].vertex_index].co 
            thisUV = uv_layer[index].uv
            #check if already in the list
            i = 0
            found = 0
            for v in vertBuff:
                if(abs(v.x-thisVertex.x) <= max(1e-09 * max(abs(v.x), abs(thisVertex.x)), 0.0)):
                    if(abs(v.y-thisVertex.y) <= max(1e-09 * max(abs(v.y), abs(thisVertex.y)), 0.0)):
                        if(abs(v.z-thisVertex.z) <= max(1e-09 * max(abs(v.z), abs(thisVertex.z)), 0.0)):
                            if(abs(uvBuff[i].x-thisUV.x) <= max(1e-09 * max(abs(uvBuff[i].x), abs(thisUV.x)), 0.0)):
                                if(abs(uvBuff[i].y-thisUV.y) <= max(1e-09 * max(abs(uvBuff[i].y), abs(thisUV.y)), 0.0)):
                                    found = 1
            #otherwise stash a new vertex
                faceBuff.append(len(vertBuff)) #index
                vertBuff.append(thisVertex)    #float, float, float
                uvBuff.append(thisUV)          #float, float
    #write to file
    if(self.format == "OPT_A"):
        with open(self.filepath, 'w') as ofile:
            ofile.write("%d " % len(vertBuff)) #num unique vertex/uv pairs
            ofile.write("%d " % len(faceBuff)) #num indices
            for v in vertBuff:
                ofile.write("%f %f %f " % v[:])
            for t in uvBuff:
                ofile.write("%f %f " % t[:])
            for p in faceBuff:
                ofile.write("%d " % p)
        return {'FINISHED'}
        with open(self.filepath, 'wb') as ofile:
            ofile.write(struct.pack('H', len(vertBuff))) 
            ofile.write(struct.pack('H', len(faceBuff)))
            for v in vertBuff:
                ofile.write(struct.pack('3f', v.x, v.y, v.z)) #v[:])) #"%f %f %f " % v[:])
            for t in uvBuff:
                ofile.write(struct.pack('2f', t.x, t.y)) #t[:])) #"%f %f " % t[:])
            for p in faceBuff:
                ofile.write(struct.pack('H', p)) #"%d " % p)
        return {'FINISHED'}    

class ObjectExport(bpy.types.Operator):
    """My object export script"""
    bl_idname = "object.export_geo"
    bl_label = "Geo Format Export"
    bl_options = {'REGISTER', 'UNDO'}
    filename_ext = ".geo"
    total           = bpy.props.IntProperty(name="Steps", default=2, min=1, max=100)
    filter_glob     = bpy.props.StringProperty(default="*.geo", options={'HIDDEN'}, maxlen=255)
    use_setting     = bpy.props.BoolProperty(name="Selected only", description="Export selected mesh items only", default=True)
    use_triangulate = bpy.props.BoolProperty(name="Triangulate", description="Triangulate object", default=True)
    format          = bpy.props.EnumProperty(name="Format", description="Choose between two items", items=(('OPT_A', "ASCII ", "Text file format"), ('OPT_B', "Binary", "Binary file format")), default='OPT_A')

    filepath = bpy.props.StringProperty(subtype='FILE_PATH')    
    def execute(self, context):
        if(context.active_object.mode == 'EDIT'):
        writeObject(self, context);        
        return {'FINISHED'}

    def invoke(self, context, event):
        return {'RUNNING_MODAL'}

# Add trigger into a dynamic menu
def menu_func_export(self, context):
    self.layout.operator(ObjectExport.bl_idname, text="Geometry Export (.geo)")

def register():

def unregister():

if __name__ == "__main__":


Sign in to follow this  


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!