#!BPY

"""
Name: 'MDL (.mdl)'
Blender: 243
Group: 'Export'
Tooltip: 'Export to QuakeI file format (.mdl)'
"""

__author__  = "Andrew Apted"
__url__     = ("http://openarena.ws")
__version__ = "0.55 2008-09-11"

__bpydoc__  = """\
This script exports a QuakeI file (MDL).
"""

import math
import struct
import Blender
import BPyMesh


#q_shared
MAX_QPATH = 64
MDL_IDENT = "IDPO"
MDL_VERSION = 6
MDL_MAX_SKINS = 64
MDL_MAX_VERTICES = 1024
MDL_MAX_TRIANGLES = 2048
MDL_MAX_FRAMES = 256
MDL_XYZ_SCALE = (1.0 / 64.0)
MDL_BLENDER_SCALE = (1.0 / 1.0)


# strips file type extension
def StripExtension(name):
	if name.find('.') != -1:
		name = name[:name.find('.')]
	return name
	
#q_math
def VectorLength(v):
	return math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])

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)


# our own logger class. it works just the same as a normal logger except
# all info messages get show. 
class Logger:
	def __init__(self, name):
		self.has_warnings = False
		self.has_errors = False
		self.has_critical = False
		self.message = ""
		self.name = name
		self.start = 0
		
		if name not in ( text.getName() for text in Blender.Text.Get() ):
			self.outtext = Blender.Text.New(name)
		else:
			self.outtext = Blender.Text.Get(name)
			self.outtext.clear()
		
		self.outtext.write("\n___START___\n\n")
	
	def log(self, type, msg, *args):
			self.message = type.ljust(10) + ":" + msg % args
			self.outtext.write(self.message+'\n')
			print self.message
	
	def info(self, msg, *args):
		self.log("info", msg, *args)
	
	def warning(self, msg, *args):
		self.log("warning", msg, *args)
		self.has_warnings = True
	
	def error(self, msg, *args):
		self.log("error", msg, *args)
		self.has_errors = True
		
	def critical(self, msg, *args):
		self.log("critical", msg, *args)
		self.has_errors = True
	
class BlenderGui:
	def __init__(self, log):
		text = ["A log has been written to a blender text window.",
			"Change this window type to a text window.", 
			"You will be able to select the file %s." % log.name ]
		
		text+=["Parsed in %i seconds"%(Blender.sys.time() - log.start)]
		
		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"]
		
		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:
			Blender.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


log = Logger("mdl_export_log")


#------------------------------------------------------------------------#


