# This importer is designed to be flexible with the use of regular expressions.
# Enabling this importer to import a wider range of XSI formats might just require
# changing a few of the regular expressions.

# Here are the known limitations of this importer so far:
# 	All frames should have a unique name. (In order to sync envelopes and animations with objects properly.) (Currently not supported, though.)
# 	The parser can not read XSI files that do not have the opening brace on the same line as the section. Works on all stock XSI's I've tested on.

# Note: Blender objects are name sorted, so the order of objects in your import may differ,
# but they will retain their parent-child hierarchy.

bl_info = {
	"name": "BZ2 XSI Model Format (.xsi)",
	"author": "The Vearidons (Stroket@hotmail.com)",
	"version": (1, 0, 2),
	"blender:": (2, 67, 0),
	"location": "File > Import > BZ2XSI (.xsi)",
	"description": "Import a BZ2 XSI model.",
	"warning": "Accept Jesus Christ as your personal saviour or burn in hell forever.",
	"wiki_url": "http://www.meatspin.cc/",
	"tracker_url": "http://www.bz2maps.us/phpBB/viewtopic.php?f=50&t=4395/",
	"category": "Import"}

# os.path texture filename stuff.
# time for Import Time info.
import os.path, re, time

try:
	# Executed if this is being ran by blender.
	import bpy
	from mathutils import Matrix, Vector, Color
	from bpy.props import StringProperty, BoolProperty
	from bpy_extras.io_utils import axis_conversion
	InBlender = True
except:
	# Executed if being ran by python alone.
	# This is primarily for debugging parser data.
	print("Doing non-blender test run.")
	InBlender = False

# Prefixes, postfixes, and other.
RePrefix = r"(?i)\s*"
ReSectionPrefix = RePrefix + r"(?:SI_)?"
ReNameChars = r"[\w\.]"
ReOpenBrace = r"\s*{\s*[\r\n]+"
ReCloseBrace = r"\s*}"
ReFloatOrInt = r"[-+]?\b[0-9e\-\+]*\.?[0-9e\-\+]+\b"
ReFloat = r"[-+]?\b[0-9e\-\+]*\.[0-9e\-\+]+\b"
ReInt = r"\b\d+\b"

# Float/Int
ReM_FloatOrInt = re.compile(ReFloatOrInt)
ReM_Float = re.compile(ReFloat)
ReM_Int = re.compile(ReInt)

# Other
ReM_StrContent = re.compile(r"\"[^\"\r\n]+\"")
ReM_FileOnly = re.compile(r"[^\\/:*?\"<>|\r\n]+$")

# Header
ReM_SI_CoordinateSystem = re.compile(ReSectionPrefix + r"CoordinateSystem\s+(%s*)%s" % (ReNameChars, ReOpenBrace))
ReM_SI_Angle = re.compile(ReSectionPrefix + r"Angle%s" % ReOpenBrace)
ReM_SI_Camera = re.compile(ReSectionPrefix + r"Camera(?:\s+(%s*))?%s" % (ReNameChars, ReOpenBrace))
ReM_SI_Ambience = re.compile(ReSectionPrefix + r"Ambience%s" % ReOpenBrace)
ReM_SI_Light = re.compile(ReSectionPrefix + r"Light(?:\s+(%s*))?%s" % (ReNameChars, ReOpenBrace))
ReM_SI_Fog = re.compile(ReSectionPrefix + r"Fog%s" % ReOpenBrace)

# Frame
ReM_Frame = re.compile(ReSectionPrefix + r"Frame\s+(?:frm-)*(%s+)%s" % (ReNameChars, ReOpenBrace))
ReM_FrameTransformMatrix = re.compile(ReSectionPrefix + r"FrameTransformMatrix" + ReOpenBrace)
ReM_SI_FrameBasePoseMatrix = re.compile(ReSectionPrefix + r"FrameBasePoseMatrix" + ReOpenBrace)
ReM_Mesh = re.compile(ReSectionPrefix + r"Mesh(?:\s+(%s*))?%s" % (ReNameChars, ReOpenBrace))

# Frame > Mesh
ReM_MeshMaterialList = re.compile(ReSectionPrefix + r"MeshMaterialList" + ReOpenBrace)
ReM_SI_MeshNormals = re.compile(ReSectionPrefix + r"MeshNormals" + ReOpenBrace)
ReM_SI_MeshTextureCoords = re.compile(ReSectionPrefix + r"MeshTextureCoords" + ReOpenBrace)
ReM_SI_MeshVertexColors = re.compile(ReSectionPrefix + r"MeshVertexColors" + ReOpenBrace)

# Frame > Mesh > MeshMaterialList
ReM_SI_Material = re.compile(ReSectionPrefix + r"Material" + ReOpenBrace)

# Frame > Mesh > MeshMaterialList > SI_Material
ReM_SI_Texture2D = re.compile(ReSectionPrefix + r"Texture(?:2D)?(?:Filename)?" + ReOpenBrace)

