Paste Code
Paste Blends
Paste Images
#"""
#Name: 'Quake Model 5 (.md5)...'
#Blender: 257
#Group: 'Export'
#Tooltip: 'Export a Quake Model 5 File'
#
#credit to der_ton for the 2.4x Blender export script
#"""

bl_info = { # changed from bl_addon_info in 2.57 -mikshaw
"name": "Export idTech4 (.md5)",
"author": "Paul Zirkle aka Keless, credit to der_ton, ported to Blender 2.62 by motorsep and tested by kat, special thanks to MCampagnini",
"version": (1,0,0),
"blender": (2, 6, 3),
"api": 31847,
"location": "File > Export > Skeletal Mesh/Animation Data (.md5mesh/.md5anim)",
"description": "Export idTech4 (.md5)",
"warning": "",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
"Scripts/File_I-O/idTech4_md5",
"tracker_url": "http://www.katsbits.com",
"category": "Import-Export"}

import bpy,struct,math,os,time,sys,mathutils

#MATH UTILTY

def vector_crossproduct(v1, v2):
return [
v1[1] * v2[2] - v1[2] * v2[1],
v1[2] * v2[0] - v1[0] * v2[2],
v1[0] * v2[1] - v1[1] * v2[0],
]

def point_by_matrix(p, m):
#print( str(type( p )) + " " + str(type(m)) )
return [p[0] * m[0][0] + p[1] * m[1][0] + p[2] * m[2][0] + m[3][0],
p[0] * m[0][1] + p[1] * m[1][1] + p[2] * m[2][1] + m[3][1],
p[0] * m[0][2] + p[1] * m[1][2] + p[2] * m[2][2] + m[3][2]]

def vector_by_matrix(p, m):
return [p[0] * m.col[0][0] + p[1] * m.col[1][0] + p[2] * m.col[2][0],
p[0] * m.col[0][1] + p[1] * m.col[1][1] + p[2] * m.col[2][1],
p[0] * m.col[0][2] + p[1] * m.col[1][2] + p[2] * m.col[2][2]]

def vector_normalize(v):
l = math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
try:
return v[0] / l, v[1] / l, v[2] / l
except:
return 1, 0, 0

def matrix2quaternion(m):
s = math.sqrt(abs(m.col[0][0] + m.col[1][1] + m.col[2][2] + m.col[3][3]))
if s == 0.0:
x = abs(m.col[2][1] - m.col[1][2])
y = abs(m.col[0][2] - m.col[2][0])
z = abs(m.col[1][0] - m.col[0][1])
if (x >= y) and (x >= z): return 1.0, 0.0, 0.0, 0.0
elif (y >= x) and (y >= z): return 0.0, 1.0, 0.0, 0.0
else: return 0.0, 0.0, 1.0, 0.0
return quaternion_normalize([
-(m.col[2][1] - m.col[1][2]) / (2.0 * s),
-(m.col[0][2] - m.col[2][0]) / (2.0 * s),
-(m.col[1][0] - m.col[0][1]) / (2.0 * s),
0.5 * s,
])

def matrix_invert(m):
det = (m.col[0][0] * (m.col[1][1] * m.col[2][2] - m.col[2][1] * m.col[1][2])
- m.col[1][0] * (m.col[0][1] * m.col[2][2] - m.col[2][1] * m.col[0][2])
+ m.col[2][0] * (m.col[0][1] * m.col[1][2] - m.col[1][1] * m.col[0][2]))
if det == 0.0: return None
det = 1.0 / det
# transposed matrix
# r = [ [
# det * (m.col[1][1] * m.col[2][2] - m.col[2][1] * m.col[1][2]),
# - det * (m.col[1][0] * m.col[2][2] - m.col[2][0] * m.col[1][2]),
# det * (m.col[1][0] * m.col[2][1] - m.col[2][0] * m.col[1][1]),
# ], [
# - det * (m.col[0][1] * m.col[2][2] - m.col[2][1] * m.col[0][2]),
# det * (m.col[0][0] * m.col[2][2] - m.col[2][0] * m.col[0][2]),
# - det * (m.col[0][0] * m.col[2][1] - m.col[2][0] * m.col[0][1]),
# ], [
# det * (m.col[0][1] * m.col[1][2] - m.col[1][1] * m.col[0][2]),
# - det * (m.col[0][0] * m.col[1][2] - m.col[1][0] * m.col[0][2]),
# det * (m.col[0][0] * m.col[1][1] - m.col[1][0] * m.col[0][1]),
# ], [
# 0.0,
# 0.0,
# 0.0,
# ] ]
# original matrix from 2.61 compaticle script adopted for 2.62; the mesh with this matric is consistent, but rotated 180 degrees around Z axis and centered at 0 0 0
r = [ [
det * (m.col[1][1] * m.col[2][2] - m.col[2][1] * m.col[1][2]),
- det * (m.col[0][1] * m.col[2][2] - m.col[2][1] * m.col[0][2]),
det * (m.col[0][1] * m.col[1][2] - m.col[1][1] * m.col[0][2]),
0.0,
], [
- det * (m.col[1][0] * m.col[2][2] - m.col[2][0] * m.col[1][2]),
det * (m.col[0][0] * m.col[2][2] - m.col[2][0] * m.col[0][2]),
- det * (m.col[0][0] * m.col[1][2] - m.col[1][0] * m.col[0][2]),
0.0
], [
det * (m.col[1][0] * m.col[2][1] - m.col[2][0] * m.col[1][1]),
- det * (m.col[0][0] * m.col[2][1] - m.col[2][0] * m.col[0][1]),
det * (m.col[0][0] * m.col[1][1] - m.col[1][0] * m.col[0][1]),
0.0,
] ]
r.append([
-(m.col[3][0] * r[0][0] + m.col[3][1] * r[1][0] + m.col[3][2] * r[2][0]),
-(m.col[3][0] * r[0][1] + m.col[3][1] * r[1][1] + m.col[3][2] * r[2][1]),
-(m.col[3][0] * r[0][2] + m.col[3][1] * r[1][2] + m.col[3][2] * r[2][2]),
1.0,
])
return r

def quaternion_normalize(q):
l = math.sqrt(q.col[0] * q.col[0] + q.col[1] * q.col[1] + q.col[2] * q.col[2] + q.col[3] * q.col[3])
return q.col[0] / l, q.col[1] / l, q.col[2] / l, q.col[3] / l


#shader material
class Material:
name = "" #string
def __init__(self, textureFileName):
self.name = textureFileName

def to_md5mesh(self):
return self.name;

#the 'Model' class, contains all submeshes
class Mesh:
name = "" #string
submeshes = [] #array of SubMesh
next_submesh_id = 0 #int

def __init__(self, name):
self.name = name
self.submeshes = []

self.next_submesh_id = 0


def to_md5mesh(self):
meshnumber=0
buf = ""
for submesh in self.submeshes:
buf=buf + "mesh {\n"
# buf=buf + "mesh {\n\t// meshes: " + submesh.name + "\n" # used for Sauerbraten -mikshaw
meshnumber += 1
buf=buf + submesh.to_md5mesh()
buf=buf + "}\n\n"

return buf


#submeshes reference a parent mesh
class SubMesh:
def __init__(self, mesh, material):
self.material = material
self.vertices = []
self.faces = []
self.nb_lodsteps = 0
self.springs = []
self.weights = []

self.next_vertex_id = 0
self.next_weight_id = 0

self.mesh = mesh
self.name = mesh.name
self.id = mesh.next_submesh_id
mesh.next_submesh_id += 1
mesh.submeshes.append(self)

def bindtomesh (self, mesh):
# HACK: this is needed for md5 output, for the time being...
# appending this submesh to the specified mesh, disconnecting it from the original one
self.mesh.submeshes.remove(self)
self.mesh = mesh
self.id = mesh.next_submesh_id
mesh.next_submesh_id += 1
mesh.submeshes.append(self)

def generateweights(self):
self.weights = []
self.next_weight_id = 0
for vert in self.vertices:
vert.generateweights()

def reportdoublefaces(self):
for face in self.faces:
for face2 in self.faces:
if not face == face2:
if (not face.vertex1==face2.vertex1) and (not face.vertex1==face2.vertex2) and (not face.vertex1==face2.vertex3):
return
if (not face.vertex2==face2.vertex1) and (not face.vertex2==face2.vertex2) and (not face.vertex2==face2.vertex3):
return
if (not face.vertex3==face2.vertex1) and (not face.vertex3==face2.vertex2) and (not face.vertex3==face2.vertex3):
return
print('doubleface! %s %s' % (face, face2))

def to_md5mesh(self):
self.generateweights()

self.reportdoublefaces()

buf="\tshader \"%s\"\n\n" % (self.material.to_md5mesh())
if len(self.weights) == 0:
buf=buf + "\tnumverts 0\n"
buf=buf + "\n\tnumtris 0\n"
buf=buf + "\n\tnumweights 0\n"
return buf

# output vertices
buf=buf + "\tnumverts %i\n" % (len(self.vertices))
vnumber=0
for vert in self.vertices:
buf=buf + "\tvert %i %s\n" % (vnumber, vert.to_md5mesh())
vnumber += 1

# output faces
buf=buf + "\n\tnumtris %i\n" % (len(self.faces))
facenumber=0
for face in self.faces:
buf=buf + "\ttri %i %s\n" % (facenumber, face.to_md5mesh())
facenumber += 1

# output weights
buf=buf + "\n\tnumweights %i\n" % (len(self.weights))
weightnumber=0
for weight in self.weights:
buf=buf + "\tweight %i %s\n" % (weightnumber, weight.to_md5mesh())
weightnumber += 1

return buf

