#!BPY
"""
Name: 'MD3 Import 234'
Blender: 234
Group: 'Import'
Submenu: 'All' all
Submenu: 'Selected' sel
Submenu: 'Configure (gui)' gui
Tooltip: 'Import ID Software MD3 format.'
"""

"""md3import.py
 Import MD3 into Blender
    Copyright 2004  PhaethonH <phaethon@linux.ucla.edu>
Permission granted to use, copy, distribute, and modify, provided this
copyright notice remains intact.  Software is provided as-is, with absolutely
no warranty of any kind.
"""

# md3import.py version 0009 (2004.10.23)


"""
Overhauled for 2.34.

Development platform:
 CPU: AMD Athlon 1200MHz
 kernel: linux-x86 2.4.26
 libc: GNU glibc-2.3.2
 OS: Debian GNU/Linux sid (2002.08.21)
 python libs: python-2.3.4
 Blender: blender-2.34-linux-glibc2.2.5-i386
"""

"""
TODO:
 * use current frame as initial frame, instead of overwriting 1
"""

#Set this to where the base directory of unpacked data starts.
pk3path="."

#Modify this if needed (e.g. FileSelector doesn't work)
md3file="blends/railgun.md3"



import string
import os.path
from os import F_OK, R_OK, W_OK, X_OK

# The MD3 parsing module.
import md3

# Targeted for Blender 2.22.  Plan for later Python API mess.
import Blender
from Blender import Window, Scene, Object, NMesh, Material, Image, Texture



#Default scale factor is 1/64
MD3BLENDER_SCALE = md3.MD3_XYZ_SCALE



def make_bounds_box (md3obj):
  """Create bounds box in the current scene and layer.
 This is the box used in Q3A for determining collision.
 Return value is undefined."""

  ourscene = Scene.GetCurrent ()
#  thisobj = Object.New('Mesh')
#  thismesh = Blender210.Mesh("bounds")
#  Blender210.connect(thisobj, thismesh)
#  Blender210.connect(ourscene, thisobj)
#  Blender210.setCurrentFrame(1)

  #Create the box (first frame).
  minx = md3obj.frames[0].bounds[0][0]
  miny = md3obj.frames[0].bounds[0][1]
  minz = md3obj.frames[0].bounds[0][2]
  maxx = md3obj.frames[0].bounds[1][0]
  maxy = md3obj.frames[0].bounds[1][1]
  maxz = md3obj.frames[0].bounds[1][2]

  """ 8 ________ 6
       /       /|
      /       / |
   4 /______2/  |
     |      z|  |
     |   7 . | .|  x
     | .     | / 5
     |_______|/
  y 3       1
  """
  #We have eight vertices.
  vertices = ()
  vertices = vertices + ((minx, miny, minz),)
  vertices = vertices + ((minx, miny, maxz),)
  vertices = vertices + ((minx, maxy, minz),)
  vertices = vertices + ((minx, maxy, maxz),)
  vertices = vertices + ((maxx, miny, minz),)
  vertices = vertices + ((maxx, miny, maxz),)
  vertices = vertices + ((maxx, maxy, minz),)
  vertices = vertices + ((maxx, maxy, maxz),)

  #six faces.
  faces = ()
  faces = faces + ((0, 1, 3, 2),)
  faces = faces + ((4, 5, 7, 6),)
  faces = faces + ((0, 1, 5, 4),)
  faces = faces + ((2, 3, 7, 6),)
  faces = faces + ((0, 2, 6, 4),)
  faces = faces + ((1, 3, 7, 5),)


  thismesh = NMesh.New()
  bvertices = [ NMesh.Vert(*x)  for x in vertices ]
  z = [ [bvertices[x[0]], bvertices[x[1]], bvertices[x[2]], bvertices[x[3]]] for x in faces ]
  bfaces = []
  for i in xrange(0, len(z)):
    bfaces.append(NMesh.Face(z[i]))
#  bfaces = [ NMesh.Face(*y) for y in [ [bvertices[x[0]], bvertices[x[1]], bvertices[x[2]]]  for x in faces ] ]
  thismesh.verts = bvertices
  thismesh.faces = bfaces
  NMesh.PutRaw(thismesh, "bounds1", True)

#  thisobj.link(thismesh)
#  thisobj = thismesh
#  ourscene.link(thisobj)

  ### Animation
  for i in xrange(0, len(md3obj.frames)):
    Blender.Set('curframe', i+1)
    #Move the vertices around
    #Insert VertexKey IPO key
  Blender.Set('curframe', 1)   #Restore frame number.


