diff --git a/polymer/eduke32/build/src/util/md3_export.py b/polymer/eduke32/build/src/util/md3_export.py new file mode 100644 index 000000000..6a21e4513 --- /dev/null +++ b/polymer/eduke32/build/src/util/md3_export.py @@ -0,0 +1,1219 @@ +#!BPY + +""" +Name: 'Quake3 (.md3)...' +Blender: 242 +Group: 'Export' +Tooltip: 'Export to Quake3 file format. (.md3)' +""" +__author__ = "PhaethonH, Bob Holcomb, Damien McGinnes, Robert (Tr3B) Beckebans" +__url__ = ("http://xreal.sourceforge.net") +__version__ = "0.7 2006-11-12" + +__bpydoc__ = """\ +This script exports a Quake3 file (MD3). + +Supported:
+ Surfaces, Materials and Tags. + +Missing:
+ None. + +Known issues:
+ None. + +Notes:
+ TODO +""" + +import sys, os, os.path, struct, string, math + +import Blender +from Blender import * +from Blender.Draw import * +from Blender.BGL import * +from Blender.Window import * + +import types + +import textwrap + +import logging +reload(logging) + +import sys, struct, string, math +from types import * + +import os +from os import path + +GAMEDIR = 'D:/Games/XreaL_testing/base/' +#GAMEDIR = '/opt/XreaL/base/' +MAX_QPATH = 64 + +import sys, struct, string, math +from types import * + +import os +from os import path + +import q_shared +from q_shared import * + +MD3_IDENT = "IDP3" +MD3_VERSION = 15 +MD3_MAX_TAGS = 16 +MD3_MAX_SURFACES = 32 +MD3_MAX_FRAMES = 1024 +MD3_MAX_SHADERS = 256 +MD3_MAX_VERTICES = 4096 +MD3_MAX_TRIANGLES = 8192 +MD3_XYZ_SCALE = (1.0 / 64.0) +MD3_BLENDER_SCALE = (1.0 / 1.0) + + +class md3Vert: + xyz = [] + normal = 0 + binaryFormat = "<3hh" + + def __init__(self): + self.xyz = [0, 0, 0] + self.normal = 0 + + def GetSize(self): + return struct.calcsize(self.binaryFormat) + + # copied from PhaethonH md3.py + def Decode(self, latlng): + lat = (latlng >> 8) & 0xFF; + lng = (latlng) & 0xFF; + lat *= math.pi / 128; + lng *= math.pi / 128; + x = math.cos(lat) * math.sin(lng) + y = math.sin(lat) * math.sin(lng) + z = math.cos(lng) + retval = [ x, y, z ] + return retval + + # copied from PhaethonH md3.py + def Encode(self, normal): + x, y, z = normal + + # normalise + l = math.sqrt((x*x) + (y*y) + (z*z)) + if l == 0: + return 0 + x = x/l + y = y/l + z = z/l + + if (x == 0.0) & (y == 0.0) : + if z > 0.0: + return 0 + else: + return (128 << 8) + + # Encode a normal vector into a 16-bit latitude-longitude value + #lng = math.acos(z) + #lat = math.acos(x / math.sin(lng)) + #retval = ((lat & 0xFF) << 8) | (lng & 0xFF) + lng = math.acos(z) * 255 / (2 * math.pi) + lat = math.atan2(y, x) * 255 / (2 * math.pi) + retval = ((int(lat) & 0xFF) << 8) | (int(lng) & 0xFF) + return retval + + def Load(self, file): + tmpData = file.read(struct.calcsize(self.binaryFormat)) + data = struct.unpack(self.binaryFormat, tmpData) + self.xyz[0] = data[0] * MD3_XYZ_SCALE + self.xyz[1] = data[1] * MD3_XYZ_SCALE + self.xyz[2] = data[2] * MD3_XYZ_SCALE + self.normal = data[3] + return self + + def Save(self, file): + tmpData = [0] * 4 + tmpData[0] = self.xyz[0] / MD3_XYZ_SCALE + tmpData[1] = self.xyz[1] / MD3_XYZ_SCALE + tmpData[2] = self.xyz[2] / MD3_XYZ_SCALE + tmpData[3] = self.normal + data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1], tmpData[2], tmpData[3]) + file.write(data) + #print "Wrote MD3 Vertex: ", data + + def Dump(self): + log.info("MD3 Vertex") + log.info("X: %s", self.xyz[0]) + log.info("Y: %s", self.xyz[1]) + log.info("Z: %s", self.xyz[2]) + log.info("Normal: %s", self.normal) + log.info("") + +class md3TexCoord: + u = 0.0 + v = 0.0 + + binaryFormat = "<2f" + + def __init__(self): + self.u = 0.0 + self.v = 0.0 + + def GetSize(self): + return struct.calcsize(self.binaryFormat) + + def Load(self, file): + tmpData = file.read(struct.calcsize(self.binaryFormat)) + data = struct.unpack(self.binaryFormat, tmpData) + # for some reason quake3 texture maps are upside down, flip that + self.u = data[0] + self.v = 1.0 - data[1] + return self + + def Save(self, file): + tmpData = [0] * 2 + tmpData[0] = self.u + tmpData[1] = 1.0 - self.v + data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1]) + file.write(data) + #print "wrote MD3 texture coordinate structure: ", data + + def Dump(self): + log.info("MD3 Texture Coordinates") + log.info("U: %s", self.u) + log.info("V: %s", self.v) + log.info("") + + +class md3Triangle: + indexes = [] + + binaryFormat = "<3i" + + def __init__(self): + self.indexes = [ 0, 0, 0 ] + + def GetSize(self): + return struct.calcsize(self.binaryFormat) + + def Load(self, file): + tmpData = file.read(struct.calcsize(self.binaryFormat)) + data = struct.unpack(self.binaryFormat, tmpData) + self.indexes[0] = data[0] + self.indexes[1] = data[2] # reverse + self.indexes[2] = data[1] # reverse + return self + + def Save(self, file): + tmpData = [0] * 3 + tmpData[0] = self.indexes[0] + tmpData[1] = self.indexes[2] # reverse + tmpData[2] = self.indexes[1] # reverse + data = struct.pack(self.binaryFormat,tmpData[0], tmpData[1], tmpData[2]) + file.write(data) + #print "wrote MD3 face structure: ",data + + def Dump(self, log): + log.info("MD3 Triangle") + log.info("Indices: %s", self.indexes) + log.info("") + + +class md3Shader: + name = "" + index = 0 + + binaryFormat = "<%dsi" % MAX_QPATH + + def __init__(self): + self.name = "" + self.index = 0 + + def GetSize(self): + return struct.calcsize(self.binaryFormat) + + def Load(self, file): + tmpData = file.read(struct.calcsize(self.binaryFormat)) + data = struct.unpack(self.binaryFormat, tmpData) + self.name = asciiz(data[0]) + self.index = data[1] + return self + + def Save(self, file): + tmpData = [0] * 2 + tmpData[0] = self.name + tmpData[1] = self.index + data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1]) + file.write(data) + #print "wrote MD3 shader structure: ",data + + def Dump(self, log): + log.info("MD3 Shader") + log.info("Name: %s", self.name) + log.info("Index: %s", self.index) + log.info("") + + +class md3Surface: + ident = "" + name = "" + flags = 0 + numFrames = 0 + numShaders = 0 + numVerts = 0 + numTriangles = 0 + ofsTriangles = 0 + ofsShaders = 0 + ofsUV = 0 + ofsVerts = 0 + ofsEnd = 0 + shaders = [] + triangles = [] + uv = [] + verts = [] + + binaryFormat = "<4s%ds10i" % MAX_QPATH # 1 int, name, then 10 ints + + def __init__(self): + self.ident = "" + self.name = "" + self.flags = 0 + self.numFrames = 0 + self.numShaders = 0 + self.numVerts = 0 + self.numTriangles = 0 + self.ofsTriangles = 0 + self.ofsShaders = 0 + self.ofsUV = 0 + self.ofsVerts = 0 + self.ofsEnd + self.shaders = [] + self.triangles = [] + self.uv = [] + self.verts = [] + + def GetSize(self): + sz = struct.calcsize(self.binaryFormat) + self.ofsTriangles = sz + for t in self.triangles: + sz += t.GetSize() + self.ofsShaders = sz + for s in self.shaders: + sz += s.GetSize() + self.ofsUV = sz + for u in self.uv: + sz += u.GetSize() + self.ofsVerts = sz + for v in self.verts: + sz += v.GetSize() + self.ofsEnd = sz + return self.ofsEnd + + def Load(self, file, log): + # where are we in the file (for calculating real offsets) + ofsBegin = file.tell() + tmpData = file.read(struct.calcsize(self.binaryFormat)) + data = struct.unpack(self.binaryFormat, tmpData) + self.ident = data[0] + self.name = asciiz(data[1]) + self.flags = data[2] + self.numFrames = data[3] + self.numShaders = data[4] + self.numVerts = data[5] + self.numTriangles = data[6] + self.ofsTriangles = data[7] + self.ofsShaders = data[8] + self.ofsUV = data[9] + self.ofsVerts = data[10] + self.ofsEnd = data[11] + + # load the tri info + file.seek(ofsBegin + self.ofsTriangles, 0) + for i in range(0, self.numTriangles): + self.triangles.append(md3Triangle()) + self.triangles[i].Load(file) + #self.triangles[i].Dump(log) + + # load the shader info + file.seek(ofsBegin + self.ofsShaders, 0) + for i in range(0, self.numShaders): + self.shaders.append(md3Shader()) + self.shaders[i].Load(file) + #self.shaders[i].Dump(log) + + # load the uv info + file.seek(ofsBegin + self.ofsUV, 0) + for i in range(0, self.numVerts): + self.uv.append(md3TexCoord()) + self.uv[i].Load(file) + #self.uv[i].Dump(log) + + # load the verts info + file.seek(ofsBegin + self.ofsVerts, 0) + for i in range(0, self.numFrames): + for j in range(0, self.numVerts): + self.verts.append(md3Vert()) + #i*self.numVerts+j=where in the surface vertex list the vert position for this frame is + self.verts[(i * self.numVerts) + j].Load(file) + #self.verts[j].Dump(log) + + # go to the end of this structure + file.seek(ofsBegin+self.ofsEnd, 0) + + return self + + def Save(self, file): + self.GetSize() + tmpData = [0] * 12 + tmpData[0] = self.ident + tmpData[1] = self.name + tmpData[2] = self.flags + tmpData[3] = self.numFrames + tmpData[4] = self.numShaders + tmpData[5] = self.numVerts + tmpData[6] = self.numTriangles + tmpData[7] = self.ofsTriangles + tmpData[8] = self.ofsShaders + tmpData[9] = self.ofsUV + tmpData[10] = self.ofsVerts + tmpData[11] = self.ofsEnd + data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6],tmpData[7],tmpData[8],tmpData[9],tmpData[10],tmpData[11]) + file.write(data) + + # write the tri data + for t in self.triangles: + t.Save(file) + + # save the shader coordinates + for s in self.shaders: + s.Save(file) + + # save the uv info + for u in self.uv: + u.Save(file) + + # save the verts + for v in self.verts: + v.Save(file) + + def Dump(self, log): + log.info("MD3 Surface") + log.info("Ident: %s", self.ident) + log.info("Name: %s", self.name) + log.info("Flags: %s", self.flags) + log.info("Number of Frames: %s", self.numFrames) + log.info("Number of Shaders: %s", self.numShaders) + log.info("Number of Verts: %s", self.numVerts) + log.info("Number of Triangles: %s", self.numTriangles) + log.info("Offset to Triangles: %s", self.ofsTriangles) + log.info("Offset to Shaders: %s", self.ofsShaders) + log.info("Offset to UV: %s", self.ofsUV) + log.info("Offset to Verts: %s", self.ofsVerts) + log.info("Offset to end: %s", self.ofsEnd) + log.info("") + + +class md3Tag: + name = "" + origin = [] + axis = [] + + binaryFormat="<%ds3f9f" % MAX_QPATH + + def __init__(self): + self.name = "" + self.origin = [0, 0, 0] + self.axis = [0, 0, 0, 0, 0, 0, 0, 0, 0] + + def GetSize(self): + return struct.calcsize(self.binaryFormat) + + def Load(self, file): + tmpData = file.read(struct.calcsize(self.binaryFormat)) + data = struct.unpack(self.binaryFormat, tmpData) + self.name = asciiz(data[0]) + self.origin[0] = data[1] + self.origin[1] = data[2] + self.origin[2] = data[3] + self.axis[0] = data[4] + self.axis[1] = data[5] + self.axis[2] = data[6] + self.axis[3] = data[7] + self.axis[4] = data[8] + self.axis[5] = data[9] + self.axis[6] = data[10] + self.axis[7] = data[11] + self.axis[8] = data[12] + return self + + def Save(self, file): + tmpData = [0] * 13 + tmpData[0] = self.name + tmpData[1] = float(self.origin[0]) + tmpData[2] = float(self.origin[1]) + tmpData[3] = float(self.origin[2]) + tmpData[4] = float(self.axis[0]) + tmpData[5] = float(self.axis[1]) + tmpData[6] = float(self.axis[2]) + tmpData[7] = float(self.axis[3]) + tmpData[8] = float(self.axis[4]) + tmpData[9] = float(self.axis[5]) + tmpData[10] = float(self.axis[6]) + tmpData[11] = float(self.axis[7]) + tmpData[12] = float(self.axis[8]) + data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6], tmpData[7], tmpData[8], tmpData[9], tmpData[10], tmpData[11], tmpData[12]) + file.write(data) + #print "wrote MD3 Tag structure: ",data + + def Dump(self, log): + log.info("MD3 Tag") + log.info("Name: %s", self.name) + log.info("Origin: %s", self.origin) + log.info("Axis: %s", self.axis) + log.info("") + +class md3Frame: + mins = 0 + maxs = 0 + localOrigin = 0 + radius = 0.0 + name = "" + + binaryFormat="<3f3f3ff16s" + + def __init__(self): + self.mins = [0, 0, 0] + self.maxs = [0, 0, 0] + self.localOrigin = [0, 0, 0] + self.radius = 0.0 + self.name = "" + + def GetSize(self): + return struct.calcsize(self.binaryFormat) + + def Load(self, file): + tmpData = file.read(struct.calcsize(self.binaryFormat)) + data = struct.unpack(self.binaryFormat, tmpData) + self.mins[0] = data[0] + self.mins[1] = data[1] + self.mins[2] = data[2] + self.maxs[0] = data[3] + self.maxs[1] = data[4] + self.maxs[2] = data[5] + self.localOrigin[0] = data[6] + self.localOrigin[1] = data[7] + self.localOrigin[2] = data[8] + self.radius = data[9] + self.name = asciiz(data[10]) + return self + + def Save(self, file): + tmpData = [0] * 11 + tmpData[0] = self.mins[0] + tmpData[1] = self.mins[1] + tmpData[2] = self.mins[2] + tmpData[3] = self.maxs[0] + tmpData[4] = self.maxs[1] + tmpData[5] = self.maxs[2] + tmpData[6] = self.localOrigin[0] + tmpData[7] = self.localOrigin[1] + tmpData[8] = self.localOrigin[2] + tmpData[9] = self.radius + tmpData[10] = self.name + data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6],tmpData[7], tmpData[8], tmpData[9], tmpData[10]) + file.write(data) + #print "wrote MD3 frame structure: ",data + + def Dump(self, log): + log.info("MD3 Frame") + log.info("Min Bounds: %s", self.mins) + log.info("Max Bounds: %s", self.maxs) + log.info("Local Origin: %s", self.localOrigin) + log.info("Radius: %s", self.radius) + log.info("Name: %s", self.name) + log.info("") + +class md3Object: + # header structure + ident = "" # this is used to identify the file (must be IDP3) + version = 0 # the version number of the file (Must be 15) + name = "" + flags = 0 + numFrames = 0 + numTags = 0 + numSurfaces = 0 + numSkins = 0 + ofsFrames = 0 + ofsTags = 0 + ofsSurfaces = 0 + ofsEnd = 0 + frames = [] + tags = [] + surfaces = [] + + binaryFormat="<4si%ds9i" % MAX_QPATH # little-endian (<), 17 integers (17i) + + def __init__(self): + self.ident = 0 + self.version = 0 + self.name = "" + self.flags = 0 + self.numFrames = 0 + self.numTags = 0 + self.numSurfaces = 0 + self.numSkins = 0 + self.ofsFrames = 0 + self.ofsTags = 0 + self.ofsSurfaces = 0 + self.ofsEnd = 0 + self.frames = [] + self.tags = [] + self.surfaces = [] + + def GetSize(self): + self.ofsFrames = struct.calcsize(self.binaryFormat) + self.ofsTags = self.ofsFrames + for f in self.frames: + self.ofsTags += f.GetSize() + self.ofsSurfaces += self.ofsTags + for t in self.tags: + self.ofsSurfaces += t.GetSize() + self.ofsEnd = self.ofsSurfaces + for s in self.surfaces: + self.ofsEnd += s.GetSize() + return self.ofsEnd + + def Load(self, file, log): + tmpData = file.read(struct.calcsize(self.binaryFormat)) + data = struct.unpack(self.binaryFormat, tmpData) + + self.ident = data[0] + self.version = data[1] + + if(self.ident != "IDP3" or self.version != 15): + log.error("Not a valid MD3 file") + log.error("Ident: %s", self.ident) + log.error("Version: %s", self.version) + Exit() + + self.name = asciiz(data[2]) + self.flags = data[3] + self.numFrames = data[4] + self.numTags = data[5] + self.numSurfaces = data[6] + self.numSkins = data[7] + self.ofsFrames = data[8] + self.ofsTags = data[9] + self.ofsSurfaces = data[10] + self.ofsEnd = data[11] + + # load the frame info + file.seek(self.ofsFrames, 0) + for i in range(0, self.numFrames): + self.frames.append(md3Frame()) + self.frames[i].Load(file) + #self.frames[i].Dump(log) + + # load the tags info + file.seek(self.ofsTags, 0) + for i in range(0, self.numFrames): + for j in range(0, self.numTags): + tag = md3Tag() + tag.Load(file) + #tag.Dump(log) + self.tags.append(tag) + + # load the surface info + file.seek(self.ofsSurfaces, 0) + for i in range(0, self.numSurfaces): + self.surfaces.append(md3Surface()) + self.surfaces[i].Load(file, log) + self.surfaces[i].Dump(log) + return self + + def Save(self, file): + self.GetSize() + tmpData = [0] * 12 + tmpData[0] = self.ident + tmpData[1] = self.version + tmpData[2] = self.name + tmpData[3] = self.flags + tmpData[4] = self.numFrames + tmpData[5] = self.numTags + tmpData[6] = self.numSurfaces + tmpData[7] = self.numSkins + tmpData[8] = self.ofsFrames + tmpData[9] = self.ofsTags + tmpData[10] = self.ofsSurfaces + tmpData[11] = self.ofsEnd + + data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6],tmpData[7], tmpData[8], tmpData[9], tmpData[10], tmpData[11]) + file.write(data) + + for f in self.frames: + f.Save(file) + + for t in self.tags: + t.Save(file) + + for s in self.surfaces: + s.Save(file) + + def Dump(self, log): + log.info("Header Information") + log.info("Ident: %s", self.ident) + log.info("Version: %s", self.version) + log.info("Name: %s", self.name) + log.info("Flags: %s", self.flags) + log.info("Number of Frames: %s",self.numFrames) + log.info("Number of Tags: %s", self.numTags) + log.info("Number of Surfaces: %s", self.numSurfaces) + log.info("Number of Skins: %s", self.numSkins) + log.info("Offset Frames: %s", self.ofsFrames) + log.info("Offset Tags: %s", self.ofsTags) + log.info("Offset Surfaces: %s", self.ofsSurfaces) + log.info("Offset end: %s", self.ofsEnd) + log.info("") + +def asciiz(s): + n = 0 + while(ord(s[n]) != 0): + n = n + 1 + return s[0:n] + +# strips the slashes from the back of a string +def StripPath(path): + for c in range(len(path), 0, -1): + if path[c-1] == "/" or path[c-1] == "\\": + path = path[c:] + break + return path + +# strips the model from path +def StripModel(path): + for c in range(len(path), 0, -1): + if path[c-1] == "/" or path[c-1] == "\\": + path = path[:c] + break + return path + +# strips file type extension +def StripExtension(name): + n = 0 + best = len(name) + while(n != -1): + n = name.find('.',n+1) + if(n != -1): + best = n + name = name[0:best] + return name + +# strips gamedir +def StripGamePath(name): + gamepath = GAMEDIR.replace( '\\', '/' ) + namepath = name.replace( '\\', '/' ) + if namepath[0:len(gamepath)] == gamepath: + namepath= namepath[len(gamepath):len(namepath)] + return namepath + +import sys, struct, string, math + +def ANGLE2SHORT(x): + return int((x * 65536 / 360) & 65535) + +def SHORT2ANGLE(x): + return x * (360.0 / 65536.0) + +def DEG2RAD(a): + return (a * math.pi) / 180.0 + +def RAD2DEG(a): + return (a * 180.0) / math.pi + +def DotProduct(x, y): + return x[0] * y[0] + x[1] * y[1] + x[2] * y[2] + +def CrossProduct(a,b): + return [a[1]*b[2] - a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0]] + +def VectorLength(v): + return math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) + +def VectorSubtract(a, b): + return [a[0] - b[0], a[1] - b[1], a[2] - b[2]] + +def VectorAdd(a, b): + return [a[0] + b[0], a[1] + b[1], a[2] + b[2]] + +def VectorCopy(v): + return [v[0], v[1], v[2]] + +def VectorInverse(v): + return [-v[0], -v[1], -v[2]] + +#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) +#define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s)) +#define VectorMA(v, s, b, o) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s)) + +def RadiusFromBounds(mins, maxs): + corner = [0, 0, 0] + a = 0 + b = 0 + + for i in range(0, 3): + a = abs(mins[i]) + b = abs(maxs[i]) + if a > b: + corner[i] = a + else: + corner[i] = b + + return VectorLength(corner) + + +# NOTE: Tr3B - matrix is in column-major order +def MatrixIdentity(): + return [[1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0]] + +def MatrixFromAngles(pitch, yaw, roll): + sp = math.sin(DEG2RAD(pitch)) + cp = math.cos(DEG2RAD(pitch)) + + sy = math.sin(DEG2RAD(yaw)) + cy = math.cos(DEG2RAD(yaw)) + + sr = math.sin(DEG2RAD(roll)) + cr = math.cos(DEG2RAD(roll)) + +# return [[cp * cy, (sr * sp * cy + cr * -sy), (cr * sp * cy + -sr * -sy), 0.0], +# [cp * sy, (sr * sp * sy + cr * cy), (cr * sp * sy + -sr * cy), 0.0], +# [-sp, sr * cp, cr * cp, 0.0], +# [0.0, 0.0, 0.0, 1.0]] + + return [[cp * cy, cp * sy, -sp, 0.0], + [(sr * sp * cy + cr * -sy), (sr * sp * sy + cr * cy), sr * cp, 0.0], + [(cr * sp * cy + -sr * -sy), (cr * sp * sy + -sr * cy), cr * cp, 0.0], + [0.0, 0.0, 0.0, 1.0]] + +def MatrixTransformPoint(m, p): + return [m[0][0] * p[0] + m[1][0] * p[1] + m[2][0] * p[2] + m[3][0], + m[0][1] * p[0] + m[1][1] * p[1] + m[2][1] * p[2] + m[3][1], + m[0][2] * p[0] + m[1][2] * p[1] + m[2][2] * p[2] + m[3][2]] + + +def MatrixTransformNormal(m, p): + return [m[0][0] * p[0] + m[1][0] * p[1] + m[2][0] * p[2], + m[0][1] * p[0] + m[1][1] * p[1] + m[2][1] * p[2], + m[0][2] * p[0] + m[1][2] * p[1] + m[2][2] * p[2]] + +def MatrixMultiply(b, a): + return [[ + a[0][0] * b[0][0] + a[0][1] * b[1][0] + a[0][2] * b[2][0], + a[0][0] * b[0][1] + a[0][1] * b[1][1] + a[0][2] * b[2][1], + a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2] * b[2][2], + 0.0, + ],[ + a[1][0] * b[0][0] + a[1][1] * b[1][0] + a[1][2] * b[2][0], + a[1][0] * b[0][1] + a[1][1] * b[1][1] + a[1][2] * b[2][1], + a[1][0] * b[0][2] + a[1][1] * b[1][2] + a[1][2] * b[2][2], + 0.0, + ],[ + a[2][0] * b[0][0] + a[2][1] * b[1][0] + a[2][2] * b[2][0], + a[2][0] * b[0][1] + a[2][1] * b[1][1] + a[2][2] * b[2][1], + a[2][0] * b[0][2] + a[2][1] * b[1][2] + a[2][2] * b[2][2], + 0.0, + ],[ + a[3][0] * b[0][0] + a[3][1] * b[1][0] + a[3][2] * b[2][0] + b[3][0], + a[3][0] * b[0][1] + a[3][1] * b[1][1] + a[3][2] * b[2][1] + b[3][1], + a[3][0] * b[0][2] + a[3][1] * b[1][2] + a[3][2] * b[2][2] + b[3][2], + 1.0, + ]] + +def MatrixSetupTransform(forward, left, up, origin): + return [[forward[0], forward[1], forward[2], origin[0]], + [left[0], left[1], left[2], origin[1]], + [up[0], up[1], up[2], origin[2]], + [0.0, 0.0, 0.0, 1.0]] + +# our own logger class. it works just the same as a normal logger except +# all info messages get show. +class Logger(logging.Logger): + def __init__(self, name,level = logging.NOTSET): + logging.Logger.__init__(self, name, level) + + self.has_warnings = False + self.has_errors = False + self.has_critical = False + + def info(self, msg, *args, **kwargs): + apply(self._log,(logging.INFO, msg, args), kwargs) + + def warning(self, msg, *args, **kwargs): + logging.Logger.warning(self, msg, *args, **kwargs) + self.has_warnings = True + + def error(self, msg, *args, **kwargs): + logging.Logger.error(self, msg, *args, **kwargs) + self.has_errors = True + + def critical(self, msg, *args, **kwargs): + logging.Logger.critical(self, msg, *args, **kwargs) + self.has_errors = True + +# should be able to make this print to stdout in realtime and save MESSAGES +# as well. perhaps also have a log to file option +class LogHandler(logging.StreamHandler): + def __init__(self): + logging.StreamHandler.__init__(self, sys.stdout) + + if "md3_export_log" not in Blender.Text.Get(): + self.outtext = Blender.Text.New("md3_export_log") + else: + self.outtext = Blender.Text.Get('md3_export_log') + self.outtext.clear() + + self.lastmsg = '' + + def emit(self, record): + # print to stdout and to a new blender text object + msg = self.format(record) + + if msg == self.lastmsg: + return + + self.lastmsg = msg + self.outtext.write("%s\n" %msg) + + logging.StreamHandler.emit(self, record) + +logging.setLoggerClass(Logger) +log = logging.getLogger('md3_export') + +handler = LogHandler() +formatter = logging.Formatter('%(levelname)s %(message)s') +handler.setFormatter(formatter) + +log.addHandler(handler) +# set this to minimum output level. eg. logging.DEBUG, logging.WARNING, logging.ERROR +# logging.CRITICAL. logging.INFO will make little difference as these always get +# output'd +log.setLevel(logging.WARNING) + + +class BlenderGui: + def __init__(self): + text = """A log has been written to a blender text window. Change this window type to +a text window and you will be able to select the file md3_export_log.""" + + text = textwrap.wrap(text,40) + text += [''] + + if log.has_critical: + text += ['There were critical errors!!!!'] + + elif log.has_errors: + text += ['There were errors!'] + + elif log.has_warnings: + text += ['There were warnings'] + + # add any more text before here + text.reverse() + + self.msg = text + + Blender.Draw.Register(self.gui, self.event, self.button_event) + + def gui(self,): + quitbutton = Blender.Draw.Button("Exit", 1, 0, 0, 100, 20, "Close Window") + + y = 35 + + for line in self.msg: + BGL.glRasterPos2i(10,y) + Blender.Draw.Text(line) + y+=15 + + def event(self,evt, val): + if evt == Blender.Draw.ESCKEY: + Blender.Draw.Exit() + return + + def button_event(self,evt): + if evt == 1: + Blender.Draw.Exit() + return + + +def ApplyTransform(vert, matrix): + return vert * matrix + + +def UpdateFrameBounds(v, f): + for i in range(0, 3): + f.mins[i] = min(v[i], f.mins[i]) + for i in range(0, 3): + f.maxs[i] = max(v[i], f.maxs[i]) + + +def UpdateFrameRadius(f): + f.radius = RadiusFromBounds(f.mins, f.maxs) + + +def ProcessSurface(scene, blenderObject, md3, pathName, modelName): + # because md3 doesnt suppoort faceUVs like blender, we need to duplicate + # any vertex that has multiple uv coords + + vertDict = {} + indexDict = {} # maps a vertex index to the revised index after duplicating to account for uv + vertList = [] # list of vertices ordered by revised index + numVerts = 0 + uvList = [] # list of tex coords ordered by revised index + faceList = [] # list of faces (they index into vertList) + numFaces = 0 + + scene.makeCurrent() + Blender.Set("curframe", 1) + Blender.Window.Redraw() + + # get the object (not just name) and the Mesh, not NMesh + mesh = blenderObject.getData(False, True) + matrix = blenderObject.getMatrix('worldspace') + + surf = md3Surface() + surf.numFrames = md3.numFrames + surf.name = blenderObject.getName() + surf.ident = MD3_IDENT + + # create shader for surface + surf.shaders.append(md3Shader()) + surf.numShaders += 1 + surf.shaders[0].index = 0 + + log.info("Materials: %s", mesh.materials) + # :P + #shaderpath=Blender.Draw.PupStrInput("shader path for "+blenderObject.name+":", "", MAX_QPATH ) + shaderpath="" + if shaderpath == "" : + if not mesh.materials: + surf.shaders[0].name = pathName + blenderObject.name + else: + surf.shaders[0].name = pathName + mesh.materials[0].name + else: + if not mesh.materials: + surf.shaders[0].name = shaderpath + blenderObject.name + else: + surf.shaders[0].name = shaderpath + mesh.materials[0].name + + # process each face in the mesh + for face in mesh.faces: + + tris_in_this_face = [] #to handle quads and up... + + # this makes a list of indices for each tri in this face. a quad will be [[0,1,1],[0,2,3]] + for vi in range(1, len(face.v)-1): + tris_in_this_face.append([0, vi, vi + 1]) + + # loop across each tri in the face, then each vertex in the tri + for this_tri in tris_in_this_face: + numFaces += 1 + tri = md3Triangle() + tri_ind = 0 + for i in this_tri: + # get the vertex index, coords and uv coords + index = face.v[i].index + v = face.v[i].co + if mesh.faceUV == True: + uv = (face.uv[i][0], face.uv[i][1]) + elif mesh.vertexUV: + uv = (face.v[i].uvco[0], face.v[i].uvco[1]) + else: + uv = (0.0, 0.0) # handle case with no tex coords + + + if vertDict.has_key((index, uv)): + # if we've seen this exact vertex before, simply add it + # to the tris list of vertex indices + tri.indexes[tri_ind] = vertDict[(index, uv)] + else: + # havent seen this tri before + # (or its uv coord is different, so we need to duplicate it) + + vertDict[(index, uv)] = numVerts + + # put the uv coord into the list + # (uv coord are directly related to each vertex) + tex = md3TexCoord() + tex.u = uv[0] + tex.v = uv[1] + uvList.append(tex) + + tri.indexes[tri_ind] = numVerts + + # now because we have created a new index, + # we need a way to link it to the index that + # blender returns for NMVert.index + if indexDict.has_key(index): + # already there - each of the entries against + # this key represents the same vertex with a + # different uv value + ilist = indexDict[index] + ilist.append(numVerts) + indexDict[index] = ilist + else: + # this is a new one + indexDict[index] = [numVerts] + + numVerts += 1 + tri_ind +=1 + faceList.append(tri) + + # we're done with faces and uv coords + for t in uvList: + surf.uv.append(t) + + for f in faceList: + surf.triangles.append(f) + + surf.numTriangles = len(faceList) + surf.numVerts = numVerts + + # now vertices are stored as frames - + # all vertices for frame 1, all vertices for frame 2...., all vertices for frame n + # so we need to iterate across blender's frames, and copy out each vertex + for frameNum in range(1, md3.numFrames + 1): + Blender.Set("curframe", frameNum) + Blender.Window.Redraw() + + m = NMesh.GetRawFromObject(blenderObject.name) + + vlist = [0] * numVerts + for vertex in m.verts: + try: + vindices = indexDict[vertex.index] + except: + log.warning("Found a vertex in %s that is not part of a face", blenderObject.name) + continue + + vTx = ApplyTransform(vertex.co, matrix) + nTx = ApplyTransform(vertex.no, matrix) + UpdateFrameBounds(vTx, md3.frames[frameNum - 1]) + vert = md3Vert() + #vert.xyz = vertex.co[0:3] + #vert.normal = vert.Encode(vertex.no[0:3]) + vert.xyz = vTx[0:3] + vert.normal = vert.Encode(vertex.no[0:3]) + #print vertex.no + for ind in vindices: # apply the position to all the duplicated vertices + vlist[ind] = vert + + UpdateFrameRadius(md3.frames[frameNum - 1]) + + for vl in vlist: + surf.verts.append(vl) + + surf.Dump(log) + md3.surfaces.append(surf) + md3.numSurfaces += 1 + + +def Export(fileName): + if(fileName.find('.md3', -4) <= 0): + fileName += '.md3' + + log.info("Starting ...") + + log.info("Exporting MD3 format to: %s", fileName) + + pathName = StripGamePath(StripModel(fileName)) + log.info("Shader path name: %s", pathName) + + modelName = StripExtension(StripPath(fileName)) + log.info("Model name: %s", modelName) + + md3 = md3Object() + md3.ident = MD3_IDENT + md3.version = MD3_VERSION + + tagList = [] + + # get the scene + scene = Blender.Scene.getCurrent() + context = scene.getRenderingContext() + + scene.makeCurrent() + md3.numFrames = Blender.Get("curframe") + Blender.Set("curframe", 1) + + # create a bunch of blank frames, they'll be filled in by 'ProcessSurface' + for i in range(1, md3.numFrames + 1): + frame = md3Frame() + frame.name = "frame_" + str(i) + md3.frames.append(frame) + + # export all selected objects + objlist = Blender.Object.GetSelected() + + # process each object for the export + for obj in objlist: + # check if it's a mesh object + if obj.getType() == "Mesh": + log.info("Processing surface: %s", obj.name) + if len(md3.surfaces) == MD3_MAX_SURFACES: + log.warning("Hit md3 limit (%i) for number of surfaces, skipping ...", MD3_MAX_SURFACES, obj.getName()) + else: + ProcessSurface(scene, obj, md3, pathName, modelName) + elif obj.getType() == "Empty": # for tags, we just put em in a list so we can process them all together + if obj.name[0:4] == "tag_": + log.info("Processing tag: %s", obj.name) + tagList.append(obj) + md3.numTags += 1 + else: + log.info("Skipping object: %s", obj.name) + + + # work out the transforms for the tags for each frame of the export + for i in range(1, md3.numFrames + 1): + + # needed to update IPO's value, but probably not the best way for that... + scene.makeCurrent() + Blender.Set("curframe", i) + Blender.Window.Redraw() + for tag in tagList: + t = md3Tag() + matrix = tag.getMatrix('worldspace') + t.origin[0] = matrix[3][0] + t.origin[1] = matrix[3][1] + t.origin[2] = matrix[3][2] + + t.axis[0] = matrix[0][0] + t.axis[1] = matrix[0][1] + t.axis[2] = matrix[0][2] + + t.axis[3] = matrix[1][0] + t.axis[4] = matrix[1][1] + t.axis[5] = matrix[1][2] + + t.axis[6] = matrix[2][0] + t.axis[7] = matrix[2][1] + t.axis[8] = matrix[2][2] + t.name = tag.name + #t.Dump(log) + md3.tags.append(t) + + # export! + file = open(fileName, "wb") + md3.Save(file) + file.close() + md3.Dump(log) + +def FileSelectorCallback(fileName): + Export(fileName) + + BlenderGui() + +Blender.Window.FileSelector(FileSelectorCallback, "Export Quake3 MD3") \ No newline at end of file