# AnimationSet
ReM_AnimationSet = re.compile(ReSectionPrefix + r"AnimationSet" + ReOpenBrace)
ReM_Animation = re.compile(ReSectionPrefix + r"Animation\s+(?:\s*anim-)?(%s+)%s" % (ReNameChars, ReOpenBrace))
ReM_AnimFrm = re.compile(ReSectionPrefix + r"{(?:\s*frm-)(%s+)\s*}" % ReNameChars)
ReM_SI_AnimationKey = re.compile(ReSectionPrefix + "AnimationKey" + ReOpenBrace)

# SI_EnvelopeList
ReM_SI_EnvelopeList = re.compile(ReSectionPrefix + r"EnvelopeList" + ReOpenBrace)
ReM_SI_Envelope = re.compile(ReSectionPrefix + r"Envelope" + ReOpenBrace)

# This will be the most called function during the program,
# Optimizing it may increase preformance significantly.
# This is called for every line that isn't an opening/closing brace.
def GetDataTypes(Str):
	Types = []
	Values = []
	Total = re.findall(ReM_FloatOrInt, Str)
	for Type in Total:
		if not "." in Type:
			Types += ["I"]
			Values += [int(Type)]
		else:
			Types += ["F"]
			Values += [float(Type)]
	return Types, Values

# Used less commonly, when expecting lines like in SI_Texture2D, Animation, or SI_Envelope.
def GetDataStr(Str):
	Data = re.findall(ReM_StrContent, Str)
	if Data:
		return Data[0][1:-1]
	return ""


# XSI Frame.
# One to unlimited instances exist per XSI_Importer.
class XSIFrm:
	def __init__(self, name = "Obj", parent = None):
		# Basic
		self.parent = parent
		self.name = name
		
		# Matrix (list[4][4])
		self.matrix = []
		
		# Mesh (XSIMesh or None)
		self.Mesh = None
		
		# Animation keys. (XSIAnimationKey list.)
		self.Anim = []
		
		# How do we know if this frame is a bone?
		# It will be referenced in the
		# second string of SI_Envelope.
		self.isBone = False
		
		# Created real blender object
		# is assigned to this later on.
		self.Obj = None
		
		# Autodetermined from name:
		UnderScr = 0
		flags = ""
		for fl in self.name:
			if UnderScr == 2:
				flags += fl
			else:
				if fl == "_":
					UnderScr += 1
				else:
					UnderScr = 0
		self.isHardpoint = (self.name[0:3].lower() == "hp_")
		self.isCollision = ("c" in flags)
		self.isHidden = ("h" in flags)
		#~ self.is2Sided = ("2" in flags)
		#~ self.isGlowing = ("g" in flags)
	def GetParentCount(self):
		Count = 0
		Frm = self
		while Frm.parent:
			Frm = Frm.parent
			Count += 1
		return Count

# XSI Mesh.
# One or zero instances exist per XSIFrm.
class XSIMesh:
	def __init__(self, name = ""):
		# Basic
		self.name = name
		
		# Mesh
		self.verts = []
		self.faces = []
		
		# Mesh Materials (faces)
		self.matInd = []
		self.MatList = []
		self.matCount = 0
		
		# Mesh Normals
		#~ self.norm_coord = []
		#~ self.norm_poly = []
		
		# UV Mapping
		self.uvmap_coord = []
		self.uvmap_poly = []
		
		# Vertex Colors
		self.vcol_coord = []
		self.vcol_poly = []

# Returns a new vertex list based
# on vertice references in polygons.
def GetFullVertList(verts, faces):
	NewVerts = []
	
	for face in faces:
		for faceVert in face:
			NewVerts += [verts[faceVert]]
	return NewVerts

# XSI SI_Material.
# One to unlimited instances may exist as a list item in XSIMaterialList.
class XSIMat:
	def __init__(self, textureName = "", diffuseRGBA = (1.0, 1.0, 1.0, 1.0),
	specularityHardness = 1.0, specularityRGB = (1.0, 1.0, 1.0)):
		self.textureName = textureName
		self.textureBaseName = os.path.basename(textureName)
		self.diffuseRGBA = diffuseRGBA
		self.specularityHardness = specularityHardness
		self.specularityRGB = specularityRGB
		self.localIndex = -1
		self.index = -1
	def __eq__(self, other):
		if (self.diffuseRGBA == other.diffuseRGBA
		and self.specularityHardness == other.specularityHardness
		and self.specularityRGB == other.specularityRGB
		and self.specularityRGB == other.specularityRGB
		and self.textureBaseName == other.textureBaseName):
			return True
	def __ne__(self, other):
		# Not sure if this is correct or pythonic, seems to work, though.
		return not self.__eq__(other)
	def __str__(self):
		return "MAT %d, Txtr=%s DiffRGBA=(%.2f, %.2f, %.2f, %.2f), SpecRGBH=(%.2f, %.2f, %.2f, %.2f)" % (
		self.index,
		("\""+self.textureBaseName+"\"")[0:16].ljust(16),
		self.diffuseRGBA[0],
		self.diffuseRGBA[1],
		self.diffuseRGBA[2],
		self.diffuseRGBA[3],
		self.specularityRGB[0],
		self.specularityRGB[1],
		self.specularityRGB[2],
		self.specularityHardness)