#vertex class contains and outputs 'verts' but also generates 'weights' data
class Vertex:
def __init__(self, submesh, loc, normal):
self.loc = loc
self.normal = normal
self.collapse_to = None
self.face_collapse_count = 0
self.maps = []
self.influences = []
self.weights = []
self.weight = None
self.firstweightindx = 0
self.cloned_from = None
self.clones = []

self.submesh = submesh
self.id = submesh.next_vertex_id
submesh.next_vertex_id += 1
submesh.vertices.append(self)

def generateweights(self):
self.firstweightindx = self.submesh.next_weight_id
for influence in self.influences:
weightindx = self.submesh.next_weight_id
self.submesh.next_weight_id += 1
newweight = Weight(influence.bone, influence.weight, self, weightindx, self.loc[0], self.loc[1], self.loc[2])
self.submesh.weights.append(newweight)
self.weights.append(newweight)

def to_md5mesh(self):
if self.maps:
buf = self.maps[0].to_md5mesh()
else:
buf = "( %f %f )" % (self.loc[0], self.loc[1])
buf = buf + " %i %i" % (self.firstweightindx, len(self.influences))
return buf

#texture coordinate map
class Map:
def __init__(self, u, v):
self.u = u
self.v = v


def to_md5mesh(self):
buf = "( %f %f )" % (self.u, self.v)
return buf

#NOTE: uses global 'scale' to scale the size of model verticies
#generated and stored in Vertex class
class Weight:
def __init__(self, bone, weight, vertex, weightindx, x, y, z):
self.bone = bone
self.weight = weight
self.vertex = vertex
self.indx = weightindx
invbonematrix = matrix_invert(self.bone.matrix)
self.x, self.y, self.z = point_by_matrix ((x, y, z), invbonematrix)

def to_md5mesh(self):
buf = "%i %f ( %f %f %f )" % (self.bone.id, self.weight, self.x*scale, self.y*scale, self.z*scale)
return buf

#used by SubMesh class
class Influence:
def __init__(self, bone, weight):
self.bone = bone
self.weight = weight

#outputs the 'tris' data
class Face:
def __init__(self, submesh, vertex1, vertex2, vertex3):
self.vertex1 = vertex1
self.vertex2 = vertex2
self.vertex3 = vertex3

self.can_collapse = 0

self.submesh = submesh
submesh.faces.append(self)


def to_md5mesh(self):
buf = "%i %i %i" % (self.vertex1.id, self.vertex3.id, self.vertex2.id)
return buf

#holds bone skeleton data and outputs header above the Mesh class
class Skeleton:
def __init__(self, MD5Version = 10, commandline = ""):
self.bones = []
self.MD5Version = MD5Version
self.commandline = commandline
self.next_bone_id = 0


def to_md5mesh(self, numsubmeshes):
buf = "MD5Version %i\n" % (self.MD5Version)
buf = buf + "commandline \"%s\"\n\n" % (self.commandline)
buf = buf + "numJoints %i\n" % (self.next_bone_id)
buf = buf + "numMeshes %i\n\n" % (numsubmeshes)
buf = buf + "joints {\n"
for bone in self.bones:
buf = buf + bone.to_md5mesh()
buf = buf + "}\n\n"
return buf

BONES = {}

#held by Skeleton, generates individual 'joint' data
class Bone:
def __init__(self, skeleton, parent, name, mat, theboneobj):
self.parent = parent #Bone
self.name = name #string
self.children = [] #list of Bone objects
self.theboneobj = theboneobj #Blender.Armature.Bone
# HACK: this flags if the bone is animated in the one animation that we export
self.is_animated = 0 # = 1, if there is an ipo that animates this bone

self.matrix = mat
if parent:
parent.children.append(self)

self.skeleton = skeleton
self.id = skeleton.next_bone_id
skeleton.next_bone_id += 1
skeleton.bones.append(self)

BONES[name] = self


def to_md5mesh(self):
buf= "\t\"%s\"\t" % (self.name)
parentindex = -1
if self.parent:
parentindex=self.parent.id
buf=buf+"%i " % (parentindex)

pos1, pos2, pos3= self.matrix.col[3][0], self.matrix.col[3][1], self.matrix.col[3][2]
buf=buf+"( %f %f %f ) " % (pos1*scale, pos2*scale, pos3*scale)
#qx, qy, qz, qw = matrix2quaternion(self.matrix)
#if qw<0:
# qx = -qx
# qy = -qy
# qz = -qz
m = self.matrix
# bquat = self.matrix.to_quat() #changed from matrix.toQuat() in blender 2.4x script
bquat = self.matrix.to_quaternion() #changed from to_quat in 2.57 -mikshaw
bquat.normalize()
qx = bquat.x
qy = bquat.y
qz = bquat.z
if bquat.w > 0:
qx = -qx
qy = -qy
qz = -qz
buf=buf+"( %f %f %f )\t\t// " % (qx, qy, qz)
if self.parent:
buf=buf+"%s" % (self.parent.name)

buf=buf+"\n"
return buf


class MD5Animation:
def __init__(self, md5skel, MD5Version = 10, commandline = ""):
self.framedata = [] # framedata[boneid] holds the data for each frame
self.bounds = []
self.baseframe = []
self.skeleton = md5skel
self.boneflags = [] # stores the md5 flags for each bone in the skeleton
self.boneframedataindex = [] # stores the md5 framedataindex for each bone in the skeleton
self.MD5Version = MD5Version
self.commandline = commandline
self.numanimatedcomponents = 0
self.framerate = 24
self.numframes = 0
for b in self.skeleton.bones:
self.framedata.append([])
self.baseframe.append([])
self.boneflags.append(0)
self.boneframedataindex.append(0)

def to_md5anim(self):
currentframedataindex = 0
for bone in self.skeleton.bones:
if (len(self.framedata[bone.id])>0):
if (len(self.framedata[bone.id])>self.numframes):
self.numframes=len(self.framedata[bone.id])
(x,y,z),(qw,qx,qy,qz) = self.framedata[bone.id][0]
self.baseframe[bone.id]= (x*scale,y*scale,z*scale,qx,qy,qz)
self.boneframedataindex[bone.id]=currentframedataindex
self.boneflags[bone.id] = 63
currentframedataindex += 6
self.numanimatedcomponents = currentframedataindex
else:
rot=bone.matrix.to_quaternion()
rot.normalize()
qx=rot.x
qy=rot.y
qz=rot.z
if rot.w > 0:
qx = -qx
qy = -qy
qz = -qz
self.baseframe.col[bone.id]= (bone.matrix.col[3][0]*scale, bone.matrix.col[3][1]*scale, bone.matrix.col[3][2]*scale, qx, qy, qz)

buf = "MD5Version %i\n" % (self.MD5Version)
buf = buf + "commandline \"%s\"\n\n" % (self.commandline)
buf = buf + "numFrames %i\n" % (self.numframes)
buf = buf + "numJoints %i\n" % (len(self.skeleton.bones))
buf = buf + "frameRate %i\n" % (self.framerate)
buf = buf + "numAnimatedComponents %i\n\n" % (self.numanimatedcomponents)
buf = buf + "hierarchy {\n"

for bone in self.skeleton.bones:
parentindex = -1
flags = self.boneflags[bone.id]
framedataindex = self.boneframedataindex[bone.id]
if bone.parent:
parentindex=bone.parent.id
buf = buf + "\t\"%s\"\t%i %i %i\t//" % (bone.name, parentindex, flags, framedataindex)
if bone.parent:
buf = buf + " " + bone.parent.name
buf = buf + "\n"
buf = buf + "}\n\n"

buf = buf + "bounds {\n"
for b in self.bounds:
buf = buf + "\t( %f %f %f ) ( %f %f %f )\n" % (b)
buf = buf + "}\n\n"

buf = buf + "baseframe {\n"
for b in self.baseframe:
buf = buf + "\t( %f %f %f ) ( %f %f %f )\n" % (b)
buf = buf + "}\n\n"

for f in range(0, self.numframes):
buf = buf + "frame %i {\n" % (f)
for b in self.skeleton.bones:
if (len(self.framedata[b.id])>0):
(x,y,z),(qw,qx,qy,qz) = self.framedata[b.id][f]
if qw>0:
qx,qy,qz = -qx,-qy,-qz
buf = buf + "\t%f %f %f %f %f %f\n" % (x*scale, y*scale, z*scale, qx,qy,qz)
buf = buf + "}\n\n"

return buf

def addkeyforbone(self, boneid, time, loc, rot):
# time is ignored. the keys are expected to come in sequentially
# it might be useful for future changes or modifications for other export formats
self.framedata[boneid].append((loc, rot))
return


def getminmax(listofpoints):
if len(listofpoints[0]) == 0: return ([0,0,0],[0,0,0])
min = [listofpoints[0][0], listofpoints[1][0], listofpoints[2][0]]
max = [listofpoints[0][0], listofpoints[1][0], listofpoints[2][0]]
if len(listofpoints[0])>1:
for i in range(1, len(listofpoints[0])):
if listofpoints[i][0]>max[0]: max[0]=listofpoints[i][0]
if listofpoints[i][1]>max[1]: max[1]=listofpoints[i][1]
if listofpoints[i][2]>max[2]: max[2]=listofpoints[i][2]
if listofpoints[i][0]<min[0]: min[0]=listofpoints[i][0]
if listofpoints[i][1]<min[1]: min[1]=listofpoints[i][1]
if listofpoints[i][2]<min[2]: min[2]=listofpoints[i][2]
return (min, max)

def generateboundingbox(objects, md5animation, framerange):
scene = bpy.context.scene #Blender.Scene.getCurrent()
context = scene.render #scene.getRenderingContext()
for i in range(framerange[0], framerange[1]+1):
corners = []
#context.currentFrame(i)
#scene.makeCurrent()
scene.frame_set( i )

