/ source / tools / src / md3_export.py
md3_export.py
   1  #!BPY
   2  
   3  """
   4  Name: 'Quake3 (.md3)...'
   5  Blender: 242
   6  Group: 'Export'
   7  Tooltip: 'Export to Quake3 file format. (.md3)'
   8  """
   9  __author__ = "PhaethonH, Bob Holcomb, Damien McGinnes, Robert (Tr3B) Beckebans"
  10  __url__ = ("http://xreal.sourceforge.net")
  11  __version__ = "0.7 2006-11-12"
  12  
  13  __bpydoc__ = """\
  14  This script exports a Quake3 file (MD3).
  15  
  16  Supported:<br>
  17      Surfaces, Materials and Tags.
  18  
  19  Missing:<br>
  20      None.
  21  
  22  Known issues:<br>
  23      None.
  24  
  25  Notes:<br>
  26      TODO
  27  """
  28  
  29  import sys, os, os.path, struct, string, math
  30  
  31  import Blender
  32  from Blender import *
  33  from Blender.Draw import *
  34  from Blender.BGL import *
  35  from Blender.Window import *
  36  
  37  import types
  38  
  39  import textwrap
  40  
  41  import logging
  42  reload(logging)
  43  
  44  import sys, struct, string, math
  45  from types import *
  46  
  47  import os
  48  from os import path
  49  
  50  GAMEDIR = 'D:/Games/XreaL_testing/base/'
  51  #GAMEDIR = '/opt/XreaL/base/'
  52  MAX_QPATH = 64
  53  
  54  import sys, struct, string, math
  55  from types import *
  56  
  57  import os
  58  from os import path
  59  
  60  import q_shared
  61  from q_shared import *
  62  
  63  MD3_IDENT = "IDP3"
  64  MD3_VERSION = 15
  65  MD3_MAX_TAGS = 16
  66  MD3_MAX_SURFACES = 32
  67  MD3_MAX_FRAMES = 1024
  68  MD3_MAX_SHADERS = 256
  69  MD3_MAX_VERTICES = 4096
  70  MD3_MAX_TRIANGLES = 8192
  71  MD3_XYZ_SCALE = (1.0 / 64.0)
  72  MD3_BLENDER_SCALE = (1.0 / 1.0)
  73  
  74  
  75  class md3Vert:
  76  	xyz = []
  77  	normal = 0
  78  	binaryFormat = "<3hh"
  79  
  80  	def __init__(self):
  81  		self.xyz = [0, 0, 0]
  82  		self.normal = 0
  83  
  84  	def GetSize(self):
  85  		return struct.calcsize(self.binaryFormat)
  86  
  87  	# copied from PhaethonH <phaethon@linux.ucla.edu> md3.py
  88  	def Decode(self, latlng):
  89  		lat = (latlng >> 8) & 0xFF;
  90  		lng = (latlng) & 0xFF;
  91  		lat *= math.pi / 128;
  92  		lng *= math.pi / 128;
  93  		x = math.cos(lat) * math.sin(lng)
  94  		y = math.sin(lat) * math.sin(lng)
  95  		z =                 math.cos(lng)
  96  		retval = [ x, y, z ]
  97  		return retval
  98  
  99  	# copied from PhaethonH <phaethon@linux.ucla.edu> md3.py
 100  	def Encode(self, normal):
 101  		x, y, z = normal
 102  
 103  		# normalise
 104  		l = math.sqrt((x*x) + (y*y) + (z*z))
 105  		if l == 0:
 106  			return 0
 107  		x = x/l
 108  		y = y/l
 109  		z = z/l
 110  
 111  		if (x == 0.0) & (y == 0.0) :
 112  			if z > 0.0:
 113  				return 0
 114  			else:
 115  				return (128 << 8)
 116  
 117  		# Encode a normal vector into a 16-bit latitude-longitude value
 118  		#lng = math.acos(z)
 119  		#lat = math.acos(x / math.sin(lng))
 120  		#retval = ((lat & 0xFF) << 8) | (lng & 0xFF)
 121  		lng = math.acos(z) * 255 / (2 * math.pi)
 122  		lat = math.atan2(y, x) * 255 / (2 * math.pi)
 123  		retval = ((int(lat) & 0xFF) << 8) | (int(lng) & 0xFF)
 124  		return retval
 125  
 126  	def Load(self, file):
 127  		tmpData = file.read(struct.calcsize(self.binaryFormat))
 128  		data = struct.unpack(self.binaryFormat, tmpData)
 129  		self.xyz[0] = data[0] * MD3_XYZ_SCALE
 130  		self.xyz[1] = data[1] * MD3_XYZ_SCALE
 131  		self.xyz[2] = data[2] * MD3_XYZ_SCALE
 132  		self.normal = data[3]
 133  		return self
 134  
 135  	def Save(self, file):
 136  		tmpData = [0] * 4
 137  		tmpData[0] = self.xyz[0] / MD3_XYZ_SCALE
 138  		tmpData[1] = self.xyz[1] / MD3_XYZ_SCALE
 139  		tmpData[2] = self.xyz[2] / MD3_XYZ_SCALE
 140  		tmpData[3] = self.normal
 141  		data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1], tmpData[2], tmpData[3])
 142  		file.write(data)
 143  		#print "Wrote MD3 Vertex: ", data
 144  
 145  	def Dump(self):
 146  		log.info("MD3 Vertex")
 147  		log.info("X: %s", self.xyz[0])
 148  		log.info("Y: %s", self.xyz[1])
 149  		log.info("Z: %s", self.xyz[2])
 150  		log.info("Normal: %s", self.normal)
 151  		log.info("")
 152  
 153  class md3TexCoord:
 154  	u = 0.0
 155  	v = 0.0
 156  
 157  	binaryFormat = "<2f"
 158  
 159  	def __init__(self):
 160  		self.u = 0.0
 161  		self.v = 0.0
 162  
 163  	def GetSize(self):
 164  		return struct.calcsize(self.binaryFormat)
 165  
 166  	def Load(self, file):
 167  		tmpData = file.read(struct.calcsize(self.binaryFormat))
 168  		data = struct.unpack(self.binaryFormat, tmpData)
 169  		# for some reason quake3 texture maps are upside down, flip that
 170  		self.u = data[0]
 171  		self.v = 1.0 - data[1]
 172  		return self
 173  
 174  	def Save(self, file):
 175  		tmpData = [0] * 2
 176  		tmpData[0] = self.u
 177  		tmpData[1] = 1.0 - self.v
 178  		data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1])
 179  		file.write(data)
 180  		#print "wrote MD3 texture coordinate structure: ", data
 181  
 182  	def Dump(self):
 183  		log.info("MD3 Texture Coordinates")
 184  		log.info("U: %s", self.u)
 185  		log.info("V: %s", self.v)
 186  		log.info("")
 187  
 188  
 189  class md3Triangle:
 190  	indexes = []
 191  
 192  	binaryFormat = "<3i"
 193  
 194  	def __init__(self):
 195  		self.indexes = [ 0, 0, 0 ]
 196  
 197  	def GetSize(self):
 198  		return struct.calcsize(self.binaryFormat)
 199  
 200  	def Load(self, file):
 201  		tmpData = file.read(struct.calcsize(self.binaryFormat))
 202  		data = struct.unpack(self.binaryFormat, tmpData)
 203  		self.indexes[0] = data[0]
 204  		self.indexes[1] = data[2] # reverse
 205  		self.indexes[2] = data[1] # reverse
 206  		return self
 207  
 208  	def Save(self, file):
 209  		tmpData = [0] * 3
 210  		tmpData[0] = self.indexes[0]
 211  		tmpData[1] = self.indexes[2] # reverse
 212  		tmpData[2] = self.indexes[1] # reverse
 213  		data = struct.pack(self.binaryFormat,tmpData[0], tmpData[1], tmpData[2])
 214  		file.write(data)
 215  		#print "wrote MD3 face structure: ",data
 216  
 217  	def Dump(self, log):
 218  		log.info("MD3 Triangle")
 219  		log.info("Indices: %s", self.indexes)
 220  		log.info("")
 221  
 222  
 223  class md3Shader:
 224  	name = ""
 225  	index = 0
 226  
 227  	binaryFormat = "<%dsi" % MAX_QPATH
 228  
 229  	def __init__(self):
 230  		self.name = ""
 231  		self.index = 0
 232  
 233  	def GetSize(self):
 234  		return struct.calcsize(self.binaryFormat)
 235  
 236  	def Load(self, file):
 237  		tmpData = file.read(struct.calcsize(self.binaryFormat))
 238  		data = struct.unpack(self.binaryFormat, tmpData)
 239  		self.name = asciiz(data[0])
 240  		self.index = data[1]
 241  		return self
 242  
 243  	def Save(self, file):
 244  		tmpData = [0] * 2
 245  		tmpData[0] = self.name
 246  		tmpData[1] = self.index
 247  		data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1])
 248  		file.write(data)
 249  		#print "wrote MD3 shader structure: ",data
 250  
 251  	def Dump(self, log):
 252  		log.info("MD3 Shader")
 253  		log.info("Name: %s", self.name)
 254  		log.info("Index: %s", self.index)
 255  		log.info("")
 256  
 257  
 258  class md3Surface:
 259  	ident = ""
 260  	name = ""
 261  	flags = 0
 262  	numFrames = 0
 263  	numShaders = 0
 264  	numVerts = 0
 265  	numTriangles = 0
 266  	ofsTriangles = 0
 267  	ofsShaders = 0
 268  	ofsUV = 0
 269  	ofsVerts = 0
 270  	ofsEnd = 0
 271  	shaders = []
 272  	triangles = []
 273  	uv = []
 274  	verts = []
 275  
 276  	binaryFormat = "<4s%ds10i" % MAX_QPATH  # 1 int, name, then 10 ints
 277  
 278  	def __init__(self):
 279  		self.ident = ""
 280  		self.name = ""
 281  		self.flags = 0
 282  		self.numFrames = 0
 283  		self.numShaders = 0
 284  		self.numVerts = 0
 285  		self.numTriangles = 0
 286  		self.ofsTriangles = 0
 287  		self.ofsShaders = 0
 288  		self.ofsUV = 0
 289  		self.ofsVerts = 0
 290  		self.ofsEnd
 291  		self.shaders = []
 292  		self.triangles = []
 293  		self.uv = []
 294  		self.verts = []
 295  
 296  	def GetSize(self):
 297  		sz = struct.calcsize(self.binaryFormat)
 298  		self.ofsTriangles = sz
 299  		for t in self.triangles:
 300  			sz += t.GetSize()
 301  		self.ofsShaders = sz
 302  		for s in self.shaders:
 303  			sz += s.GetSize()
 304  		self.ofsUV = sz
 305  		for u in self.uv:
 306  			sz += u.GetSize()
 307  		self.ofsVerts = sz
 308  		for v in self.verts:
 309  			sz += v.GetSize()
 310  		self.ofsEnd = sz
 311  		return self.ofsEnd
 312  
 313  	def Load(self, file, log):
 314  		# where are we in the file (for calculating real offsets)
 315  		ofsBegin = file.tell()
 316  		tmpData = file.read(struct.calcsize(self.binaryFormat))
 317  		data = struct.unpack(self.binaryFormat, tmpData)
 318  		self.ident = data[0]
 319  		self.name = asciiz(data[1])
 320  		self.flags = data[2]
 321  		self.numFrames = data[3]
 322  		self.numShaders = data[4]
 323  		self.numVerts = data[5]
 324  		self.numTriangles = data[6]
 325  		self.ofsTriangles = data[7]
 326  		self.ofsShaders = data[8]
 327  		self.ofsUV = data[9]
 328  		self.ofsVerts = data[10]
 329  		self.ofsEnd = data[11]
 330  
 331  		# load the tri info
 332  		file.seek(ofsBegin + self.ofsTriangles, 0)
 333  		for i in range(0, self.numTriangles):
 334  			self.triangles.append(md3Triangle())
 335  			self.triangles[i].Load(file)
 336  			#self.triangles[i].Dump(log)
 337  
 338  		# load the shader info
 339  		file.seek(ofsBegin + self.ofsShaders, 0)
 340  		for i in range(0, self.numShaders):
 341  			self.shaders.append(md3Shader())
 342  			self.shaders[i].Load(file)
 343  			#self.shaders[i].Dump(log)
 344  
 345  		# load the uv info
 346  		file.seek(ofsBegin + self.ofsUV, 0)
 347  		for i in range(0, self.numVerts):
 348  			self.uv.append(md3TexCoord())
 349  			self.uv[i].Load(file)
 350  			#self.uv[i].Dump(log)
 351  
 352  		# load the verts info
 353  		file.seek(ofsBegin + self.ofsVerts, 0)
 354  		for i in range(0, self.numFrames):
 355  			for j in range(0, self.numVerts):
 356  				self.verts.append(md3Vert())
 357  				#i*self.numVerts+j=where in the surface vertex list the vert position for this frame is
 358  				self.verts[(i * self.numVerts) + j].Load(file)
 359  				#self.verts[j].Dump(log)
 360  
 361  		# go to the end of this structure
 362  		file.seek(ofsBegin+self.ofsEnd, 0)
 363  
 364  		return self
 365  
 366  	def Save(self, file):
 367  		self.GetSize()
 368  		tmpData = [0] * 12
 369  		tmpData[0] = self.ident
 370  		tmpData[1] = self.name
 371  		tmpData[2] = self.flags
 372  		tmpData[3] = self.numFrames
 373  		tmpData[4] = self.numShaders
 374  		tmpData[5] = self.numVerts
 375  		tmpData[6] = self.numTriangles
 376  		tmpData[7] = self.ofsTriangles
 377  		tmpData[8] = self.ofsShaders
 378  		tmpData[9] = self.ofsUV
 379  		tmpData[10] = self.ofsVerts
 380  		tmpData[11] = self.ofsEnd
 381  		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])
 382  		file.write(data)
 383  
 384  		# write the tri data
 385  		for t in self.triangles:
 386  			t.Save(file)
 387  
 388  		# save the shader coordinates
 389  		for s in self.shaders:
 390  			s.Save(file)
 391  
 392  		# save the uv info
 393  		for u in self.uv:
 394  			u.Save(file)
 395  
 396  		# save the verts
 397  		for v in self.verts:
 398  			v.Save(file)
 399  
 400  	def Dump(self, log):
 401  		log.info("MD3 Surface")
 402  		log.info("Ident: %s", self.ident)
 403  		log.info("Name: %s", self.name)
 404  		log.info("Flags: %s", self.flags)
 405  		log.info("Number of Frames: %s", self.numFrames)
 406  		log.info("Number of Shaders: %s", self.numShaders)
 407  		log.info("Number of Verts: %s", self.numVerts)
 408  		log.info("Number of Triangles: %s", self.numTriangles)
 409  		log.info("Offset to Triangles: %s", self.ofsTriangles)
 410  		log.info("Offset to Shaders: %s", self.ofsShaders)
 411  		log.info("Offset to UV: %s", self.ofsUV)
 412  		log.info("Offset to Verts: %s", self.ofsVerts)
 413  		log.info("Offset to end: %s", self.ofsEnd)
 414  		log.info("")
 415  
 416  
 417  class md3Tag:
 418  	name = ""
 419  	origin = []
 420  	axis = []
 421  
 422  	binaryFormat="<%ds3f9f" % MAX_QPATH
 423  
 424  	def __init__(self):
 425  		self.name = ""
 426  		self.origin = [0, 0, 0]
 427  		self.axis = [0, 0, 0, 0, 0, 0, 0, 0, 0]
 428  
 429  	def GetSize(self):
 430  		return struct.calcsize(self.binaryFormat)
 431  
 432  	def Load(self, file):
 433  		tmpData = file.read(struct.calcsize(self.binaryFormat))
 434  		data = struct.unpack(self.binaryFormat, tmpData)
 435  		self.name = asciiz(data[0])
 436  		self.origin[0] = data[1]
 437  		self.origin[1] = data[2]
 438  		self.origin[2] = data[3]
 439  		self.axis[0] = data[4]
 440  		self.axis[1] = data[5]
 441  		self.axis[2] = data[6]
 442  		self.axis[3] = data[7]
 443  		self.axis[4] = data[8]
 444  		self.axis[5] = data[9]
 445  		self.axis[6] = data[10]
 446  		self.axis[7] = data[11]
 447  		self.axis[8] = data[12]
 448  		return self
 449  
 450  	def Save(self, file):
 451  		tmpData = [0] * 13
 452  		tmpData[0] = self.name
 453  		tmpData[1] = float(self.origin[0])
 454  		tmpData[2] = float(self.origin[1])
 455  		tmpData[3] = float(self.origin[2])
 456  		tmpData[4] = float(self.axis[0])
 457  		tmpData[5] = float(self.axis[1])
 458  		tmpData[6] = float(self.axis[2])
 459  		tmpData[7] = float(self.axis[3])
 460  		tmpData[8] = float(self.axis[4])
 461  		tmpData[9] = float(self.axis[5])
 462  		tmpData[10] = float(self.axis[6])
 463  		tmpData[11] = float(self.axis[7])
 464  		tmpData[12] = float(self.axis[8])
 465  		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])
 466  		file.write(data)
 467  		#print "wrote MD3 Tag structure: ",data
 468  
 469  	def Dump(self, log):
 470  		log.info("MD3 Tag")
 471  		log.info("Name: %s", self.name)
 472  		log.info("Origin: %s", self.origin)
 473  		log.info("Axis: %s", self.axis)
 474  		log.info("")
 475  
 476  class md3Frame:
 477  	mins = 0
 478  	maxs = 0
 479  	localOrigin = 0
 480  	radius = 0.0
 481  	name = ""
 482  
 483  	binaryFormat="<3f3f3ff16s"
 484  
 485  	def __init__(self):
 486  		self.mins = [0, 0, 0]
 487  		self.maxs = [0, 0, 0]
 488  		self.localOrigin = [0, 0, 0]
 489  		self.radius = 0.0
 490  		self.name = ""
 491  
 492  	def GetSize(self):
 493  		return struct.calcsize(self.binaryFormat)
 494  
 495  	def Load(self, file):
 496  		tmpData = file.read(struct.calcsize(self.binaryFormat))
 497  		data = struct.unpack(self.binaryFormat, tmpData)
 498  		self.mins[0] = data[0]
 499  		self.mins[1] = data[1]
 500  		self.mins[2] = data[2]
 501  		self.maxs[0] = data[3]
 502  		self.maxs[1] = data[4]
 503  		self.maxs[2] = data[5]
 504  		self.localOrigin[0] = data[6]
 505  		self.localOrigin[1] = data[7]
 506  		self.localOrigin[2] = data[8]
 507  		self.radius = data[9]
 508  		self.name = asciiz(data[10])
 509  		return self
 510  
 511  	def Save(self, file):
 512  		tmpData = [0] * 11
 513  		tmpData[0] = self.mins[0]
 514  		tmpData[1] = self.mins[1]
 515  		tmpData[2] = self.mins[2]
 516  		tmpData[3] = self.maxs[0]
 517  		tmpData[4] = self.maxs[1]
 518  		tmpData[5] = self.maxs[2]
 519  		tmpData[6] = self.localOrigin[0]
 520  		tmpData[7] = self.localOrigin[1]
 521  		tmpData[8] = self.localOrigin[2]
 522  		tmpData[9] = self.radius
 523  		tmpData[10] = self.name
 524  		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])
 525  		file.write(data)
 526  		#print "wrote MD3 frame structure: ",data
 527  
 528  	def Dump(self, log):
 529  		log.info("MD3 Frame")
 530  		log.info("Min Bounds: %s", self.mins)
 531  		log.info("Max Bounds: %s", self.maxs)
 532  		log.info("Local Origin: %s", self.localOrigin)
 533  		log.info("Radius: %s", self.radius)
 534  		log.info("Name: %s", self.name)
 535  		log.info("")
 536  
 537  class md3Object:
 538  	# header structure
 539  	ident = ""			# this is used to identify the file (must be IDP3)
 540  	version = 0			# the version number of the file (Must be 15)
 541  	name = ""
 542  	flags = 0
 543  	numFrames = 0
 544  	numTags = 0
 545  	numSurfaces = 0
 546  	numSkins = 0
 547  	ofsFrames = 0
 548  	ofsTags = 0
 549  	ofsSurfaces = 0
 550  	ofsEnd = 0
 551  	frames = []
 552  	tags = []
 553  	surfaces = []
 554  
 555  	binaryFormat="<4si%ds9i" % MAX_QPATH  # little-endian (<), 17 integers (17i)
 556  
 557  	def __init__(self):
 558  		self.ident = 0
 559  		self.version = 0
 560  		self.name = ""
 561  		self.flags = 0
 562  		self.numFrames = 0
 563  		self.numTags = 0
 564  		self.numSurfaces = 0
 565  		self.numSkins = 0
 566  		self.ofsFrames = 0
 567  		self.ofsTags = 0
 568  		self.ofsSurfaces = 0
 569  		self.ofsEnd = 0
 570  		self.frames = []
 571  		self.tags = []
 572  		self.surfaces = []
 573  
 574  	def GetSize(self):
 575  		self.ofsFrames = struct.calcsize(self.binaryFormat)
 576  		self.ofsTags = self.ofsFrames
 577  		for f in self.frames:
 578  			self.ofsTags += f.GetSize()
 579  		self.ofsSurfaces += self.ofsTags
 580  		for t in self.tags:
 581  			self.ofsSurfaces += t.GetSize()
 582  		self.ofsEnd = self.ofsSurfaces
 583  		for s in self.surfaces:
 584  			self.ofsEnd += s.GetSize()
 585  		return self.ofsEnd
 586  
 587  	def Load(self, file, log):
 588  		tmpData = file.read(struct.calcsize(self.binaryFormat))
 589  		data = struct.unpack(self.binaryFormat, tmpData)
 590  
 591  		self.ident = data[0]
 592  		self.version = data[1]
 593  
 594  		if(self.ident != "IDP3" or self.version != 15):
 595  			log.error("Not a valid MD3 file")
 596  			log.error("Ident: %s", self.ident)
 597  			log.error("Version: %s", self.version)
 598  			Exit()
 599  
 600  		self.name = asciiz(data[2])
 601  		self.flags = data[3]
 602  		self.numFrames = data[4]
 603  		self.numTags = data[5]
 604  		self.numSurfaces = data[6]
 605  		self.numSkins = data[7]
 606  		self.ofsFrames = data[8]
 607  		self.ofsTags = data[9]
 608  		self.ofsSurfaces = data[10]
 609  		self.ofsEnd = data[11]
 610  
 611  		# load the frame info
 612  		file.seek(self.ofsFrames, 0)
 613  		for i in range(0, self.numFrames):
 614  			self.frames.append(md3Frame())
 615  			self.frames[i].Load(file)
 616  			#self.frames[i].Dump(log)
 617  
 618  		# load the tags info
 619  		file.seek(self.ofsTags, 0)
 620  		for i in range(0, self.numFrames):
 621  			for j in range(0, self.numTags):
 622  				tag = md3Tag()
 623  				tag.Load(file)
 624  				#tag.Dump(log)
 625  				self.tags.append(tag)
 626  
 627  		# load the surface info
 628  		file.seek(self.ofsSurfaces, 0)
 629  		for i in range(0, self.numSurfaces):
 630  			self.surfaces.append(md3Surface())
 631  			self.surfaces[i].Load(file, log)
 632  			self.surfaces[i].Dump(log)
 633  		return self
 634  
 635  	def Save(self, file):
 636  		self.GetSize()
 637  		tmpData = [0] * 12
 638  		tmpData[0] = self.ident
 639  		tmpData[1] = self.version
 640  		tmpData[2] = self.name
 641  		tmpData[3] = self.flags
 642  		tmpData[4] = self.numFrames
 643  		tmpData[5] = self.numTags
 644  		tmpData[6] = self.numSurfaces
 645  		tmpData[7] = self.numSkins
 646  		tmpData[8] = self.ofsFrames
 647  		tmpData[9] = self.ofsTags
 648  		tmpData[10] = self.ofsSurfaces
 649  		tmpData[11] = self.ofsEnd
 650  
 651  		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])
 652  		file.write(data)
 653  
 654  		for f in self.frames:
 655  			f.Save(file)
 656  
 657  		for t in self.tags:
 658  			t.Save(file)
 659  
 660  		for s in self.surfaces:
 661  			s.Save(file)
 662  
 663  	def Dump(self, log):
 664  		log.info("Header Information")
 665  		log.info("Ident: %s", self.ident)
 666  		log.info("Version: %s", self.version)
 667  		log.info("Name: %s", self.name)
 668  		log.info("Flags: %s", self.flags)
 669  		log.info("Number of Frames: %s",self.numFrames)
 670  		log.info("Number of Tags: %s", self.numTags)
 671  		log.info("Number of Surfaces: %s", self.numSurfaces)
 672  		log.info("Number of Skins: %s", self.numSkins)
 673  		log.info("Offset Frames: %s", self.ofsFrames)
 674  		log.info("Offset Tags: %s", self.ofsTags)
 675  		log.info("Offset Surfaces: %s", self.ofsSurfaces)
 676  		log.info("Offset end: %s", self.ofsEnd)
 677  		log.info("")
 678  
 679  def asciiz(s):
 680  	n = 0
 681  	while(ord(s[n]) != 0):
 682  		n = n + 1
 683  	return s[0:n]
 684  
 685  # strips the slashes from the back of a string
 686  def StripPath(path):
 687  	for c in range(len(path), 0, -1):
 688  		if path[c-1] == "/" or path[c-1] == "\\":
 689  			path = path[c:]
 690  			break
 691  	return path
 692  
 693  # strips the model from path
 694  def StripModel(path):
 695  	for c in range(len(path), 0, -1):
 696  		if path[c-1] == "/" or path[c-1] == "\\":
 697  			path = path[:c]
 698  			break
 699  	return path
 700  
 701  # strips file type extension
 702  def StripExtension(name):
 703  	n = 0
 704  	best = len(name)
 705  	while(n != -1):
 706  		n = name.find('.',n+1)
 707  		if(n != -1):
 708  			best = n
 709  	name = name[0:best]
 710  	return name
 711  
 712  # strips gamedir
 713  def StripGamePath(name):
 714  	gamepath = GAMEDIR.replace( '\\', '/' )
 715  	namepath = name.replace( '\\', '/' )
 716  	if namepath[0:len(gamepath)] == gamepath:
 717  		namepath= namepath[len(gamepath):len(namepath)]
 718  	return namepath
 719  
 720  import sys, struct, string, math
 721  
 722  def ANGLE2SHORT(x):
 723  		return int((x * 65536 / 360) & 65535)
 724  
 725  def SHORT2ANGLE(x):
 726  		return x * (360.0 / 65536.0)
 727  
 728  def DEG2RAD(a):
 729  	return (a * math.pi) / 180.0
 730  
 731  def RAD2DEG(a):
 732  	return (a * 180.0) / math.pi
 733  
 734  def DotProduct(x, y):
 735  	return x[0] * y[0] + x[1] * y[1] + x[2] * y[2]
 736  
 737  def CrossProduct(a,b):
 738  	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]]
 739  
 740  def VectorLength(v):
 741  	return math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
 742  
 743  def VectorSubtract(a, b):
 744  	return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
 745  
 746  def VectorAdd(a, b):
 747  	return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
 748  
 749  def VectorCopy(v):
 750  	return [v[0], v[1], v[2]]
 751  
 752  def VectorInverse(v):
 753  	return [-v[0], -v[1], -v[2]]
 754  
 755  #define VectorCopy(a,b)			((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2])
 756  #define	VectorScale(v, s, o)	((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s))
 757  #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))
 758  
 759  def RadiusFromBounds(mins, maxs):
 760  	corner = [0, 0, 0]
 761  	a = 0
 762  	b = 0
 763  
 764  	for i in range(0, 3):
 765  		a = abs(mins[i])
 766  		b = abs(maxs[i])
 767  		if a > b:
 768  			corner[i] = a
 769  		else:
 770  			corner[i] = b
 771  
 772  	return VectorLength(corner)
 773  
 774  
 775  # NOTE: Tr3B - matrix is in column-major order
 776  def MatrixIdentity():
 777  	return [[1.0, 0.0, 0.0, 0.0],
 778  			[0.0, 1.0, 0.0, 0.0],
 779  			[0.0, 0.0, 1.0, 0.0],
 780  			[0.0, 0.0, 0.0, 1.0]]
 781  
 782  def MatrixFromAngles(pitch, yaw, roll):
 783  	sp = math.sin(DEG2RAD(pitch))
 784  	cp = math.cos(DEG2RAD(pitch))
 785  
 786  	sy = math.sin(DEG2RAD(yaw))
 787  	cy = math.cos(DEG2RAD(yaw))
 788  
 789  	sr = math.sin(DEG2RAD(roll))
 790  	cr = math.cos(DEG2RAD(roll))
 791  
 792  #	return [[cp * cy, (sr * sp * cy + cr * -sy), (cr * sp * cy + -sr * -sy), 0.0],
 793  #			[cp * sy, (sr * sp * sy + cr * cy),  (cr * sp * sy + -sr * cy),  0.0],
 794  #			[-sp,      sr * cp,                   cr * cp,                   0.0],
 795  #			[0.0,      0.0,                       0.0,                       1.0]]
 796  
 797  	return [[cp * cy,                     cp * sy,                 -sp,       0.0],
 798  			[(sr * sp * cy + cr * -sy),  (sr * sp * sy + cr * cy),  sr * cp,  0.0],
 799  			[(cr * sp * cy + -sr * -sy), (cr * sp * sy + -sr * cy), cr * cp,  0.0],
 800  			[0.0,                         0.0,                      0.0,      1.0]]
 801  
 802  def MatrixTransformPoint(m, p):
 803  	return [m[0][0] * p[0] + m[1][0] * p[1] + m[2][0] * p[2] + m[3][0],
 804  			m[0][1] * p[0] + m[1][1] * p[1] + m[2][1] * p[2] + m[3][1],
 805  			m[0][2] * p[0] + m[1][2] * p[1] + m[2][2] * p[2] + m[3][2]]
 806  
 807  
 808  def MatrixTransformNormal(m, p):
 809  	return [m[0][0] * p[0] + m[1][0] * p[1] + m[2][0] * p[2],
 810  			m[0][1] * p[0] + m[1][1] * p[1] + m[2][1] * p[2],
 811  			m[0][2] * p[0] + m[1][2] * p[1] + m[2][2] * p[2]]
 812  
 813  def MatrixMultiply(b, a):
 814  	return [[
 815  			a[0][0] * b[0][0] + a[0][1] * b[1][0] + a[0][2] * b[2][0],
 816  			a[0][0] * b[0][1] + a[0][1] * b[1][1] + a[0][2] * b[2][1],
 817  			a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2] * b[2][2],
 818  			0.0,
 819  			],[
 820  			a[1][0] * b[0][0] + a[1][1] * b[1][0] + a[1][2] * b[2][0],
 821  			a[1][0] * b[0][1] + a[1][1] * b[1][1] + a[1][2] * b[2][1],
 822  			a[1][0] * b[0][2] + a[1][1] * b[1][2] + a[1][2] * b[2][2],
 823  			0.0,
 824  			],[
 825  			a[2][0] * b[0][0] + a[2][1] * b[1][0] + a[2][2] * b[2][0],
 826  			a[2][0] * b[0][1] + a[2][1] * b[1][1] + a[2][2] * b[2][1],
 827  			a[2][0] * b[0][2] + a[2][1] * b[1][2] + a[2][2] * b[2][2],
 828  			0.0,
 829  			],[
 830  			a[3][0] * b[0][0] + a[3][1] * b[1][0] + a[3][2] * b[2][0] + b[3][0],
 831  			a[3][0] * b[0][1] + a[3][1] * b[1][1] + a[3][2] * b[2][1] + b[3][1],
 832  			a[3][0] * b[0][2] + a[3][1] * b[1][2] + a[3][2] * b[2][2] + b[3][2],
 833  			1.0,
 834  			]]
 835  
 836  def MatrixSetupTransform(forward, left, up, origin):
 837  	return [[forward[0], forward[1], forward[2], origin[0]],
 838  			[left[0],    left[1],   left[2],     origin[1]],
 839  			[up[0],      up[1],     up[2],       origin[2]],
 840  			[0.0,        0.0,       0.0,         1.0]]
 841  
 842  # our own logger class. it works just the same as a normal logger except
 843  # all info messages get show.
 844  class Logger(logging.Logger):
 845  	def __init__(self, name,level = logging.NOTSET):
 846  		logging.Logger.__init__(self, name, level)
 847  
 848  		self.has_warnings = False
 849  		self.has_errors = False
 850  		self.has_critical = False
 851  
 852  	def info(self, msg, *args, **kwargs):
 853  		apply(self._log,(logging.INFO, msg, args), kwargs)
 854  
 855  	def warning(self, msg, *args, **kwargs):
 856  		logging.Logger.warning(self, msg, *args, **kwargs)
 857  		self.has_warnings = True
 858  
 859  	def error(self, msg, *args, **kwargs):
 860  		logging.Logger.error(self, msg, *args, **kwargs)
 861  		self.has_errors = True
 862  
 863  	def critical(self, msg, *args, **kwargs):
 864  		logging.Logger.critical(self, msg, *args, **kwargs)
 865  		self.has_errors = True
 866  
 867  # should be able to make this print to stdout in realtime and save MESSAGES
 868  # as well. perhaps also have a log to file option
 869  class LogHandler(logging.StreamHandler):
 870  	def __init__(self):
 871  		logging.StreamHandler.__init__(self, sys.stdout)
 872  
 873  		if "md3_export_log" not in Blender.Text.Get():
 874  			self.outtext = Blender.Text.New("md3_export_log")
 875  		else:
 876  			self.outtext = Blender.Text.Get('md3_export_log')
 877  			self.outtext.clear()
 878  
 879  		self.lastmsg = ''
 880  
 881  	def emit(self, record):
 882  		# print to stdout and  to a new blender text object
 883  		msg = self.format(record)
 884  
 885  		if msg == self.lastmsg:
 886  			return
 887  
 888  		self.lastmsg = msg
 889  		self.outtext.write("%s\n" %msg)
 890  
 891  		logging.StreamHandler.emit(self, record)
 892  
 893  logging.setLoggerClass(Logger)
 894  log = logging.getLogger('md3_export')
 895  
 896  handler = LogHandler()
 897  formatter = logging.Formatter('%(levelname)s %(message)s')
 898  handler.setFormatter(formatter)
 899  
 900  log.addHandler(handler)
 901  # set this to minimum output level. eg. logging.DEBUG, logging.WARNING, logging.ERROR
 902  # logging.CRITICAL. logging.INFO will make little difference as these always get
 903  # output'd
 904  log.setLevel(logging.WARNING)
 905  
 906  
 907  class BlenderGui:
 908  	def __init__(self):
 909  		text = """A log has been written to a blender text window. Change this window type to
 910  a text window and you will be able to select the file md3_export_log."""
 911  
 912  		text = textwrap.wrap(text,40)
 913  		text += ['']
 914  
 915  		if log.has_critical:
 916  			text += ['There were critical errors!!!!']
 917  
 918  		elif log.has_errors:
 919  			text += ['There were errors!']
 920  
 921  		elif log.has_warnings:
 922  			text += ['There were warnings']
 923  
 924  		# add any more text before here
 925  		text.reverse()
 926  
 927  		self.msg = text
 928  
 929  		Blender.Draw.Register(self.gui, self.event, self.button_event)
 930  
 931  	def gui(self,):
 932  		quitbutton = Blender.Draw.Button("Exit", 1, 0, 0, 100, 20, "Close Window")
 933  
 934  		y = 35
 935  
 936  		for line in self.msg:
 937  			BGL.glRasterPos2i(10,y)
 938  			Blender.Draw.Text(line)
 939  			y+=15
 940  
 941  	def event(self,evt, val):
 942  		if evt == Blender.Draw.ESCKEY:
 943  			Blender.Draw.Exit()
 944  			return
 945  
 946  	def button_event(self,evt):
 947  		if evt == 1:
 948  			Blender.Draw.Exit()
 949  			return
 950  
 951  
 952  def ApplyTransform(vert, matrix):
 953  	return vert * matrix
 954  
 955  
 956  def UpdateFrameBounds(v, f):
 957  	for i in range(0, 3):
 958  		f.mins[i] = min(v[i], f.mins[i])
 959  	for i in range(0, 3):
 960  		f.maxs[i] = max(v[i], f.maxs[i])
 961  
 962  
 963  def UpdateFrameRadius(f):
 964  	f.radius = RadiusFromBounds(f.mins, f.maxs)
 965  
 966  
 967  def ProcessSurface(scene, blenderObject, md3, pathName, modelName):
 968  	# because md3 doesnt suppoort faceUVs like blender, we need to duplicate
 969  	# any vertex that has multiple uv coords
 970  
 971  	vertDict = {}
 972  	indexDict = {} # maps a vertex index to the revised index after duplicating to account for uv
 973  	vertList = [] # list of vertices ordered by revised index
 974  	numVerts = 0
 975  	uvList = [] # list of tex coords ordered by revised index
 976  	faceList = [] # list of faces (they index into vertList)
 977  	numFaces = 0
 978  
 979  	scene.makeCurrent()
 980  	Blender.Set("curframe", 1)
 981  	Blender.Window.Redraw()
 982  
 983  	# get the object (not just name) and the Mesh, not NMesh
 984  	mesh = blenderObject.getData(False, True)
 985  	matrix = blenderObject.getMatrix('worldspace')
 986  
 987  	surf = md3Surface()
 988  	surf.numFrames = md3.numFrames
 989  	surf.name = blenderObject.getName()
 990  	surf.ident = MD3_IDENT
 991  
 992  	# create shader for surface
 993  	surf.shaders.append(md3Shader())
 994  	surf.numShaders += 1
 995  	surf.shaders[0].index = 0
 996  
 997  	log.info("Materials: %s", mesh.materials)
 998  	# :P
 999  	#shaderpath=Blender.Draw.PupStrInput("shader path for "+blenderObject.name+":", "", MAX_QPATH )
