Blender export plugin

The current version of my Blender plugin. This exports data in my own file formats. It's not actually complete at this time, however it exports everything my engine currently uses, and I will update it as my engine progresses.




#!BPY
#NOTE: mesh must be triangulated before it can be exported (select faces and press ctrl+t).
#NOTE: "UV" button must be selected in Buttons Window->Panels:Shading:Material Buttons->Map Input tab (As opposed to "Orco", "Stick", "Win" etc), otherwise the UV's will be wrong or it will crash.
#NOTE: Bone rotations are counter-clockwise around the axis eg. a bone along the X-axis is rotated -90 around the Z-axis, a bone along the Z-axis is rotated 90 around the X-axis.



"""
Name: 'AD Formats (AMT, ART, AAT)'
Blender: 249
Group: 'Export'
Tooltip: 'Export model elements in the AD formats.'
"""


import Blender
import bpy
from Blender import *
from Blender.Mathutils import *
from Blender.sys import *




#--------------------------------------------------------------------------------------------------------------------------------------
#GENERAL
#--------------------------------------------------------------------------------------------------------------------------------------


def outMatrix(out, m):
out.write('%f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f' % (m[0][0], m[0][1], m[0][2], m[0][3], m[1][0], m[1][1], m[1][2], m[1][3], m[2][0], m[2][1], m[2][2], m[2][3], m[3][0], m[3][1], m[3][2], m[3][3]) )
#out.write('%f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f' % (m[0][0], m[1][0], m[2][0], m[3][0], m[0][1], m[1][1], m[2][1], m[3][1], m[0][2], m[1][2], m[2][2], m[3][2], m[0][3], m[1][3], m[2][3], m[3][3]) )

def printMatrix(m):
i1 = 0
while i1 < 4: print '| ', i2 = 0 while i2 < 4: print "% f" % m[i2][i1], i2 += 1 print ' |' i1 += 1 def printMatrix2(m0, m1): i1 = 0 while i1 < 4: print '| ', i2 = 0 while i2 < 4: print "% f" % m0[i2][i1], i2 += 1 print ' |\t| ', i2 = 0 while i2 < 4: print "% f" % m1[i2][i1], i2 += 1 print ' |' i1 += 1 def printMatrix3(m0, m1, m2): i1 = 0 while i1 < 4: print '| ', i2 = 0 while i2 < 4: print "% i" % round(m0[i2][i1]), i2 += 1 print ' |\t| ', i2 = 0 while i2 < 4: print "% i" % round(m1[i2][i1]), i2 += 1 print ' |\t| ', i2 = 0 while i2 < 4: print "% i" % round(m2[i2][i1]), i2 += 1 print ' |' i1 += 1 def isAlmostEqual(n0, n1, epsilon=0.00001): return n0>(n1-epsilon) and n0<(n1+epsilon) def isEqual(v0 = [], v1 = []): i1 = 0 while i1 1:
print 'ERROR - Rig has more than one root:', # IS THIS ACTUALLY A PROBLEM?
for name in rootBone:
print name,
print ''
return rootBone





#--------------------------------------------------------------------------------------------------------------------------------------
#AMT: Ali Mesh Text
#--------------------------------------------------------------------------------------------------------------------------------------


rigged = False
cuttable = False
riggedFlag = 0x00000001
cuttableFlag = 0x00000002
maxBoneWeights = 4



def addBonesToDictionary(bone, dict, counter):
dict[bone.name] = counter
#print counter, bone.name
counter += 1
for child in bone.children:
counter = addBonesToDictionary(child, dict, counter)
return counter



def saveMeshTXT(filename):
global rigged
global cuttable
global riggedFlag
global cuttableFlag
global maxBoneWeights
vertGroups = {}
boneIDs = {}
if filename[-4:] != '.amt':
filename += '.amt'
print "Saving file as", filename
sce = bpy.data.scenes.active
#ob = sce.objects.active
obs = Object.GetSelected()
for ob in obs:
if ob.type == 'Mesh':
mesh = ob.getData(mesh=1)
elif ob.type == 'Armature':
rig = ob.getData()
#mesh = ob.getData(mesh=1) #Passing mesh=1 returns the mesh instead of the NMesh.
if mesh.faceUV == False:
print 'ERROR - UVs absent, try attaching a texture and activating "Panels:Shading:Materials->Map Input->UV".'
return
for face in mesh.faces:
if len(face.verts)>3:
print 'Error - mesh contains non-triangulated faces. Please select all faces and press ctrl+t to triangulate.'
return
if rigged:
i1 = 0
roots = getRootBone(rig) #Create a dictionary of bone name to output index.
for root in roots:
i1 = addBonesToDictionary(rig.bones[root], boneIDs, i1)
for vert in mesh.verts:
weights = mesh.getVertexInfluences(vert.index)
if len(weights) > maxBoneWeights:
print 'Error - vertices exist with too many bone weights (max is', maxBoneWeights, ').'
return #A dictionary of vertex group name / index
groups = mesh.getVertGroupNames() # !TEMPORARY! WE NEED TO ORDER BASED ON THE HERIARCHY, SO THAT INDICES ARE THE SAME AS WHEN WE EXPORT THE RIG!
i1 = 0
for group in groups:
if group in vertGroups:
print 'Error - vertex groups exist with identical names:', group
return
if group in boneIDs:
pass
else:
print 'Error - vertex group',group, 'does not match any bone name!'
return
vertGroups[group] = i1
i1 += 1
if cuttable:
#CHECK FOR "SOLID STATE" USING CONNECTIVITY (error message can include removing double vertices)
pass