def make_tags (md3obj):
  """Tags are named points in the model space.
 These are naturally represented by Empty objects.
 Return value is undefined."""
  ourscene = Scene.GetCurrent()
  for i in xrange(0, len(md3obj.tags)):
#    print " tag %d (%s) @ " % (i, md3obj.tags[i].name), md3obj.tags[i].origin, md3obj.tags[i].axis
    print " tag %d:" % i, md3obj.tags[i].name
    #This should be Empty object.
    thistag = Object.New("Empty", md3obj.tags[i].name);
    ourscene.link(thistag)
    #Note: Quake3 uses left-hand geometry.
    x = md3obj.tags[i].origin[0]
    y = md3obj.tags[i].origin[1]
    z = md3obj.tags[i].origin[2]
    thistag.setLocation([x, y, z])
    rx = md3obj.tags[i].axis[2][0]
    ry = md3obj.tags[i].axis[2][1]
    rz = md3obj.tags[i].axis[2][2]
#    thistag.setRotation([rx, ry, rz])
#    thistag.RotX = rx
#    thistag.RotY = ry
#    thistag.RotZ = rz



def find_texfile (tname):
  """Find texture file named `tname'
 Search through paths if needed.
 Return value is undefined."""
  retval = tname
  if os.path.exists(retval):
    print "TEX =", retval
    return retval
  p = pk3path + os.pathsep + os.getenv("PK3PATH", ".")
  paths = string.split(p, os.pathsep)
  for p in paths:
    retval = os.path.normpath(p + os.sep + tname)
    if os.path.exists(retval):
      print "TEX =", retval
      return retval
  print "TEX not found:", tname
  return tname


def make_surfaces (md3obj):
  """What MD3 calls a "surface", Blender calls a "mesh".
 Return value is undefined."""
  ourscene = Scene.GetCurrent()
  for i in xrange(0, len(md3obj.surfaces)):
    print " mesh %d," % i, md3obj.surfaces[i].name

    thisname = md3obj.surfaces[i].name
    thisobj = Object.New("Mesh", thisname)
    thismesh = NMesh.New(thisname)
    thisobj.link(thismesh)
    ourscene.link(thisobj)

    ### Vertices
    vertices = ()
    normals = ()
    for j in xrange(0, md3obj.surfaces[i].numVerts):
#      print "  vertex %d = " % j, md3obj.surfaces[i].verts[0][j].xyz, md3obj.surfaces[i].verts[0][j].normal
      #Note: Quake3 uses left-hand geometry.
      vx = md3obj.surfaces[i].verts[0][j].xyz[0] * MD3BLENDER_SCALE
      vy = md3obj.surfaces[i].verts[0][j].xyz[1] * MD3BLENDER_SCALE
      vz = md3obj.surfaces[i].verts[0][j].xyz[2] * MD3BLENDER_SCALE
      nx = md3obj.surfaces[i].verts[0][j].normal[0]
      ny = md3obj.surfaces[i].verts[0][j].normal[1]
      nz = md3obj.surfaces[i].verts[0][j].normal[2]
      vertices = vertices + ((vx, vy, vz),)
      normals = normals + ((nx, ny, nz),)

    ### Polygon faces (triangles)
    faces = ()
    for j in xrange(0, len(md3obj.surfaces[i].triangles)):
#      print "  tri %d = " % j, md3obj.surfaces[i].triangles[j].indexes
      #Ensures vertex 0 won't occur, so addFace won't mess up.
      a = md3obj.surfaces[i].triangles[j].indexes[0]
      b = md3obj.surfaces[i].triangles[j].indexes[1]
      c = md3obj.surfaces[i].triangles[j].indexes[2]
      d = 0
#      thismesh.addFace(a, b, c, d, 0, 0)
      faces = faces + ((c, b, a, 0),)

    bvertices = [ NMesh.Vert(*x)  for x in vertices ]
    bnormals = ()
    for i in xrange(0, len(normals)):
      bvertices[i].no[0] = normals[i][0]
      bvertices[i].no[1] = normals[i][1]
      bvertices[i].no[2] = normals[i][2]
    z = [ [bvertices[x[0]], bvertices[x[1]], bvertices[x[2]]] for x in faces ]
#    print "processed1[0] =", z[0]
#    bfaces = [ NMesh.Face(y) for y in z ]
    bfaces = []
    for i in xrange(0, len(z)):