class q1ColorCache:
	def __init__(self, gamma, fullbright):
		self.gamma = gamma
		self.fullbright = fullbright
		self.cache = {}
		self.MakeGammaTab()
		if fullbright:
			self.color_range = range(0,256)
		else:
			self.color_range = range(0,224)
		self.palette = (
		(  0,   0,   0), ( 15,  15,  15), ( 31,  31,  31), ( 47,  47,  47), 
		( 63,  63,  63), ( 75,  75,  75), ( 91,  91,  91), (107, 107, 107), 
		(123, 123, 123), (139, 139, 139), (155, 155, 155), (171, 171, 171), 
		(187, 187, 187), (203, 203, 203), (219, 219, 219), (235, 235, 235), 
		( 15,  11,   7), ( 23,  15,  11), ( 31,  23,  11), ( 39,  27,  15), 
		( 47,  35,  19), ( 55,  43,  23), ( 63,  47,  23), ( 75,  55,  27), 
		( 83,  59,  27), ( 91,  67,  31), ( 99,  75,  31), (107,  83,  31), 
		(115,  87,  31), (123,  95,  35), (131, 103,  35), (143, 111,  35), 
		( 11,  11,  15), ( 19,  19,  27), ( 27,  27,  39), ( 39,  39,  51), 
		( 47,  47,  63), ( 55,  55,  75), ( 63,  63,  87), ( 71,  71, 103), 
		( 79,  79, 115), ( 91,  91, 127), ( 99,  99, 139), (107, 107, 151), 
		(115, 115, 163), (123, 123, 175), (131, 131, 187), (139, 139, 203), 
		(  0,   0,   0), (  7,   7,   0), ( 11,  11,   0), ( 19,  19,   0), 
		( 27,  27,   0), ( 35,  35,   0), ( 43,  43,   7), ( 47,  47,   7), 
		( 55,  55,   7), ( 63,  63,   7), ( 71,  71,   7), ( 75,  75,  11), 
		( 83,  83,  11), ( 91,  91,  11), ( 99,  99,  11), (107, 107,  15), 
		(  7,   0,   0), ( 15,   0,   0), ( 23,   0,   0), ( 31,   0,   0), 
		( 39,   0,   0), ( 47,   0,   0), ( 55,   0,   0), ( 63,   0,   0), 
		( 71,   0,   0), ( 79,   0,   0), ( 87,   0,   0), ( 95,   0,   0), 
		(103,   0,   0), (111,   0,   0), (119,   0,   0), (127,   0,   0), 
		( 19,  19,   0), ( 27,  27,   0), ( 35,  35,   0), ( 47,  43,   0), 
		( 55,  47,   0), ( 67,  55,   0), ( 75,  59,   7), ( 87,  67,   7), 
		( 95,  71,   7), (107,  75,  11), (119,  83,  15), (131,  87,  19), 
		(139,  91,  19), (151,  95,  27), (163,  99,  31), (175, 103,  35), 
		( 35,  19,   7), ( 47,  23,  11), ( 59,  31,  15), ( 75,  35,  19), 
		( 87,  43,  23), ( 99,  47,  31), (115,  55,  35), (127,  59,  43), 
		(143,  67,  51), (159,  79,  51), (175,  99,  47), (191, 119,  47), 
		(207, 143,  43), (223, 171,  39), (239, 203,  31), (255, 243,  27), 
		( 11,   7,   0), ( 27,  19,   0), ( 43,  35,  15), ( 55,  43,  19), 
		( 71,  51,  27), ( 83,  55,  35), ( 99,  63,  43), (111,  71,  51), 
		(127,  83,  63), (139,  95,  71), (155, 107,  83), (167, 123,  95), 
		(183, 135, 107), (195, 147, 123), (211, 163, 139), (227, 179, 151), 
		(171, 139, 163), (159, 127, 151), (147, 115, 135), (139, 103, 123), 
		(127,  91, 111), (119,  83,  99), (107,  75,  87), ( 95,  63,  75), 
		( 87,  55,  67), ( 75,  47,  55), ( 67,  39,  47), ( 55,  31,  35), 
		( 43,  23,  27), ( 35,  19,  19), ( 23,  11,  11), ( 15,   7,   7), 
		(187, 115, 159), (175, 107, 143), (163,  95, 131), (151,  87, 119), 
		(139,  79, 107), (127,  75,  95), (115,  67,  83), (107,  59,  75), 
		( 95,  51,  63), ( 83,  43,  55), ( 71,  35,  43), ( 59,  31,  35), 
		( 47,  23,  27), ( 35,  19,  19), ( 23,  11,  11), ( 15,   7,   7), 
		(219, 195, 187), (203, 179, 167), (191, 163, 155), (175, 151, 139), 
		(163, 135, 123), (151, 123, 111), (135, 111,  95), (123,  99,  83), 
		(107,  87,  71), ( 95,  75,  59), ( 83,  63,  51), ( 67,  51,  39), 
		( 55,  43,  31), ( 39,  31,  23), ( 27,  19,  15), ( 15,  11,   7), 
		(111, 131, 123), (103, 123, 111), ( 95, 115, 103), ( 87, 107,  95), 
		( 79,  99,  87), ( 71,  91,  79), ( 63,  83,  71), ( 55,  75,  63), 
		( 47,  67,  55), ( 43,  59,  47), ( 35,  51,  39), ( 31,  43,  31), 
		( 23,  35,  23), ( 15,  27,  19), ( 11,  19,  11), (  7,  11,   7), 
		(255, 243,  27), (239, 223,  23), (219, 203,  19), (203, 183,  15), 
		(187, 167,  15), (171, 151,  11), (155, 131,   7), (139, 115,   7), 
		(123,  99,   7), (107,  83,   0), ( 91,  71,   0), ( 75,  55,   0), 
		( 59,  43,   0), ( 43,  31,   0), ( 27,  15,   0), ( 11,   7,   0), 
		(  0,   0, 255), ( 11,  11, 239), ( 19,  19, 223), ( 27,  27, 207), 
		( 35,  35, 191), ( 43,  43, 175), ( 47,  47, 159), ( 47,  47, 143), 
		( 47,  47, 127), ( 47,  47, 111), ( 47,  47,  95), ( 43,  43,  79), 
		( 35,  35,  63), ( 27,  27,  47), ( 19,  19,  31), ( 11,  11,  15), 
		( 43,   0,   0), ( 59,   0,   0), ( 75,   7,   0), ( 95,   7,   0), 
		(111,  15,   0), (127,  23,   7), (147,  31,   7), (163,  39,  11), 
		(183,  51,  15), (195,  75,  27), (207,  99,  43), (219, 127,  59), 
		(227, 151,  79), (231, 171,  95), (239, 191, 119), (247, 211, 139), 
		(167, 123,  59), (183, 155,  55), (199, 195,  55), (231, 227,  87), 
		(127, 191, 255), (171, 231, 255), (215, 255, 255), (103,   0,   0), 
		(139,   0,   0), (179,   0,   0), (215,   0,   0), (255,   0,   0), 
		(255, 243, 147), (255, 247, 199), (255, 255, 255), (159,  91,  83))
	#
	def MakeGammaTab(self):
		self.gamma_map = {}
		for r in range(0,256):
			k = r / 255.0
			k = k ** (1.0 / self.gamma)
			self.gamma_map[r] = int(k * 255.0)
	#
	def LookupColor(self, r, g, b):
		# AJA: find the closest color
		# (this might be better done in HSV space, giving more weight
		#  to the HUE and less weight to the VALUE).
   		r = self.gamma_map[r]
   		g = self.gamma_map[g]
   		b = self.gamma_map[b]
		#
		best = 0
		best_dist = 99999999
		#
		for i in self.color_range:
			dr = self.palette[i][0] - r
			dg = self.palette[i][1] - g
			db = self.palette[i][2] - b

			dist = dr*dr + dg*dg + db*db
			if dist == 0:  # exact match
				return i

			if dist < best_dist:
				best = i
				best_dist = dist

		return best

	def MapColor(self, r, g, b):
		rgb = r*65536 + g*256 + b

		if self.cache.has_key(rgb):
			return self.cache[rgb]

		#
		# don't let the cache grow without limit
		if len(self.cache) > 8000:
			self.cache.clear()

		pixel = self.LookupColor(r, g, b)

		self.cache[rgb] = pixel
		return pixel


