#!/usr/bin/env python
"""md3import.py
 Import MD3 into Blender
    Copyright 2002  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 0006 (2002.03.03)


"""
Versions of Blender 2.20-2.23 should not need anything modified.
Older versions may need to rename "Blender210", get rid of "import GUI",
 set md3file accordingly, and modify the code further on down to use a
 straight call to md3import() instead of using the FileSelector widget.
Newer versions (>2.23) might need complete overhaul.

Development platform:
 CPU: AMD Athlon 1200MHz
 kernel: linux-x86 2.4.17
 libc: GNU glibc-2.2.5
 OS: Debian GNU/Linux sid (2002.02.26)
 python libs: python-2.1.2
 Blender: creator-2.22-linux-glibc2.1.2-i386
"""

"""

As of January 2002, development of the Creator line is temporarily suspended
until NaN gets rich enough, or something.  There is an annoying bug in Blender
2.23 for linux-x86 that breaks file append (library load) -- a real showstopper
for mapping.  This bug is not present in 2.22, which, in turn, has a buggy
Armature-IKA (which isn't "critical" for Quake modelling/animation, though
probably useful for generating certain types of animations).
My opinion: keep around as many different versions of Blender as feasible.

Python API is something of a mess in 2.2x.
The module "Blender" represents pre-2.0 API, a far more complete interface to
Blender's capabilities.  This module was overhauled in 2.00-2.10, and in effect
became a different module with a different API, but still called "Blender".
In 2.1x, this new Blender module was renamed "Blender210" and the old pre-2.0
module was reinstated as just "Blender".  Furthermore, post-2.23 Blender
[Publisher] will have an expanded Python Modelling/Animation API based on
"Blender210", and probably won't be called "Blender210" by the time the
Creator line is resurrected.  I don't forsee a return of the Creator line
until after Nov 2002, and am planning accordingly.

In the meantime, I expect to use both of 2.22's Blender and Blender210 modules.
The 210 module has a more natural, serialized interface for importing/exporting
formats, but has incomplete interface to the rest of the Blender modelling
facilities.  The older module fills the vacancy.

If you want to help push Blender development with some cash (and help
revitalize the Creator line), you can go and pay for Blender Publisher
(http://www.blender3d.com/).  However, this MD3 script will probably break
in Publisher, meaning you'll still be firing up Creator.  Bit of a Catch-22.
On the bright side, files made by Creator are natively understood by Publisher
(this has been one shining attribute of Blender -- all versions have always
been forward-compatible).

Python 2.0 is not packaged with Debian sid.  As a workaround, I have to
specify the environment variable PYTHONPATH to include the newer python libs
(e.g. /usr/lib/python2.1) before starting Blender.  YMMV.


I think it may be best to move as much of the MD3 import/export stuff into the
general parsing module.
"""


"""
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="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 Blender210
import GUI
import Blender



#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 = Blender210.getCurrentScene()
  thisobj = Blender210.Object("bounds")
  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]
  thismesh.enterEditMode()
  thismesh.addVertex(0,0,0,0,0,0)  #Throw-away vertex
  thismesh.addVertex(minx, miny, minz, 0, 0, 0)
  thismesh.addVertex(minx, miny, maxz, 0, 0, 0)
  thismesh.addVertex(minx, maxy, minz, 0, 0, 0)
  thismesh.addVertex(minx, maxy, maxz, 0, 0, 0)
  thismesh.addVertex(maxx, miny, minz, 0, 0, 0)
  thismesh.addVertex(maxx, miny, maxz, 0, 0, 0)
  thismesh.addVertex(maxx, maxy, minz, 0, 0, 0)
  thismesh.addVertex(maxx, maxy, maxz, 0, 0, 0)
  """ 8 ________ 6
       /       /|
      /       / |
   4 /______2/  |
     |      z|  |
     |   7 . | .|  x
     | .     | / 5
     |_______|/
  y 3       1
  """
  thismesh.addFace(1, 2, 4, 3, 0, 0)
  thismesh.addFace(5, 6, 8, 7, 0, 0)
  thismesh.addFace(1, 2, 6, 5, 0, 0)
  thismesh.addFace(3, 4, 8, 7, 0, 0)
  thismesh.addFace(1, 3, 7, 5, 0, 0)
  thismesh.addFace(2, 4, 8, 6, 0, 0)

  ### Animation
  for i in xrange(0, len(md3obj.frames)):
    Blender210.setCurrentFrame(i+1)
    #Move the vertices around
    #Insert VertexKey IPO key
  del thismesh.vertices[0]   #Remove throw-away vertex.
  thismesh.leaveEditMode()   #Commit
  Blender210.setCurrentFrame(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 = Blender210.getCurrentScene()
  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 = Blender210.Object(md3obj.tags[i].name)
    Blender210.connect(ourscene, 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.loc = tuple([[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.rot = tuple([[rx, ry, 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 = Blender210.getCurrentScene()
  for i in xrange(0, len(md3obj.surfaces)):
    print " mesh %d," % i, md3obj.surfaces[i].name

    thisobj = Blender210.Object(md3obj.surfaces[i].name)
    thismesh = Blender210.Mesh(md3obj.surfaces[i].name)
    Blender210.connect(thisobj, thismesh)
    Blender210.connect(ourscene, thisobj)

    ### Vertices
    thismesh.enterEditMode()
    #Make a throw-away vertex 0, so that addFace doesn't mess up.
    thismesh.addVertex(0,0,0,0,0,0)
    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]
      thismesh.addVertex(vx, vy, vz, nx, ny, nz)

    ### Polygon faces (triangles)
    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] + 1
      b = md3obj.surfaces[i].triangles[j].indexes[1] + 1
      c = md3obj.surfaces[i].triangles[j].indexes[2] + 1
      d = 0
#      thismesh.addFace(a, b, c, d, 0, 0)
      thismesh.addFace(c, b, a, 0, 0, 0)

    #Remove throw-away vertex 0 now that triangles are done.
    del thismesh.vertices[0]
    thismesh.removeDoubles()
    thismesh.leaveEditMode()  #Commit.

    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.
This is broken pretty badly in Blender 2.22, 2.23
"""
  ### Textures
  for i in xrange(0, len(md3obj.surfaces)):
    thismesh = Blender210.getMesh(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):
      Blender210.setCurrentFrame(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 = md3.qmodel()
  f = open(fname, "rb")
  if f:
    if md3obj.parse(f):
      md3_import(md3obj)
    f.close()


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


if __name__ == '__main__':
  FS = GUI.FileSelector()
  FS.activate(FScallback, FS)
#  md3import(md3file)