#Process the vertices:
arrVertices = []
arrIndices = []
arrEdges = []
for face in mesh.faces:
i1 = 0
while i1<0.001: print 'Warning! Vertex found with total bone weight 0! Attaching to first root bone.' out.write('0 1 ') count += 1 sum = 1 elif sum < 0.999: print 'Warning! Vertex found with total weight', sum, '!', if count >= maxBoneWeights:
print 'Cannot add weight to root bone, scaling existing bone weights.'
else:
print 'Adding missing weight to root bone.'
out.write('0 %f ' % (1-sum))
count += 1
sum = 1
for weight in weights:
out.write('%i %f ' % (boneIDs[weight[0]], weight[1]/sum)) #Print the bone index and scaled weight.
while count 2:
block = []
block.append("Too many objects selected!")
value = Blender.Draw.PupBlock("ERROR!", block)
return
for ob in obs:
if ob.type == 'Mesh':
#mesh = ob
b_mesh = True
elif ob.type == 'Armature':
#rig = ob
b_rig = True
if b_mesh == False:
block = []
block.append("No mesh selected!")
value = Blender.Draw.PupBlock("ERROR!", block)
return
if rigged==True and b_rig==False:
#block = []
#block.append("You must select both a mesh")
#block.append("and an armature if the mesh")
#block.append("is rigged!")
#value = Blender.Draw.PupBlock("ERROR!", block)
Blender.Draw.PupMenu("ERROR! - You must select both a mesh and an armature if the mesh is rigged!%t|Ok")
return
Blender.Window.FileSelector(saveMeshTXT, "Export", makename(ext='.amt', strip=1))






#--------------------------------------------------------------------------------------------------------------------------------------
#ART: Ali Rig Text
#--------------------------------------------------------------------------------------------------------------------------------------


upAxis = int(1) #0 = X-axis, 1 = Y-axis, 2 = Z-axis.
AbsoluteMat = False
ReverseX = False
ReverseZ = False



def processMtx(boneMtx, boneLength, invMtxPrev, rootBone):
global upAxis
global AbsoluteMat
if AbsoluteMat:
finalMtx = boneMtx.copy()
else:
finalMtx = boneMtx * invMtxPrev #This bone's final (relative) matrix is the bone's absolute matrix by the inverse of the parent bone matrix.
y_vector = Vector(0,boneLength,0,0)
translation_vector = y_vector * boneMtx
boneMtx[3] += translation_vector
boneMtx.invert() #The inverse matrix for children bones.
if rootBone==True:
if upAxis==2:
mE = Euler(-90, 0, 0)
#mE = Euler(0, 180, 0)
rotMtx = mE.toMatrix()
rotMtx = rotMtx.resize4x4()
finalMtx*= rotMtx
elif upAxis==0:
mE = Euler(0, 0, 90)
rotMtx = mE.toMatrix()
rotMtx = rotMtx.resize4x4()
finalMtx*= rotMtx
if ReverseX or ReverseZ:
scaleMtx = Matrix()
scaleMtx.identity()
if ReverseX: scaleMtx[0][0] = -1
if ReverseZ: scaleMtx[2][2] = -1
finalMtx = finalMtx * scaleMtx
return finalMtx



def outputBone(out, bone, num, type, invMtx, rootBone):
invMtxNext = bone.matrix[type].copy()
#print '\n\nBONE', bone.name
m = processMtx(invMtxNext, bone.length, invMtx, rootBone)
#print 'Original matrix, inverse of previous matrix, final matrix:'
#printMatrix3(bone.matrix[type], invMtx, m)
out.write('BONE %i ' % num)
outMatrix(out, m)
out.write(' %f\n' % bone.length)
currentBone = num + 1
for child in bone.children:
currentBone = outputBone(out, child, currentBone, type, invMtxNext, False)
out.write('ENDBONE #%i %s\n' % (num, bone.name))
return currentBone



