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