Blender Mesh Export Script

posted in NubDevice
Published March 14, 2019
Advertisement

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. 

io_geo_format.jpg.944cb6f48bb196de89001bf97a4996d5.jpg

 


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()
    bm.from_mesh(me)
    bmesh.ops.triangulate(bm, faces=bm.faces[:], quad_method=0, ngon_method=0)
    bm.to_mesh(me)
    bm.free()
    
    
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)):
                                    faceBuff.append(int(i))
                                    found = 1
                                    break
                i+=1
            #otherwise stash a new vertex
            if(found==0):
                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)
            ofile.close()
        return {'FINISHED'}
    else:
        
        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)
            ofile.close()
        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'):
            bpy.ops.object.mode_set(mode='OBJECT')
            
        if(self.use_triangulate):
            triangulateObject(context.active_object)
            
        writeObject(self, context);        
        return {'FINISHED'}

    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        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():
    bpy.utils.register_class(ObjectExport)
    bpy.types.INFO_MT_file_export.append(menu_func_export)


def unregister():
    bpy.utils.unregister_class(ObjectExport)
    bpy.types.INFO_MT_file_export.remove(menu_func_export)


if __name__ == "__main__":
    register()

 

Next Entry Back to Work
1 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement