#
# PYTHON IMPORTS
#
import array
import collections
import math
import os
import struct
import sys

#
# BLENDER IMPORTS
#
import Blender
from Blender import Image
from Blender import Material
from Blender import Mesh
from Blender import Modifier
from Blender import Mathutils
from Blender.Mathutils import *
from Blender import Texture
from Blender import Types
from Blender import Window
import bpy

#
# MATH UTILITIES
#

def Array4x4ToBlenderMatrix(data):

	r1 = [data[ 0], data[ 1], data[ 2], data[ 3]]
	r2 = [data[ 4], data[ 5], data[ 6], data[ 7]]
	r3 = [data[ 8], data[ 9], data[10], data[11]]
	r4 = [data[12], data[13], data[14], data[15]]
	return Blender.Mathutils.Matrix(r1, r2, r3, r4)

def NormalizeVector(data):

	norm = math.sqrt(data[0]*data[0] + data[1]*data[1] + data[2]*data[2])
	temp = [data[0]/norm, data[1]/norm, data[2]/norm]
	return temp
		
#
# STRING UTILITIES
#

def StripNullsFromStr(str):
	'''
	Strips null characters from a string.
	Notes: Blender does not like strings that contain null characters. This can
	happen when we load strings from binary files that were created using lang-
	uages such as C or C++.
	'''
	# make a copy, removing null characters
	retval = ''
	for c in str:
		if c != '\x00':
			retval += c
	return retval

#
# INPUT FILE STREAM
#

class InputFileStream:
	'''
	'''

	# CONSTRUCTOR
	def __init__(self):
		
		pass
	
	# PUBLIC METHOD: OPEN
	def open(self, filename):
		
		# open file
		try: self.ifile = open(filename, "rb")
		except: raise Exception('SMC Import Error: Failed to open file.')

	# PUBLIC METHOD: TELL
	def tell(self):
	
		return self.ifile.tell()
		
	# PUBLIC METHOD: SEEK
	def seek(self, position):
	
		# seek position
		self.ifile.seek(position)
		
	# PUBLIC METHOD: SKIP
	def skip(self, offset):
	
		# seek position
		self.ifile.seek(offset, 1)

	# PUBLIC METHOD: READ STRING
	def read_string(self, n):
	
		tmp = array.array('c')
		tmp.fromfile(self.ifile, n)
		ret = ''.join(tmp)
		return StripNullsFromStr(ret)
	
	# PUBLIC METHOD: READ UINT08
	def LE_read_uint08(self):
		
		tmp = self.ifile.read(1)
		if len(tmp) == 1: return struct.unpack('<B', tmp)[0];
		raise EOFError()

	# PUBLIC METHOD: READ UINT16
	def LE_read_uint16(self):
		
		tmp = self.ifile.read(2)
		if len(tmp) == 2: return struct.unpack('<H', tmp)[0];
		raise EOFError()

	# PUBLIC METHOD: READ UINT32
	def LE_read_uint32(self):
		
		tmp = self.ifile.read(4);
		if len(tmp) == 4: return struct.unpack('<I', tmp)[0];
		raise EOFError()

	# PUBLIC METHOD: READ UINT64
	def LE_read_uint64(self):
		
		tmp = self.ifile.read(8);
		if len(tmp) == 8: return struct.unpack('<Q', tmp)[0];
		raise EOFError()

	# PUBLIC METHOD: READ UINT08 ARRAY
	def LE_read_uint08_array(self, n):

		if(n == 0): raise Exception('SMC Import Error: Arrays must have at least one item.')
		ret = array.array('B')
		ret.fromfile(self.ifile, n)
		return ret;
		
	# PUBLIC METHOD: READ UINT16 ARRAY
	def LE_read_uint16_array(self, n):

		if(n == 0): raise Exception('SMC Import Error: Arrays must have at least one item.')
		ret = array.array('H')
		ret.fromfile(self.ifile, n)
		if sys.byteorder == "big": ret.byteswap()
		return ret;

	# PUBLIC METHOD: READ UINT32 ARRAY
	def LE_read_uint32_array(self, n):

		if(n == 0): raise Exception('SMC Import Error: Arrays must have at least one item.')		
		ret = array.array('I')
		ret.fromfile(self.ifile, n)
		if sys.byteorder == "big": ret.byteswap()
		return ret;
	
	# PUBLIC METHOD: READ UINT64 ARRAY
	def LE_read_uint64_array(self, n):

		if(n == 0): raise Exception('SMC Import Error: Arrays must have at least one item.')
		ret = array.array('Q')
		ret.fromfile(self.ifile, n)
		if sys.byteorder == "big": ret.byteswap()
		return ret;

	# PUBLIC METHOD: READ REAL32
	def LE_read_real32(self):
		
		tmp = self.ifile.read(4);
		if len(tmp) == 4: return struct.unpack('<f', tmp)[0];
		raise EOFError()

	# PUBLIC METHOD: READ REAL64
	def LE_read_real64(self):
		
		tmp = self.ifile.read(8);
		if len(tmp) == 8: return struct.unpack('<d', tmp)[0];
		raise EOFError()

	# PUBLIC METHOD: READ REAL32 ARRAY
	def LE_read_real32_array(self, n):

		if(n == 0): raise Exception('SMC Import Error: Arrays must have at least one item.')		
		ret = array.array('f')
		ret.fromfile(self.ifile, n)
		if sys.byteorder == "big": ret.byteswap()
		return ret;
	
	# PUBLIC METHOD: READ REAL64 ARRAY
	def LE_read_real64_array(self, n):
		
		if(n == 0): raise Exception('SMC Import Error: Arrays must have at least one item.')
		ret = array.array('d')
		ret.fromfile(self.ifile, n)
		if sys.byteorder == "big": ret.byteswap()
		return ret;

#
# STRUCTURES
#

class SMCHEADER:
	# uint32 magic
	# uint16 major_version
	# uint16 minor_version
	# uint32 n_vbuffers
	# uint32 n_ibuffers
	# uint32 n_images
	# uint32 n_surfaces
	# uint32 n_surfmats
	# uint32 n_skeletons
	# uint32 n_animations
	# uint32 reserved1
	# uint32 reserved2
	# uint32 reserved3
	# uint32 reserved4
	# uint32 reserved5
	# uint32 reserved6
	# uint32 reserved7
	pass

class SMCVTXBUFFER:
	# uint32 flags
	# string name
	# uint16 uvchan
	# uint16 uvtype[i]
	# uint16 colors
	# uint32 elem
	# real32 positions[[]]
	# real32 normals[[]]
	# real32 tangents[[]]
	# real32 binormals[[]]
	# real32 blendweights[[]]
	# uint16 blendindices[[]]
	# real32 texcoords[[]]
	# real32 vtxcolors[[]]
	pass

class SMCIDXBUFFER:
	# uint08 format
	# uint08 primitive
	# uint08 direction
	# string name
	# uint32 elem
	# T data[]
	pass