class q1Normalizer:
	def __init__(self):
		self.group_range = range(0,11)
		self.x_group = (
		(1.0000, 0.0000, 0.0000), (52,52,52,52,143,143,143,143),
		(0.9554, 0.2952, 0.0000), (51,51,55,55,141,141,145,145),
		(0.9511, 0.1625, 0.2629), (53,63,57,70,142,148,146,151),
		(0.8642, 0.4429, 0.2389), (46,61,56,69,19,147,123,150),
		(0.8507, 0.5257, 0.0000), (41,41,54,54,18,18,116,116),
		(0.8507, 0.0000, 0.5257), (60,67,60,67,144,155,144,155),
		(0.8090, 0.3090, 0.5000), (48,62,58,68,16,149,124,152),
		(0.7166, 0.6817, 0.1476), (42,43,111,100,20,25,118,117),
		(0.6882, 0.5878, 0.4253), (47,76,140,101,21,156,125,161),
		(0.6817, 0.1476, 0.7166), (49,65,59,66,15,153,126,154),
		(0.5878, 0.4253, 0.6882), (50,75,139,102,17,157,128,160) )
		#
		self.y_group = (
		(0.0000, 1.0000, 0.0000), (32,32,104,104,32,32,104,104),
		(0.0000, 0.9554, 0.2952), (33,30,107,103,33,30,107,103),
		(0.2629, 0.9511, 0.1625), (36,39,109,105,34,31,122,115),
		(0.2389, 0.8642, 0.4429), (35,38,108,97,23,29,121,113),
		(0.5257, 0.8507, 0.0000), (44,44,112,112,27,27,119,119),
		(0.0000, 0.8507, 0.5257), (6,28,106,90,6,28,106,90),
		(0.5000, 0.8090, 0.3090), (37,40,110,98,22,26,120,114),
		(0.1476, 0.7166, 0.6817), (8,71,136,92,7,77,130,91),
		(0.4253, 0.6882, 0.5878), (45,73,138,99,24,158,131,159),
		(0.7166, 0.6817, 0.1476), (42,43,111,100,20,25,118,117),
		(0.6882, 0.5878, 0.4253), (47,76,140,101,21,156,125,161) )
		#
		self.z_group = (
		(0.0000, 0.0000, 1.0000), (5,84,5,84,5,84,5,84),
		(0.2952, 0.0000, 0.9554), (12,85,12,85,2,82,2,82),
		(0.1625, 0.2629, 0.9511), (14,86,134,96,4,83,132,89),
		(0.4429, 0.2389, 0.8642), (13,74,133,95,1,81,127,87),
		(0.5257, 0.0000, 0.8507), (11,64,11,64,0,80,0,80),
		(0.0000, 0.5257, 0.8507), (9,79,137,93,9,79,137,93),
		(0.3090, 0.5000, 0.8090), (10,72,135,94,3,78,129,88),
		(0.6817, 0.1476, 0.7166), (49,65,59,66,15,153,126,154),
		(0.5878, 0.4253, 0.6882), (50,75,139,102,17,157,128,160),
		(0.1476, 0.7166, 0.6817), (8,71,136,92,7,77,130,91),
		(0.4253, 0.6882, 0.5878), (45,73,138,99,24,158,131,159) )
	#
	def MapNormal(self, x, y, z):
		# AJA: I use the following shortcuts to speed up normal lookup:
		#      
		# Firstly, a preliminary match only uses the first quadrant
		# (where all coords are >= 0).  Then we use the appropriate
		# normal index for the actual quadrant.  We can do this because
		# the 162 MDL/MD2 normals are not arbitrary but are mirrored in
		# every quadrant.  The eight numbers in the lists above are the
		# indices for each quadrant (+++ ++- +-+ +-- -++ -+- --+ ---).
		#
		# Secondly we use the axis with the greatest magnitude (of the
		# incoming normal) to search an axis-specific group, which means
		# we only need to check about 1/3rd of the normals.
		#
		fx = abs(x)
		fy = abs(y)
		fz = abs(z)
		#
		group = self.x_group
		if (fy > fx) and (fy > fz):
			group = self.y_group
		elif (fz > fx) and (fz > fy):
			group = self.z_group
		#
		best = 0
		best_dot = -1
		#
		for i in self.group_range:
			dot = group[i*2][0] * fx + group[i*2][1] * fy + group[i*2][2] * fz
			#
			if dot > best_dot:
				best = i
				best_dot = dot
		#
		quadrant = 0
		if x < 0:
			quadrant += 4
		if y < 0:
			quadrant += 2
		if z < 0:
			quadrant += 1
		#
		return group[best*2+1][quadrant]