for obj in objects:
data = obj.data #obj.getData()
#if (type(data) is Blender.Types.NMeshType) and data.faces:
if obj.type == 'MESH' and data.polygons:
#obj.makeDisplayList()
#(lx, ly, lz) = obj.getLocation()
(lx, ly, lz ) = obj.location
#bbox = obj.getBoundBox()
bbox = obj.bound_box
# transposed matrix
# matrix = [[1.0, 0.0, 0.0, 0.0],
# [0.0, 1.0, 1.0, 0.0],
# [0.0, 0.0, 1.0, 0.0],
# [0.0, 0.0, 0.0, 1.0],
# ]
# original matrix from the 2.61 compatible script
matrix = [[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 1.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
]
for v in bbox:
corners.append(point_by_matrix (v, matrix))
(min, max) = getminmax(corners)
md5animation.bounds.append((min[0]*scale, min[1]*scale, min[2]*scale, max[0]*scale, max[1]*scale, max[2]*scale))


#exporter settings
class md5Settings:
def __init__(self,
savepath,
exportMode,
scale=1.0
# scale,
):
self.savepath = savepath
self.exportMode = exportMode
self.scale = scale

#scale = 1.0

#SERIALIZE FUNCTION
def save_md5(settings):
print("Exporting selected objects...")
bpy.ops.object.mode_set(mode='OBJECT')

scale = settings.scale



thearmature = 0 #null to start, will assign in next section

#first pass on selected data, pull one skeleton
skeleton = Skeleton(10, "Exported from Blender by io_export_md5.py by Paul Zirkle")
bpy.context.scene.frame_set(bpy.context.scene.frame_start)
for obj in bpy.context.selected_objects:
if obj.type == 'ARMATURE':
#skeleton.name = obj.name
thearmature = obj
w_matrix = obj.matrix_world

#define recursive bone parsing function
def treat_bone(b, parent = None):
if (parent and not b.parent.name==parent.name):
return #only catch direct children

mat = mathutils.Matrix(w_matrix) * mathutils.Matrix(b.matrix_local) #reversed order of multiplication from 2.4 to 2.5!!! ARRRGGG

bone = Bone(skeleton, parent, b.name, mat, b)

if( b.children ):
for child in b.children: treat_bone(child, bone)

for b in thearmature.data.bones:
if( not b.parent ): #only treat root bones'
print( "root bone: " + b.name )
treat_bone(b)

break #only pull one skeleton out

#second pass on selected data, pull meshes
meshes = []
for obj in bpy.context.selected_objects:
if ((obj.type == 'MESH') and ( len(obj.data.vertices.values()) > 0 )):
#for each non-empty mesh
mesh = Mesh(obj.name)
obj.data.update(calc_tessface=True)
print( "Processing mesh: "+ obj.name )
meshes.append(mesh)

numTris = 0
numWeights = 0
for f in obj.data.polygons:
numTris += len(f.vertices) - 2
for v in obj.data.vertices:
numWeights += len( v.groups )

w_matrix = obj.matrix_world
verts = obj.data.vertices

uv_textures = obj.data.tessface_uv_textures
faces = []
for f in obj.data.polygons:
faces.append( f )

createVertexA = 0
createVertexB = 0
createVertexC = 0

while faces:
material_index = faces[0].material_index
material = Material(obj.data.materials[0].name ) #call the shader name by the material's name

submesh = SubMesh(mesh, material)
vertices = {}
for face in faces[:]:
# der_ton: i added this check to make sure a face has at least 3 vertices.
# (pdz) also checks for and removes duplicate verts
if len(face.vertices) < 3: # throw away faces that have less than 3 vertices
faces.remove(face)
elif face.vertices[0] == face.vertices[1]: #throw away degenerate triangles
faces.remove(face)
elif face.vertices[0] == face.vertices[2]:
faces.remove(face)
elif face.vertices[1] == face.vertices[2]:
faces.remove(face)
elif face.material_index == material_index:
#all faces in each sub-mesh must have the same material applied
faces.remove(face)

if not face.use_smooth :
p1 = verts[ face.vertices[0] ].co
p2 = verts[ face.vertices[1] ].co
p3 = verts[ face.vertices[2] ].co
normal = vector_normalize(vector_by_matrix(vector_crossproduct( \
[p3[0] - p2[0], p3[1] - p2[1], p3[2] - p2[2]], \
[p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2]], \
), w_matrix))

#for each vertex in this face, add unique to vertices dictionary
face_vertices = []
for i in range(len(face.vertices)):
vertex = False
if face.vertices[i] in vertices:
vertex = vertices[ face.vertices[i] ] #type of Vertex
if not vertex: #found unique vertex, add to list
coord = point_by_matrix( verts[face.vertices[i]].co, w_matrix ) #TODO: fix possible bug here
if face.use_smooth: normal = vector_normalize(vector_by_matrix( verts[face.vertices[i]].normal, w_matrix ))
vertex = vertices[face.vertices[i]] = Vertex(submesh, coord, normal)
createVertexA += 1

influences = []
for j in range(len( obj.data.vertices[ face.vertices[i] ].groups )):
inf = [obj.vertex_groups[ obj.data.vertices[ face.vertices[i] ].groups[j].group ].name, obj.data.vertices[ face.vertices[i] ].groups[j].weight]
influences.append( inf )

if not influences:
print( "There is a vertex without attachment to a bone in mesh: " + mesh.name )
sum = 0.0
for bone_name, weight in influences: sum += weight

for bone_name, weight in influences:
if sum != 0:
try:
vertex.influences.append(Influence(BONES[bone_name], weight / sum))
except:
continue
else: # we have a vertex that is probably not skinned. export anyway
try:
vertex.influences.append(Influence(BONES[bone_name], weight))
except:
continue

#print( "vert " + str( face.vertices[i] ) + " has " + str(len( vertex.influences ) ) + " influences ")

elif not face.use_smooth:
# We cannot share vertex for non-smooth faces, since Cal3D does not
# support vertex sharing for 2 vertices with different normals.
# => we must clone the vertex.

old_vertex = vertex
vertex = Vertex(submesh, vertex.loc, normal)
createVertexB += 1
vertex.cloned_from = old_vertex
vertex.influences = old_vertex.influences
old_vertex.clones.append(vertex)

hasFaceUV = len(uv_textures) > 0 #borrowed from export_obj.py

if hasFaceUV:
uv = [uv_textures.active.data[face.index].uv[i][0], uv_textures.active.data[face.index].uv[i][1]]
uv[1] = 1.0 - uv[1] # should we flip Y? yes, new in Blender 2.5x
if not vertex.maps: vertex.maps.append(Map(*uv))
elif (vertex.maps[0].u != uv[0]) or (vertex.maps[0].v != uv[1]):
# This vertex can be shared for Blender, but not for MD5
# MD5 does not support vertex sharing for 2 vertices with
# different UV texture coodinates.
# => we must clone the vertex.

for clone in vertex.clones:
if (clone.maps[0].u == uv[0]) and (clone.maps[0].v == uv[1]):
vertex = clone
break
else: # Not yet cloned... (PDZ) note: this ELSE belongs attached to the FOR loop.. python can do that apparently
old_vertex = vertex
vertex = Vertex(submesh, vertex.loc, vertex.normal)
createVertexC += 1
vertex.cloned_from = old_vertex
vertex.influences = old_vertex.influences
vertex.maps.append(Map(*uv))
old_vertex.clones.append(vertex)

face_vertices.append(vertex)

# Split faces with more than 3 vertices
for i in range(1, len(face.vertices) - 1):
Face(submesh, face_vertices[0], face_vertices[i], face_vertices[i + 1])
else:
print( "found face with invalid material!!!!" )
print( "created verts at A " + str(createVertexA) + ", B " + str( createVertexB ) + ", C " + str( createVertexC ) )

# Export animations
ANIMATIONS = {}

arm_action = thearmature.animation_data.action
rangestart = 0
rangeend = 0
if arm_action:
animation = ANIMATIONS[arm_action.name] = MD5Animation(skeleton)
# armature.animation_data.action = action
bpy.context.scene.update()
armature = bpy.context.active_object
action = armature.animation_data.action
# framemin, framemax = bpy.context.active_object.animation_data.Action(fcurves.frame_range)
framemin, framemax = action.frame_range
rangestart = int(framemin)
rangeend = int(framemax)
# rangestart = int( bpy.context.scene.frame_start ) # int( arm_action.frame_range[0] )
# rangeend = int( bpy.context.scene.frame_end ) #int( arm_action.frame_range[1] )
currenttime = rangestart
while currenttime <= rangeend:
bpy.context.scene.frame_set(currenttime)
time = (currenttime - 1.0) / 24.0 #(assuming default 24fps for md5 anim)
pose = thearmature.pose

for bonename in thearmature.data.bones.keys():
posebonemat = mathutils.Matrix(pose.bones[bonename].matrix ) # @ivar poseMatrix: The total transformation of this PoseBone including constraints. -- different from localMatrix

try:
bone = BONES[bonename] #look up md5bone
except:
print( "found a posebone animating a bone that is not part of the exported armature: " + bonename )
continue
if bone.parent: # need parentspace-matrix
parentposemat = mathutils.Matrix(pose.bones[bone.parent.name].matrix ) # @ivar poseMatrix: The total transformation of this PoseBone including constraints. -- different from localMatrix
# posebonemat = parentposemat.invert() * posebonemat #reverse order of multiplication!!!
parentposemat.invert() # mikshaw
posebonemat = parentposemat * posebonemat # mikshaw
else:
posebonemat = thearmature.matrix_world * posebonemat #reverse order of multiplication!!!
loc = [posebonemat.col[3][0],
posebonemat.col[3][1],
posebonemat.col[3][2],
]
# rot = posebonemat.to_quat().normalize()
rot = posebonemat.to_quaternion() # changed from to_quat in 2.57 -mikshaw
rot.normalize() # mikshaw
rot = [rot.w,rot.x,rot.y,rot.z]