def traceRig(rig, out, type):
global AbsoluteMat
boneNum = 0
flags = 0
sum = 0
mtx = Matrix()
mtx.identity()
if AbsoluteMat: flags |= 1
roots = getRootBone(rig)
for root in roots:
children = rig.bones[root].getAllChildren()
sum += (len(children) + 1)
out.write('%i %i\n' % (sum, flags)) #Total number of bones, output flags.
for root in roots:
outputBone(out, rig.bones[root], boneNum, type, mtx, True)



def saveRigTXT(filename):
sce = bpy.data.scenes.active
ob = sce.objects.active
rig = ob.getData()
print 'Exporting bind pose for rig:', rig
if filename[-4:] != '.art':
filename += '.art'
out = file(filename, "w")
result = traceRig(rig, out, 'ARMATURESPACE')
#result = traceRig(rig, out, 'BONESPACE')
out.close()
print 'File', filename, 'saved successfully.'
return result



def saveRig():
sce = bpy.data.scenes.active
ob = sce.objects.active
if ob.type != 'Armature':
#block = []
#block.append("Object is not an armature!")
#value = Blender.Draw.PupBlock("ERROR!", block)
Blender.Draw.PupMenu("ERROR! Object is not an armature!%t|Ok")
else:
Blender.Window.FileSelector(saveRigTXT, "Export", makename(ext='.art', strip=1))




#--------------------------------------------------------------------------------------------------------------------------------------
#AAT: Ali Anim Text
#--------------------------------------------------------------------------------------------------------------------------------------


frame_start = 1
frame_end = 2
time = 1000



def outputBoneFrame(out, name, pose, rig, invMtx, rootBone):
invMtxNext = pose.bones[name].poseMatrix.copy()
m = processMtx(invMtxNext, rig.bones[name].length, invMtx, rootBone)
outMatrix(out, m)
out.write('\n')
for child in rig.bones[name].children:
outputBoneFrame(out, child.name, pose, rig, invMtxNext, False)



