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