class SMCSURFMAT:
	# string name
	# uint08 twoside
	# uint08 unused1
	# uint08 unused2
	# uint08 unused3
	# uint16 basemap
	# uint16 specmap
	# uint16 tranmap
	# uint16 bumpmap
	# uint16 normmap
	# uint16 lgthmap
	# uint16 envimap
	# uint16 glssmap
	# uint16 resmap1
	# uint16 resmap2
	# uint16 resmap3
	# uint16 resmap4
	# uint16 resmap5
	# uint16 resmap6
	# uint16 resmap7
	# uint16 resmap8
	# uint08 basemapchan
	# uint08 specmapchan
	# uint08 tranmapchan
	# uint08 bumpmapchan
	# uint08 normmapchan
	# uint08 lghtmapchan
	# uint08 envimapchan
	# uint08 glssmapchan
	# uint08 resmapchan1
	# uint08 resmapchan2
	# uint08 resmapchan3
	# uint08 resmapchan4
	# uint08 resmapchan5
	# uint08 resmapchan6
	# uint08 resmapchan7
	# uint08 resmapchan8
	pass

class SMCSURFACE:
	# string name
	# uint32 references
	# SMCREFERENCE reflist[]
	# uint16 surfmat
	pass

class SMCSURFREF:
	# uint32 vb_index
	# uint32 vb_start
	# uint32 vb_width
	# uint32 ib_index
	# uint32 ib_start
	# uint32 ib_width
	# uint32 jm_index
	pass

class SMCJOINT:
	# string name
	# uint32 id
	# uint32 parent
	# real32 matrix[16]
	# real32 quaternion[4]
	# real32 position[4]
	pass

class SMCJOINTNODE:
	# Blender.Mathutils.Vector p_world
	# Blender.Mathutils.Matrix m_world
	pass

class SMCSKELETON:
	# uint16 format
	# real32 tiplen (optional)
	# string name
	# uint32 n_joints
	# SMCJOINT joints[]
	pass

#
# JOINT TRAVERSAL CLASS
#
class SMCJointVisitor:

	# dictionary self.jntmap[joint_id] -> [jnt_index, [list of children joint ids]];
	# SMCSKELETON self.skeleton;
	# SMCJOINT self.joints[];
	# Blender.Armature self.armature;

	DEFAULT_JOINT_LENGTH = 0.1
	
	# CONSTRUCTOR
	def __init__(self, jntmap, skeleton, armature):
		"""
		
		"""
		
		self.jntmap = jntmap
		self.joints = skeleton.joints
		self.secmap = {}
		self.skeleton = skeleton
		self.armature = armature
	
	# PUBLIC METHOD: VISIT
	def visit(self, jntid, parentBone):
		"""
		Visits a joint node and recursively visits child joints.
		node -> The uint32 identifier of the node to visit.
		"""
		
		# joint must be present
		if not(jntid in self.jntmap):
			raise Exception('SMC Import Error: Joint identifier not found.')
		
		# tree must not be cyclic and joints cannot have multiple parents
		# each one of these conditions would cause us to process the same
		# joint more than once during depth first search
		if jntid in self.secmap:
			raise Exception('SMC Import Error: Invalid joint tree.')
		
		#
		nodedata = self.jntmap[jntid]
		jntindex = nodedata[0]
		children = nodedata[1]

		# if parent joint exists
		jntdata = self.joints[jntindex]
		jntnode = SMCJOINTNODE()
		if jntdata.parent != SMC.INVALID_JOINT:

			# secondary parent data must exist
			parent_id = jntdata.parent
			if not(parent_id in self.secmap):
				raise Exception('SMC Import Error: Invalid joint tree; secondary parent data is missing.')

			# get parent data
			pardata = self.joints[self.jntmap[parent_id][0]]
			parnode = self.secmap[parent_id]

			# get matrix (child)
			matrix = Array4x4ToBlenderMatrix(jntdata.matrix)
			matrix[0][3] = jntdata.position[0]
			matrix[1][3] = jntdata.position[1]
			matrix[2][3] = jntdata.position[2]

			# compute joint position in world coordinates
			if self.skeleton.format & SMC.JOINT_FORMAT_ABSOLUTE:
				jntnode.m_world = matrix
			else:
				jntnode.m_world = parnode.m_world * matrix

			# joint coordinate system
			x = [matrix[0][0], matrix[0][1], matrix[0][2]]
			y = [matrix[1][0], matrix[1][1], matrix[1][2]]
			z = [matrix[2][0], matrix[2][1], matrix[2][2]]
			joint_X_axis = Blender.Mathutils.Vector(NormalizeVector(x))
			joint_Y_axis = Blender.Mathutils.Vector(NormalizeVector(y))
			joint_Z_axis = Blender.Mathutils.Vector(NormalizeVector(z))
			
			# joint position
			jntnode.p_world = Blender.Mathutils.Vector([0.0, 0.0, 0.0])
			jntnode.p_world[0] = jntnode.m_world[0][3]
			jntnode.p_world[1] = jntnode.m_world[1][3]
			jntnode.p_world[2] = jntnode.m_world[2][3]

			# create blender bone
			bone = Blender.Armature.Editbone()
			bone.head = jntnode.p_world
			bone.tail = jntnode.p_world + SMCJointVisitor.DEFAULT_JOINT_LENGTH*joint_X_axis
			bone.roll = 0
			if parentBone != None: bone.parent = parentBone
			
			# assign bone to armature
			bonename = jntdata.name
			self.armature.bones[bonename] = bone

			# insert joint into secondary map
			self.secmap[jntid] = jntnode
			
			# recursively visit children
			for child in children:
				self.visit(child, bone)
				
		# parent joint does not exists
		else:

			# get joint matrix
			matrix = Array4x4ToBlenderMatrix(jntdata.matrix)
			matrix[0][3] = jntdata.position[0]
			matrix[1][3] = jntdata.position[1]
			matrix[2][3] = jntdata.position[2]
			matrix[3][3] = 1.0
			
			# set joint's orientation in world space
			jntnode.m_world = matrix

			# set joint's position in world space
			jntnode.p_world = Blender.Mathutils.Vector([0.0, 0.0, 0.0])
			jntnode.p_world[0] = jntdata.position[0]
			jntnode.p_world[1] = jntdata.position[1]
			jntnode.p_world[2] = jntdata.position[2]

			# get joint coordinate system
			x = [matrix[0][0], matrix[0][1], matrix[0][2]]
			y = [matrix[1][0], matrix[1][1], matrix[1][2]]
			z = [matrix[2][0], matrix[2][1], matrix[2][2]]
			joint_X_axis = Blender.Mathutils.Vector(NormalizeVector(x))
			joint_Y_axis = Blender.Mathutils.Vector(NormalizeVector(y))
			joint_Z_axis = Blender.Mathutils.Vector(NormalizeVector(z))
			
			# create blender bone
			bone = Blender.Armature.Editbone()
			bone.head = jntnode.p_world
			bone.tail = jntnode.p_world + SMCJointVisitor.DEFAULT_JOINT_LENGTH*joint_X_axis
			bone.roll = 0
			
			# assign bone to armature
			bonename = jntdata.name
			self.armature.bones[bonename] = bone
			
			# insert joint into secondary map
			self.secmap[jntid] = jntnode
			
			# recursively visit children
			for child in children:
				self.visit(child, bone)
			
