   3DSMax or Blender exporter tutorials?
  EDIT: I'll update this post rather than keep posting huge source blocks.  For those just reading, currently I'm working on a Blender exporter (see below) before moving on to a MAXScript exporter.I've come a really long way, thanks in no small part to those who've already posted.  I can't express my gratitude, and I'd point anyone looking to write an exporter to the API and the scripts that come with Blender.  They serve as extraordinarily useful examples and I've learned from them by leaps and bounds.All that's left now is a scale slider (trivial) and the tag information and I'm essentially finished.#!BPY"""Name: 'Zero Hassle 3D (.zh3)'Blender: 249Group: 'Export'Tooltip: 'Export to Zero Hassle 3D format'"""__author__ = "Zouflain"__bpydoc__ = """A very simple file exporter for an ASCIIformated model dubbed "Zero Hassle 3D."The zh3 is reminiscent of IDSoftware's MD3and MD5 formats, and is designed to combinethe ease of vertex/frame interpolation withthe legibility of MD5.UV coordinates may be sticky or per vertex.This, like other vertex methods, does notallow changes in the order or number ofvertices within the mesh."""import Blenderimport bpyfrom Blender import *from Blender.BGL import *from Blender.Window import *from Blender.Draw import *#===================================================##Streamlines the export process by requiring a simple input field#This is where animations are user defined#This is also where the location of the Shader is derived#eventsEVENT_NONE = 0EVENT_CANCEL = 1EVENT_EXPORT = 2#fieldsFIELD_ANIMATION = Create("example")FIELD_FSTART = Create(0)FIELD_FEND = Create(10)FIELD_FPS = Create(30.0)FIELD_SHADER = Create("res/textures/example.bmp")FIELD_FRAMES = Create(10)FIELD_LOOP = Create(0)ANIMATIONS = []#function call backsdef callback_setshader(event, val):global FIELD_LOOPFIELD_LOOP.val = valdef callback_setanimation(event, val):global FIELD_ANIMATIONFIELD_ANIMATION.val = valdef callback_addanimation(event, val):global FIELD_FPS,FIELD_FSTART,FIELD_FEND,FIELD_ANIMATIONglobal FIELD_LOOPANIMATIONS.append("    %s %d %d %f %d\n" % (FIELD_ANIMATION.val,FIELD_FSTART.val,FIELD_FEND.val,FIELD_FEND.val/FIELD_FPS.val,FIELD_LOOP.val ) )def callback_dropanimation(event, val):global ANIMATIONSif len(ANIMATIONS) > 0:ANIMATIONS.pop()def callback_setfps(event, val):global FIELD_FPSFIELD_FPS.val = valdef callback_setfstart(event, val):global FIELD_FENDFIELD_FSTART.val = valdef callback_setfend(event, val):global FIELD_FENDFIELD_FEND.val = valdef callback_setframes(event,val):global VAL_FSTART,VAL_FEND,VAL_FRAMESFIELD_FRAMES.val = valif FIELD_FSTART.val > FIELD_FRAMES.val-1:VAL_FSTART.val = FIELD_FRAMES.val-1if FIELD_FEND.val > FIELD_FRAMES.val-FIELD_FSTART.val:FIELD_FEND.val = FIELD_FRAMES.val-FIELD_FSTART.valdef callback_setloop(event,val):global FIELD_LOOPFIELD_LOOP.val = val#GUI main functionsdef ExportGUI_Main():global EVENT_NONE,EVENT_CANCEL,EVENT_EXPORTglobal FIELD_ANIMATION,FIELD_FSTART,FIELD_FEND,FIELD_FPS,FIELD_SHADERglobal FIELD_FRAMES,FIELD_LOOPglobal ANIMATIONStxt_y = 10*len(ANIMATIONS)glClear(GL_COLOR_BUFFER_BIT)glRasterPos2i(315,txt_y+10)Text("Animations List:")for i, str in enumerate(ANIMATIONS):glRasterPos2i(305,txt_y-10*i)Text(str)FIELD_FRAMES = Number("Export Frames: ", EVENT_NONE, 10, 135, 210, 18,FIELD_FRAMES.val, 1, 250, "export how many frames?",callback_setframes)FIELD_ANIMATION = String("Animation: ", EVENT_NONE, 10, 105, 210, 18,FIELD_ANIMATION.val, 255, "new animation name",callback_setanimation)FIELD_FSTART = Number("Start Frame: ", EVENT_NONE, 10, 85, 105, 18, FIELD_FSTART.val, 0, FIELD_FRAMES.val-1,"starting frame index",callback_setfstart)FIELD_FEND = Number("Frames: ", EVENT_NONE, 120, 85, 100, 18, FIELD_FEND.val, 1, FIELD_FRAMES.val-FIELD_FSTART.val,"total frames in animation",callback_setfend)FIELD_FPS = Number("FPS: ", EVENT_NONE, 10, 65, 105, 18, FIELD_FPS.val, 1.0, 60.0,"frames per second",callback_setfps)FIELD_LOOP = Number("Behavior: ", EVENT_NONE, 120, 65, 100, 18, FIELD_LOOP.val, 0, 1,"0: End, 1: Loop",callback_setloop)FIELD_SHADER = String("Shader: ", EVENT_NONE, 10, 35, 210, 18,FIELD_SHADER.val, 255, "path to shader",callback_setshader)Button("Export",EVENT_EXPORT, 10, 10, 80, 18,"write data to file")Button("Add",EVENT_NONE, 230, 85, 80, 18,"add new animation",callback_addanimation)Button("Drop",EVENT_NONE, 230, 65, 80, 18,"drop last animation",callback_dropanimation)Button("Exit",EVENT_CANCEL , 170, 10, 80, 18,"cancel")def ExportGUI_Event(event,val):if event == QKEY and not val:Exit()def ExportGUI_Button(event):global EVENT_NONE,EVENT_CANCEL,EVENT_EXPORTif event==EVENT_CANCEL:Exit()elif event==EVENT_EXPORT:Blender.Window.FileSelector(Write,'Export ZH3',sys.makename(ext='.zh3') )Exit()elif event==EVENT_NONE:Draw()#===================================================##Actual export scriptdef Write(filename):#delcare variablesglobal ANIMATIONS,FIELD_SHADER,FIELD_FRAMESout = file(filename,"w")sce = bpy.data.scenes.activecontext = sce.getRenderingContext()f_original = Blender.Get('curframe')f_count = FIELD_FRAMES.val#setup BlenderWindow.EditMode(0)Blender.Window.WaitCursor(1)#print headernum_verts = 0num_tags = 0out.write ('version 01\n')for mesh in [obj.getData(mesh=1) for obj in sce.objects if obj.type == 'Mesh']:for vert in mesh.verts:num_verts = num_verts + 1out.write('Vertices %i\n' % (num_verts) )out.write('Tags %i\n' % (num_tags) )out.write('Frames %i\n' % (f_count) )out.write('Shader %s\n\n' % (FIELD_SHADER.val) )#copy animationsout.write('Animations{\n')for anim in ANIMATIONS:out.write(anim)out.write('}\n')#copy trianglesout.write('Triangles{\n')for mesh in [obj.getData(mesh=1) for obj in sce.objects if obj.type == 'Mesh']:for face in mesh.faces:tris = []for i in range(1,len(face.v)-1):tris.append([0,i,i+1])for tri in tris:out.write( ('    %i %i %i') % (face.v[tri[0] ].index,face.v[tri[1] ].index,face.v[tri[2] ].index) )for i in range(0,3):if mesh.faceUV:coord_x = face.uv.xcoord_y = face.uv.yelif mesh.vertexUV:coord_x = vert.uvco.xcoord_y = vert.uvco.yelse:coord_x = 0coord_y = 0out.write( (' (%f,%f)') % (coord_x,coord_y) )out.write('\n')out.write('}\n')#copy TagNames??  More custom data.  Name of each Tag object#copy framesfor frame in range(f_count):Blender.Set('curframe',frame)sce.update(1)out.write('Frame{\n')for obj in sce.objects:if obj.type=='Mesh':mesh = obj.getData(mesh=1).copy()#don't edit anything!mesh.getFromObject(obj)mesh.transform(obj.matrixWorld)for vert in mesh.verts:out.write('    Vertex (%f %f %f) ( %f %f %f)\n' % (vert.co.x, vert.co.z, vert.co.y, vert.no.x, vert.no.z, vert.no.y) )out.write('}\n')#close up and exitout.close()Blender.Set('curframe',f_original)Blender.Window.WaitCursor(0)#===================================================#Register(ExportGUI_Main,ExportGUI_Event,ExportGUI_Button)---Original Post---I've written a fairly useful ASCII file format as a means of finally breaking into the realm of animated 3D.  Basically it takes the vertex animation of .MD3 and fuses it with the style and legibility of .MD5. Thing is, I don't have the actual files to put it to any use.  I'd like to write a Blender and MAXScript exporter (preferably both, but either will do as file transfer between the two is not difficult as I understand) that can create these files.  Maya would be icing on the cake, but I'm not particularly interested.Thing is, I have no experience with 3DSMax, and very little with blender.  I'm also entirely ignorant on how MAXScript functions and have no experience in python.  I'm willing to learn (if I can be productive in doing so, most tutorials explain how to make a sphere and stop long before reaching exporters), and I'd love to read any pertinent tutorials you're aware of.  All of my google searching has turned up already completed exporters, but not tutorials.  I have read the files on gamedev, and they're a start, but I would really appreciate more.I also have a question that may or may not be common in writing file exporters.  The .md3 format uses a system of "tags" that signify where another model will be attached.  This is just a point in object space that gets animated along with the mesh, rather like a vertex.  It also has an orientation.  How hard is it to create this sort of custom data?Here's a sample of what I'm trying to export to (the values here are mostly garbage values, but it's in the proper format):Version 01Vertices 4Tags 3Shader res/sample.bmp //no quotes, path to textureAnimations{idle 0 3 3.000 1 //name|begin|frame_count|duration|loop?dead 3 1 1.000 0}Triangles{2 1 0 (0.0,0.0)(0.0,0.0)(0.0,0.0)//UV coords for each vertex0 3 2 (0.0,0.0)(0.0,0.0)(0.0,0.0)}Frame{//frame0 = bind pose (pose without animation)Tag hand (0.000 0.000 0.000) (0.000 0.000 0.000 0.000) //name|location(xyz)|quaternion(wxyz)Tag head (0.000 0.000 0.000) (0.000 0.000 0.000 0.000)Tag torso (0.000 0.000 0.000) (0.000 0.000 0.000 0.000)Vertex (0.000 0.000 0.000) (1.000 1.000 1.000) //location(xyz)|normal(xyz)Vertex (10.000 0.000 0.000) (1.000 1.000 1.000)Vertex (10.000 10.000 0.000) (1.000 1.000 1.000)Vertex (0.000 10.000 0.000) (1.000 1.000 1.000)}Frame{Tag hand (0.000 0.000 0.000) (0.000 0.000 0.000 0.000)Tag head (0.000 0.000 0.000) (0.000 0.000 0.000 0.000)Tag torso (0.000 0.000 0.000) (0.000 0.000 0.000 0.000)Vertex (1.000 1.000 1.000) (1.000 1.000 1.000)Vertex (1.000 1.000 1.000) (1.000 1.000 1.000)Vertex (1.000 1.000 1.000) (1.000 1.000 1.000)Vertex (1.000 1.000 1.000) (1.000 1.000 1.000)}Frame{Tag hand (0.000 0.000 0.000) (0.000 0.000 0.000 0.000)Tag head (0.000 0.000 0.000) (0.000 0.000 0.000 0.000)Tag torso (0.000 0.000 0.000) (0.000 0.000 0.000 0.000)Vertex (1.000 1.000 1.000) (1.000 1.000 1.000)Vertex (1.000 1.000 1.000) (1.000 1.000 1.000)Vertex (1.000 1.000 1.000) (1.000 1.000 1.000)Vertex (1.000 1.000 1.000) (1.000 1.000 1.000)}Frame{Tag hand (0.000 0.000 0.000) (0.000 0.000 0.000 0.000)Tag head (0.000 0.000 0.000) (0.000 0.000 0.000 0.000)Tag torso (0.000 0.000 0.000) (0.000 0.000 0.000 0.000)Vertex (1.000 1.000 1.000) (1.000 1.000 1.000)Vertex (1.000 1.000 1.000) (1.000 1.000 1.000)Vertex (1.000 1.000 1.000) (1.000 1.000 1.000)Vertex (1.000 1.000 1.000) (1.000 1.000 1.000)}[Edited by - Zouflain on April 7, 2010 12:00:32 PM]
  Have you tried this?   ;  Thank you, that's an excellent start for writing an exporter for Blender.  I'd forgotten about how well documented Blender was - I was spending most of my effort looking for information on MaxScript.  Thing is, that tutorial doesn't make any mention of keyframes, and though I looked through the wikibook, I couldn't piece together A) how keyframes and animations work in Blender, and B) how to export vertex positions at a frame.  I really stress animation because still images are incredibly easy to export/load.   ;  Some snippets I found in a test exporter I wrote some time ago for Blender 2.48:#...snipcontext = sce.getRenderingContext()frame_start = context.startFrame()frame_end = context.endFrame()#...snip#Loop through all frames and write the verticesfor frame in xrange(frame_start, frame_end + 1):    Blender.Set('curframe', frame)  me = BPyMesh.getMeshFromObject(your_object, None, True, False, sce)   #Write frame-specific vertices using for example:    #    position = me.verts[index].co * object.worldMatThis seems to work well enough, but I wouldn't be surprised to see better methods. I have not looked into getting *keyframes* from the IPO/action view, but digging around the blender python API docs might give you some pointers (or checking out the included .fbx exporter might be useful too).   ;  Thank you very much.  Because of your efforts, this is actually going a lot more smoothly than I would have dreamed.  Still, I've hit a few road blocks with my export script:#!BPY"""Name: 'ZH3 Exporter'Blender: 244Group: 'Export'ToolTile: 'Zero Hassle 3D Exporter'"""import Blenderimport bpydef write(filename):out = file(filename,"w")sce = bpy.data.scenes.activecontext = sce.getRenderingContext()f_start = context.startFrame()f_end = context.endFrame()#print headernum_verts = 0num_tags = 0texture_name = 'How_to_retrieve_this?'out.write ('version 01\n')for ob in sce.objects:mesh = obj.getData(mesh=1)for vert in mesh.verts:num_verts = num_verts + 1out.write('Vertices %i\n' % (num_verts) )out.write('Shader %s\n' % (texture_name) )out.write('Tags %i\n' % (num_tags)#copy animations?? No idea about this.  See sample#copy trianglesout.write('Triangles{\n')for ob in sce.objects:mesh = ob.getData(mesh=1)for face in mesh.faces:for vert in face.vout.write(' %i' % (vert.index+1) )out.write('\n')out.write('}\n')#copy UV coordinates?? This should be easier... but I don't know how#copy TagNames??  More custom data.  Name of each Tag object#copy framesfor frame in xrange(f_start,f_end+1):out.write('Frame{\n')Blender.Set('curframe',frame)for ob in sce.objects:mesh = ob.getData(mesh=1)for vert in mesh.verts:out.write('Vertex (%f %f %f) ( x x x)\n' % (vert.co.x, vert.co.y, vert.co.z) )out.write('}\n')out.close()Blender.Window.FileSelector(write,"Export")Note that I didn't write this to be the utmost in speediness or optimization - I wanted it to print things in order, so I have a lot of repetitive loops.  Technically the file format would work without this ordering, but for cleanliness I've set it up this way.About the road blocks:  The first is that I have no idea how custom data works, or how to retrieve it.  Basically all I want to do is flag a vertex, bone, or anything else as a site to affix other models (like, a sword in a soldier's hand).  Ideally this custom structure would have a name, a location (in 3-space) and an orientation (as a unit quaternion).  I also need a way to count the number of these things (hence num_tags) and how to extract their names.The second has to do with the texture (which is just a name, reffered to as "shader" above) and the UV coordinates per vertex.  I don't know where UV coordinates are stored or how to retrieve them, so I can't export them at this time.Next is the vertex normals.  If you notice, I listed them as x x x because I don't know how to reference them.  I'm checking the API now, but I don't remember seeing them as an element of verts.Last is animations, and this is probably going to be the hardest.  I don't know how or even if Blender stores animations (as opposed to just a set of frames).One concern I have, though, is that the way this is set up, animations are series of contiguous frames.  Will this method for extracting data per frame keep the correct order?  Or will designers have to work double time to code each set of key frames in order?  What will that do if a designer later adds a key frame?   ;  Blender stores IPOs (keyframes, interpolations, etc.).Here's a tip: if you want to poke around the inside of Blender's Python API, there's an interactive python console you can run in the Scripts window from the Scripts->System->Interactive Python Console. That'll allow you to look what's inside of lists, what attributes do objects have, and so on. It's a great time saver.   ;  I'm learning to hate python, but Blender's being a real champ at the very least.I'll keep updating my source - the one I posted above was merely a precursor to a lot of pain. <rant>1) I didn't know mesh=1 doesn't actually mean return a mesh so I was taking camera.verts for instance.  2) Spaces are utterly, utterly stupid for code - I never use them except where absolutely necessary, but Python's IDLE seems HELLBENT on FORCING you to use spaces.  Yes, mixing spaces and tabs is very bad, especially in python which is sensitive to mismatches... which is why I exclusively use TABS... It's much easier to overlook 1 too many or  too few spaces than it is a whole bloody tab...</rant>  Forgive the rage, I'm just annoyed at the moment.  Still, progress:#!BPY"""Name: 'ZH3 Exporter'Blender: 244Group: 'Export'ToolTile: 'Zero Hassle 3D Exporter'"""import Blenderimport bpydef write(filename):out = file(filename,"w")sce = bpy.data.scenes.activecontext = sce.getRenderingContext()f_start = context.startFrame()f_end = context.endFrame()#print headernum_verts = 0num_tags = 0texture_name = 'How_to_retrieve_this?'out.write ('version 01\n')for obj in sce.objects:mesh = obj.getData(mesh=1)if obj.type == 'Mesh':for vert in mesh.verts:num_verts = num_verts + 1out.write('Vertices %i\n' % (num_verts) )out.write('Shader %s\n' % (texture_name) )out.write('Tags %i\n' % (num_tags) )#copy animations?? No idea about this.  See sample#copy trianglesout.write('Triangles{\n')for obj in sce.objects:mesh = obj.getData(mesh=1)if obj.type == 'Mesh':for face in mesh.faces:out.write('    ')for vert in face.v:out.write('%i ' % (vert.index+1) )out.write('\n')out.write('}\n')#copy UV coordinates?? This should be easier... but I don't know how#copy TagNames??  More custom data.  Name of each Tag object#copy framesfor frame in xrange(f_start,f_end+1):out.write('Frame{\n')Blender.Set('curframe',frame)for obj in sce.objects:mesh = obj.getData(mesh=1)if obj.type == 'Mesh':for vert in mesh.verts:out.write('    Vertex (%f %f %f) ( %f %f %f)\n' % (vert.co.x, vert.co.y, vert.co.z, vert.no.x, vert.no.y, vert.no.z) )out.write('}\n')out.close()Blender.Window.FileSelector(write,"Export")This outputs a truly insane number of frames, even when starting with the default Blender cube.  I think there's something off with that...Would you care to elaborate more on the ipo?  Neither  this nor Blender are being very informative.  Heck, help(IPO) can't even identify the structure and prints an error.  I can't see how to go from this IPO object to any sort of information.Thank you for the help by the way.  You've saved me hours of work, and I really appreciate you taking the time to do so.---Edit---Well after 5 straight hours of work, my exporter's gotten a lot closer to being complete (see first post for progress).  I understand now what was happening with the massive frame dump - by default Blender has a huge number of frames.  I don't know how to get animation data out of this, but at least I can do the vertex transforms (heck that was easy!).  I can't seem to get the UV coordinates, but I think that has to do more with the fact that Blender's designed around per face UV coords rather than per vertex and I have no idea how to set up sticky coordinates (I think it would read them if they were sticky though.  For the sake of my sanity, I'm forcing any artist to use UVcoords - the game this format was meant for directly feeds UVcoords to openGL and therefore must have a 1:1 vertex:coord array.After that, it's just the user defined objects that I have to worry about.[Edited by - Zouflain on April 6, 2010 4:14:48 AM]