#      print "Face(%s)" % (z[i],)
#      bfaces = bfaces.append(NMesh.Face(faces[i]))
      bfaces.append(NMesh.Face(z[i]))
    ###Remove doubles.
#    thismesh.removeDoubles()
    thismesh.verts = bvertices
    thismesh.faces = bfaces
    NMesh.PutRaw(thismesh, thisname, False)

#    Blender210.setCurrentFrame(1)  #Reset animation frame to 1.
#    print "thismesh %d = " % i, dir(thismesh)
  return 1



def make_textures (md3obj):
  """Set up textures and texture coordinates.
"""
  ### Textures
  for i in xrange(0, len(md3obj.surfaces)):
    thismesh = NMesh.get(md3obj.surfaces[i].name)
    #Blender210 only allows 1 texture per mesh.
    for j in xrange(0, len(md3obj.surfaces[i].shaders)):
      pass
    tname = find_texfile(md3obj.surfaces[i].shaders[0].name)
#    thismesh.addTexture(tname)

    ### Texture coordinates
    for j in xrange(0, len(md3obj.surfaces[i].triangles)):
#      print " tri %d" % j
      a = md3obj.surfaces[i].triangles[j].indexes[0]
      b = md3obj.surfaces[i].triangles[j].indexes[1]
      c = md3obj.surfaces[i].triangles[j].indexes[2]
      d = 0
      u1, v1 = md3obj.surfaces[i].texcoords[a-1].st
      u2, v2 = md3obj.surfaces[i].texcoords[b-1].st
      u3, v3 = md3obj.surfaces[i].texcoords[c-1].st
#      thismesh.addTexCoords(u1, v1, u2, v2, u3, v3, 0, 0)
#      thismesh.addTexCoords(u3, v3, u2, v2, u1, v1, 0, 0)
#      thismesh.texcoords[j] = [[u3, v3, u2, v2, u1, v1, 0, 0]]
#  Blender210.setCurrentFrame(1)



def make_animation (md3obj):
  """Set up animation of object.
 Doable, but requires extensive use of module `Blender'
"""
  ### Animate
  for i in xrange(0, len(md3obj.surfaces)):
    for framenum in xrange(0, md3obj.surfaces[i].numFrames):
      Blender.Set('curframe', framenum+1)
      #Move around all the vertices.
      for j in xrange(0, md3obj.surfaces[i].numVerts):
        vx = md3obj.surfaces[i].verts[0][j].xyz[0] * MD3BLENDER_SCALE
        vy = md3obj.surfaces[i].verts[0][j].xyz[1] * MD3BLENDER_SCALE
        vz = md3obj.surfaces[i].verts[0][j].xyz[2] * MD3BLENDER_SCALE
        nx = md3obj.surfaces[i].verts[0][j].normal[0]
        ny = md3obj.surfaces[i].verts[0][j].normal[1]
        nz = md3obj.surfaces[i].verts[0][j].normal[2]
#        thismesh.vertices[j] = tuple([[vx, vy, vz]])
#        thismesh.normals[j] = tuple([[nx, ny, nz]])
      #Insert VertexKey IPO key
#  Blender210.setCurrentFrame(1)




def md3_import (md3obj):
  """Import MD3 object into Blender.
 Parameter is a parsed MD3 object populated with data (from md3obj.parse()).
 Return value is undefined."""
  print "importing MD3", md3obj.name, "..."
#  print "MODEL:"
#  print " NAME:", md3obj.name
#  print " #FRAMES:", len(md3obj.frames)
#  print " #EMPTIES:", len(md3obj.tags)
#  print " #MESHES:", len(md3obj.surfaces)
  #Warning: Quake3 uses left-hand geometry.  Blender uses right-hand.
  # - MD3 surfaces, meshes per frame
  make_surfaces(md3obj)
  # - bounds box
  make_bounds_box(md3obj)
  # - MD3 tags
  make_tags(md3obj)
  # - Textures
  # make_textures(md3obj)
  # - Animation
  # make_animation(md3obj)
  print "...MD3 finished"


def md3import (fname):
  """Import MD3 by filename into Blender."""
  md3obj = 0
  md3obj = md3.qmodel()
  f = open(fname, "rb")
  if f:
    f.seek(0)
    if md3obj.parse(f):
      md3_import(md3obj)
    f.close()


def FScallback (filename):
  """Callback handler for FileSelector widget."""
  fname = filename
#  print "Filename", fname
  md3import(fname)


if __name__ == '__main__':
  FS = Window.FileSelector(FScallback, "Import MD3", md3file)
#  md3import(md3file)