animation.addkeyforbone(bone.id, time, loc, rot)
currenttime += 1

# here begins md5mesh and anim output
# this is how it works
# first the skeleton is output, using the data that was collected by the above code in this export function
# then the mesh data is output (into the same md5mesh file)

if( settings.exportMode == "mesh & anim" or settings.exportMode == "mesh only" ):
md5mesh_filename = settings.savepath + ".md5mesh"

#save all submeshes in the first mesh
if len(meshes)>1:
for mesh in range (1, len(meshes)):
for submesh in meshes[mesh].submeshes:
submesh.bindtomesh(meshes[0])
if (md5mesh_filename != ""):
try:
file = open(md5mesh_filename, 'w')
except IOError:
errmsg = "IOError " #%s: %s" % (errno, strerror)
buffer = skeleton.to_md5mesh(len(meshes[0].submeshes))
#for mesh in meshes:
buffer = buffer + meshes[0].to_md5mesh()
file.write(buffer)
file.close()
print( "saved mesh to " + md5mesh_filename )
else:
print( "No md5mesh file was generated." )

if( settings.exportMode == "mesh & anim" or settings.exportMode == "anim only" ):
md5anim_filename = settings.savepath + ".md5anim"

#save animation file
if len(ANIMATIONS)>0:
anim = ANIMATIONS.popitem()[1] #ANIMATIONS.values()[0]
print( str( anim ) )
try:
file = open(md5anim_filename, 'w')
except IOError:
errmsg = "IOError " #%s: %s" % (errno, strerror)
objects = []
for submesh in meshes[0].submeshes:
if len(submesh.weights) > 0:
obj = None
for sob in bpy.context.selected_objects:
if sob and sob.type == 'MESH' and sob.name == submesh.name:
obj = sob
objects.append (obj)
generateboundingbox(objects, anim, [rangestart, rangeend])
buffer = anim.to_md5anim()
file.write(buffer)
file.close()
print( "saved anim to " + md5anim_filename )
else:
print( "No md5anim file was generated." )

##########
#export class registration and interface
from bpy.props import *
class ExportMD5(bpy.types.Operator):
'''Export to idTech 4 MD5 (.md5mesh .md5anim)'''
bl_idname = "export.md5"
bl_label = 'idTech 4 MD5'

logenum = [("console","Console","log to console"),
("append","Append","append to log file"),
("overwrite","Overwrite","overwrite log file")]

#search for list of actions to export as .md5anims
#md5animtargets = []
#for anim in bpy.data.actions:
# md5animtargets.append( (anim.name, anim.name, anim.name) )

#md5animtarget = None
#if( len( md5animtargets ) > 0 ):
# md5animtarget = EnumProperty( name="Anim", items = md5animtargets, description = "choose animation to export", default = md5animtargets[0] )

exportModes = [("mesh & anim", "Mesh & Anim", "Export .md5mesh and .md5anim files."),
("anim only", "Anim only.", "Export .md5anim only."),
("mesh only", "Mesh only.", "Export .md5mesh only.")]

filepath = StringProperty(subtype = 'FILE_PATH',name="File Path", description="Filepath for exporting", maxlen= 1024, default= "")
md5name = StringProperty(name="MD5 Name", description="MD3 header name / skin path (64 bytes)",maxlen=64,default="")
md5exportList = EnumProperty(name="Exports", items=exportModes, description="Choose export mode.", default='mesh & anim')
#md5logtype = EnumProperty(name="Save log", items=logenum, description="File logging options",default = 'console')
md5scale = FloatProperty(name="Scale", description="Scale all objects from world origin (0,0,0)", min=0.001, max=1000.0, default=1.0,precision=6)
#md5offsetx = FloatProperty(name="Offset X", description="Transition scene along x axis",default=0.0,precision=5)
#md5offsety = FloatProperty(name="Offset Y", description="Transition scene along y axis",default=0.0,precision=5)
#md5offsetz = FloatProperty(name="Offset Z", description="Transition scene along z axis",default=0.0,precision=5)



def execute(self, context):
global scale
scale = self.md5scale
settings = md5Settings(savepath = self.properties.filepath,
exportMode = self.properties.md5exportList
)
save_md5(settings)
return {'FINISHED'}

def invoke(self, context, event):
WindowManager = context.window_manager
# fixed for 2.56? Katsbits.com (via Nic B)
# original WindowManager.add_fileselect(self)
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}

def menu_func(self, context):
default_path = os.path.splitext(bpy.data.filepath)[0]
self.layout.operator(ExportMD5.bl_idname, text="idTech 4 MD5 (.md5mesh .md5anim)", icon='BLENDER').filepath = default_path

def register():
bpy.utils.register_module(__name__) #mikshaw
bpy.types.INFO_MT_file_export.append(menu_func)

def unregister():
bpy.utils.unregister_module(__name__) #mikshaw
bpy.types.INFO_MT_file_export.remove(menu_func)

