#!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")