#!BPY """ Registration info for Blender menus: Name: 'oX3ich v0.10' Blender: 249 Group: 'Export' Tip: 'Export an OpenGL friendly asset library' """ import Blender import bpy import struct from Blender import Modifier from Blender import Draw from Blender import Material from Blender import World # OX3ICH V0.10 - A SIMPLE SCRIPT FOR EXPORTING MESHES AND ANIMATIONS FROM # BLENDER # SOURCE: http://www.oogtech.org/content/ # COPYRIGHT T.E.A de SOUZA 2010 for Anime 3D SFX # LICENSE: LGPL # ############################ ADVERTISEMENT :) ######################### # # Check out our first title *Antistar 3D Rising* on the app store. # # http://itunes.apple.com/us/app/antistar-3d-rising/id383382828?mt=8 # # ####################################################################### # most resources are exported as datablocks. # Materials are exported within object blocks. # Action files are not datablocks (at least not yet). # datablock files (dbk) use a short to identify their type # WORLD_BLOCK=5 # LAMP_BLOCK=4, # OBJECT_BLOCK=1, # ACTOR_BLOCK=2, # CAMERA_BLOCK=3, # UNKNOWN_BLOCK=0 # --------------------------------------------------- # EXPORT ASSETS TO THE SPECIFIED DIRECTORY ---------- # --------------------------------------------------- def exportAssets(path): # write export directory name to a .x3nfo file # for next time. INFO_HANDLE=open(INFO_PATH,'w') INFO_HANDLE.write(path) INFO_HANDLE.close() #Draw.PupMenu("exportAssets:"+path) scene=Blender.Scene.GetCurrent() for object in scene.objects: if object.type=='Mesh': exportObject(object,path) if object.type=='Camera': exportCamera(object,path) if object.type=='Lamp': exportLamp(object,path) # also export current world node, mainly # for mist params and bg color. exportWorld(path) DEBUG_HANDLE.close() def writeVersion(handle): writeFloat(0.10,handle) def exportObject(object,path): #Draw.PupMenu("export object:"+object.name) if isActor(object): debug("WRITE ACTOR OR ITEM: "+object.name) File(object,path) actions=getActions(object) armature=getArmature(object) exportActionFiles(actions,armature,object,path) elif isCloth(object): debug("WRITE CLOTH OBJECT: "+object.name) exportItemFile(object,path) actions=getActions(object) armature=getArmatureForCloth(object) exportActionFiles(actions,armature,object,path) else: debug("WRITE OBJECT: "+object.name) exportItemFile(object,path) def exportWorld(path): datablock=World.GetCurrent() if datablock!=None: debug("WRITE WORLD: "+datablock.name) exportWorldFile(datablock,path) else: debug("NO WORLD NODE IS ATTACHED TO THE CURRENT SCENE") def exportCamera(object,path): debug("WRITE CAMERA: "+object.name) exportCameraFile(object,path) def exportLamp(object,path): debug("WRITE LAMP: "+object.name) exportLampFile(object,path) def exportActorFile(object,path): name=object.getData(mesh=1).name fpath=Blender.sys.join(path,name+".bba") debug("write actor to: "+fpath) handle=open(fpath,'wb') writeVersion(handle) writeDataCode(2,handle) exportActor(object,handle) handle.close() def exportActionFile(action,armature,object,path): name=action.name.split('@')[0] actorName=name.split('.')[0] actionName=name.split('.')[1] filename=object.name+"."+actionName fpath=Blender.sys.join(path,filename+".act") debug("write "+name+" to: "+fpath) handle=open(fpath,'wb') writeVersion(handle) writeAction(action,armature,object,handle) handle.close() def exportActionFiles(actions,armature,object,path): debug("write actions") for action in actions: exportActionFile(action,armature,object,path) def exportItemFile(object,path): name=object.getData(mesh=1).name fpath=Blender.sys.join(path,name+".dbk") debug("write item to: "+fpath) handle=open(fpath,'wb') writeVersion(handle) writeDataCode(1,handle) writeItem(object,handle) handle.close() def exportWorldFile(object,path): name=object.name fpath=Blender.sys.join(path,name+".dbk") debug("write world to: "+fpath) handle=open(fpath,'wb') writeVersion(handle) writeDataCode(5,handle) writeWorld(object,handle) handle.close() def exportCameraFile(object,path): name=object.getData().name fpath=Blender.sys.join(path,name+".dbk") debug("write camera to: "+fpath) handle=open(fpath,'wb') writeVersion(handle) writeDataCode(3,handle) writeCamera(object,handle) handle.close() def exportLampFile(object,path): name=object.getData().name fpath=Blender.sys.join(path,name+".dbk") debug("write camera to: "+fpath) handle=open(fpath,'wb') writeVersion(handle) writeDataCode(4,handle) writeLamp(object,handle) handle.close() def exportActor(actor,handle): actions=getActions(actor) armature=getArmature(actor) mesh=actor.getData(mesh=1) debug("export actor...") writeActor(actor,armature,actions,handle) def getActions(actor): allActions=Blender.Armature.NLA.GetActions() actions=[] actionGroupName=actor.name.split('@')[-1] for key in allActions: action=allActions.get(key) descriptor=action.name.split('.') if descriptor[0]==actionGroupName : actions.append(action) else: debug("skip action:"+action.name+" - no match with actor name:"+actor.name) return actions def getArmatureForCloth(cloth): influencingActor=bpy.data.objects[cloth.name.split('@')[-1]] return getArmature(influencingActor) def getArmature(actor): for modifier in actor.modifiers: if modifier.type==Modifier.Types.ARMATURE: armature=modifier[Modifier.Settings.OBJECT] return armature return null def isActor(object): for x in object.modifiers: if x.type==Modifier.Types.ARMATURE: return 1 return 0 def isCloth(object): for x in object.modifiers: if x.type==Modifier.Types.CLOTH: return 1 return 0 def debug(str): DEBUG_HANDLE.write(str+"\n") # ############################################### # ############################################### # ############################################### # ----------------------------------------------- # WRITE ACTORS, ITEMS, CAMERA, LAMP, WORLD ------ # ----------------------------------------------- def writeActor(object,armature,actions,out): debug("write actor") mesh = object.getData(mesh=1) materials = mesh.materials writeMaterials(materials,out) writeFaces(mesh.faces,out) def writeItem(object,out): debug("write item") mesh = object.getData(mesh=1) materials = mesh.materials writeMaterials(materials,out) writeFaces(mesh.faces,out) writeMesh(object,out) def writeCamera(object,out): debug("write camera data") x=object.getData() # 0 = perspective camera # 1 = ortho camera # 2 = unknown camera if x.type=='persp': writeInt(0,out) elif x.type=='orth': writeInt(1,out) else: writeInt(2,out) # camera angle is in degrees # scale is only relevant to ortho camera writeFloat(x.angle,out) writeFloat(x.getScale(),out) def writeWorld(x,out): debug("write world data") writeCol(x.getAmb(),out) # ambient writeCol(x.getHor(),out) # horizon writeCol(x.getZen(),out) # zenith # skytype uses bitwise flags # &1 to get blend mode # &2 to get real mode # &4 to get paper mode writeInt(x.skytype,out) # bitwise flags to get mist/star status (and other, unlisted things) # &1 => enable mist # &2 => enable starfield writeInt(x.getMode(),out) mist=x.getMist() writeFloat(mist[0],out) # intensity writeFloat(mist[1],out) # start writeFloat(mist[2],out) # distance writeFloat(mist[3],out) # height (zero for ambient mist) def writeLamp(object,out): debug("write lamp data") x=object.getData() # 0:point, 1:sun, 2:spot, 3:hemi, 4:area writeInt(x.getType(),out) writeFloat(x.R,out) writeFloat(x.G,out) writeFloat(x.B,out) writeFloat(x.getEnergy(),out) writeFloat(x.getDist(),out) writeFloat(x.getSpotSize(),out) writeFloat(x.getSpotBlend(),out) writeFloat(x.getAreaSizeX(),out) writeFloat(x.getAreaSizeY(),out) # ----------------------------------------------- # WRITE MATERIALS ------------------------------- # ----------------------------------------------- def writeMaterials(materials,out): debug("write materials") writeInt(len(materials),out) for m in materials: writeMaterial(m,out) def writeMaterial(material,out): # for now, if material is none, i write a material with zero values. if material==None: debug("write: ") writeString("",out) else: debug("write: "+material.name) writeString(material.name,out) diffuse = material.rgbCol mirror = material.getMirCol() specular = material.getSpecCol() ambient = material.getAmb() emit = material.getEmit() reflectivity = material.getRef() specAmount = material.getSpec() specTrans = material.getSpecTransp() hardness = material.hard haloSize = material.getHaloSize() zOffset = material.getZOffset() flags = material.getMode() shadeless = flags & Material.Modes['SHADELESS'] shadow = flags & Material.Modes['SHADOW'] wire = flags & Material.Modes['WIRE'] no_mist = flags & Material.Modes['NOMIST'] halo = flags & Material.Modes['HALO'] writeCol(diffuse,out) writeCol(mirror,out) writeCol(specular,out) writeFloat(ambient,out) writeFloat(emit,out) writeFloat(reflectivity,out) writeFloat(specAmount,out) writeFloat(specTrans,out) writeInt(hardness,out) writeFloat(haloSize,out) writeFloat(zOffset,out) writeInt(shadeless,out) writeInt(shadow,out) writeInt(wire,out) writeInt(halo,out) writeInt(no_mist,out) # ----------------------------------------------- # WRITE FACES ----------------------------------- # ----------------------------------------------- def writeFaces(faces,out): writeInt(getTriangleCount(faces),out) for f in faces: writeFaceIndices(f,out) for f in faces: writeFaceMatIndices(f,out) # given a mix of triangle and square faces, return # the equivalent triangle count def getTriangleCount(faces): count=0 for f in faces: count=count+1 if len(f.verts)==4 : count=count+1 return count # write vertex indices for each face # each square face generates two triangles def writeFaceIndices(face,out): if len(face.verts)==3 : for vertex in face.verts: writeInt(vertex.index,out) else: writeInt(face.verts[0].index,out) writeInt(face.verts[1].index,out) writeInt(face.verts[2].index,out) writeInt(face.verts[2].index,out) writeInt(face.verts[3].index,out) writeInt(face.verts[0].index,out) # 1 index for a triangle face, 2 indices for a square face # since all square faces generate 2 triangles def writeFaceMatIndices(face,out): writeInt(face.mat,out) if len(face.verts)==4 : writeInt(face.mat,out) # ---------------------------------------------- # WRITE MESH ----------------------------------- # ---------------------------------------------- def writeMesh(object,out): debug("write mesh") mesh=bpy.data.meshes.new('_dup') mesh.getFromObject(object) writeVertices(mesh.verts,out) writeNormals(mesh.verts,out) # ---------------------------------------------- # WRITE KEY FRAMES FOR EACH ACTION ------------- # ---------------------------------------------- def writeActions(actions,armature,object,out): debug("write actions") writeInt(len(actions),out) for action in actions: name=action.name.split('.')[1] writeString(name,out) writeAction(action,armature,object,out) def writeAction(action,armature,object,out): debug("write action: "+action.name) #print("make action active in "+armature.name) action.setActive(armature) keyFrameIndices=action.getFrameNumbers() range=getFrameRange(action) while keyFrameIndices[0]range[1]: keyFrameIndices=keyFrameIndices[:-1] if len(keyFrameIndices)==0: keyFrameIndices[0]=range[0] keyFrameIndices[1]=range[1] else: if(keyFrameIndices[0]!=range[0]): keyFrameIndices.insert(0,range[0]) if(keyFrameIndices[-1]!=range[1]): keyFrameIndices.append(range[1]) #debug("got key frame indices "+keyFrameIndices) writeInt(len(keyFrameIndices),out) frameZeroWritten=False for k in keyFrameIndices: # blender indices start at 1, output indices start at zero writeMeshFrame(k-range[0],k,object,out) def getFrameRange(action): range=[1,30000] parts=action.name.split('@') if len(parts)==2: rngString=parts[1].split('-') range[0]=int(rngString[0]) range[1]=int(rngString[1]) return range def writeMeshFrame(outputIndex,actualIndex,object,out): debug("write keyframe: "+str(actualIndex)+"("+str(outputIndex)+")") writeInt(outputIndex,out) scene = Blender.Scene.GetCurrent() context = scene.getRenderingContext() context.currentFrame(actualIndex) scene.update(1) mesh=bpy.data.meshes.new('_dup') mesh.getFromObject(object) writeVertices(mesh.verts,out) writeNormals(mesh.verts,out) # ---------------------------------------------- # MISCELLANEOUS WRITE METHODS ------------- # ---------------------------------------------- def writeVertices(vertices,out): writeInt(len(vertices),out) for v in vertices: writeVertex(v,out) def writeNormals(vertices,out): for v in vertices: writeNormal(v,out) def writeVertex(vertex,out): writeFloat(vertex.co.x,out) # in Blender, z is up. I'd rather have y pointing up. writeFloat(vertex.co.z,out) # in Blender y is depth, I'd rather have z pointing back. writeFloat(-vertex.co.y,out) def writeNormal(vertex,out): writeFloat(vertex.no.x,out) # in Blender, z is up. I'd rather have y pointing up. writeFloat(vertex.no.z,out) # in Blender y is depth, I'd rather have z pointing back. writeFloat(-vertex.no.y,out) def writeCol(color,out): for f in color: writeInt(int(f*255),out) def writeFloat(value,out): # value*=1000 # if value<-15000:value=-15000 # if value>15000:value=15000 # out.write(struct.pack('h',int(value))) out.write(struct.pack('f',value)) def writeInt(value,out): if value<-15000:value=-15000 if value>15000:value=15000 out.write(struct.pack('h',value)) #out.write(struct.pack('i',value)) def writeDataCode(value,out): out.write(struct.pack('h',value)) def writeString(str,out): writeInt(len(str),out); out.write(str); # ####################################################### # ####################################################### # ####################################################### # open a file for debug output DEBUG_PATH = Blender.sys.makename(ext='.ox3ch.txt') DEBUG_HANDLE = open(DEBUG_PATH,'w') # open the .x3nfo file, if exists. We retrieve the previous # export location from there. # otherwise suggest an export directory INFO_PATH = Blender.sys.makename(ext='.x3nfo') HAS_INFO = Blender.sys.exists(INFO_PATH) if HAS_INFO: INFO_HANDLE=open(INFO_PATH,'r') PATH=INFO_HANDLE.read() INFO_HANDLE.close() else: PATH=Blender.sys.makename(ext='_assets') # let the user change path selection if they feel like # then call 'exportAssets' Blender.Window.FileSelector(exportAssets,'Export to Dir',PATH)