if __name__ == "__main__":
register()
  1. #"""
  2. #Name: 'Quake Model 5 (.md5)...'
  3. #Blender: 257
  4. #Group: 'Export'
  5. #Tooltip: 'Export a Quake Model 5 File'
  6. #
  7. #credit to der_ton for the 2.4x Blender export script
  8. #"""
  9.  
  10. bl_info = { # changed from bl_addon_info in 2.57 -mikshaw
  11.     "name": "Export idTech4 (.md5)",
  12.     "author": "Paul Zirkle aka Keless, credit to der_ton, ported to Blender 2.62 by motorsep and tested by kat, special thanks to MCampagnini",
  13.     "version": (1,0,0),
  14.     "blender": (2, 6, 3),
  15.     "api": 31847,
  16.     "location": "File > Export > Skeletal Mesh/Animation Data (.md5mesh/.md5anim)",
  17.     "description": "Export idTech4 (.md5)",
  18.     "warning": "",
  19.     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
  20.         "Scripts/File_I-O/idTech4_md5",
  21.     "tracker_url": "http://www.katsbits.com",
  22.     "category": "Import-Export"}
  23.  
  24. import bpy,struct,math,os,time,sys,mathutils
  25.  
  26. #MATH UTILTY
  27.  
  28. def vector_crossproduct(v1, v2):
  29.   return [
  30.     v1[1] * v2[2] - v1[2] * v2[1],
  31.     v1[2] * v2[0] - v1[0] * v2[2],
  32.     v1[0] * v2[1] - v1[1] * v2[0],
  33.     ]
  34.  
  35. def point_by_matrix(p, m):
  36.   #print( str(type( p )) + " " + str(type(m)) )
  37.   return [p[0] * m[0][0] + p[1] * m[1][0] + p[2] * m[2][0] + m[3][0],
  38.           p[0] * m[0][1] + p[1] * m[1][1] + p[2] * m[2][1] + m[3][1],
  39.           p[0] * m[0][2] + p[1] * m[1][2] + p[2] * m[2][2] + m[3][2]]
  40.  
  41. def vector_by_matrix(p, m):
  42.   return [p[0] * m.col[0][0] + p[1] * m.col[1][0] + p[2] * m.col[2][0],
  43.           p[0] * m.col[0][1] + p[1] * m.col[1][1] + p[2] * m.col[2][1],
  44.           p[0] * m.col[0][2] + p[1] * m.col[1][2] + p[2] * m.col[2][2]]
  45.  
  46. def vector_normalize(v):
  47.   l = math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
  48.   try:
  49.     return v[0] / l, v[1] / l, v[2] / l
  50.   except:
  51.     return 1, 0, 0
  52.  
  53. def matrix2quaternion(m):
  54.   s = math.sqrt(abs(m.col[0][0] + m.col[1][1] + m.col[2][2] + m.col[3][3]))
  55.   if s == 0.0:
  56.     x = abs(m.col[2][1] - m.col[1][2])
  57.     y = abs(m.col[0][2] - m.col[2][0])
  58.     z = abs(m.col[1][0] - m.col[0][1])
  59.     if   (x >= y) and (x >= z): return 1.0, 0.0, 0.0, 0.0
  60.     elif (y >= x) and (y >= z): return 0.0, 1.0, 0.0, 0.0
  61.     else:                       return 0.0, 0.0, 1.0, 0.0
  62.   return quaternion_normalize([
  63.     -(m.col[2][1] - m.col[1][2]) / (2.0 * s),
  64.     -(m.col[0][2] - m.col[2][0]) / (2.0 * s),
  65.     -(m.col[1][0] - m.col[0][1]) / (2.0 * s),
  66.     0.5 * s,
  67.     ])
  68.    
  69. def matrix_invert(m):
  70.   det = (m.col[0][0] * (m.col[1][1] * m.col[2][2] - m.col[2][1] * m.col[1][2])
  71.        - m.col[1][0] * (m.col[0][1] * m.col[2][2] - m.col[2][1] * m.col[0][2])
  72.        + m.col[2][0] * (m.col[0][1] * m.col[1][2] - m.col[1][1] * m.col[0][2]))
  73.   if det == 0.0: return None
  74.   det = 1.0 / det
  75. # transposed matrix
  76. #  r = [ [
  77. #      det * (m.col[1][1] * m.col[2][2] - m.col[2][1] * m.col[1][2]),
  78. #    - det * (m.col[1][0] * m.col[2][2] - m.col[2][0] * m.col[1][2]),
  79. #      det * (m.col[1][0] * m.col[2][1] - m.col[2][0] * m.col[1][1]),
  80. #    ], [
  81. #    - det * (m.col[0][1] * m.col[2][2] - m.col[2][1] * m.col[0][2]),
  82. #     det * (m.col[0][0] * m.col[2][2] - m.col[2][0] * m.col[0][2]),
  83. #   - det * (m.col[0][0] * m.col[2][1] - m.col[2][0] * m.col[0][1]),
  84. #    ], [
  85. #      det * (m.col[0][1] * m.col[1][2] - m.col[1][1] * m.col[0][2]),
  86. #    - det * (m.col[0][0] * m.col[1][2] - m.col[1][0] * m.col[0][2]),
  87. #      det * (m.col[0][0] * m.col[1][1] - m.col[1][0] * m.col[0][1]),
  88. #    ], [
  89. #      0.0,
  90. #      0.0,
  91. #      0.0,
  92. #    ] ]
  93. # original matrix from 2.61 compaticle script adopted for 2.62; the mesh with this matric is consistent, but rotated 180 degrees around Z axis and centered at 0 0 0
  94.   r = [ [
  95.       det * (m.col[1][1] * m.col[2][2] - m.col[2][1] * m.col[1][2]),
  96.     - det * (m.col[0][1] * m.col[2][2] - m.col[2][1] * m.col[0][2]),
  97.       det * (m.col[0][1] * m.col[1][2] - m.col[1][1] * m.col[0][2]),
  98.       0.0,
  99.     ], [
  100.     - det * (m.col[1][0] * m.col[2][2] - m.col[2][0] * m.col[1][2]),
  101.       det * (m.col[0][0] * m.col[2][2] - m.col[2][0] * m.col[0][2]),
  102.     - det * (m.col[0][0] * m.col[1][2] - m.col[1][0] * m.col[0][2]),
  103.       0.0
  104.     ], [
  105.       det * (m.col[1][0] * m.col[2][1] - m.col[2][0] * m.col[1][1]),
  106.     - det * (m.col[0][0] * m.col[2][1] - m.col[2][0] * m.col[0][1]),
  107.       det * (m.col[0][0] * m.col[1][1] - m.col[1][0] * m.col[0][1]),
  108.       0.0,
  109.     ] ]
  110.   r.append([
  111.     -(m.col[3][0] * r[0][0] + m.col[3][1] * r[1][0] + m.col[3][2] * r[2][0]),
  112.     -(m.col[3][0] * r[0][1] + m.col[3][1] * r[1][1] + m.col[3][2] * r[2][1]),
  113.     -(m.col[3][0] * r[0][2] + m.col[3][1] * r[1][2] + m.col[3][2] * r[2][2]),
  114.     1.0,
  115.     ])
  116.   return r
  117.    
  118. def quaternion_normalize(q):
  119.   l = math.sqrt(q.col[0] * q.col[0] + q.col[1] * q.col[1] + q.col[2] * q.col[2] + q.col[3] * q.col[3])
  120.   return q.col[0] / l, q.col[1] / l, q.col[2] / l, q.col[3] / l
  121.  
  122.  
  123. #shader material
  124. class Material:
  125.   name = ""             #string
  126.   def __init__(self, textureFileName):
  127.     self.name = textureFileName
  128.  
  129.   def to_md5mesh(self):
  130.     return self.name;
  131.  
  132. #the 'Model' class, contains all submeshes
  133. class Mesh:
  134.   name = ""             #string
  135.   submeshes = []        #array of SubMesh
  136.   next_submesh_id = 0   #int
  137.  
  138.   def __init__(self, name):
  139.     self.name      = name
  140.     self.submeshes = []
  141.    
  142.     self.next_submesh_id = 0
  143.  
  144.    
  145.   def to_md5mesh(self):
  146.     meshnumber=0
  147.     buf = ""
  148.     for submesh in self.submeshes:
  149.       buf=buf + "mesh {\n"
  150. #      buf=buf + "mesh {\n\t// meshes: " + submesh.name + "\n"  # used for Sauerbraten -mikshaw
  151.       meshnumber += 1
  152.       buf=buf + submesh.to_md5mesh()
  153.       buf=buf + "}\n\n"
  154.  
  155.     return buf
  156.  
  157.  
  158. #submeshes reference a parent mesh
  159. class SubMesh:
  160.   def __init__(self, mesh, material):
  161.     self.material   = material
  162.     self.vertices   = []
  163.     self.faces      = []
  164.     self.nb_lodsteps = 0
  165.     self.springs    = []
  166.     self.weights    = []
  167.    
  168.     self.next_vertex_id = 0
  169.     self.next_weight_id = 0
  170.    
  171.     self.mesh = mesh
  172.     self.name = mesh.name
  173.     self.id = mesh.next_submesh_id
  174.     mesh.next_submesh_id += 1
  175.     mesh.submeshes.append(self)
  176.  
  177.   def bindtomesh (self, mesh):
  178.     # HACK: this is needed for md5 output, for the time being...
  179.     # appending this submesh to the specified mesh, disconnecting it from the original one
  180.     self.mesh.submeshes.remove(self)
  181.     self.mesh = mesh
  182.     self.id = mesh.next_submesh_id
  183.     mesh.next_submesh_id += 1
  184.     mesh.submeshes.append(self)
  185.  
  186.   def generateweights(self):
  187.     self.weights = []
  188.     self.next_weight_id = 0
  189.     for vert in self.vertices:
  190.       vert.generateweights()
  191.  
  192.   def reportdoublefaces(self):
  193.     for face in self.faces:
  194.       for face2 in self.faces:
  195.         if not face == face2:
  196.           if (not face.vertex1==face2.vertex1) and (not face.vertex1==face2.vertex2) and (not face.vertex1==face2.vertex3):
  197.             return
  198.           if (not face.vertex2==face2.vertex1) and (not face.vertex2==face2.vertex2) and (not face.vertex2==face2.vertex3):
  199.             return
  200.           if (not face.vertex3==face2.vertex1) and (not face.vertex3==face2.vertex2) and (not face.vertex3==face2.vertex3):
  201.             return
  202.           print('doubleface! %s %s' % (face, face2))
  203.          
  204.   def to_md5mesh(self):
  205.     self.generateweights()
  206.  
  207.     self.reportdoublefaces()
  208.    
  209.     buf="\tshader \"%s\"\n\n" % (self.material.to_md5mesh())
  210.     if len(self.weights) == 0:
  211.       buf=buf + "\tnumverts 0\n"
  212.       buf=buf + "\n\tnumtris 0\n"
  213.       buf=buf + "\n\tnumweights 0\n"
  214.       return buf
  215.    
  216.     # output vertices
  217.     buf=buf + "\tnumverts %i\n" % (len(self.vertices))
  218.     vnumber=0
  219.     for vert in self.vertices:
  220.       buf=buf + "\tvert %i %s\n" % (vnumber, vert.to_md5mesh())
  221.       vnumber += 1
  222.    
  223.     # output faces
  224.     buf=buf + "\n\tnumtris %i\n" % (len(self.faces))
  225.     facenumber=0
  226.     for face in self.faces:
  227.       buf=buf + "\ttri %i %s\n" % (facenumber, face.to_md5mesh())
  228.       facenumber += 1
  229.      
  230.     # output weights
  231.     buf=buf + "\n\tnumweights %i\n" % (len(self.weights))
  232.     weightnumber=0
  233.     for weight in self.weights:
  234.       buf=buf + "\tweight %i %s\n" % (weightnumber, weight.to_md5mesh())
  235.       weightnumber += 1
  236.      
  237.     return buf
  238.    
  239. #vertex class contains and outputs 'verts' but also generates 'weights' data
  240. class Vertex:
  241.   def __init__(self, submesh, loc, normal):
  242.     self.loc    = loc
  243.     self.normal = normal
  244.     self.collapse_to         = None
  245.     self.face_collapse_count = 0
  246.     self.maps       = []
  247.     self.influences = []
  248.     self.weights = []
  249.     self.weight = None
  250.     self.firstweightindx = 0
  251.     self.cloned_from = None
  252.     self.clones      = []
  253.    
  254.     self.submesh = submesh
  255.     self.id = submesh.next_vertex_id
  256.     submesh.next_vertex_id += 1
  257.     submesh.vertices.append(self)
  258.    
  259.   def generateweights(self):
  260.     self.firstweightindx = self.submesh.next_weight_id
  261.     for influence in self.influences:
  262.       weightindx = self.submesh.next_weight_id
  263.       self.submesh.next_weight_id += 1
  264.       newweight = Weight(influence.bone, influence.weight, self, weightindx, self.loc[0], self.loc[1], self.loc[2])
  265.       self.submesh.weights.append(newweight)
  266.       self.weights.append(newweight)
  267.  
  268.   def to_md5mesh(self):
  269.     if self.maps:
  270.       buf = self.maps[0].to_md5mesh()
  271.     else:
  272.       buf = "( %f %f )" % (self.loc[0], self.loc[1])
  273.     buf = buf + " %i %i" % (self.firstweightindx, len(self.influences))
  274.     return buf    
  275.    
  276. #texture coordinate map
  277. class Map:
  278.   def __init__(self, u, v):
  279.     self.u = u
  280.     self.v = v
  281.    
  282.  
  283.   def to_md5mesh(self):
  284.     buf = "( %f %f )" % (self.u, self.v)
  285.     return buf
  286.  
  287. #NOTE: uses global 'scale' to scale the size of model verticies
  288. #generated and stored in Vertex class
  289. class Weight:
  290.   def __init__(self, bone, weight, vertex, weightindx, x, y, z):
  291.     self.bone = bone
  292.     self.weight = weight
  293.     self.vertex = vertex
  294.     self.indx = weightindx
  295.     invbonematrix = matrix_invert(self.bone.matrix)
  296.     self.x, self.y, self.z = point_by_matrix ((x, y, z), invbonematrix)
  297.    
  298.   def to_md5mesh(self):
  299.     buf = "%i %f ( %f %f %f )" % (self.bone.id, self.weight, self.x*scale, self.y*scale, self.z*scale)
  300.     return buf
  301.  
  302. #used by SubMesh class
  303. class Influence:
  304.   def __init__(self, bone, weight):
  305.     self.bone   = bone
  306.     self.weight = weight
  307.    
  308. #outputs the 'tris' data
  309. class Face:
  310.   def __init__(self, submesh, vertex1, vertex2, vertex3):
  311.     self.vertex1 = vertex1
  312.     self.vertex2 = vertex2
  313.     self.vertex3 = vertex3
  314.    
  315.     self.can_collapse = 0
  316.    
  317.     self.submesh = submesh
  318.     submesh.faces.append(self)
  319.    
  320.  
  321.   def to_md5mesh(self):
  322.     buf = "%i %i %i" % (self.vertex1.id, self.vertex3.id, self.vertex2.id)
  323.     return buf
  324.  
  325. #holds bone skeleton data and outputs header above the Mesh class
  326. class Skeleton:
  327.   def __init__(self, MD5Version = 10, commandline = ""):
  328.     self.bones = []
  329.     self.MD5Version = MD5Version
  330.     self.commandline = commandline
  331.     self.next_bone_id = 0
  332.    
  333.  
  334.   def to_md5mesh(self, numsubmeshes):
  335.     buf = "MD5Version %i\n" % (self.MD5Version)
  336.     buf = buf + "commandline \"%s\"\n\n" % (self.commandline)
  337.     buf = buf + "numJoints %i\n" % (self.next_bone_id)
  338.     buf = buf + "numMeshes %i\n\n" % (numsubmeshes)
  339.     buf = buf + "joints {\n"
  340.     for bone in self.bones:
  341.       buf = buf + bone.to_md5mesh()
  342.     buf = buf + "}\n\n"
  343.     return buf
  344.  
  345. BONES = {}
  346.  
  347. #held by Skeleton, generates individual 'joint' data
  348. class Bone:
  349.   def __init__(self, skeleton, parent, name, mat, theboneobj):
  350.     self.parent = parent #Bone
  351.     self.name   = name   #string
  352.     self.children = []   #list of Bone objects
  353.     self.theboneobj = theboneobj #Blender.Armature.Bone
  354.     # HACK: this flags if the bone is animated in the one animation that we export
  355.     self.is_animated = 0  # = 1, if there is an ipo that animates this bone
  356.  
  357.     self.matrix = mat
  358.     if parent:
  359.       parent.children.append(self)
  360.    
  361.     self.skeleton = skeleton
  362.     self.id = skeleton.next_bone_id
  363.     skeleton.next_bone_id += 1
  364.     skeleton.bones.append(self)
  365.    
  366.     BONES[name] = self
  367.  
  368.  
  369.   def to_md5mesh(self):
  370.     buf= "\t\"%s\"\t" % (self.name)
  371.     parentindex = -1
  372.     if self.parent:
  373.         parentindex=self.parent.id
  374.     buf=buf+"%i " % (parentindex)
  375.    
  376.     pos1, pos2, pos3= self.matrix.col[3][0], self.matrix.col[3][1], self.matrix.col[3][2]
  377.     buf=buf+"( %f %f %f ) " % (pos1*scale, pos2*scale, pos3*scale)
  378.     #qx, qy, qz, qw = matrix2quaternion(self.matrix)
  379.     #if qw<0:
  380.     #    qx = -qx
  381.     #    qy = -qy
  382.     #    qz = -qz
  383.     m = self.matrix
  384. #    bquat = self.matrix.to_quat()  #changed from matrix.toQuat() in blender 2.4x script
  385.     bquat = self.matrix.to_quaternion()  #changed from to_quat in 2.57 -mikshaw
  386.     bquat.normalize()
  387.     qx = bquat.x
  388.     qy = bquat.y
  389.     qz = bquat.z
  390.     if bquat.w > 0:
  391.         qx = -qx
  392.         qy = -qy
  393.         qz = -qz
  394.     buf=buf+"( %f %f %f )\t\t// " % (qx, qy, qz)
  395.     if self.parent:
  396.         buf=buf+"%s" % (self.parent.name)    
  397.    
  398.     buf=buf+"\n"
  399.     return buf
  400.  
  401.  
  402. class MD5Animation:
  403.   def __init__(self, md5skel, MD5Version = 10, commandline = ""):
  404.     self.framedata    = [] # framedata[boneid] holds the data for each frame
  405.     self.bounds       = []
  406.     self.baseframe    = []
  407.     self.skeleton     = md5skel
  408.     self.boneflags    = []  # stores the md5 flags for each bone in the skeleton
  409.     self.boneframedataindex = [] # stores the md5 framedataindex for each bone in the skeleton
  410.     self.MD5Version   = MD5Version
  411.     self.commandline  = commandline
  412.     self.numanimatedcomponents = 0
  413.     self.framerate    = 24
  414.     self.numframes    = 0
  415.     for b in self.skeleton.bones:
  416.       self.framedata.append([])
  417.       self.baseframe.append([])
  418.       self.boneflags.append(0)
  419.       self.boneframedataindex.append(0)
  420.      
  421.   def to_md5anim(self):
  422.     currentframedataindex = 0
  423.     for bone in self.skeleton.bones:
  424.       if (len(self.framedata[bone.id])>0):
  425.         if (len(self.framedata[bone.id])>self.numframes):
  426.           self.numframes=len(self.framedata[bone.id])
  427.         (x,y,z),(qw,qx,qy,qz) = self.framedata[bone.id][0]
  428.         self.baseframe[bone.id]= (x*scale,y*scale,z*scale,qx,qy,qz)
  429.         self.boneframedataindex[bone.id]=currentframedataindex
  430.         self.boneflags[bone.id] = 63
  431.         currentframedataindex += 6
  432.         self.numanimatedcomponents = currentframedataindex
  433.       else:
  434.         rot=bone.matrix.to_quaternion()
  435.         rot.normalize()
  436.         qx=rot.x
  437.         qy=rot.y
  438.         qz=rot.z
  439.         if rot.w > 0:
  440.             qx = -qx
  441.             qy = -qy
  442.             qz = -qz            
  443.         self.baseframe.col[bone.id]= (bone.matrix.col[3][0]*scale, bone.matrix.col[3][1]*scale, bone.matrix.col[3][2]*scale, qx, qy, qz)
  444.        
  445.     buf = "MD5Version %i\n" % (self.MD5Version)
  446.     buf = buf + "commandline \"%s\"\n\n" % (self.commandline)
  447.     buf = buf + "numFrames %i\n" % (self.numframes)
  448.     buf = buf + "numJoints %i\n" % (len(self.skeleton.bones))
  449.     buf = buf + "frameRate %i\n" % (self.framerate)
  450.     buf = buf + "numAnimatedComponents %i\n\n" % (self.numanimatedcomponents)
  451.     buf = buf + "hierarchy {\n"
  452.  
  453.     for bone in self.skeleton.bones:
  454.       parentindex = -1
  455.       flags = self.boneflags[bone.id]
  456.       framedataindex = self.boneframedataindex[bone.id]
  457.       if bone.parent:
  458.         parentindex=bone.parent.id
  459.       buf = buf + "\t\"%s\"\t%i %i %i\t//" % (bone.name, parentindex, flags, framedataindex)
  460.       if bone.parent:
  461.         buf = buf + " " + bone.parent.name
  462.       buf = buf + "\n"
  463.     buf = buf + "}\n\n"
  464.  
  465.     buf = buf + "bounds {\n"
  466.     for b in self.bounds:
  467.       buf = buf + "\t( %f %f %f ) ( %f %f %f )\n" % (b)
  468.     buf = buf + "}\n\n"
  469.  
  470.     buf = buf + "baseframe {\n"
  471.     for b in self.baseframe:
  472.       buf = buf + "\t( %f %f %f ) ( %f %f %f )\n" % (b)
  473.     buf = buf + "}\n\n"
  474.  
  475.     for f in range(0, self.numframes):
  476.       buf = buf + "frame %i {\n" % (f)
  477.       for b in self.skeleton.bones:
  478.         if (len(self.framedata[b.id])>0):
  479.           (x,y,z),(qw,qx,qy,qz) = self.framedata[b.id][f]
  480.           if qw>0:
  481.             qx,qy,qz = -qx,-qy,-qz
  482.           buf = buf + "\t%f %f %f %f %f %f\n" % (x*scale, y*scale, z*scale, qx,qy,qz)
  483.       buf = buf + "}\n\n"
  484.      
  485.     return buf
  486.  
  487.   def addkeyforbone(self, boneid, time, loc, rot):
  488.     # time is ignored. the keys are expected to come in sequentially
  489.     # it might be useful for future changes or modifications for other export formats
  490.     self.framedata[boneid].append((loc, rot))
  491.     return
  492.    
  493.  
  494. def getminmax(listofpoints):
  495.   if len(listofpoints[0]) == 0: return ([0,0,0],[0,0,0])
  496.   min = [listofpoints[0][0], listofpoints[1][0], listofpoints[2][0]]
  497.   max = [listofpoints[0][0], listofpoints[1][0], listofpoints[2][0]]
  498.   if len(listofpoints[0])>1:
  499.     for i in range(1, len(listofpoints[0])):
  500.       if listofpoints[i][0]>max[0]: max[0]=listofpoints[i][0]
  501.       if listofpoints[i][1]>max[1]: max[1]=listofpoints[i][1]
  502.       if listofpoints[i][2]>max[2]: max[2]=listofpoints[i][2]
  503.       if listofpoints[i][0]<min[0]: min[0]=listofpoints[i][0]
  504.       if listofpoints[i][1]<min[1]: min[1]=listofpoints[i][1]
  505.       if listofpoints[i][2]<min[2]: min[2]=listofpoints[i][2]
  506.   return (min, max)
  507.  
  508. def generateboundingbox(objects, md5animation, framerange):
  509.   scene = bpy.context.scene #Blender.Scene.getCurrent()
  510.   context = scene.render #scene.getRenderingContext()
  511.   for i in range(framerange[0], framerange[1]+1):
  512.     corners = []
  513.     #context.currentFrame(i)
  514.     #scene.makeCurrent()
  515.     scene.frame_set( i )
  516.    
  517.     for obj in objects:
  518.       data = obj.data #obj.getData()
  519.       #if (type(data) is Blender.Types.NMeshType) and data.faces:
  520.       if obj.type == 'MESH' and data.polygons:
  521.         #obj.makeDisplayList()
  522.         #(lx, ly, lz) = obj.getLocation()
  523.         (lx, ly, lz ) = obj.location
  524.         #bbox = obj.getBoundBox()
  525.         bbox = obj.bound_box
  526. # transposed matrix
  527. #        matrix = [[1.0,  0.0,  0.0,  0.0],
  528. #          [0.0,  1.0,  1.0,  0.0],
  529. #          [0.0,  0.0,  1.0,  0.0],
  530. #          [0.0,  0.0,  0.0,  1.0],
  531. #          ]
  532. # original matrix from the 2.61 compatible script
  533.         matrix = [[1.0,  0.0, 0.0, 0.0],
  534.           [0.0,  1.0, 0.0, 0.0],
  535.           [0.0,  1.0, 1.0, 0.0],
  536.           [0.0,  0.0, 0.0, 1.0],
  537.           ]
  538.         for v in bbox:
  539.           corners.append(point_by_matrix (v, matrix))
  540.     (min, max) = getminmax(corners)
  541.     md5animation.bounds.append((min[0]*scale, min[1]*scale, min[2]*scale, max[0]*scale, max[1]*scale, max[2]*scale))
  542.  
  543.    
  544. #exporter settings
  545. class md5Settings:
  546.   def __init__(self,
  547.                savepath,
  548.                exportMode,
  549.                scale=1.0
  550. #               scale,
  551.                ):
  552.     self.savepath = savepath
  553.     self.exportMode = exportMode
  554.     self.scale = scale
  555.  
  556. #scale = 1.0
  557.  
  558. #SERIALIZE FUNCTION
  559. def save_md5(settings):
  560.   print("Exporting selected objects...")
  561.   bpy.ops.object.mode_set(mode='OBJECT')
  562.  
  563.   scale = settings.scale
  564.  
  565.  
  566.  
  567.   thearmature = 0  #null to start, will assign in next section
  568.  
  569.   #first pass on selected data, pull one skeleton
  570.   skeleton = Skeleton(10, "Exported from Blender by io_export_md5.py by Paul Zirkle")
  571.   bpy.context.scene.frame_set(bpy.context.scene.frame_start)
  572.   for obj in bpy.context.selected_objects:
  573.     if obj.type == 'ARMATURE':
  574.       #skeleton.name = obj.name
  575.       thearmature = obj
  576.       w_matrix = obj.matrix_world
  577.      
  578.       #define recursive bone parsing function
  579.       def treat_bone(b, parent = None):
  580.         if (parent and not b.parent.name==parent.name):
  581.           return #only catch direct children
  582.        
  583.         mat =  mathutils.Matrix(w_matrix) * mathutils.Matrix(b.matrix_local)  #reversed order of multiplication from 2.4 to 2.5!!! ARRRGGG
  584.        
  585.         bone = Bone(skeleton, parent, b.name, mat, b)
  586.        
  587.         if( b.children ):
  588.           for child in b.children: treat_bone(child, bone)
  589.          
  590.       for b in thearmature.data.bones:
  591.         if( not b.parent ): #only treat root bones'
  592.           print( "root bone: " + b.name )
  593.           treat_bone(b)
  594.    
  595.       break #only pull one skeleton out
  596.  
  597.   #second pass on selected data, pull meshes
  598.   meshes = []
  599.   for obj in bpy.context.selected_objects:
  600.     if ((obj.type == 'MESH') and ( len(obj.data.vertices.values()) > 0 )):
  601.       #for each non-empty mesh
  602.       mesh = Mesh(obj.name)
  603.       obj.data.update(calc_tessface=True)
  604.       print( "Processing mesh: "+ obj.name )
  605.       meshes.append(mesh)
  606.  
  607.       numTris = 0
  608.       numWeights = 0
  609.       for f in obj.data.polygons:
  610.         numTris += len(f.vertices) - 2
  611.       for v in obj.data.vertices:
  612.         numWeights += len( v.groups )
  613.        
  614.       w_matrix = obj.matrix_world
  615.       verts = obj.data.vertices
  616.      
  617.       uv_textures = obj.data.tessface_uv_textures
  618.       faces = []
  619.       for f in obj.data.polygons:
  620.         faces.append( f )
  621.      
  622.       createVertexA = 0
  623.       createVertexB = 0
  624.       createVertexC = 0
  625.        
  626.       while faces:
  627.         material_index = faces[0].material_index
  628.         material = Material(obj.data.materials[0].name ) #call the shader name by the material's name
  629.        
  630.         submesh = SubMesh(mesh, material)
  631.         vertices = {}
  632.         for face in faces[:]:
  633.           # der_ton: i added this check to make sure a face has at least 3 vertices.
  634.           # (pdz) also checks for and removes duplicate verts
  635.           if len(face.vertices) < 3: # throw away faces that have less than 3 vertices
  636.             faces.remove(face)
  637.           elif face.vertices[0] == face.vertices[1]:  #throw away degenerate triangles
  638.             faces.remove(face)
  639.           elif face.vertices[0] == face.vertices[2]:
  640.             faces.remove(face)
  641.           elif face.vertices[1] == face.vertices[2]:
  642.             faces.remove(face)
  643.           elif face.material_index == material_index:
  644.             #all faces in each sub-mesh must have the same material applied
  645.             faces.remove(face)
  646.            
  647.             if not face.use_smooth :
  648.               p1 = verts[ face.vertices[0] ].co
  649.               p2 = verts[ face.vertices[1] ].co
  650.               p3 = verts[ face.vertices[2] ].co
  651.               normal = vector_normalize(vector_by_matrix(vector_crossproduct( \
  652.                 [p3[0] - p2[0], p3[1] - p2[1], p3[2] - p2[2]], \
  653.                 [p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2]], \
  654.                 ), w_matrix))
  655.            
  656.             #for each vertex in this face, add unique to vertices dictionary
  657.             face_vertices = []
  658.             for i in range(len(face.vertices)):
  659.               vertex = False
  660.               if face.vertices[i] in vertices:
  661.                 vertex = vertices[  face.vertices[i] ] #type of Vertex
  662.               if not vertex: #found unique vertex, add to list
  663.                 coord  = point_by_matrix( verts[face.vertices[i]].co, w_matrix ) #TODO: fix possible bug here
  664.                 if face.use_smooth: normal = vector_normalize(vector_by_matrix( verts[face.vertices[i]].normal, w_matrix ))
  665.                 vertex  = vertices[face.vertices[i]] = Vertex(submesh, coord, normal)
  666.                 createVertexA += 1
  667.                
  668.                 influences = []
  669.                 for j in range(len( obj.data.vertices[ face.vertices[i] ].groups )):
  670.                   inf = [obj.vertex_groups[ obj.data.vertices[ face.vertices[i] ].groups[j].group ].name, obj.data.vertices[ face.vertices[i] ].groups[j].weight]
  671.                   influences.append( inf )
  672.                
  673.                 if not influences:
  674.                   print( "There is a vertex without attachment to a bone in mesh: " + mesh.name )
  675.                 sum = 0.0
  676.                 for bone_name, weight in influences: sum += weight
  677.                
  678.                 for bone_name, weight in influences:
  679.                   if sum != 0:
  680.                     try:
  681.                         vertex.influences.append(Influence(BONES[bone_name], weight / sum))
  682.                     except:
  683.                         continue
  684.                   else: # we have a vertex that is probably not skinned. export anyway
  685.                     try:
  686.                         vertex.influences.append(Influence(BONES[bone_name], weight))
  687.                     except:
  688.                         continue
  689.                
  690.                 #print( "vert " + str( face.vertices[i] ) + " has " + str(len( vertex.influences ) ) + " influences ")
  691.                        
  692.               elif not face.use_smooth:
  693.                 # We cannot share vertex for non-smooth faces, since Cal3D does not
  694.                 # support vertex sharing for 2 vertices with different normals.
  695.                 # => we must clone the vertex.
  696.                
  697.                 old_vertex = vertex
  698.                 vertex = Vertex(submesh, vertex.loc, normal)
  699.                 createVertexB += 1
  700.                 vertex.cloned_from = old_vertex
  701.                 vertex.influences = old_vertex.influences
  702.                 old_vertex.clones.append(vertex)
  703.              
  704.               hasFaceUV = len(uv_textures) > 0 #borrowed from export_obj.py
  705.              
  706.               if hasFaceUV:
  707.                 uv = [uv_textures.active.data[face.index].uv[i][0], uv_textures.active.data[face.index].uv[i][1]]
  708.                 uv[1] = 1.0 - uv[1]  # should we flip Y? yes, new in Blender 2.5x
  709.                 if not vertex.maps: vertex.maps.append(Map(*uv))
  710.                 elif (vertex.maps[0].u != uv[0]) or (vertex.maps[0].v != uv[1]):
  711.                   # This vertex can be shared for Blender, but not for MD5
  712.                   # MD5 does not support vertex sharing for 2 vertices with
  713.                   # different UV texture coodinates.
  714.                   # => we must clone the vertex.
  715.  
  716.                   for clone in vertex.clones:
  717.                     if (clone.maps[0].u == uv[0]) and (clone.maps[0].v == uv[1]):
  718.                       vertex = clone
  719.                       break
  720.                   else: # Not yet cloned...  (PDZ) note: this ELSE belongs attached to the FOR loop.. python can do that apparently
  721.                     old_vertex = vertex
  722.                     vertex = Vertex(submesh, vertex.loc, vertex.normal)
  723.                     createVertexC += 1
  724.                     vertex.cloned_from = old_vertex
  725.                     vertex.influences = old_vertex.influences
  726.                     vertex.maps.append(Map(*uv))
  727.                     old_vertex.clones.append(vertex)
  728.  
  729.               face_vertices.append(vertex)
  730.              
  731.             # Split faces with more than 3 vertices
  732.             for i in range(1, len(face.vertices) - 1):
  733.               Face(submesh, face_vertices[0], face_vertices[i], face_vertices[i + 1])
  734.           else:
  735.             print( "found face with invalid material!!!!" )
  736.       print( "created verts at A " + str(createVertexA) + ", B " + str( createVertexB ) + ", C " + str( createVertexC ) )
  737.      
  738.   # Export animations
  739.   ANIMATIONS = {}
  740.  
  741.   arm_action = thearmature.animation_data.action
  742.   rangestart = 0
  743.   rangeend = 0
  744.   if arm_action:
  745.     animation = ANIMATIONS[arm_action.name] = MD5Animation(skeleton)
  746. #    armature.animation_data.action = action
  747.     bpy.context.scene.update()
  748.     armature = bpy.context.active_object
  749.     action = armature.animation_data.action
  750. #    framemin, framemax = bpy.context.active_object.animation_data.Action(fcurves.frame_range)
  751.     framemin, framemax  = action.frame_range
  752.     rangestart = int(framemin)
  753.     rangeend = int(framemax)
  754. #    rangestart = int( bpy.context.scene.frame_start ) # int( arm_action.frame_range[0] )
  755. #    rangeend = int( bpy.context.scene.frame_end ) #int( arm_action.frame_range[1] )
  756.     currenttime = rangestart
  757.     while currenttime <= rangeend:
  758.       bpy.context.scene.frame_set(currenttime)
  759.       time = (currenttime - 1.0) / 24.0 #(assuming default 24fps for md5 anim)
  760.       pose = thearmature.pose
  761.  
  762.       for bonename in thearmature.data.bones.keys():
  763.         posebonemat = mathutils.Matrix(pose.bones[bonename].matrix ) # @ivar poseMatrix: The total transformation of this PoseBone including constraints. -- different from localMatrix
  764.  
  765.         try:
  766.           bone  = BONES[bonename] #look up md5bone
  767.         except:
  768.           print( "found a posebone animating a bone that is not part of the exported armature: " + bonename )
  769.           continue
  770.         if bone.parent: # need parentspace-matrix
  771.           parentposemat = mathutils.Matrix(pose.bones[bone.parent.name].matrix ) # @ivar poseMatrix: The total transformation of this PoseBone including constraints. -- different from localMatrix
  772. #          posebonemat = parentposemat.invert() * posebonemat #reverse order of multiplication!!!
  773.           parentposemat.invert() # mikshaw
  774.           posebonemat = parentposemat * posebonemat # mikshaw
  775.         else:
  776.           posebonemat = thearmature.matrix_world * posebonemat  #reverse order of multiplication!!!
  777.         loc = [posebonemat.col[3][0],
  778.             posebonemat.col[3][1],
  779.             posebonemat.col[3][2],
  780.             ]
  781. #        rot = posebonemat.to_quat().normalize()
  782.         rot = posebonemat.to_quaternion() # changed from to_quat in 2.57 -mikshaw
  783.         rot.normalize() # mikshaw
  784.         rot = [rot.w,rot.x,rot.y,rot.z]
  785.        
  786.         animation.addkeyforbone(bone.id, time, loc, rot)
  787.       currenttime += 1
  788.        
  789.   # here begins md5mesh and anim output
  790.   # this is how it works
  791.   # first the skeleton is output, using the data that was collected by the above code in this export function
  792.   # then the mesh data is output (into the same md5mesh file)
  793.  
  794.   if( settings.exportMode == "mesh & anim" or settings.exportMode == "mesh only" ):
  795.           md5mesh_filename = settings.savepath + ".md5mesh"
  796.  
  797.           #save all submeshes in the first mesh
  798.           if len(meshes)>1:
  799.             for mesh in range (1, len(meshes)):
  800.               for submesh in meshes[mesh].submeshes:
  801.                 submesh.bindtomesh(meshes[0])
  802.           if (md5mesh_filename != ""):
  803.             try:
  804.               file = open(md5mesh_filename, 'w')
  805.             except IOError:
  806.               errmsg = "IOError " #%s: %s" % (errno, strerror)
  807.             buffer = skeleton.to_md5mesh(len(meshes[0].submeshes))
  808.             #for mesh in meshes:
  809.             buffer = buffer + meshes[0].to_md5mesh()
  810.             file.write(buffer)
  811.             file.close()
  812.             print( "saved mesh to " + md5mesh_filename )
  813.           else:
  814.             print( "No md5mesh file was generated." )
  815.  
  816.   if( settings.exportMode == "mesh & anim" or settings.exportMode == "anim only" ):
  817.           md5anim_filename = settings.savepath + ".md5anim"
  818.  
  819.           #save animation file
  820.           if len(ANIMATIONS)>0:
  821.             anim = ANIMATIONS.popitem()[1] #ANIMATIONS.values()[0]
  822.             print( str( anim ) )
  823.             try:
  824.               file = open(md5anim_filename, 'w')
  825.             except IOError:
  826.               errmsg = "IOError " #%s: %s" % (errno, strerror)
  827.             objects = []
  828.             for submesh in meshes[0].submeshes:
  829.               if len(submesh.weights) > 0:
  830.                 obj = None
  831.                 for sob in bpy.context.selected_objects:
  832.                     if sob and sob.type == 'MESH' and sob.name == submesh.name:
  833.                       obj = sob
  834.                 objects.append (obj)
  835.             generateboundingbox(objects, anim, [rangestart, rangeend])
  836.             buffer = anim.to_md5anim()
  837.             file.write(buffer)
  838.             file.close()
  839.             print( "saved anim to " + md5anim_filename )
  840.           else:
  841.             print( "No md5anim file was generated." )
  842.  
  843. ##########
  844. #export class registration and interface
  845. from bpy.props import *
  846. class ExportMD5(bpy.types.Operator):
  847.   '''Export to idTech 4 MD5 (.md5mesh .md5anim)'''
  848.   bl_idname = "export.md5"
  849.   bl_label = 'idTech 4 MD5'
  850.  
  851.   logenum = [("console","Console","log to console"),
  852.              ("append","Append","append to log file"),
  853.              ("overwrite","Overwrite","overwrite log file")]
  854.              
  855.   #search for list of actions to export as .md5anims
  856.   #md5animtargets = []
  857.   #for anim in bpy.data.actions:
  858.   #     md5animtargets.append( (anim.name, anim.name, anim.name) )
  859.        
  860.   #md5animtarget = None
  861.   #if( len( md5animtargets ) > 0 ):
  862.   #     md5animtarget = EnumProperty( name="Anim", items = md5animtargets, description = "choose animation to export", default = md5animtargets[0] )
  863.        
  864.   exportModes = [("mesh & anim", "Mesh & Anim", "Export .md5mesh and .md5anim files."),
  865.                  ("anim only", "Anim only.", "Export .md5anim only."),
  866.                  ("mesh only", "Mesh only.", "Export .md5mesh only.")]
  867.  
  868.   filepath = StringProperty(subtype = 'FILE_PATH',name="File Path", description="Filepath for exporting", maxlen= 1024, default= "")
  869.   md5name = StringProperty(name="MD5 Name", description="MD3 header name / skin path (64 bytes)",maxlen=64,default="")
  870.   md5exportList = EnumProperty(name="Exports", items=exportModes, description="Choose export mode.", default='mesh & anim')
  871.   #md5logtype = EnumProperty(name="Save log", items=logenum, description="File logging options",default = 'console')
  872.   md5scale = FloatProperty(name="Scale", description="Scale all objects from world origin (0,0,0)", min=0.001, max=1000.0, default=1.0,precision=6)
  873.   #md5offsetx = FloatProperty(name="Offset X", description="Transition scene along x axis",default=0.0,precision=5)
  874.   #md5offsety = FloatProperty(name="Offset Y", description="Transition scene along y axis",default=0.0,precision=5)
  875.   #md5offsetz = FloatProperty(name="Offset Z", description="Transition scene along z axis",default=0.0,precision=5)
  876.  
  877.  
  878.  
  879.   def execute(self, context):
  880.     global scale
  881.     scale = self.md5scale
  882.     settings = md5Settings(savepath = self.properties.filepath,
  883.                            exportMode = self.properties.md5exportList
  884.                            )
  885.     save_md5(settings)
  886.     return {'FINISHED'}
  887.  
  888.   def invoke(self, context, event):
  889.         WindowManager = context.window_manager
  890.         # fixed for 2.56? Katsbits.com (via Nic B)
  891.         # original WindowManager.add_fileselect(self)
  892.         WindowManager.fileselect_add(self)
  893.         return {"RUNNING_MODAL"}  
  894.  
  895. def menu_func(self, context):
  896.   default_path = os.path.splitext(bpy.data.filepath)[0]
  897.   self.layout.operator(ExportMD5.bl_idname, text="idTech 4 MD5 (.md5mesh .md5anim)", icon='BLENDER').filepath = default_path
  898.  
  899. def register():
  900.   bpy.utils.register_module(__name__)  #mikshaw
  901.   bpy.types.INFO_MT_file_export.append(menu_func)
  902.  
  903. def unregister():
  904.   bpy.utils.unregister_module(__name__)  #mikshaw
  905.   bpy.types.INFO_MT_file_export.remove(menu_func)
  906.  
  907. if __name__ == "__main__":
  908.   register()
go to heaven