# XSI MeshMaterialList.
# One instance exist per XSI_Importer.
class XSIMaterialList:
	def __init__(self):
		self.Material = []
	def NewMaterial(self, NMat, Delete = True):
		# Check if an XSIMat identical to NMat already exists.
		# If it does, return that one, if not, add NMat and return it.
		index = 0
		for Mat in self.Material:
			index += 1
			if Mat == NMat:
				if Delete:
					del NMat
				return Mat
		self.Material += [NMat]
		NMat.index = index
		return NMat

# XSI SI_Envelope.
# One to unlimited instances exist in XSI_Importer as a list.
class XSIEnvelope:
	def __init__(self, Frm = None, FrmBone = None):
		# XSIFrm
		self.Frm = Frm
		self.FrmBone = FrmBone
		
		# Vert # and Vert Weight
		self.vertWeight = []
	def __str__(self):
		return "VertWeights=%s Bone=%s Object=%s" % (
		str(len(self.vertWeight)).ljust(6),
		("\""+self.FrmBone.name+"\"").ljust(22),
		("\""+self.Frm.name+"\"")
		)

# XSI SI_AnimationKey.
class XSIAnimationKey:
	def __init__(self, animType):
		# 0 = quaternion rotations
		# 1 = scaling
		# 2 = translation
		# 3 = Euler rotations
		self.animType = animType
		self.keyFrame = []