def saveAnimTXT(filename):
global frame_start, frame_end
global time
sce = bpy.data.scenes.active
ob = sce.objects.active
rig = ob.getData()
pose = ob.getPose()
action = ob.getAction()
#names = action.getChannelNames()
#print names
#print 'Action:', action
keyframes = action.getFrameNumbers()
#print frames
print 'Exporting animation'# for rig:', rig
if filename[-4:] != '.aat':
filename += '.aat'
out = file(filename, "w")
roots = getRootBone(pose)
sum = 0
for root in roots:
children = rig.bones[root].getAllChildren()
sum += (len(children) + 1)
num_frames = 0
for i1 in keyframes:
if i1>=frame_start and i1<=frame_end: num_frames += 1 out.write('%i %i %i\n' % (sum, num_frames, time)) mtx = Matrix() mtx.identity() for i1 in keyframes: if i1>=frame_start and i1<=frame_end: ob.evaluatePose(i1) pose = ob.getPose() out.write('\n') for root in roots: outputBoneFrame(out, root, pose, rig, mtx, True) #i1 += 1 out.close() print 'File', filename, 'saved successfully.' def saveAnim(): global frame_start global frame_end global time block = [] sce = bpy.data.scenes.active ob = sce.objects.active if ob.type != 'Armature': #block.append("Object is not an armature!") #result = Blender.Draw.PupBlock("ERROR!", block) Blender.Draw.PupMenu("Error! Object is not an armature!%t|Ok") else: pose = ob.getPose() if pose: b_frame_start = Blender.Draw.Create(frame_start) b_frame_end = Blender.Draw.Create(frame_end) b_time = Blender.Draw.Create(time) block.append(("Start frame: ", b_frame_start, 1, 1000000)) block.append(("End frame: ", b_frame_end, 1, 1000000)) block.append(("Duration (ms): ", b_time, 0, 1000000)) result = Blender.Draw.PupBlock("ANIMATION", block) if result == 1: frame_start = b_frame_start.val frame_end = b_frame_end.val time = b_time.val Blender.Window.FileSelector(saveAnimTXT, "Export", makename(ext='.aat', strip=1)) else: #block.append("Object is not an armature!") #result = Blender.Draw.PupBlock("ERROR!", block) Blender.Draw.PupMenu("ERROR! Object is not an armature!%t|Ok") #-------------------------------------------------------------------------------------------------------------------------------------- #Misc #-------------------------------------------------------------------------------------------------------------------------------------- def getRotMtx(x, y, z): rot = Euler(x, y, z) mtx = rot.toMatrix() mtx = mtx.resize4x4() return mtx def writeMtx(out, preTxt, m, pstTxt): out.write(preTxt) out.write('%f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f' % (m[0][0], m[1][0], m[2][0], m[3][0], m[0][1], m[1][1], m[2][1], m[3][1], m[0][2], m[1][2], m[2][2], m[3][2], m[0][3], m[1][3], m[2][3], m[3][3]) ) out.write(pstTxt) def writeRotationMatrices(filename): out = file(filename, "w") mtx = getRotMtx(90, 0, 0) writeMtx(out, 'X 90 ', mtx, '\n') mtx = getRotMtx(-90, 0, 0) writeMtx(out, 'X -90 ', mtx, '\n') mtx = getRotMtx(0, 90, 0) writeMtx(out, 'Y 90 ', mtx, '\n') mtx = getRotMtx(0, -90, 0) writeMtx(out, 'Y -90 ', mtx, '\n') mtx = getRotMtx(0, 0, 90) writeMtx(out, 'Z 90 ', mtx, '\n') mtx = getRotMtx(0, 0, -90) writeMtx(out, 'Z -90 ', mtx, '\n') out.close() #-------------------------------------------------------------------------------------------------------------------------------------- #Interface #-------------------------------------------------------------------------------------------------------------------------------------- def drawGui(): #global button0, button1, button2, button3, button4, button5, button6, button7, button8, button9, button10, button11, button12, button13, button14 global button global cuttable, rigged, AbsolouteRot, ReverseX, ReverseZ global upAxis i = 0; height = 20 BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT) button=Draw.PushButton("Exit", i, 10,height,200,20, "Exit the menu.") i += 1 height += 30 button=Draw.PushButton("Export animation", i, 10,height,200,20, "Export the matrices for the rig for this animation.") i += 1 height += 30 button=Draw.PushButton("Export rig", i, 10,height,200,20, "Export the selected rig.") i += 1 height += 20 button=Draw.Toggle("Absolute matrices", i, 10,height,100,20, AbsoluteMat, "Export final position matrices rather than relative position matrices.") i += 1 button=Draw.Toggle("Invert X", i, 110,height,50,20, ReverseX, "Invert the X values.") i += 1 button=Draw.Toggle("Invert Z", i, 160,height,50,20, ReverseZ, "Invert the Z values.") i += 1 height += 20 button=Draw.Label("Up axis:", 10,height,100,20) button=Draw.Menu("Z-Axis%x2|Y-Axis%x1|X-Axis%x0", i, 110,height,100,20, upAxis, "Which axis in Blender corresponds to the up (Y) axis in the engine.", axisMenu) i += 1 height += 30 button=Draw.PushButton("Export mesh", i, 10,height,200,20, "Export the selected mesh.") i += 1 height += 20 button=Draw.Toggle("Rigged", i, 110,height,100,20, rigged, "If the mesh is rigged and you wish to include the vertex weights.") i += 1 button=Draw.Toggle("Cuttable", i, 10,height,100,20, cuttable, "If mesh is cuttable, edges will be included.") #TODO add button for "binary" def axisMenu(evt, val): upAxis = val def event(evt, val): #The code of the keyboard key pressed is passed to evt if evt == Blender.Draw.ESCKEY: Blender.Draw.Exit() return def button(evt): global cuttable global rigged global upAxis global AbsoluteMat global ReverseX global ReverseZ i = 0; if evt == i: #The identifier of the button. Blender.Draw.Exit() i += 1 if evt == i: saveAnim() #Blender.Window.FileSelector(saveAnimTXT, "Export") i += 1 if evt == i: saveRig() #Blender.Window.FileSelector(saveRigTXT, "Export") i += 1 if evt == i: if AbsoluteMat: AbsoluteMat = False else: AbsoluteMat = True i += 1 if evt == i: if ReverseX: ReverseX = False else: ReverseX = True i += 1 if evt == i: if ReverseZ: ReverseZ = False else: ReverseZ = True i += 1 if evt == i: pass i += 1 if evt == i: saveMesh() #Blender.Window.FileSelector(saveMeshTXT, "Export") #Blender.Draw.Exit() i += 1 if evt == i: if rigged: rigged = False else: rigged = True i += 1 if evt == i: if cuttable: cuttable = False else: cuttable = True #Blender.Window.Redraw() Blender.Draw.Register(drawGui, event, button)