1000          shaderpath=""
1001  	if 	shaderpath == "" :
1002  		if not mesh.materials:
1003  			surf.shaders[0].name = pathName + blenderObject.name
1004  		else:
1005  			surf.shaders[0].name = pathName + mesh.materials[0].name
1006  	else:
1007  		if not mesh.materials:
1008  			surf.shaders[0].name = shaderpath + blenderObject.name
1009  		else:
1010  			surf.shaders[0].name = shaderpath + mesh.materials[0].name
1011  
1012  	# process each face in the mesh
1013  	for face in mesh.faces:
1014  
1015  		tris_in_this_face = []  #to handle quads and up...
1016  
1017  		# this makes a list of indices for each tri in this face. a quad will be [[0,1,1],[0,2,3]]
1018  		for vi in range(1, len(face.v)-1):
1019  			tris_in_this_face.append([0, vi, vi + 1])
1020  
1021  		# loop across each tri in the face, then each vertex in the tri
1022  		for this_tri in tris_in_this_face:
1023  			numFaces += 1
1024  			tri = md3Triangle()
1025  			tri_ind = 0
1026  			for i in this_tri:
1027  				# get the vertex index, coords and uv coords
1028  				index = face.v[i].index
1029  				v = face.v[i].co
1030  				if mesh.faceUV == True:
1031  					uv = (face.uv[i][0], face.uv[i][1])
1032  				elif mesh.vertexUV:
1033  					uv = (face.v[i].uvco[0], face.v[i].uvco[1])
1034  				else:
1035  					uv = (0.0, 0.0) # handle case with no tex coords
1036  
1037  
1038  				if vertDict.has_key((index, uv)):
1039  					# if we've seen this exact vertex before, simply add it
1040  					# to the tris list of vertex indices
1041  					tri.indexes[tri_ind] = vertDict[(index, uv)]
1042  				else:
1043  					# havent seen this tri before
1044  					# (or its uv coord is different, so we need to duplicate it)
1045  
1046  					vertDict[(index, uv)] = numVerts
1047  
1048  					# put the uv coord into the list
1049  					# (uv coord are directly related to each vertex)
1050  					tex = md3TexCoord()
1051  					tex.u = uv[0]
1052  					tex.v = uv[1]
1053  					uvList.append(tex)
1054  
1055  					tri.indexes[tri_ind] = numVerts
1056  
1057  					# now because we have created a new index,
1058  					# we need a way to link it to the index that
1059  					# blender returns for NMVert.index
1060  					if indexDict.has_key(index):
1061  						# already there - each of the entries against
1062  						# this key represents  the same vertex with a
1063  						# different uv value
1064  						ilist = indexDict[index]
1065  						ilist.append(numVerts)
1066  						indexDict[index] = ilist
1067  					else:
1068  						# this is a new one
1069  						indexDict[index] = [numVerts]
1070  
1071  					numVerts += 1
1072  				tri_ind +=1
1073  			faceList.append(tri)
1074  
1075  	# we're done with faces and uv coords
1076  	for t in uvList:
1077  		surf.uv.append(t)
1078  
1079  	for f in faceList:
1080  		surf.triangles.append(f)
1081  
1082  	surf.numTriangles = len(faceList)
1083  	surf.numVerts = numVerts
1084  
1085  	# now vertices are stored as frames -
1086  	# all vertices for frame 1, all vertices for frame 2...., all vertices for frame n
1087  	# so we need to iterate across blender's frames, and copy out each vertex
1088  	for	frameNum in range(1, md3.numFrames + 1):
1089  		Blender.Set("curframe", frameNum)
1090  		Blender.Window.Redraw()
1091  
1092  		m = NMesh.GetRawFromObject(blenderObject.name)
1093  
1094  		vlist = [0] * numVerts
1095  		for vertex in m.verts:
1096  			try:
1097  				vindices = indexDict[vertex.index]
1098  			except:
1099  				log.warning("Found a vertex in %s that is not part of a face", blenderObject.name)
1100  				continue
1101  
1102  			vTx = ApplyTransform(vertex.co, matrix)
1103  			nTx = ApplyTransform(vertex.no, matrix)
1104  			UpdateFrameBounds(vTx, md3.frames[frameNum - 1])
1105  			vert = md3Vert()
1106  			#vert.xyz = vertex.co[0:3]
1107  			#vert.normal = vert.Encode(vertex.no[0:3])
1108  			vert.xyz = vTx[0:3]
1109  			vert.normal = vert.Encode(vertex.no[0:3])
1110                          #print vertex.no
1111  			for ind in vindices:  # apply the position to all the duplicated vertices
1112  				vlist[ind] = vert
1113  
1114  		UpdateFrameRadius(md3.frames[frameNum - 1])
1115  
1116  		for vl in vlist:
1117  			surf.verts.append(vl)
1118  
1119  	surf.Dump(log)
1120  	md3.surfaces.append(surf)
1121  	md3.numSurfaces += 1
1122  
1123  
1124  def Export(fileName):
1125  	if(fileName.find('.md3', -4) <= 0):
1126  		fileName += '.md3'
1127  
1128  	log.info("Starting ...")
1129  
1130  	log.info("Exporting MD3 format to: %s", fileName)
1131  
1132  	pathName = StripGamePath(StripModel(fileName))
1133  	log.info("Shader path name: %s", pathName)
1134  
1135  	modelName = StripExtension(StripPath(fileName))
1136  	log.info("Model name: %s", modelName)
1137  
1138  	md3 = md3Object()
1139  	md3.ident = MD3_IDENT
1140  	md3.version = MD3_VERSION
1141  
1142  	tagList = []
1143  
1144  	# get the scene
1145  	scene = Blender.Scene.getCurrent()
1146  	context = scene.getRenderingContext()
1147  
1148  	scene.makeCurrent()
1149  	md3.numFrames = Blender.Get("curframe")
1150  	Blender.Set("curframe", 1)
1151  
1152  	# create a bunch of blank frames, they'll be filled in by 'ProcessSurface'
1153  	for i in range(1, md3.numFrames + 1):
1154  		frame = md3Frame()
1155  		frame.name = "frame_" + str(i)
1156  		md3.frames.append(frame)
1157  
1158  	# export all selected objects
1159  	objlist = Blender.Object.GetSelected()
1160  
1161  	# process each object for the export
1162  	for obj in objlist:
1163  		# check if it's a mesh object
1164  		if obj.getType() == "Mesh":
1165  			log.info("Processing surface: %s", obj.name)
1166  			if len(md3.surfaces) == MD3_MAX_SURFACES:
1167  				log.warning("Hit md3 limit (%i) for number of surfaces, skipping ...", MD3_MAX_SURFACES, obj.getName())
1168  			else:
1169  				ProcessSurface(scene, obj, md3, pathName, modelName)
1170  		elif obj.getType() == "Empty":   # for tags, we just put em in a list so we can process them all together
1171  			if obj.name[0:4] == "tag_":
1172  				log.info("Processing tag: %s", obj.name)
1173  				tagList.append(obj)
1174  				md3.numTags += 1
1175  		else:
1176  			log.info("Skipping object: %s", obj.name)
1177  
1178  
1179  	# work out the transforms for the tags for each frame of the export
1180  	for i in range(1, md3.numFrames + 1):
1181  
1182  		# needed to update IPO's value, but probably not the best way for that...
1183  		scene.makeCurrent()
1184  		Blender.Set("curframe", i)
1185  		Blender.Window.Redraw()
1186  		for tag in tagList:
1187  			t = md3Tag()
1188  			matrix = tag.getMatrix('worldspace')
1189  			t.origin[0] = matrix[3][0]
1190  			t.origin[1] = matrix[3][1]
1191  			t.origin[2] = matrix[3][2]
1192  
1193  			t.axis[0] = matrix[0][0]
1194  			t.axis[1] = matrix[0][1]
1195  			t.axis[2] = matrix[0][2]
1196  
1197  			t.axis[3] = matrix[1][0]
1198  			t.axis[4] = matrix[1][1]
1199  			t.axis[5] = matrix[1][2]
1200  
1201  			t.axis[6] = matrix[2][0]
1202  			t.axis[7] = matrix[2][1]
1203  			t.axis[8] = matrix[2][2]
1204  			t.name = tag.name
1205  			#t.Dump(log)
1206  			md3.tags.append(t)
1207  
1208  	# export!
1209  	file = open(fileName, "wb")
1210  	md3.Save(file)
1211  	file.close()
1212  	md3.Dump(log)
1213  
1214  def FileSelectorCallback(fileName):
1215  	Export(fileName)
1216  
1217  	BlenderGui()
1218  
1219  Blender.Window.FileSelector(FileSelectorCallback, "Export Quake3 MD3")