# Processes the XSI one line at a time.
class XSI_Importer:
	def __init__(self, ImportFilePath, ImportHPMesh = False, TxtrRemovePath = True, OneMatPerTexture = False, EmulateFlags = True):
		# Options that are or will be settable in the import window:
		# Import HP's with their Mesh rather than as \"EMPTY\" objects.
		self.ImportHPMesh = ImportHPMesh
		# Remove file path from textureName.
		self.TxtrRemovePath = TxtrRemovePath
		# If multiple materials have the same texture, merge them into one material.
		self.OneMatPerTexture = OneMatPerTexture
		# Works for any combination of: *__h, *__c
		# Todo: *__2 (two-sided)
		self.EmulateFlags = EmulateFlags
		# Import animations? (Not yet supported.)
		self.importAnims = False
		
		# Default material settings
		self.Def_Diffuse = [0.7, 0.7, 0.7, 1.0]
		self.Def_Spec = [0.0, 0.0, 0.0]
		self.Def_Hardness = 0.0
		
		# Start
		print("Importing BZ2 XSI: %s" % ImportFilePath)
		self.ImportFilePath = ImportFilePath
		self.File = open(ImportFilePath, "r")
		self.Frm = [None]
		self.Parse()
		self.File.close()
		if InBlender:
			bpy.context.scene.update()
	def Parse(self):
		# Debugging shit
		Timer = time.clock()
		Indnt = "|   "
		LineNumber = 0
		DebugOnNewSection = False
		
		# Determines the current brace we're in.
		InBrace = 0
		InSection = ["N/A"]
		InSectionName = [""]
		IgnoreUntilBrace = 0
		
		CurrentFrame = [None]
		
		# THA FROIMS, MOITE.
		Frm = [None]
		FrmDict = {}
		# Contains all materials we will use.
		MatList = XSIMaterialList()
		# Temporarily contains all materials in a MeshMaterialList.
		MeshMats = []
		
		# Envelopes
		Envelopes = []
		
		# Used for processing actual vertices/faces, etc...
		DataType = []
		Data = []
		DataIndex = 0
		
		for ln in self.File:
			LineNumber += 1
			if "{" in ln and not "}" in ln:
				# This line contains an opening brace; a new section.
				InBrace += 1
				DataIndex = 0
				PolyType = 0
				SectionType, SectionName = self.SectionType(ln)
				InSection += [SectionType]
				InSectionName += [SectionName]
				
				# Debug
				if DebugOnNewSection:
					print("%s:%d:\tIN SEC %s%s"%(self.ImportFilePath, LineNumber, str("  ")*InBrace, SectionType+" "+SectionName))
				
				# Set current frame to operate on. (Defined as last CurrentFrame, CurrentFrame[-1].)
				if SectionType == "Frame":
					Frm += [XSIFrm(SectionName, CurrentFrame[-1])]
					FrmDict[SectionName] = Frm[-1]
					CurrentFrame += [Frm[-1]]
				elif SectionType == "Mesh":
					CurrentFrame[-1].Mesh = XSIMesh(SectionName)
				elif SectionType == "MeshMaterialList":
					MeshMats = []
				elif SectionType == "SI_Material":
					# Material Defaults
					Mat_Diffuse = [self.Def_Diffuse[0], self.Def_Diffuse[1], self.Def_Diffuse[2], self.Def_Diffuse[3]]
					Mat_Specularity = [self.Def_Spec[0], self.Def_Spec[1], self.Def_Spec[2]]
					Mat_Hardness = self.Def_Hardness
					Mat_Texture = ""
				elif SectionType == "SI_Envelope":
					EnvelopeObj = ""
					EnvelopeBone = ""
			elif "}" in ln and not "{" in ln:
				# This line contains a closing brace.
				
				# Are there imbalanced braces?
				if InBrace <= 0:
					print("! - ERROR: Found extra closing brace '}', see line below for more information.")
					print("%s:%d:See error above."%(self.ImportFilePath, LineNumber))
				else:
					if InSection[-1] == "Frame":
						del CurrentFrame[-1]
					elif InSection[-1] == "SI_Material":
						MeshMats += [MatList.NewMaterial(XSIMat(Mat_Texture, Mat_Diffuse, Mat_Hardness, Mat_Specularity))]
					elif InSection[-1] == "MeshMaterialList":
						for matInd in CurrentFrame[-1].Mesh.matInd:
							CurrentFrame[-1].Mesh.MatList += [MeshMats[matInd]]
					
					del InSection[-1]
					del InSectionName[-1]
					
					InBrace -= 1
					if InBrace <= IgnoreUntilBrace:
						# Stop skipping lines.
						IgnoreUntilBrace = 0
			elif not IgnoreUntilBrace:
				# This line neither closes off nor opens any braces,
				# therfore potentially contains the meat & potatoes.
				if InSection[-1] == "Mesh":
					DataType, Data = GetDataTypes(ln)
					if DataType == ["I"]:
						DataIndex += 1
					elif DataIndex == 1 and DataType == ["F", "F", "F"]: # 3 floats.
						CurrentFrame[-1].Mesh.verts += [Data]
					elif DataIndex == 2 and DataType[0:2] == ["I", "I"]:
						# Determine data storage type for faces.
						# 1 = [index], [count], [polygons...]
						# 2 = [count], [polygons...]
						if not PolyType:
							# If Data[0] is 0, it is an [index], and Data[1] will be a [count].
							# [count] will never be 0, or if it is, the file isn't worth importing anyways.
							if Data[0] == 0:
								PolyType = 1
							else:
								PolyType = 2
						if PolyType == 1:
							CurrentFrame[-1].Mesh.faces += [Data[2:]]
						else:
							CurrentFrame[-1].Mesh.faces += [Data[1:]]
				# In Mesh
				elif len(InSection) >= 2 and InSection[-2] == "Mesh":
					DataType, Data = GetDataTypes(ln)
					# Last section is a "Mesh", one we're inside of.
					if InSection[-1] == "MeshMaterialList":
						if DataType == ["I"]:
							if DataIndex == 0:
								# Amount of different materials used.
								CurrentFrame[-1].Mesh.matCount = Data[0]
								DataIndex += 1
							elif DataIndex == 1:
								# Amount of faces, skip.
								DataIndex += 1
							else:
								CurrentFrame[-1].Mesh.matInd += Data
					elif InSection[-1] == "SI_MeshTextureCoords":
						if DataType == ["I"]:
							DataIndex += 1
						elif DataIndex == 1 and DataType == ["F", "F"]:
							CurrentFrame[-1].Mesh.uvmap_coord += [Data]
						elif DataIndex == 2 and DataType[0:2] == ["I", "I"]:
							# Determine data storage type for faces.
							# 1 = [index], [count], [polygons...]
							# 2 = [count], [polygons...]
							if not PolyType:
								# If Data[0] is 0, it is an [index], and Data[1] will be a [count].
								# [count] will never be 0, or if it is, the file isn't worth importing anyways.
								if Data[0] == 0:
									PolyType = 1
								else:
									PolyType = 2
							if PolyType == 1:
								CurrentFrame[-1].Mesh.uvmap_poly += [Data[2:]]
							else:
								CurrentFrame[-1].Mesh.uvmap_poly += [Data[1:]]
					elif InSection[-1] == "SI_MeshVertexColors":
						if DataType == ["I"]:
							DataIndex += 1
						elif DataIndex == 1 and DataType[0:3] == ["F", "F", "F"]:
							CurrentFrame[-1].Mesh.vcol_coord += [Data]
						elif DataIndex == 2 and DataType[0:2] == ["I", "I"]:
							# Determine data storage type for faces.
							# 1 = [index], [count], [polygons...]
							# 2 = [count], [polygons...]
							if not PolyType:
								# If Data[0] is 0, it is an [index], and Data[1] will be a [count].
								# [count] will never be 0, or if it is, the file isn't worth importing anyways.
								if Data[0] == 0:
									PolyType = 1
								else:
									PolyType = 2
							if PolyType == 1:
								CurrentFrame[-1].Mesh.vcol_poly += [Data[2:]]
							else:
								CurrentFrame[-1].Mesh.vcol_poly += [Data[1:]]
				elif InSection[-1] == "FrameTransformMatrix":
					DataType, Data = GetDataTypes(ln)
					if len(Data) == 4:
						Frm[-1].matrix += [[Data[0], Data[1], Data[2], Data[3]]]
				# In Mesh > MeshMaterialList
				elif len(InSection) >= 3 and InSection[-2] == "MeshMaterialList":
					if InSection[-1] == "SI_Material" and not self.OneMatPerTexture:
						DataType, Data = GetDataTypes(ln)
						if DataIndex == 0:
							# Diffuse RGBA
							if DataType == ["F", "F", "F", "F"]:
								Mat_Diffuse = Data
								DataIndex += 1
						elif DataIndex == 1:
							# Specularity/Hardness
							if DataType == ["F"]:
								Mat_Hardness = Data[0]
								DataIndex += 1
						elif DataIndex == 2:
							# Specularity RGB
							if DataType == ["F", "F", "F"]:
								Mat_Specularity = Data
				# In Mesh > MeshMaterialList > SI_Material
				elif len(InSection) >= 4 and InSection[-2] == "SI_Material":
					if InSection[-1] == "SI_Texture2D":
						Mat_Texture = GetDataStr(ln)
						if not self.TxtrRemovePath:
							Mat_Texture = re.findall(ReM_FileOnly, Mat_Texture)[0]
						# This is the only thing we get from SI_Texture2D.
						IgnoreUntilBrace = InBrace
				# Bone Envelopes...
				elif InSection[-1] == "SI_Envelope":
					if DataIndex == 0:
						EnvelopeObj = GetDataStr(ln).replace("frm-", "", 1)
						DataIndex += 1
					elif DataIndex == 1:
						EnvelopeBone = GetDataStr(ln).replace("frm-", "", 1)
						DataIndex += 1
						Envelopes += [XSIEnvelope(FrmDict[EnvelopeObj], FrmDict[EnvelopeBone])]
						FrmDict[EnvelopeBone].isBone = True
					elif DataIndex == 2:
						DataType, Data = GetDataTypes(ln)
						if DataType == ["I"]:
							DataIndex += 1
					else:
						DataType, Data = GetDataTypes(ln)
						if DataType == ["I", "F"]:
							Envelopes[-1].vertWeight += [[Data[0], Data[1]]]
				# Animations...
				elif self.importAnims and len(InSection) >= 2 and InSection[-2] == "Animation":
					if InSection[-1] == "SI_AnimationKey":
						DataType, Data = GetDataTypes(ln)
						# print(InSectionName[-2])
						if DataIndex == 0 and DataType == ["I"]:
							# Anim type.
							FrmDict[InSectionName[-2]].Anim = [XSIAnimationKey(Data[0])]
							DataIndex += 1
						elif DataIndex == 1 and DataType == ["I"]:
							DataIndex += 1
						else:
							FrmDict[InSectionName[-2]].Anim[-1].keyFrame += [Data]
		
		# Time.
		print("XSI Import Time: %f (%d lines processed.)" % (time.clock()-Timer, LineNumber))
		
		# [0] being root "world" frame for file reading, no longer needed.
		del Frm[0]
		
		# Import
		if InBlender:
			self.BlenderImportFrames(Frm, MatList)
		else:
			self.PrintImportData(Frm, MatList, Envelopes, "|  ")
	def PrintImportData(self, FrameList, XSI_MatList, EnvelopeList, Indnt):
		MatList = XSI_MatList.Material
		SOL = ["+", "-"] # I use this alternation for different highlighting in my editor's output.
		for Frm in FrameList:
			prefix = SOL[0]+": %s" % (Indnt*Frm.GetParentCount())
			print(prefix+"frm-"+Frm.name)
			if Frm.Mesh:
				uvmap_coord_full = GetFullVertList(Frm.Mesh.uvmap_coord, Frm.Mesh.uvmap_poly)
				fullVerts = GetFullVertList(Frm.Mesh.verts, Frm.Mesh.faces)
				
				properties = (
				("Verts", len(Frm.Mesh.verts)),
				("Faces", len(Frm.Mesh.faces)),
				("Unique UV Verts", len(Frm.Mesh.uvmap_coord)),
				("UV Polygons", len(Frm.Mesh.uvmap_poly)),
				("UV Vert References", len(uvmap_coord_full)),
				("Verts Referenced", len(fullVerts))
				)
				
				printLine = ""
				for prop in properties:
					printLine += "  "+prop[0]
				print(prefix+printLine)
				
				printLine = ""
				for prop in properties:
					printLine += "  "+str(prop[1]).ljust(len(prop[0]))
				print(prefix+printLine)
				
				#~ for index, face in enumerate(Frm.Mesh.faces):
					#~ print(index, face)
			SOL = SOL[::-1]
		
		# Materials
		print("Total Unique Materials: %d" % len(MatList))
		for Index, Mat in enumerate(MatList):
			print(" %s" % Mat)
		
		# Envelopes
		print("Total Envelopes: %d" % len(EnvelopeList))
		for Envelope in EnvelopeList:
			print(" %s" % Envelope)
			if False:
				for VertWeight in Envelope.vertWeight:
					print("  v%d:\tw%f" % (VertWeight[0], VertWeight[1]))
	def SectionType(self, Section):
		MatchType = {}
		MatchType["Frame"] =			(ReM_Frame, True)
		MatchType["FrameTransformMatrix"] =	(ReM_FrameTransformMatrix, False)
		MatchType["SI_FrameBasePoseMatrix"] =	(ReM_SI_FrameBasePoseMatrix, False)
		MatchType["Mesh"] =			(ReM_Mesh, True)
		MatchType["MeshMaterialList"] =		(ReM_MeshMaterialList, False)
		MatchType["SI_MeshNormals"] =		(ReM_SI_MeshNormals, False)
		MatchType["SI_MeshTextureCoords"] =	(ReM_SI_MeshTextureCoords, False)
		MatchType["SI_MeshVertexColors"] =	(ReM_SI_MeshVertexColors, False)
		MatchType["SI_Material"] =		(ReM_SI_Material, False)
		MatchType["SI_Texture2D"] =		(ReM_SI_Texture2D, False)
		MatchType["AnimationSet"] =		(ReM_AnimationSet, False)
		MatchType["Animation"] =		(ReM_Animation, True)
		#~ MatchType["AnimFrm"] =			(ReM_AnimFrm, True)
		MatchType["SI_AnimationKey"] =		(ReM_SI_AnimationKey, False)
		MatchType["SI_EnvelopeList"] =		(ReM_SI_EnvelopeList, False)
		MatchType["SI_Envelope"] =		(ReM_SI_Envelope, False)
		MatchType["SI_CoordinateSystem"] =	(ReM_SI_CoordinateSystem, True)
		MatchType["SI_Angle"] =			(ReM_SI_Angle, False)
		MatchType["SI_Camera"] =		(ReM_SI_Camera, True)
		MatchType["SI_Ambience"] =		(ReM_SI_Ambience, False)
		MatchType["SI_Light"] =			(ReM_SI_Light, True)
		MatchType["SI_Fog"] =			(ReM_SI_Fog, False)
		
		for Type in MatchType:
			Match = re.match(MatchType[Type][0], Section)
			if Match:
				if MatchType[Type][1]:
					return Type, Match.group(1)
				else:
					return Type, ""
		return "", ""
	def BlenderImportFrames(self, FrameList, XSI_MatList):
		axisConv = axis_conversion(from_forward="Z", from_up="Y").to_4x4()
		
		# Create materials first.
		BlendMatList = self.GetMaterials(XSI_MatList)
		
		# If bones exist, create a root armature object.
		#~ BoneList = [Fr for Fr in FrameList if Fr.isBone]
		#~ if BoneList:
			#~ Armature = bpy.data.armatures.new("Armature")
			#~ ArmatureObject = bpy.data.objects.new("Armature", Armature)
			#~ bpy.ops.object.mode_set(mode='EDIT')
		#~ else:
			#~ ArmatureObject = None
		
		#~ for Bone in BoneList:
			# ---
		
		# Create objects from frames.
		for Frm in FrameList:
			print("%s%s" % (("|   "*Frm.GetParentCount()), Frm.name))
			
			# Create the object in blender.
			if not Frm.isBone:
				if Frm.Mesh and not (not self.ImportHPMesh and Frm.isHardpoint):
					Mesh = self.GetMesh(Frm.Mesh, XSI_MatList, BlendMatList)
					Frm.Obj = bpy.data.objects.new(Frm.name, Mesh)
				else:
					Frm.Obj = bpy.data.objects.new(Frm.name, None)
					Frm.Obj.empty_draw_type = "SINGLE_ARROW"
				
				# Add the object attributes.
				Frm.Obj.name = Frm.name
				Frm.Obj.matrix_local = self.GetMatrix(Frm.matrix)
			else:
				# Lame retarded shit. No way to directly add bones by data, only by using the operator functions.
				# Absolutley retarded API. What the retarded stupid fuck. Dumb as cock-nigger shit with aids.
				# I didn't plan on doing stupid shit like "selecting objects, then setting them in edit mode, then
				# calling operator functions to preform GUI tasks on them.", and won't bother resorting to such.
				
				#~ Armature.edit_bones.new(Frm.name)
				Frm.Obj = bpy.data.objects.new(Frm.name, None)
				Frm.Obj.empty_draw_type = "SINGLE_ARROW"
		
		object_list = []
		#~ bpy.context.scene.objects.link(ArmatureObject)
		
		# Set parenting:
		for Frm in FrameList:
			if Frm.parent:
				Frm.Obj.parent = Frm.parent.Obj
			else:
				Frm.Obj.matrix_local *= axisConv
				posy, posz = Frm.Obj.matrix_local[1][3], Frm.Obj.matrix_local[2][3]
				Frm.Obj.matrix_local[2][3] = posy
				Frm.Obj.matrix_local[1][3] = posz
			
			object_list += [Frm.Obj]
			
			# Done
			bpy.context.scene.objects.link(Frm.Obj)
			if Frm.isHardpoint and self.EmulateFlags:
				Frm.Obj.show_name = True
			if self.EmulateFlags:
				if Frm.isCollision:
					Frm.Obj.show_wire = True
				if Frm.isHidden:
					Frm.Obj.hide = True
		
		# Materials
		print("Total Unique Materials: %d" % len(XSI_MatList.Material))
		for Index, Mat in enumerate(XSI_MatList.Material):
			print(" %s" % Mat)
		
		bpy.ops.object.select_all(action='DESELECT')
		
		for Obj in object_list:
			Obj.select = True
	def GetMatrix(self, mat):
		return Matrix(mat).transposed()
	def GetMesh(self, XSI_Mesh, XSI_MatList, BlendMatList):
		# Compile verts & faces into a blender mesh.
		Mesh = bpy.data.meshes.new(XSI_Mesh.name)
		verts = XSI_Mesh.verts
		faces = XSI_Mesh.faces
		
		# Create actual blender mesh.
		Mesh.from_pydata(verts, [], faces)
		
		# Make sure all materials used are applied to the object first.
		for Mat in XSI_MatList.Material:
			if Mat in XSI_Mesh.MatList:
				Mesh.materials.append(BlendMatList[Mat.index])
		
		# UV Mapping:
		uvmap_coord2 = GetFullVertList(XSI_Mesh.uvmap_coord, XSI_Mesh.uvmap_poly)
		if uvmap_coord2:
			Mesh.uv_textures.new().data
			UVL = Mesh.uv_layers.active.data
			if len(uvmap_coord2) == len(UVL):
				for index in range(len(UVL)):
					UVL[index].uv = Vector((uvmap_coord2[index][0], uvmap_coord2[index][1]))
			else:
				print("! - ERROR in Mesh %r: XSI UV's (%d) != BLENDER UV's (%d)\n! - Could not import UV Coordinates for this mesh." % (XSI_Mesh.name, len(uvmap_coord2), len(UVL)))
		
		# Vertex Colors:
		vcol_coord2 = GetFullVertList(XSI_Mesh.vcol_coord, XSI_Mesh.vcol_poly)
		if vcol_coord2:
			vcol = self.GetVertColors(vcol_coord2)
			Mesh.vertex_colors.new()
			VCL = Mesh.vertex_colors.active.data
			if len(vcol) == len(VCL):
				for index in range(len(VCL)):
					VCL[index].color = Color((vcol[index][0], vcol[index][1], vcol[index][2]))
			else:
				print("! - ERROR in Mesh %r: XSI Vert Colors (%d) != BLENDER Vert Colors (%d)\n! - Could not import Vertex Colors for this mesh." % (XSI_Mesh.name, len(vcol), len(VCL)))
		
		# Face Materials:
		if len(XSI_Mesh.matInd) == len(Mesh.polygons):
			for index, MatFace in enumerate(XSI_Mesh.MatList):
				Mesh.polygons[index].material_index = XSI_Mesh.matInd[index]
			if Mesh.uv_textures:
				for index, MatFace in enumerate(XSI_Mesh.MatList):
					Mesh.uv_textures[0].data.values()[index].image = BlendMatList[MatFace.index].texture_slots[0].texture.image
		else:
			print("! - ERROR in Mesh %r: XSI MAT FACES (%d) != BLENDER POLYGONS (%d)\n! - Could not import Face Materials for this mesh." % (XSI_Mesh.name, len(XSI_Mesh.matInd), len(Mesh.polygons)))
		
		# Finish
		Mesh.validate()
		Mesh.update(1)
		return Mesh
	def GetMaterials(self, XSI_MatList):
		from bpy_extras.image_utils import load_image
		# Create textures.
		TexImg = {}
		Texture = []
		for Mat in XSI_MatList.Material:
			if not Mat.textureBaseName in TexImg:
				Texture += [bpy.data.textures.new(Mat.textureName, type="IMAGE")]
				if not os.path.exists(Mat.textureName):
					Texture[-1].image = load_image(Mat.textureBaseName, bpy.context.user_preferences.filepaths.texture_directory, place_holder=False, recursive=True)
				else:
					Texture[-1].image = load_image(Mat.textureBaseName, os.path.dirname(Mat.textureName), place_holder=False, recursive=True)
				TexImg[Mat.textureName] = Texture[-1]
			else:
				Texture += [TexImg[Mat.textureName]]
		
		DebugByColor = []
		#~ DebugByColor = ([1.000000, 1.000000, 1.000000],
		#~ [1.000000, 0.000000, 0.000000], [1.000000, 1.000000, 0.000000], [0.000000, 0.000000, 1.000000],
		#~ [0.431373, 0.000000, 1.000000], [0.000000, 0.529412, 0.482353], [1.000000, 0.454902, 0.000000],
		#~ [1.000000, 0.000000, 0.988235], [0.690196, 0.694118, 0.501961], [1.000000, 0.000000, 0.592157],
		#~ [0.486275, 0.486275, 0.486275], [0.670588, 0.674510, 0.305882], [0.305882, 0.596078, 0.674510],
		#~ [0.376471, 0.254902, 0.400000], [0.376471, 0.254902, 0.000000], [0.682353, 0.443137, 1.000000],
		#~ [0.698039, 0.000000, 1.000000], [0.556863, 0.309804, 0.470588], [0.741176, 1.000000, 0.403922])
		#~ while len(DebugByColor) < len(XSI_MatList.Material):
			#~ DebugByColor += [0.000000, 0.000000, 0.000000]
		
		BlenderMat = []
		for Mat in XSI_MatList.Material:
			if self.OneMatPerTexture and Mat.textureName:
				BlenderMat += [bpy.data.materials.new(Mat.textureName)]
			else:
				BlenderMat += [bpy.data.materials.new("Material %d" % Mat.index)]
			if Texture[Mat.index]:
				Tex = BlenderMat[-1].texture_slots.add()
				Tex.texture = Texture[Mat.index]
				Tex.texture_coords = "UV"
				# Tex.uv_layer = (...).["UVMap"]
				Tex.use_map_color_diffuse = True
			if DebugByColor:
				BlenderMat[-1].diffuse_color = Color((DebugByColor[Mat.index]))
			else:
				BlenderMat[-1].diffuse_color = Color((Mat.diffuseRGBA[0], Mat.diffuseRGBA[1], Mat.diffuseRGBA[2]))
			if Mat.diffuseRGBA[3] != 1.0:
				BlenderMat[-1].alpha = Mat.diffuseRGBA[3]
				BlenderMat[-1].use_transparency = True
			BlenderMat[-1].specular_color = Color((Mat.specularityRGB[0], Mat.specularityRGB[1], Mat.specularityRGB[2]))
			BlenderMat[-1].specular_hardness = Mat.specularityHardness
		return BlenderMat
	def GetVertColors(self, coordinates):
		# Convert [R, G, B, A] into [R*A, G*A, B*A], if needed.
		vcol = []
		if coordinates:
			if len(coordinates[0]) > 3:
				for col in coordinates:
					vcol += [[col[0]*col[3], col[1]*col[3], col[2]*col[3]]]
			else:
				for col in coordinates:
					vcol += [[col[0], col[1], col[2]]]
		return vcol