#
# IMPORT SMC CLASS
#

class SMC:

	# CONSTANTS (VERTEX BUFFER FLAGS)
	VERTEX_POSITION = 0x01
	VERTEX_NORMAL   = 0x02
	VERTEX_TANGENT  = 0x04
	VERTEX_BINORMAL = 0x08
	VERTEX_WEIGHTS  = 0x10
	VERTEX_UV       = 0x20
	VERTEX_COLORS   = 0x40
	VERTEX_SKELETON = 0x80
	
	# CONSTANTS (INDEX BUFFER INDEX FORMAT)
	UINT08 = 0x00
	UINT16 = 0x01
	UINT32 = 0x02

	# CONSTANTS (INDEX BUFFER PRIMITIVE TYPE)
	TRIANGLES   = 0x00
	TRISTRIP    = 0x01
	TRISTRIPCUT = 0x02
	
	# CONSTANTS (INDEX BUFFER WINDING ORDER)
	CW  = 0x00
	CCW = 0x01
	
	# CONSTANTS (INVALID VALUES)
	INVALID_SURFMAT    = 0xFFFF
	INVALID_TEXTURE    = 0xFFFF
	INVALID_BLENDINDEX = 0xFFFF
	INVALID_JOINT      = 0xFFFFFFFF
	INVALID_INDEX      = 0xFFFFFFFF
	INVALID_JMAP_INDEX = 0xFFFFFFFF

	# CONSTANTS (TEXTURE MAPS)
	DIFFUSE_MAP     = 0x0001
	SPECULAR_MAP    = 0x0002
	BUMP_MAP        = 0x0004
	NORMAL_MAP      = 0x0008
	ALPHA_MAP       = 0x0010
	LIGHT_MAP       = 0x0020
	GLOSS_MAP       = 0x0040
	ENVIRONMENT_MAP = 0x0080
	INVALID_MAP     = 0x8000
	
	# CONSTANTS (SKELETONS)
	JOINT_FORMAT_ABSOLUTE   = 0x0001
	JOINT_FORMAT_RELATIVE   = 0x0002
	JOINT_FORMAT_MATRIX     = 0x0004
	JOINT_FORMAT_QUATERNION = 0x0008
	JOINT_FORMAT_TIP_LENGTH = 0x0010
	JOINT_FORMAT_X_BONEAXIS = 0x0020
	JOINT_FORMAT_Y_BONEAXIS = 0x0040
	JOINT_FORMAT_Z_BONEAXIS = 0x0080
	