col_cache  = q1ColorCache(0.5, False)

normalizer = q1Normalizer()


#------------------------------------------------------------------------#


class mdlFrame:
	__slots__ = 'name', 'vlist', 'mins', 'maxs'

	def __init__(self, name, vlist):
		self.name  = name
		self.vlist = vlist

		# compute bbox of this frame
		mins = [ +9e9, +9e9, +9e9 ]
		maxs = [ -9e9, -9e9, -9e9 ]

		for i in range(0, 3):
			for V in vlist:
				if V[i] < mins[i]: mins[i] = V[i]
				if V[i] > maxs[i]: maxs[i] = V[i]

		self.mins = mins
		self.maxs = maxs


class mdlObject:
	def __init__(self):
		self.skins  = []
		self.verts  = []
		self.tris   = []
		self.frames = []
		#
		self.sync_type  = 0
		self.flags      = 0
		#
		self.skin_w = 32  # temp
		self.skin_h = 32
		self.avg_tri_size = 20.0
		#
		self.scale  = [1.0] * 3 # temp stuff
		self.origin = [0.0] * 3
		self.bound_radius = 100.0
		self.eye    = [0.5, 0.5, 0.5]
		#
		self.mins = [ +9e9, +9e9, +9e9 ]
		self.maxs = [ -9e9, -9e9, -9e9 ]

	def AddSkin(self, image):
		size = image.getSize()

		if len(self.skins) == 0:
			self.skin_w = size[0]
			self.skin_h = size[1]
		## FIXME: elif size[0] != self.skin_w  ERROR

		self.skins.append(image)

	def AddVertex(self, u, v): # returns new vertex index
		result = len(self.verts)

		# map onto the skin
		if u < 0: u = 0
		if u > 1: u = 1
		if v < 0: v = 0
		if v > 1: v = 1

		u = int(u * 0.999 * self.skin_w)
		v = int(v * 0.999 * self.skin_h)
		on_seam = 0  ## FIXME

		self.verts.append( (on_seam, u, v) )
		return result
	
	def AddTriangle(self, vlist):
		on_front = 0
		self.tris.append( (on_front, vlist[0], vlist[2], vlist[1]) )
	
	def AddFrame(self, vlist):
		name = "frame_%d" % len(self.frames)
		f = mdlFrame(name, vlist)

		# update bbox of whole model
		for i in range(0, 3):
			if f.mins[i] < self.mins[i]: self.mins[i] = f.mins[i]
			if f.maxs[i] > self.maxs[i]: self.maxs[i] = f.maxs[i]

		self.frames.append(f)

	def CalcScaleOrigin(self):
		for i in range(0, 3):
			size = self.maxs[i] - self.mins[i]

			self.scale[i]  = size / 253.0
			self.origin[i] = self.mins[i] - 1.0 * self.scale[i]

		log.info("MDL Scale: (%1.3f, %1.3f, %1.3f)",
				 self.scale[0], self.scale[1], self.scale[2]);
		log.info("MDL Origin: (%1.3f, %1.3f, %1.3f)",
				 self.origin[0], self.origin[1], self.origin[2]);

	def MapCoord(self, x, y, z):
		x = int((x - self.origin[0]) / self.scale[0])
		y = int((y - self.origin[1]) / self.scale[1])
		z = int((z - self.origin[2]) / self.scale[2])

		return (x, y, z)

	def Write(self, file):
		self.CalcScaleOrigin()
		#
		# Header
		#
		file.write(struct.pack("<4si", MDL_IDENT, MDL_VERSION))
		#
		file.write(struct.pack("<3f3f4f",
				self.scale[0],  self.scale[1],  self.scale[2],
				self.origin[0], self.origin[1], self.origin[2],
				self.bound_radius,
				self.eye[0], self.eye[1], self.eye[2]))
		#
		file.write(struct.pack("<3i",
				1,  ##!!!!!!!!! FIXME
				self.skin_w,
				self.skin_h))
		#
		file.write(struct.pack("<5if",
				len(self.verts),
				len(self.tris),
				len(self.frames),
				self.sync_type,
				self.flags,
				self.avg_tri_size))
		#
		# Skins
		#
		if len(self.skins) == 0:
			self.WriteDummySkin(file)
		else:
			for S in self.skins:
				self.WriteSkin(file, S)
		#
		# Vertices (ST coords)
		self.WriteVertices(file)
		#
		# Triangles
		self.WriteTriangles(file)
		#
		# Frames
		self.WriteFrames(file)

	def WriteSkin(self, file, image):
		file.write(struct.pack("<i", 0))  # type = SINGLE

		x_range = range(0, self.skin_w)
		y_range = range(0, self.skin_h)

		for y in y_range:
			for x in x_range:
				rgb = image.getPixelI(x, y)
				pixel = col_cache.MapColor(rgb[0], rgb[1], rgb[2])
				file.write(struct.pack("B", pixel))

	def WriteDummySkin(self, file):
		file.write(struct.pack("<i", 0))  # type = SINGLE

		x_range = range(0, 16)
		y_range = range(0, 32)

		for y in y_range:
			for x in x_range:
				pix = 3 + 5 * ((x + int(y/2)) % 2)
				file.write(struct.pack("BB", pix, pix)) 

	def WriteVertices(self, file):
		for st in self.verts:
			file.write(
				struct.pack("<3i", st[0], st[1], st[2]))

	def WriteTriangles(self, file):
		for t in self.tris:
			file.write(
				struct.pack("<4i", t[0], t[1], t[2], t[3]))

	def WriteFrames(self, file):
		for f in self.frames:
			file.write(struct.pack("<i", 0))  # type = SINGLE
			#
			mins = self.MapCoord(f.mins[0], f.mins[1], f.mins[2])
			maxs = self.MapCoord(f.maxs[0], f.maxs[1], f.maxs[2])
			#
			file.write(struct.pack("<4B4B",
			           mins[0], mins[1], mins[2], 0,
			           maxs[0], maxs[1], maxs[2], 0))
			#
			file.write(struct.pack("16s", f.name))
			#
			# all vertices for this frame
			for V in f.vlist:
				xyz  = self.MapCoord(V[0], V[1], V[2])

				file.write(struct.pack("<4B",
					xyz[0], xyz[1], xyz[2], V[3]))