if InBlender:
	class BZ2XSI_Import(bpy.types.Operator):
		bl_idname = "import_scene.bz2xsi"
		bl_label = "Import BZ2XSI"
		
		filepath = StringProperty(subtype="FILE_PATH")
		
		ImportHPMesh = BoolProperty(
		name="Import HP Meshes",
		description="Import hardpoints as meshes instead of as empty objects.",
		default=True)
		
		OneMatPerTexture = BoolProperty(
		name="One Material Per Texture",
		description="Materials with the same texture are merged into one material.",
		default=False)
		
		EmulateFlags = BoolProperty(
		name="Emulate Frm Flags",
		description="Affects: hp_*, *__h, *__c.",
		default=False)
		
		TxtrRemovePath=True
		
		def execute(self, context):
			FilePath = bpy.path.ensure_ext(self.filepath, ".xsi")
			
			XSI_Importer(FilePath, self.ImportHPMesh, self.TxtrRemovePath, self.OneMatPerTexture, self.EmulateFlags)
			
			return {"FINISHED"}
		def invoke(self, context, event):
			WindowManager = context.window_manager
			WindowManager.fileselect_add(self)
			return {"RUNNING_MODAL"}
else:
	#~ XSI_Importer(r"E:/backup/Battlezone II Related/BZ2 - Icecream Race Source Stuff/XSI/dbpowe00.xsi")
	XSI_Importer(r"E:/Program Files/Games/BZ2/DATA/XSI/mepool01.xsi")
	#~ XSI_Importer(r"E:/backup/Battlezone II Related/BZ2 - Icecream Race Source Stuff/XSI/dvmbike00.xsi")
	
	#~ XSI_Importer(r"E:/Program Files/Games/BZ2/DATA/XSI/cc111100.xsi")		# Contains 2-sided objects.
	#~ XSI_Importer(r"C:/Users/Veary/Desktop/PyTest/Importer/ivtank00.xsi")		# Contains vertex colors.
	#~ XSI_Importer(r"C:/Users/Veary/Desktop/PyTest/Importer/fvscout_skel.xsi")	# Contains bones.
	#~ XSI_Importer(r"E:/Program Files/Games/BZ2/DATA/XSI/apgun00.xsi")		# Contains multiple materials per mesh.
	#~ XSI_Importer(r"E:/Program Files/Games/BZ2/DATA/XSI/fblung00.xsi")		# Contains simple animation. (Quaternion rotation)

def addMenuItem(self, context):
	self.layout.operator(BZ2XSI_Import.bl_idname, text="BZ2XSI (.xsi)")

def register():
	bpy.utils.register_class(BZ2XSI_Import)
	bpy.types.INFO_MT_file_import.append(addMenuItem)

def unregister():
	bpy.utils.unregister_class(BZ2XSI_Import)
	bpy.types.INFO_MT_file_import.remove(addMenuItem)