class SMCImporter:

	# CONSTRUCTOR
	def __init__(self):
		
		self.flip_U = False
		self.flip_V = True

	# PRIVATE METHOD:
	def __readString(self, defaultValue):

		# validate length of default value
		if len(defaultValue) == 0:
			raise Exception('SMC Import Error: Default string values cannot be null.')
			
		# read length
		namelen = self.ifile.LE_read_uint32()
		if namelen == 0: return defaultValue
		
		# excessively long string
		if namelen > 0xFF:
			raise Exception('SMC Import Error: The maximum length for all names is 255 characters.')

		# read string
		name = self.ifile.read_string(namelen)
		if len(name) < 1: raise Exception('SMC Import Error: String values cannot be null.')
		
		# return string
		return name
		
	# PRIVATE METHOD: 
	def __processSMCHeader(self):

		# read magic number
		self.header = SMCHEADER()
		self.header.magic = self.ifile.LE_read_uint32()
		if self.header.magic != 0x20434D53: raise Exception('SMC Import Error: Invalid SMC file.')
		
		# read version
		self.header.major_version = self.ifile.LE_read_uint16()
		self.header.minor_version = self.ifile.LE_read_uint16()
		if self.header.major_version != 0x01:
			raise Exception('SMC Import Error: Script is meant for SMC files of versions 1.0 and lower.')
		if self.header.minor_version != 0x00:
			raise Exception('SMC Import Error: Script is meant for SMC files of versions 1.0 and lower.')
		
		# read number of buffers
		self.header.n_vbuffers = self.ifile.LE_read_uint32()
		self.header.n_ibuffers = self.ifile.LE_read_uint32()
		
		# read number of images
		self.header.n_images = self.ifile.LE_read_uint32()
		
		# read number of surfaces and materials
		self.header.n_surfaces = self.ifile.LE_read_uint32()
		self.header.n_surfmats = self.ifile.LE_read_uint32()
		
		# read number of skeletons
		self.header.n_skeletons = self.ifile.LE_read_uint32()
		if (self.header.n_skeletons != 0 and self.header.n_skeletons != 1):
			raise Exception('SMC Import Error: Invalid number of skeletons; this value must be 0 or 1.')

		# read number of joint maps
		self.header.n_jointmaps = self.ifile.LE_read_uint32()
			
		# read number of animations
		self.header.n_animations = self.ifile.LE_read_uint32()
		if self.header.n_animations != 0:
			raise Exception('SMC Import Error: Animations are not yet supported.')
		
		# read reserved values
		self.header.reserved1 = self.ifile.LE_read_uint32()
		self.header.reserved2 = self.ifile.LE_read_uint32()
		self.header.reserved3 = self.ifile.LE_read_uint32()
		self.header.reserved4 = self.ifile.LE_read_uint32()
		self.header.reserved5 = self.ifile.LE_read_uint32()
		self.header.reserved6 = self.ifile.LE_read_uint32()

		return True
	
	# PRIVATE METHOD: 
	def __processFileList(self):

		# construct filelist
		self.images = []
		for i in range(self.header.n_images):
			namelen = self.ifile.LE_read_uint32()
			if namelen < 0x001: raise Exception('SMC Import Error: Invalid filename.')
			if namelen > 0x400: raise Exception('SMC Import Error: Invalid filename.')
			namedat = self.ifile.read_string(namelen)
			self.images.append(namedat)
		
		return True

	# PRIVATE METHOD: 
	def __processVBuffers(self):

		# initialize vertex buffer list
		self.vblist = []
		
		# for each vertex buffer
		for i in range(self.header.n_vbuffers):
			
			# read vertex buffer flags
			vb = SMCVTXBUFFER()
			vb.flags = self.ifile.LE_read_uint32()
			
			# read vertex buffer name
			len = self.ifile.LE_read_uint32()
			if (len < 0x01) or (len > 0xFF): raise Exception('SMC Import Error: Invalid vertex buffer name.')
			vb.name = self.ifile.read_string(len)
		
			# read number and type of texture channels
			vb.uvchan = self.ifile.LE_read_uint16()
			if vb.uvchan > 8: raise Exception('SMC Import Error: Invalid number of UV channels.')
			vb.uvtype = self.ifile.LE_read_uint16_array(8)

			# read number of color channels
			vb.colors = self.ifile.LE_read_uint16()
			if vb.colors > 8: raise Exception('SMC Import Error: Invalid number of color channels.')

			# read number of vertices
			vb.elem = self.ifile.LE_read_uint32()
			if vb.elem == 0: raise Exception('SMC Import Error: Vertex buffers must have at least one vertex.')

			# initialize vertex data
			vb.positions = []
			vb.normals = []
			vb.tangents = []
			vb.binormals = []
			vb.blendweights = []
			vb.blendindices = []
			vb.texcoords = [[], [], [], [], [], [], [], []]
			vb.vtxcolors = [[], [], [], [], [], [], [], []]

			# read vertices
			vb.data = []
			for j in range(vb.elem):

				# read position
				if (vb.flags & SMC.VERTEX_POSITION):
					vb.positions.append(self.ifile.LE_read_real32_array(3))

				# read normal
				if (vb.flags & SMC.VERTEX_NORMAL):
					vb.normals.append(self.ifile.LE_read_real32_array(3))

				# read tangent
				if (vb.flags & SMC.VERTEX_TANGENT):
					vb.tangents.append(self.ifile.LE_read_real32_array(3))

				# read binormal
				if (vb.flags & SMC.VERTEX_BINORMAL):
					vb.binormals.append(self.ifile.LE_read_real32_array(3))

				# read blend weights
				if (vb.flags & SMC.VERTEX_WEIGHTS):
					vb.blendweights.append(self.ifile.LE_read_real32_array(8))
					vb.blendindices.append(self.ifile.LE_read_uint16_array(8))

				# read texture coordinates
				if (vb.flags & SMC.VERTEX_UV):
					for k in range(vb.uvchan):
						tmp = self.ifile.LE_read_real32_array(2)
						if self.flip_U: tmp[0] = 1.0 - tmp[0]
						if self.flip_V: tmp[1] = 1.0 - tmp[1]
						vb.texcoords[k].append(tmp)

				# read vertex colors
				if vb.colors > 0:
					for k in range(vb.colors):
						tmp = self.ifile.LE_read_real32_array(4)
						vb.vtxcolors[k].append(tmp)

			# append vertex buffer
			self.vblist.append(vb)

		return True

	# PRIVATE METHOD: 
	def __processIBuffers(self):

		# initialize index buffer list
		self.iblist = []

		# for each index buffer
		for i in range(self.header.n_ibuffers):

			# read index buffer format
			ib = SMCIDXBUFFER()
			ib.format = self.ifile.LE_read_uint08()
			if (ib.format < SMC.UINT08) or (ib.format > SMC.UINT32):
				raise Exception('SMC Import Error: Invalid index buffer format.')

			# read index buffer primitive type
			ib.primitive = self.ifile.LE_read_uint08()
			if (ib.primitive < SMC.TRIANGLES) or (ib.primitive > SMC.TRISTRIPCUT):
				raise Exception('SMC Import Error: Invalid index buffer primitive type.')

			# read winding order
			ib.direction = self.ifile.LE_read_uint08()
			if (ib.direction != SMC.CW) and (ib.direction != SMC.CCW):
				ss = 'SMC Import Error: Invalid winding order ' + str(ib.direction) + ' at ' + str(self.ifile.tell()) + '.'
				raise Exception(ss)

			# read reserved byte
			ib.reserved = self.ifile.LE_read_uint08()
			if ib.reserved != 0: ib.reserved = 0				
				
			# read index buffer name
			len = self.ifile.LE_read_uint32()
			if (len < 0x01) or (len > 0xFF): raise Exception('SMC Import Error: Invalid index buffer name.')
			ib.name = self.ifile.read_string(len)

			# read number of indices
			ib.elem = self.ifile.LE_read_uint32()
			ib.data = []

			# read indices
			if ib.elem > 0:
				if ib.format == SMC.UINT08:
					ib.data = self.ifile.LE_read_uint08_array(ib.elem)
				elif ib.format == SMC.UINT16:
					ib.data = self.ifile.LE_read_uint16_array(ib.elem)
				elif ib.format == SMC.UINT32:
					ib.data = self.ifile.LE_read_uint32_array(ib.elem)

			# read extra byte for odd number of AMC_UINT08 indices
			if (ib.format == SMC.UINT08) and (ib.elem % 2 == 1):
				self.ifile.skip(1)			
			
			# append index buffer
			self.iblist.append(ib)

		return True

	# PRIVATE METHOD: 
	def __processSurfmats(self):

		# initialize material list
		self.surfmats = []
		
		# for each material
		for i in range(self.header.n_surfmats):

			# read material name
			sm = SMCSURFMAT()
			len = self.ifile.LE_read_uint32()
			if (len < 0x01) or (len > 0xFF): raise Exception('SMC Import Error: Invalid material name.')
			sm.name = self.ifile.read_string(len)

			# read material properties
			sm.twoside = self.ifile.LE_read_uint08()
			sm.unused1 = self.ifile.LE_read_uint08()
			sm.unused2 = self.ifile.LE_read_uint08()
			sm.unused3 = self.ifile.LE_read_uint08()
			
			# read texture references
			sm.basemap = self.ifile.LE_read_uint16()
			sm.specmap = self.ifile.LE_read_uint16()
			sm.tranmap = self.ifile.LE_read_uint16()
			sm.bumpmap = self.ifile.LE_read_uint16()
			sm.normmap = self.ifile.LE_read_uint16()
			sm.lgthmap = self.ifile.LE_read_uint16()
			sm.envimap = self.ifile.LE_read_uint16()
			sm.glssmap = self.ifile.LE_read_uint16()
			sm.resmap1 = self.ifile.LE_read_uint16()
			sm.resmap2 = self.ifile.LE_read_uint16()
			sm.resmap3 = self.ifile.LE_read_uint16()
			sm.resmap4 = self.ifile.LE_read_uint16()
			sm.resmap5 = self.ifile.LE_read_uint16()
			sm.resmap6 = self.ifile.LE_read_uint16()
			sm.resmap7 = self.ifile.LE_read_uint16()
			sm.resmap8 = self.ifile.LE_read_uint16()
			
			# read texture channel references
			sm.basemapchan = self.ifile.LE_read_uint08()
			sm.specmapchan = self.ifile.LE_read_uint08()
			sm.tranmapchan = self.ifile.LE_read_uint08()
			sm.bumpmapchan = self.ifile.LE_read_uint08()
			sm.normmapchan = self.ifile.LE_read_uint08()
			sm.lghtmapchan = self.ifile.LE_read_uint08()
			sm.envimapchan = self.ifile.LE_read_uint08()
			sm.glssmapchan = self.ifile.LE_read_uint08()
			sm.resmapchan1 = self.ifile.LE_read_uint08()
			sm.resmapchan2 = self.ifile.LE_read_uint08()
			sm.resmapchan3 = self.ifile.LE_read_uint08()
			sm.resmapchan4 = self.ifile.LE_read_uint08()
			sm.resmapchan5 = self.ifile.LE_read_uint08()
			sm.resmapchan6 = self.ifile.LE_read_uint08()
			sm.resmapchan7 = self.ifile.LE_read_uint08()
			sm.resmapchan8 = self.ifile.LE_read_uint08()
	
			# append material
			self.surfmats.append(sm)
			
		return True
	
	# PRIVATE METHOD: 
	def __processSurfaces(self):

		# initialize surface list
		self.surfaces = []
		
		# for each surface
		for i in range(self.header.n_surfaces):

			# read surface name
			surf = SMCSURFACE()
			tmp = self.ifile.LE_read_uint32()
			if (tmp < 0x01) or (tmp > 0xFF): raise Exception('SMC Import Error: Invalid surface name.')
			surf.name = self.ifile.read_string(tmp)
			if len(surf.name) < 1: raise Exception('SMC Import Error: Invalid surface name.')

			# read number of buffer references
			surf.references = self.ifile.LE_read_uint32()
			if surf.references < 1: raise Exception('SMC Import Error: A surface must have at least one buffer reference.')
						
			# read references
			surf.reflist = []
			for j in range(surf.references):
			
				# read vertex buffer index
				ref = SMCSURFREF()
				ref.vb_index = self.ifile.LE_read_uint32()
				if ref.vb_index < 0:
					raise Exception('SMC Import Error: Invalid vertex buffer reference.')
				if not(ref.vb_index < self.header.n_vbuffers):
					raise Exception('SMC Import Error: Invalid vertex buffer reference.')
				
				# read vertex buffer start index
				ref.vb_start = self.ifile.LE_read_uint32()
				if ref.vb_start < 0:
					raise Exception('SMC Import Error: Invalid vertex buffer start index.')
				if not(ref.vb_start < self.vblist[ref.vb_index].elem):
					raise Exception('SMC Import Error: Invalid vertex buffer start index.')
				
				# read vertex buffer width
				ref.vb_width = self.ifile.LE_read_uint32()
				if ref.vb_width < 1:
					raise Exception('SMC Import Error: Invalid vertex buffer surface width.')
				if self.vblist[ref.vb_index].elem < ref.vb_width:
					raise Exception('SMC Import Error: Invalid vertex buffer surface width.')
				if self.vblist[ref.vb_index].elem < (ref.vb_start + ref.vb_width):
					raise Exception('SMC Import Error: Invalid vertex buffer surface width.')

				# read index buffer index, start index, and width
				ref.ib_index = self.ifile.LE_read_uint32()
				ref.ib_start = self.ifile.LE_read_uint32()
				ref.ib_width = self.ifile.LE_read_uint32()
				
				# read joint map index (0xFFFFFFFF if not using a joint map)
				ref.jm_index = self.ifile.LE_read_uint32()

				# validate
				# note that if the index is invalid, this surface only contains points!
				if ref.ib_index != SMC.INVALID_INDEX:
				
					# validate index buffer index
					if ref.ib_index < 0:
						raise Exception('SMC Import Error: Invalid index buffer reference.')
					if not(ref.ib_index < self.header.n_ibuffers):
						raise Exception('SMC Import Error: Invalid index buffer reference.')
				
					# validate index buffer start index
					if ref.ib_start < 0:
						raise Exception('SMC Import Error: Invalid index buffer start index.')
					if not(ref.ib_start < self.iblist[ref.ib_index].elem):
						raise Exception('SMC Import Error: Invalid index buffer start index.')
				
					# validate index buffer width
					if ref.ib_width < 1:
						raise Exception('SMC Import Error: Invalid index buffer surface width.')
					if self.iblist[ref.ib_index].elem < ref.ib_width:
						raise Exception('SMC Import Error: Invalid index buffer surface width.')
					if self.iblist[ref.ib_index].elem < (ref.ib_start + ref.ib_width):
						raise Exception('SMC Import Error: Invalid index buffer surface width.')
					
				# append reference
				surf.reflist.append(ref)
			
			# read surface material
			surf.surfmat = self.ifile.LE_read_uint16()
			if surf.surfmat != SMC.INVALID_SURFMAT:
				if surf.surfmat < 0: raise Exception('SMC Import Error: Invalid material reference.')
				if not(surf.surfmat < self.header.n_surfmats): raise Exception('SMC Import Error: Invalid material reference.')

			# append surface
			self.surfaces.append(surf)

		return True

	# PRIVATE METHOD: 
	def __processSkeleton(self):

		# initialize surface list
		self.skeletons = []
		
		# for each skeleton
		for i in range(self.header.n_skeletons):

			# read format
			skel = SMCSKELETON()
			skel.format = self.ifile.LE_read_uint16()

			# validate format
			if skel.format & SMC.JOINT_FORMAT_ABSOLUTE:
				if skel.format & SMC.JOINT_FORMAT_RELATIVE:
					raise Exception('SMC Import Error: Skeletons cannot be absolute and relative.')
			elif skel.format & SMC.JOINT_FORMAT_RELATIVE:
				if skel.format & SMC.JOINT_FORMAT_ABSOLUTE:
					raise Exception('SMC Import Error: Skeletons cannot be absolute and relative.')
			else:
				raise Exception('SMC Import Error: Skeletons must be absolute or relative.')
			
			# validate format
			if skel.format & SMC.JOINT_FORMAT_MATRIX:
				if skel.format & SMC.JOINT_FORMAT_QUATERNION:
					raise Exception('SMC Import Error: Skeletons cannot use matrices and quaternions.')
			elif skel.format & SMC.JOINT_FORMAT_QUATERNION:
				if skel.format & SMC.JOINT_FORMAT_MATRIX:
					raise Exception('SMC Import Error: Skeletons cannot use matrices and quaternions.')
			else:
				raise Exception('SMC Import Error: Skeletons must use matrices or quaternions.')
			
			# validate format
			if skel.format & SMC.JOINT_FORMAT_X_BONEAXIS:
				if (skel.format & SMC.JOINT_FORMAT_Y_BONEAXIS) or (skel.format & SMC.JOINT_FORMAT_Z_BONEAXIS):
					raise Exception('SMC Import Error: Skeletons cannot use more than one bone axis.')
			elif skel.format & SMC.JOINT_FORMAT_Y_BONEAXIS:
				if (skel.format & SMC.JOINT_FORMAT_X_BONEAXIS) or (skel.format & SMC.JOINT_FORMAT_Z_BONEAXIS):
					raise Exception('SMC Import Error: Skeletons cannot use more than one bone axis.')
			elif skel.format & SMC.JOINT_FORMAT_Z_BONEAXIS:
				if (skel.format & SMC.JOINT_FORMAT_X_BONEAXIS) or (skel.format & SMC.JOINT_FORMAT_Y_BONEAXIS):
					raise Exception('SMC Import Error: Skeletons cannot use more than one bone axis.')
			else:
				raise Exception('SMC Import Error: Skeletons must define a bone axis.')

			# read tip length
			if skel.format & SMC.JOINT_FORMAT_TIP_LENGTH:
				skel.tiplen = self.ifile.LE_read_real32()
				if skel.tiplen < 0.0:
					raise Exception('SMC Import Error: Skeleton tip lengths must be positive.')
				elif skel.tiplen > 1024.0:
					raise Exception('SMC Import Error: Skeleton tip lengths must be shorter than 1024.')
			
			# read name
			skel.name = self.__readString('default')

			# read joints
			skel.n_joints = self.ifile.LE_read_uint32()
			skel.joints = []
			for j in range(skel.n_joints):

				# read name
				jnt = SMCJOINT()
				defaultValue = 'jnt_' + str(j)
				jnt.name = self.__readString(defaultValue)

				# read id
				jnt.id = self.ifile.LE_read_uint32()
				if jnt.id == SMC.INVALID_JOINT:
					raise Exception('SMC Import Error: Invalid joint identifier.')

				# read parent
				jnt.parent = self.ifile.LE_read_uint32()

				# read matrix or quaternion
				if skel.format & SMC.JOINT_FORMAT_MATRIX:
					jnt.matrix = self.ifile.LE_read_real32_array(16)
				elif skel.format & SMC.JOINT_FORMAT_QUATERNION:
					jnt.quaternion = self.ifile.LE_read_real32_array(4)

				# read position
				jnt.position = self.ifile.LE_read_real32_array(4)
				
				# insert joint
				skel.joints.append(jnt)

			# insert skeleton
			self.skeletons.append(skel)
			
		return True

	# PRIVATE METHOD:
	def __processJointMap(self):

		# initialize surface list
		self.jntmaplist = []
		
		# for each joint map
		for i in range(self.header.n_jointmaps):
			n_items = self.ifile.LE_read_uint32()
			items = []
			for j in range(n_items):
				temp = self.ifile.LE_read_uint32()
				items.append(temp)
			self.jntmaplist.append(items)
	
	# PRIVATE METHOD: 
	def __processAnimList(self):
		
		return True

	# PRIVATE METHOD:
	def __buildVBuffer(self, surf):

		# for each reference
		for index, ref in enumerate(surf.reflist):
		
			# create name for mesh object
			meshname = surf.name
			meshname += '_ref_'
			meshname += str(index)

			# create Blender mesh for this vertex buffer
			mesh = Blender.Mesh.New()
			mesh.name = meshname

			# vertex and index buffers
			vb = self.vblist[ref.vb_index]
			ib = self.iblist[ref.ib_index]
			
			# positions
			verts = []
			for dst_index in range(ref.vb_width):
				src_index = ref.vb_start + dst_index
				verts.append(vb.positions[src_index])
			mesh.verts.extend(verts)

			# normals
			if vb.flags & SMC.VERTEX_NORMAL:
				for vertex in mesh.verts:
					nx = vb.normals[ref.vb_start + vertex.index][0]
					ny = vb.normals[ref.vb_start + vertex.index][1]
					nz = vb.normals[ref.vb_start + vertex.index][2]
					vertex.no = Blender.Mathutils.Vector(nx, ny, nz)
			
			# texture coordinates
			uvmap = [[], [], [], [], [], [], [], []]
			if vb.flags & SMC.VERTEX_UV:

				# for each UV channel
				for i in range(vb.uvchan):
				
					# copy UVs if UV map channel exists
					if vb.uvtype[i] != SMC.INVALID_MAP:

						# copy UVs
						for dst_index in range(ref.vb_width):
							src_index = ref.vb_start + dst_index
							uvmap[i].append(vb.texcoords[i][src_index])

			# faces
			faces = []
			if ref.ib_index != SMC.INVALID_INDEX:

				# from triangle list
				if ib.primitive == SMC.TRIANGLES:
				
					# must have triangles
					if ref.ib_width < 3:
						raise Exception('SMC Import Error: The number of triangle indices must be greater than three.')
					if ref.ib_width % 3:
						raise Exception('SMC Import Error: The number of triangle indices must be divisible by three.')
					
					# build triangles
					for i in range((ref.ib_width / 3)):
					
						# triangle indices
						src_index = ref.ib_start + i * 3
						a = ib.data[src_index + 0]
						b = ib.data[src_index + 1]
						c = ib.data[src_index + 2]
						if not(a < ref.vb_width): raise Exception('SMC Import Error: Triangle index out of bounds.')
						if not(b < ref.vb_width): raise Exception('SMC Import Error: Triangle index out of bounds.')
						if not(c < ref.vb_width): raise Exception('SMC Import Error: Triangle index out of bounds.')
						
						# insert triangle
						if (a != b) and (a != c) and (b != c):
							if ib.direction == SMC.CW:
								faces.append([a, b, c])
							else:
								faces.append([a, c, b])
					
				elif ib.primitive == SMC.TRISTRIP:
	
					# first triangle
					a = ib.data[ref.ib_start + 0]
					b = ib.data[ref.ib_start + 1]
					c = ib.data[ref.ib_start + 2]
					if not(a < ref.vb_width): raise Exception('SMC Import Error: Triangle index out of bounds.')
					if not(b < ref.vb_width): raise Exception('SMC Import Error: Triangle index out of bounds.')
					if not(c < ref.vb_width): raise Exception('SMC Import Error: Triangle index out of bounds.')
					
					# insert first triangle
					if (a != b) and (a != c) and (b != c):
						if ib.direction == SMC.CW:
							faces.append([a, b, c])
						else:
							faces.append([a, c, b])
					
					# reamaining triangles
					for i in range(3, ref.ib_width):
					
						# triangle indices
						a = b
						b = c
						c = ib.data[ref.ib_start + i]
						if not(c < ref.vb_width): raise Exception('SMC Import Error: Triangle index out of bounds.')
						
						# insert triangle
						if (a != b) and (a != c) and (b != c):
							if i % 2:
								if ib.direction == SMC.CW:
									faces.append([a, c, b])
								else:
									faces.append([a, b, c])
							else:
								if ib.direction == SMC.CW:
									faces.append([a, b, c])
								else:
									faces.append([a, c, b])
						
				elif ib.primitive == SMC.TRISTRIPCUT:

					# maintain a tristrip index
					tristrip_data = []
					tristrip_curr = []
					
					# value of restart index depends on the data type
					strip_index = 0xFFFFFFFF
					if ib.format == SMC.UINT08:
						strip_index = 0xFF
					elif ib.format == SMC.UINT16:
						strip_index = 0xFFFF
					elif ib.format == SMC.UINT32:
						strip_index = 0xFFFFFFFF
						
					# create a list of triangle strips
					for i in range(ref.ib_width):
					
						temp = ib.data[ref.ib_start + i]
						if temp == strip_index:
							if len(tristrip_curr) < 3: raise Exception('SMC Import Error: Not enough indices for a triangle strip.')
							tristrip_data.append(tristrip_curr)
							tristrip_curr = []
						else:
							tristrip_curr.append(temp)
							if ((i + 1) == ref.ib_width): # last index is not an strip index
								tristrip_data.append(tristrip_curr)
								tristrip_curr = []								

					# process triangle strips
					for tristrip in tristrip_data:

						# first triangle
						a = tristrip[0]
						b = tristrip[1]
						c = tristrip[2]
						if not(a < ref.vb_width): raise Exception('SMC Import Error: Triangle index out of bounds.')
						if not(b < ref.vb_width): raise Exception('SMC Import Error: Triangle index out of bounds.')
						if not(c < ref.vb_width): raise Exception('SMC Import Error: Triangle index out of bounds.')
						if (a != b) and (a != c) and (b != c):
							if ib.direction == SMC.CW: faces.append([a, b, c])
							else: faces.append([a, c, b])
						
						# remaining triangles
						for i in range(3, len(tristrip)):
							a = b
							b = c
							c = tristrip[i]
							if not(c < ref.vb_width): raise Exception('SMC Import Error: Triangle index out of bounds.')
							if (a != b) and (a != c) and (b != c):
								if i % 2:
									if ib.direction == SMC.CW: faces.append([a, c, b])
									else: faces.append([a, b, c])
								else:
									if ib.direction == SMC.CW: faces.append([a, b, c])
									else: faces.append([a, c, b])

				# construct blender mesh faces
				mesh.faces.extend(faces)
				
				# smooth vertex normals
				if vb.flags & SMC.VERTEX_NORMAL:
					for face in mesh.faces:
						face.smooth = 1

				# assign texture coordinates
				if vb.flags & SMC.VERTEX_UV:
	
					# for each UV channel
					for i in range(8):

						# if UV channel exists
						if vb.uvtype[i] != SMC.INVALID_MAP:
						
							# create UV layer
							mapname = 'uvmap_channel_' + str(i)
							mesh.addUVLayer(mapname)
							mesh.activeUVLayer = mapname
					
							# assign texture coordinates to faces
							# WARNING: THIS IS OLD CODE THAT CAUSED ROTATED UV COORDINATES!!!
							#for face in mesh.faces:
							#	a = faces[face.index][0]
							#	b = faces[face.index][1]
							#	c = faces[face.index][2]
							#	uv_a = Mathutils.Vector(uvmap[i][a][0], uvmap[i][a][1])
							#	uv_b = Mathutils.Vector(uvmap[i][b][0], uvmap[i][b][1])
							#	uv_c = Mathutils.Vector(uvmap[i][c][0], uvmap[i][c][1])
							#	face.uv = [ uv_a, uv_b, uv_c ]

							# set sticky per-vertex UV coordinates
							mesh.vertexUV = True
							for j in range(len(mesh.verts)):
								mesh.verts[j].uvco = Blender.Mathutils.Vector(uvmap[i][j])
							mesh.update()
							
							# set face UV coordinates
							# NOTE: copies UV coordinates from sticky coordinates
							mesh.faceUV = True;
							for face in mesh.faces:
								face.uv = [v.uvco for v in face.verts]
								face.smooth = 1
						
							# update mesh						
							mesh.update()

				# assign colors
				# NOTE: You do not need to check AMC_VERTEX_COLORS flag to determine if there are
				# vertex colors present.
				if vb.colors > 0:

					# enable vertex colors
					mesh.vertexColors = True

					# for each color channel
					for i in range(vb.colors):
					
						# create color layer and make active
						mapname = 'color_channel_' + str(i)
						mesh.addColorLayer(mapname)
						mesh.activeColorLayer = mapname

						# create an array of colors
						colorarr = [None] * len(mesh.verts)
						for j in range(len(mesh.verts)):
							colorarr[j] = [int(vb.vtxcolors[i][j][0] * 255.0),
							               int(vb.vtxcolors[i][j][1] * 255.0),
							               int(vb.vtxcolors[i][j][2] * 255.0),
							               int(vb.vtxcolors[i][j][3] * 255.0)]
						
						# set face color coordinates
						# NOTE: This is very interesting how it works!
						# http://www.blender.org/documentation/249PythonDoc/Mesh.MFace-class.html#col
						for face in mesh.faces:
							for j, v in enumerate(face):
								face.col[j].r = colorarr[v.index][0]
								face.col[j].g = colorarr[v.index][1]
								face.col[j].b = colorarr[v.index][2]
								face.col[j].a = colorarr[v.index][3]
						
						# update mesh
						mesh.update()
				
			# update Blender mesh object
			mesh.update()
        
			# add mesh to current scene and make mesh active
			scene = Blender.Scene.GetCurrent()
			meshobject = scene.objects.new(mesh, meshname)
			scene.objects.active = meshobject
			self.meshlist.append(meshobject)

			# assign material to mesh object
			if surf.surfmat != SMC.INVALID_SURFMAT:
				meshobject.colbits = 1
				meshobject.setMaterials([self.materials[surf.surfmat]])
				mesh.update()
		
			# blend weights
			# NOTE: blend weights must be added after a mesh has been associated with a mesh object!
			bidict = {}
			if vb.flags & SMC.VERTEX_WEIGHTS:
			
				# for each set of blendweights and blendindices
				for dst_index in range(ref.vb_width):

					src_index = ref.vb_start + dst_index
					for i in range(8):
					
						# get blend weight and blend index
						bw = vb.blendweights[src_index][i]
						bi = vb.blendindices[src_index][i]

						# if blend index is valid
						if bi != SMC.INVALID_BLENDINDEX:
						
							# if this surface reference uses a joint map
							# remap the blend index using the map
							if ref.jm_index != SMC.INVALID_JMAP_INDEX:
							
								if not(ref.jm_index < len(self.jntmaplist)):
									raise Exception('SMC Import Error: Joint map index out of range.')
									
								# given a blend index, look it up in an array to get new blend index
								if not(bi < len(self.jntmaplist[ref.jm_index])):
									error = 'SMC Import Error: Joint map blending index '
									error = error + str(bi)
									error = error + ' out of range. The joint map only has '
									error = error + str(len(self.jntmaplist[ref.jm_index]))
									error = error + ' elements.'
									raise Exception(error)

								# remap blend index
								bi = self.jntmaplist[ref.jm_index][bi]

							# associate blend weight with blend index
							# 2014-04-11: DO NOT INSERT <BI, <index, BW>> PAIR IF BW = 0
							if not (bi in bidict): bidict[bi] = []
							#bidict[bi].append([dst_index, bw])
							if bw > 0.0: bidict[bi].append([dst_index, bw])

				# for each vertex weight map
				for dk, dv in bidict.iteritems():
				
					# create vertex group and assign weights
					mapname = 'jnt_' + ('%(value)03d' % {'value':dk}) # + str(dk)
					mesh.addVertGroup(mapname)
					for item in dv:
						try:
							mesh.assignVertsToGroup(mapname, [item[0]], item[1], Blender.Mesh.AssignModes.ADD)#REPLACE)
						except Exception as e:
							print('item = ' + str(item[0]) + ', ' + str(item[1]))
							print('vertices = ' + str(vb.elem) + ', ' + str(len(mesh.verts)))
							raise e
						
	# PRIVATE METHOD:
	def __buildMeshTextures(self):

		# construct textures from filelist using relative directories
		# this is why we preface filename with '//'
		# see http://www.blender.org/documentation/blender_python_api_2_59_0/bpy.path.html
		self.texlist = []
		for index, filename in enumerate(self.images):
			try:
				# create texture name based on index
				name = 'texture_' + str(index)
				# create texture
				temp = Blender.Image.Load('//' + filename)
				texture = Blender.Texture.New(name)
				texture.setImage(temp)
				# append data
				self.texlist.append(texture)
			except Exception as e:
				self.texlist.append(None)
				print('Failed to load texture file ' + filename + '.')
	
	def __buildMeshMaterials(self):

		def isTextureValid(textureIndex):
			if textureIndex == SMC.INVALID_TEXTURE: return False
			if not (textureIndex < len(self.texlist)): return False
			if self.texlist[textureIndex] is None: return False
			return True
		
		# for each surface material
		self.materials = []
		for surfmat in self.surfmats:

			# create new material
			# set material to shadeless so we can see texture using Blender GLSL Materials 
			mat = Blender.Material.New(surfmat.name)
			mat.setMode(Blender.Material.Modes['SHADELESS'])

			# each material supports up to ten texture layters
			index = 0
			if isTextureValid(surfmat.basemap):
				mat.setTexture(index, self.texlist[surfmat.basemap], Texture.TexCo.UV, Texture.MapTo.COL)
				mattex = mat.getTextures()
				mattex[index].uvlayer = 'uvmap_channel_' + str(surfmat.basemapchan)
				index = index + 1
			if isTextureValid(surfmat.specmap):
				mat.setTexture(index, self.texlist[surfmat.specmap], Texture.TexCo.UV, Texture.MapTo.SPEC)
				mattex = mat.getTextures()
				mattex[index].uvlayer = 'uvmap_channel_' + str(surfmat.specmapchan)
				index = index + 1
			if isTextureValid(surfmat.normmap):
				mat.setTexture(index, self.texlist[surfmat.normmap], Texture.TexCo.UV, Texture.MapTo.NOR)
				mattex = mat.getTextures()
				mattex[index].uvlayer = 'uvmap_channel_' + str(surfmat.normmapchan)
				index = index + 1

			# append material
			self.materials.append(mat)
		
	# PRIVATE METHOD:
	def __buildMeshObjects(self):

		# save blender mesh objects
		self.meshlist = []
		
		# for each surface
		for surf in self.surfaces:
		
			self.__buildVBuffer(surf)

		return True
	
	def __buildArmatureObjects(self):

		# for each skeleton
		for skel in self.skeletons:

			# keep track of all root nodes
			root_nodes = []
			for jnt in skel.joints:
				if jnt.parent == SMC.INVALID_JOINT:
					root_nodes.append(jnt.id)
					
			# must have at least one root node
			if len(root_nodes) == 0:
				raise Exception('SMC Import Error: A skeleton was defined with no root node.')
				
			# now that we know we have good root nodes, let's create a diction-
			# ary where given a joint ID we can index its data and find out how
			# many children joint IDs it has
			jntmap = {}
			for (jnt_index, jnt) in enumerate(skel.joints):
			
				# joint identifier must be unique
				if jnt.id in jntmap:
					raise Exception('SMC Import Error: Joint identifiers must be unique.')

				# insert joint to map with empty child list
				jntmap[jnt.id] = [jnt_index, []]
			
			# insert joints into their parent's list of children
			for jnt in skel.joints:
				if jnt.parent != SMC.INVALID_JOINT:
					if not(jnt.parent in jntmap): raise Exception('SMC Import Error: Invalid parent joint identifier.')
					jntmap[jnt.parent][1].append(jnt.id)
					
			# create blender armature
			armature = Blender.Armature.New(skel.name)
			armature.drawType = Blender.Armature.STICK
			armature.envelopes = False
			armature.vertexGroups = False

			# create blender armature object
			scene = Blender.Scene.GetCurrent()
			armatureObjectName = skel.name + '_object'
			armatureObject = scene.objects.new(armature, armatureObjectName)

			# set armature object to use vertex groups (not envelopes)
			for meshobj in self.meshlist:
				mod = meshobj.modifiers.append(Modifier.Types.ARMATURE)
				mod[Blender.Modifier.Settings.OBJECT] = armatureObject
				mod[Blender.Modifier.Settings.ENVELOPES] = False
				mod[Blender.Modifier.Settings.VGROUPS] = True
	
			# make armature editable before adding bones
			armature.makeEditable()

			# traverse joints to add bones
			jvisitor = SMCJointVisitor(jntmap, skel, armature)
			for root in root_nodes: jvisitor.visit(root, None)

			# update armature
			armature.update()
			
			# parent mesh objects to armature so it can modify the meshes
			armatureObject.makeParent(self.meshlist)
			
		return True

	# PUBLIC METHOD: IMPORT
	def run(self, filename):

		# open file
		self.ifile = InputFileStream()
		self.ifile.open(filename)
		
		# file processing
		if self.__processSMCHeader() == False: return False
		if self.__processFileList() == False: return False
		if self.__processVBuffers() == False: return False
		if self.__processIBuffers() == False: return False
		if self.__processSurfmats() == False: return False
		if self.__processSurfaces() == False: return False
		if self.__processSkeleton() == False: return False
		if self.__processJointMap() == False: return False
		if self.__processAnimList() == False: return False

		# model construction
		self.__buildMeshTextures()
		self.__buildMeshMaterials()
		self.__buildMeshObjects()
		self.__buildArmatureObjects()
		
		# success
		print "SMC import successful."
		Window.RedrawAll()
		return True

#
# IMPORT SMC
#

def importSMC(filename):

	obj = SMCImporter()
	obj.run(filename)

#
# CONTROL EXECUTION
# main() is called if and only if module is not imported
#

def main():

	Blender.Window.FileSelector(importSMC, "Import SMC", '*.smc')

if __name__ == '__main__':
	
    main()