#------------------------------------------------------------------------#


def Export(fileName):
	if (fileName.find('.mdl', -4) <= 0):
		fileName += '.mdl'
	
	#log starts here
	log.start = Blender.sys.time()
	log.info("Starting ...")
	
	log.info("Exporting MDL format to: %s", fileName)
	
	# create MDL header object (contains everything else)
	mdl = mdlObject()

	# get the scene
	scene = Blender.Scene.GetCurrent()
	scene.makeCurrent()

	# find the mesh to process
	meshOBJ = None
	for obj in Blender.Object.GetSelected():
		# check if it's a mesh object
		if obj.getType() == "Mesh":
			meshOBJ = obj
			break

	if not meshOBJ:
		print "Error: Must select a mesh to output as MDL"
		Blender.Draw.PupMenu("Selected Object must be a mesh to output as MDL%t|OK")
		return
	
	scene.makeCurrent()

	total_frames = Blender.Get("curframe")

	Blender.Set("curframe", 1)
	Blender.Window.Redraw()

	# get the object (not just name) and the Mesh, not NMesh
	mesh = meshOBJ.getData(False, True)
	matrix = meshOBJ.getMatrix('worldspace')

	log.info("Materials: %s", mesh.materials)
	#if not mesh.materials:
	## surf.shaders[0].name = pathName + meshOBJ.name
	#else:
	## surf.shaders[0].name = pathName + mesh.materials[0].name

	# find skin texture
	mesh_image = mesh.faces[0].image

	if mesh_image == "":
		mesh_image = None

	if mesh_image:
		mdl.AddSkin(mesh_image)

	# Process the Mesh....

	# because MDL doesnt suppoort faceUVs like blender, we need to duplicate
	# any vertex that has multiple uv coords

	SeenVerts = {} # maps vertex id + UV coords to the new vertex id
	OldToNew  = {} # maps old vertex id to a list of new ids after duplicating to account for UV

	# process each face in the mesh
	for face in mesh.faces:
		
		# this makes a list for each tri in this face. a quad will be [[0,1,2],[0,2,3]]
		tris_in_this_face = []
		for vi in range(1, len(face)-1):
			tris_in_this_face.append([0, vi, vi + 1])
		
		# loop across each tri in the face, then each vertex in the tri
		for cur_tri in tris_in_this_face:

			tri_verts = []

			for vi in cur_tri:
				# get the old vertex index and uv coords
				old_vert = face.v[vi].index

				if mesh.faceUV == True:
					uv = tuple(face.uv[vi])
				elif mesh.vertexUV:
					uv = (face.v[vi].uvco[0], face.v[vi].uvco[1])
				else:
					uv = (0.0, 0.0) # handle case with no tex coords	

				if SeenVerts.has_key((old_vert, uv)):
					new_vert = SeenVerts[(old_vert, uv)]
				else:
					# haven't seen this vertex/uv combo before 
					new_vert = mdl.AddVertex(uv[0], uv[1])
					
					SeenVerts[(old_vert, uv)] = new_vert

					# 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 not OldToNew.has_key(old_vert):
						OldToNew[old_vert] = []

					OldToNew[old_vert].append(new_vert)

				tri_verts.append(new_vert)
			
			mdl.AddTriangle(tri_verts)

	# we're done with faces and uv coords

	# 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	frame_idx in range(0, total_frames):
		# print "Doing frame ", frame_idx, total_frames

		Blender.Set("curframe", frame_idx + 1)
		Blender.Window.Redraw()

		m = BPyMesh.getMeshFromObject(meshOBJ)
		m.transform(matrix)

		frame_verts = [ (0,0,0,0) ] * len(mdl.verts)

		no_face_verts = 0

		for V in m.verts:
			try:
				vlist = OldToNew[V.index]
			except:
				no_face_verts += 1
				continue

			norm = normalizer.MapNormal(V.no[0], V.no[1], V.no[2])

			vert_info = ( V.co[0], V.co[1], V.co[2], norm )

			# apply the position to all the duplicated vertices
			for new_vert in vlist:
				frame_verts[new_vert] = vert_info

		mdl.AddFrame(frame_verts)

		if frame_idx == 1 and no_face_verts > 0:
			log.warning("Found %d vertices not part of a face",
			            no_face_verts)

	# export!
	file = open(fileName, "wb")
	mdl.Write(file)
	file.close()

###	mdl.DumpInfo(log)


def FileSelectorCallback(fileName):
	Export(fileName)
	BlenderGui(log)

Blender.Window.FileSelector(FileSelectorCallback, "Export Quake1 MDL", Blender.sys.makename(ext='.mdl'))

#--- editor settings ------------
# vi:ts=4:sw=4:noexpandtab
