#!BPY
# -*- coding: latin-1 -*-
#
# HL2 SMD Blender Scripts
#
# Copyright (C) 2005, Plüss Roland ( roland@rptd.dnsalias.net )
# 
# This program is free software; you can redistribute it and/or 
# modify it under the terms of the GNU General Public License 
# as published by the Free Software Foundation; either 
# version 2 of the License, or (at your option) any later 
# version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#

"""
Name: 'Half-Life 2 (.smd)...'
Blender: 248
Group: 'Export'
Submenu: 'Static Mesh...' export_static
Submenu: 'Animation Sequence...' export_anim
Submenu: 'Animation Sequences ( All ! )...' export_anim_all
Tooltip: 'Export selected mesh to Half-Life 2 File Format (.smd)'
"""

__author__ = 'Plüss Roland'
__url__ = ('Project Epsylon, http://epsylon.rptd.ch/', 'My Homepage, http://rptd.c/')
__version__ = '1.0'
__bpydoc__ = """This script exports meshes into the Half-Life 2 ASCII format SMD.
It can create different kinds of SMD files depending on your needs.

Static Mesh
-----------
Exports the selected mesh as a static mesh usable for props.
If the selected mesh is parented to an armature it is used
to export skeleton data as well. Otherwise no skeleton is
exported.

Animation Sequence
------------------
Exports one animation sequences derived from data found in
an action specified by the user. The action is sampled at
fixed time intervalls and exported. You can export one
action at the time only.



Bugs
----
Bugs? Those are random features ;=)

Credits
-------
Major part of the code, especially the framework has been
taken from the 'Drag[en]gine' Game Engine project
( http://epsylon.rptd.ch/dragengine.php )
"""

import Blender
from Blender.Mathutils import *
import struct, os, cStringIO, time, re
import math
from math import *



# constants
PI = 3.14159265
HALF_PI	= PI / 2.0
ONE_PI = PI / 180.0

# transformation matrices
# hl2 coordinate system layout:
# model faces along x axis ( is -y axis in blender )
# model right is along -y axis ( is -x axis in blender )
# model up is along z axis ( is z axis in blender )
tuv = 40.0 # scaling factor
transformPosition = Matrix( [0,1,0,0], [-1,0,0,0], [0,0,1,0], [0,0,0,0] )
scalePosition = Matrix( [tuv,0,0,0], [0,tuv,0,0], [0,0,tuv,0], [0,0,0,1] )
transformBone = Matrix( [1,0,0,0], [0,-1,0,0], [0,0,1,0], [0,0,0,0] )
transformScalePosition = transformPosition * scalePosition
transformScaleBone = transformBone * scalePosition



# global classes ( from Drag[en]gine )
########################################
class DEBone:
	def __init__( self, index, bone ):
		self.index = index
		self.bone = bone
		self.name = bone.name
		self.parent = None
		self.pos = Vector( [ 0, 0, 0 ] )
		self.rot = Vector( [ 0, 0, 0 ] )
		self.restMat = Matrix( [ 1, 0, 0, 0 ], [ 0, 1, 0, 0 ], [ 0, 0, 1, 0 ], [ 0, 0, 0, 1 ] )
	def __repr__(self):
		return '[' + str( self.index ) + ' ' + str( self.name ) + ']'
	def __str__(self):
		return self.__repr__()

class DEWeight:
	def __init__( self, bone, weight ):
		self.bone = bone
		self.weight = weight
	def __repr__(self):
		return '[' + str( self.bone ) + '=' + str( self.weight ) + ']'
	def __str__(self):
		return self.__repr__()

class DEVertex:
	def __init__( self, index, vertex ):
		self.vertex = vertex
		self.edges = []
		self.faces = []
		self.index = index
		self.weights = []
	def __repr__(self):
		return '[' + str(self.index) + ': edges(' + str(self.edges) + '), faces(' + str(self.faces) + ') ]'
	def __str__(self):
		return self.__repr__()
	def findEdge(self, edges, v2):
		for edge in self.edges:
			if edges[edge].vertices[1] == v2:
				return edge
		return -1
	def getHardEdgeCount(self, edges):
		count = 0
		for edge in self.edges:
			if edges[edge].hard:
				count = count + 1
		return count

class DEEdge:
	def __init__(self, v1, v2, index):
		self.vertices = [ v1, v2 ]
		self.faces = [ -1, -1 ]
		self.index = index
		self.hard = False
	def __repr__(self):
		return '[' + str(self.index) + ': vertices(' + str(self.vertices[0]) + ', ' + str(self.vertices[1]) + '), hard=' + str(self.hard) + ', faces(' + str(self.faces) + ') ]'
	def __str__(self):
		return self.__repr__()

class DEFace:
	def __init__(self, verts, index):
		self.vertices = [ verts[0].index, verts[1].index, verts[2].index ]
		self.normals = [ -1, -1, -1 ]
		self.edges = [ -1, -1, -1 ]
		self.index = index
		if len( verts ) == 4:
			self.vertices.append( verts[3].index )
			self.normals.append( -1 )
			self.edges.append( -1 )
	def __repr__(self):
		return '[' + str(self.index) + ': vertices(' + str(self.vertices) + '), normals(' + str(self.normals) + ', edges(' + str(self.edges) + ') ]'
	def __str__(self):
		return self.__repr__()
	def findCorner(self, vertex):
		for index in range( len(self.vertices) ):
			if self.vertices[index] == vertex:
				return index
		return -1
	def setNormalFor(self, vertex, normal):
		for index in range( len(self.vertices) ):
			if self.vertices[index] == vertex:
				self.normals[index] = normal

class DEKeyframe:
	def __init__(self, time, pos, rot):
		self.time = time
		self.pos = pos
		self.rot = rot

class DEMoveBone:
	def __init__(self, bone):
		self.bone = bone
		self.times = []
		self.keyframes = []

class DEMove:
	def __init__(self, action):
		self.action = action
		self.name = action.getName()

class DEChunk:
	def __init__(self, type):
		self.type
		self.content = ''
	def write(self, newContent):
		self.content = self.content + newContent
	def clear():
		self.content = ''
	def writeToFile(self, file):
		file.write( self.type[3] + self.type[2] + self.type[1] + self.type[0] )
		file.write( struct.pack( 'l', len( self.content ) ) )


# base exporter class ( from Drag[en]gine )
#############################################
class DEBaseExporter:
	
	# constructor
	def __init__( self ):
		self.objMesh = None
		self.objArmature = None
		self.mesh = None
		self.armature = None
		self.matrixMesh = Matrix()
		self.matrixArmature = Matrix()
		self.bones = []
		self.vertices = []
		self.edges = []
		self.faces = []
		self.triCount = 0
		self.quadCount = 0
		self.edgeCount = 0
		self.normalCount = 0
		self.maxBoneWeightCount = 0
		self.multiFoldMesh = False
		self.degeneratedFaceCount = 0
		self.scene = None
		self.context = None
		self.oldFrame = 0
		self.oldAction = None
		self.file = None
	
	# find mesh, armature and ref action
	def initFindMeshArmRef( self ):
		objects = Blender.Object.GetSelected()
		# try to find an armature for bones information and a messh
		for obj in objects:
			if obj.getType() == 'Mesh':
				if not self.objMesh:
					self.objMesh = obj
					self.mesh = self.objMesh.getData()
					self.matrixObject = self.objMesh.getMatrix().rotationPart()
					self.matrixObject.resize4x4()
					if obj.getParent() and obj.getParent().getType() == 'Armature':
						if not self.objArmature:
							self.objArmature = obj.getParent()
							self.armature = self.objArmature.getData()
							self.matrixArmature = self.objArmature.getMatrix().rotationPart()
							self.matrixArmature.resize4x4()
			elif obj.getType() == 'Armature':
				if not self.objArmature:
					self.objArmature = obj
					self.armature = self.objArmature.getData()
					self.matrixArmature = self.objArmature.getMatrix()
					self.matrixArmature = self.objArmature.getMatrix().rotationPart()
					self.matrixArmature.resize4x4()
	
	# process face corner
	def initProcessFaceCorner( self, vi, vo, c1, c2, fi ):
		vo[c1].faces.append( fi )
		edge = vo[c1].findEdge( self.edges, vi[c2] )
		if edge == -1:
			edge = vo[c2].findEdge( self.edges, vi[c1] )
		theEdge = self.edges[edge]
		if theEdge.faces[0] == -1:
			theEdge.faces[0] = fi
		elif theEdge.faces[1] == -1:
			theEdge.faces[1] = fi
		else:
			self.multiFoldMesh = True
		self.faces[fi].edges[c1] = edge
	
	# add bones helper function
	def initAddBonesHelper( self, bone, parent ):
		# add bone itself
		boneIndex = len( self.bones )
		newBone = DEBone( boneIndex, bone )
		newBone.parent = parent
		self.bones.append( newBone )
		# add all children bones in a depth search fashion
		if bone.children:
			for child in bone.children:
				self.initAddBonesHelper( child, newBone )
	
	# add bones
	def initAddBones( self ):
		if self.armature:
			# add bones in the correct order. for this we simply add
			# all bones without a parent with the recursive helper method
			for bone in self.armature.bones.values():
				if not bone.hasParent():
					self.initAddBonesHelper( bone, None )
	
	# add vertices
	def initAddVertices( self ):
		if self.mesh:
			# add vertices
			for vert in self.mesh.verts:
				self.vertices.append( DEVertex( vert.index, vert ) )
			# add weights
			groups = self.mesh.getVertGroupNames()
			for group in groups:
				for bone in self.bones:
					if group == bone.name:
						weights = self.mesh.getVertsFromGroup( group, 1 )
						for weight in weights:
							if weight[ 1 ] > 0.001:
								self.vertices[ weight[ 0 ] ].weights.append( DEWeight( bone, weight[ 1 ] ) )
						break
	
	# add edges
	def initAddEdges( self ):
		if self.mesh:
			if not self.mesh.edges:
				self.mesh.addEdgesData()
			for index in range( len( self.mesh.edges ) ):
				edge = self.mesh.edges[ index ]
				newEdge = DEEdge( edge.v1.index, edge.v2.index, index )
				newEdge.crease = edge.crease / 255
				if edge.crease > 128:
					newEdge.hard = True
				self.edges.append( newEdge )
				self.vertices[ edge.v1.index ].edges.append( index )
				self.vertices[ edge.v2.index ].edges.append( index )
	
	# add faces
	def initAddFaces( self ):
		if self.mesh:
			for index in range( len( self.mesh.faces ) ):
				face = self.mesh.faces[ index ]
				newFace = DEFace( face.v, index )
				self.faces.append( newFace )
				# gather vertices and indices
				vi = [ face.v[ 0 ].index, face.v[ 1 ].index, face.v[ 2 ].index ]
				vo = [ self.vertices[ vi[ 0 ] ], self.vertices[ vi[ 1 ] ], self.vertices[ vi[ 2 ] ] ]
				if len( face.v ) == 4:
					vi.append( face.v[ 3 ].index )
					vo.append( self.vertices[ vi[ 3 ] ] )
				# corners
				count = len( face.v )
				for c in range( count ):
					self.initProcessFaceCorner( vi, vo, c, (c + 1) % count, index )
	
	# calculate corner normals
	def initCalcCornerNormals(self):
		self.normalCount = 0
		for vert in self.vertices:
			hardEdgeCount = vert.getHardEdgeCount( self.edges )
			if hardEdgeCount == 0:
				for face in vert.faces:
					self.faces[face].setNormalFor( vert.index, self.normalCount )
				self.normalCount = self.normalCount + 1
			else:
				changed = True
				while changed:
					changed = False
					for index in vert.faces:
						face = self.faces[index]
						c = face.findCorner( vert.index )
						if face.normals[c] == -1:
							edge = self.edges[ face.edges[c] ]
							if edge.faces[0] == face.index:
								neighbor = edge.faces[1]
							else:
								neighbor = edge.faces[0]
							if edge.hard or neighbor == -1:
								face.normals[c] = self.normalCount
								self.normalCount = self.normalCount + 1
								changed = True
							else:
								neighborFace = self.faces[ neighbor ]
								neighborCorner = neighborFace.findCorner( vert.index ) 
								if neighborFace.normals[ neighborCorner ] != -1:
									face.normals[c] = neighborFace.normals[ neighborCorner ]
									changed = True
	
	# calculate informational numbers
	def initCalcInfoNumbers( self ):
		if self.mesh:
			for face in self.mesh.faces:
				if len(face.v) == 3:
					self.triCount = self.triCount + 1
				elif len(face.v) == 4:
					self.quadCount = self.quadCount + 1
				else:
					self.degeneratedFaceCount = self.degeneratedFaceCount + 1
			if self.mesh.edges:
				self.edgeCount = len( self.mesh.edges )
	
	# prints out some debugging informations about the mesh
	def printMeshDebug(self):
		print 'vertices:'
		for vert in self.vertices:
			print vert
		print 'edges:'
		for edge in self.edges:
			print edge
		print 'faces:'
		for face in self.faces:
			print face
	
	# find objects to export
	def initExporterObjects(self):
		self.initFindMeshArmRef()
		self.initAddBones()
		self.initAddVertices()
		self.initAddEdges()
		self.initAddFaces()
		self.initCalcCornerNormals()
		#self.printMeshDebug()
		self.initCalcInfoNumbers()
	
	# store old values to revert to them after we finished or something failed
	def storeOldValues(self):
		self.scene = Blender.Scene.getCurrent()
		self.context = self.scene.getRenderingContext()
		self.oldFrame = self.context.currentFrame()
		if self.armature:
			self.oldAction = self.objArmature.getAction()
	
	# restore old values
	def restoreOldValues( self ):
		self.scene.getRenderingContext().currentFrame(self.oldFrame)
		if self.objArmature and self.oldAction:
			self.oldAction.setActive( self.objArmature )
		self.scene.update( 1 )
	
	# switch to the given action
	def switchAction( self, action ):
		if self.objArmature:
			action.setActive( self.objArmature )
	
	# switch to given time and update
	def switchTime( self, time ):
		if self.armature:
			self.objArmature.evaluatePose( time )
		#self.scene.update( 1 ) <== BUGGED since 2.42 !
	
	# prints out to the console some informations
	def printInfos(self):
		if self.objMesh:
			print '*** Mesh ***'
			print 'name:    ', self.objMesh.data.name
			print 'textures:', len( self.mesh.materials )
			print 'vertices:', len( self.vertices )
			print 'faces:   ', (self.triCount + self.quadCount)
			print '- tris:  ', self.triCount
			print '- quads: ', self.quadCount
			print 'edges:   ', self.edgeCount
			print 'normals: ', self.normalCount
			print 'maxBoneWeightCount: ', self.maxBoneWeightCount
		if self.objArmature:
			print '*** Armature ***'
			print 'name:    ', self.objArmature.data.name
			print 'bones:   ', len( self.bones )
	
	# create a filename that is matching our file format.
	# overwrite if you need to tickle with the file title.
	def getExportFilename( self, filename ):
		return filename[: -len( filename.split( '.', -1 )[ -1 ] ) ] + self.getExportExtension()
	
	# prepare for exporting. here we check first if there
	# is a problem with exporting.
	def readyForExport( self ):
		self.initExporterObjects()
		self.storeOldValues()
		if not self.checkInitState():
			return False
		return True
	
	# export file
	def export( self, filename ):
		self.file = open(filename, "w")
		try:
			result = self.safeExport()
		finally:
			self.restoreOldValues()
			self.file.close()
		self.printInfos()
		return result



# static mesh exporter
########################
class HLExporterStaticMesh ( DEBaseExporter ):
	
	# constructor
	def __init__( self ):
		DEBaseExporter.__init__( self )
	
	# title of export dialog
	def getExportTitle( self ):
		return 'Export HL2-SMD Static Mesh'
	
	# default extension for export dialog
	def getExportExtension( self ):
		return 'smd'
	
	# find objects to export
	def checkInitState( self ):
		if not self.objMesh or not self.objArmature:
			alertUser( 'There is no Mesh selected with an attached Armature', 'Error' )
			return False
		if not self.mesh.edges:
			self.mesh.addEdgesData()
		if self.multiFoldMesh:
			alertUser( 'Can not Multi-Fold meshes (Edges used by more than 2 faces).', 'Error' )
			return False
		if self.degeneratedFaceCount > 0:
			alertUser( 'Degenerated Faces found.', 'Error' )
			return False
		return True
	
	# helper function to write values without excentric values
	def niceValue( self, value ):
		#if value < 1e-5:
		#	return 0
		#else:
		#	return value
		if fabs( value ) < 1e-6:
			return 0
		else:
			return round( value, 6 )
	
	# write mesh informations
	def writeMesh( self ):
		self.file.write( 'version 1\n' ) # version one file... don't ask me what else could go here
		return True
	
	# write bones
	def writeBones( self ):
		if debugLevel > 0: print 'saving bones...'
		print 'self.matrixArmature'
		print self.matrixArmature
		self.file.write( 'nodes\n' )
		self.file.write( '0 "%s" -1\n' % ( self.armature.name ) )
		if len( self.bones ) > 0:
			for bone in self.bones:
				if bone.parent:
					self.file.write( '%i "%s" %i\n' % ( bone.index + 1, bone.name, bone.parent.index + 1 ) )
				else:
					self.file.write( '%i "%s" %i\n' % ( bone.index + 1, bone.name, -1 ) )
		else:
			self.file.write( '0 "%s" -1\n' % ( self.objMesh.data.name ) )
		self.file.write( 'end\n' )
		self.file.write( 'skeleton\n' )
		self.file.write( 'time 0\n' )
		self.file.write( '%i %f %f %f %f %f %f\n' % ( 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ) )
		if len( self.bones ) > 0:
			for bone in self.bones:
				realBone = bone.bone
				bone.restMat = convertMatrix( realBone.matrix[ 'ARMATURESPACE' ] * self.matrixArmature )
				if realBone.hasParent():
					parentMat = convertMatrix( realBone.parent.matrix[ 'ARMATURESPACE' ] * self.matrixArmature )
					parentMat.invert()
					bone.restMat = bone.restMat * parentMat
				bone.pos = vector_by_matrix( bone.restMat * transformScaleBone, Vector( [ 0, 0, 0 ] ) )
				bone.rot = matrixToEuler( bone.restMat )
				# write bone informations
				if debugLevel > 1:
					print '- bone', bone.name, 'parent', bone.parent, 'pos', bone.pos, 'rot', bone.rot
				self.file.write( '%i %f %f %f %f %f %f\n' % ( bone.index + 1,
					self.niceValue( bone.pos.x ), self.niceValue( bone.pos.y ), self.niceValue( bone.pos.z ),
					self.niceValue( bone.rot.x * ONE_PI ), self.niceValue( bone.rot.y * ONE_PI ), self.niceValue( bone.rot.z * ONE_PI ) ) )
		else:
			self.file.write( '0 0 0 0 0 0 0\n' )
		self.file.write( 'end\n' )
		return True
	
	# write faces
	def writeFaces( self ):
		if debugLevel > 0:
			print 'saving faces...'
		self.file.write( 'triangles\n' )
		matrix = self.matrixObject * transformScalePosition
		for face in self.faces:
			# texture
			material = self.mesh.materials[ self.mesh.faces[ face.index ].materialIndex ]
			# write triangle (for both cases)
			if len( face.vertices ) == 3:
				if debugLevel > 2:
					print '- tri', material.getName(), ':', face.v[ 2 ].index, ',', face.v[ 1 ].index, ',', face.v[ 0 ].index
				self.file.write( '%s\n' % ( material.getName() ) )
				if not self.writeIndexedFace( face, [ 0, 1, 2 ], matrix ):
					return False
			elif len( face.vertices ) == 4:
				if debugLevel > 2:
					print '- quad', material.getName(), ':', face.v[ 3 ].index, ',', face.v[ 2 ].index, ',', face.v[ 1 ].index, ',', face.v[ 0 ].index
				self.file.write( '%s\n' % ( material.getName() ) )
				if not self.writeIndexedFace( face, [ 0, 1, 2 ], matrix ):
					return False
				self.file.write( '%s\n' % ( material.getName() ) )
				if not self.writeIndexedFace( face, [ 0, 2, 3 ], matrix ):
					return False
		self.file.write( 'end\n' )
		return True
	
	# helper function to write one single face using the given indices
	def writeIndexedFace( self, face, indexList, matrix ):
		meshFace = self.mesh.faces[ face.index ]
		for t in indexList:
			vertex = self.vertices[ face.vertices[ t ] ]
			vertPos = vector_by_matrix( matrix, meshFace.v[ t ].co )
			vertNormal = axis_by_matrix( matrix, meshFace.v[ t ].no )
			vertU = meshFace.uv[ t ][ 0 ]
			vertV = meshFace.uv[ t ][ 1 ]
			# write to file
			self.file.write( '0 %f %f %f %f %f %f %f %f %i' % ( self.niceValue( vertPos.x ), self.niceValue( vertPos.y ), self.niceValue( vertPos.z ),
				self.niceValue( vertNormal.x ), self.niceValue( vertNormal.y ), self.niceValue( vertNormal.z ),
				self.niceValue( vertU ), self.niceValue( vertV ), len( vertex.weights ) ) )
			if len( vertex.weights ) > 0:
				for weight in vertex.weights:
					self.file.write( ' %i %f' % ( weight.bone.index + 1, self.niceValue( weight.weight ) ) )
			else:
				self.file.write( ' 0 1' )
			self.file.write( '\n' )
		return True
	
	# protected export to file function
	def safeExport( self ):
		# write file
		Blender.Window.DrawProgressBar( 0.0, 'Writing Mesh Informations' )
		if not self.writeMesh():
			return False
		Blender.Window.DrawProgressBar( 0.1, 'Writing Bone Informations' )
		if not self.writeBones():
			return False
		Blender.Window.DrawProgressBar( 0.5, 'Writing Face Informations' )
		if not self.writeFaces():
			return False
		Blender.Window.DrawProgressBar( 1.0, 'Finished' )
		# finished
		return True	



# animation exporter
######################
class HLExporterAnimation( DEBaseExporter ):
	
	# constructor
	def __init__( self ):
		DEBaseExporter.__init__( self )
		self.moves = []
	
	# title of export dialog
	def getExportTitle( self ):
		return 'Export HL2-SMD Animation Sequence'
	
	# default extension for export dialog
	def getExportExtension( self ):
		return 'smd'
	
	# find objects to export
	def checkInitState( self ):
		if not self.objArmature:
			alertUser( 'There is no Armature selected. Select an Armature or an Object with an attached Armature', 'Error' )
			return False
		return True
	
	# helper function rounding the incoming value to int
	def roundToInt( self, value ):
		return int( value + 0.5 )
	
	# helper function to write values without excentric values
	def niceValue( self, value ):
		#if value < 1e-5:
		#	return 0
		#else:
		#	return value
		if fabs( value ) < 1e-6:
			return 0
		else:
			return round( value, 6 )
	
	# prepare bones
	def prepareBones( self ):
		if debugLevel > 0: print 'prepare bones...'
		for bone in self.bones:
			realBone = bone.bone
			bone.restMat = convertMatrix( realBone.matrix[ 'ARMATURESPACE' ] )
			if realBone.hasParent():
				parentMat = convertMatrix( realBone.parent.matrix[ 'ARMATURESPACE' ] )
				parentMat.invert()
				bone.restMat = bone.restMat * parentMat
			matrix = bone.restMat * self.matrixArmature * transformScaleBone
			bone.pos = vector_by_matrix( matrix, Vector( [ 0, 0, 0 ] ) )
			bone.rot = matrixToEuler( bone.restMat )
			if debugLevel > 1:
				print '- bone', bone.name, 'parent', bone.parent, 'pos', bone.pos, 'rot', bone.rot
		return True
	
	# write animation
	def writeAnimation( self, action ):
		# hack matrix
		matFixAnim = RotationMatrix( 90.0, 4, 'z' )
		# retrieve the poser
		pose = self.objArmature.getPose()
		# process move
		playtime = 0.0
		ipos = action.getAllChannelIpos()
		# determine playtime
		for bone in self.bones:
			if bone.name in ipos:
				boneIpo = ipos[bone.name]
				ipoCurve = boneIpo.getCurve( 'LocX' )
				if ipoCurve:
					for knot in ipoCurve.bezierPoints:
						if knot.pt[ 0 ] > playtime:
							playtime = knot.pt[ 0 ]
				ipoCurve = boneIpo.getCurve( 'LocY' )
				if ipoCurve:
					for knot in ipoCurve.bezierPoints:
						if knot.pt[ 0 ] > playtime:
							playtime = knot.pt[ 0 ]
				ipoCurve = boneIpo.getCurve( 'LocZ' )
				if ipoCurve:
					for knot in ipoCurve.bezierPoints:
						if knot.pt[ 0 ] > playtime:
							playtime = knot.pt[ 0 ]
				ipoCurve = boneIpo.getCurve( 'QuatX' )
				if ipoCurve:
					for knot in ipoCurve.bezierPoints:
						if knot.pt[ 0 ] > playtime:
							playtime = knot.pt[ 0 ]
				ipoCurve = boneIpo.getCurve( 'QuatY' )
				if ipoCurve:
					for knot in ipoCurve.bezierPoints:
						if knot.pt[ 0 ] > playtime:
							playtime = knot.pt[ 0 ]
				ipoCurve = boneIpo.getCurve( 'QuatZ' )
				if ipoCurve:
					for knot in ipoCurve.bezierPoints:
						if knot.pt[ 0 ] > playtime:
							playtime = knot.pt[ 0 ]
				ipoCurve = boneIpo.getCurve( 'QuatW' )
				if ipoCurve:
					for knot in ipoCurve.bezierPoints:
						if knot.pt[ 0 ] > playtime:
							playtime = knot.pt[ 0 ]
		playtime = int( round( playtime, 0 ) )
		if playtime < 1:
			playtime = 1
		if debugLevel > 0: print '- playtime', playtime
		# write header
		self.file.write( 'version 1\n' ) # version one file... don't ask me what else could go here
		if not self.writeNodes():
			return False
		self.file.write( 'skeleton\n' )
		# switch action
		self.switchAction( action )
		# fetch keyframe values
		for time in range( 1, playtime + 1 ):
			self.file.write( 'time %i\n' % ( time - 1 ) )
			self.file.write( '%i %f %f %f %f %f %f\n' % ( 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ) )
			self.switchTime( time )
			# write bones
			for bone in self.bones:
				realBone = bone.bone
				poseBone = pose.bones[ bone.name ]
				# calculate bone matrix
				restMat = convertMatrix( poseBone.poseMatrix * self.matrixArmature ) * matFixAnim
				if realBone.hasParent():
					poseParent = pose.bones[ bone.parent.name ]
					#parentMat = bone.restMat * convertMatrix( poseParent.poseMatrix )
					parentMat = convertMatrix( poseParent.poseMatrix * self.matrixArmature ) * matFixAnim
					parentMat.invert()
					restMat = restMat * parentMat
				pos = vector_by_matrix( restMat * transformScaleBone, Vector( [ 0, 0, 0 ] ) )
				rot = matrixToEuler( restMat )
				# write to file
				self.file.write( '%i %f %f %f %f %f %f\n' % ( bone.index + 1,
					self.niceValue( pos.x ), self.niceValue( pos.y ), self.niceValue( pos.z ),
					self.niceValue( rot.x * ONE_PI ), self.niceValue( rot.y * ONE_PI ), self.niceValue( rot.z * ONE_PI ) ) )
		# finsihed
		self.file.write( 'end\n' )
		return True
	
	# write the node section in the sequence file
	def writeNodes( self ):
		self.file.write( 'nodes\n' )
		self.file.write( '0 "%s" -1\n' % ( self.armature.name ) )
		if len( self.bones ) > 0:
			for bone in self.bones:
				if bone.parent:
					self.file.write( '%i "%s" %i\n' % ( bone.index + 1, bone.name, bone.parent.index + 1 ) )
				else:
					self.file.write( '%i "%s" %i\n' % ( bone.index + 1, bone.name, -1 ) )
		else:
			self.file.write( '0 "%s" -1\n' % ( self.objMesh.data.name ) )
		self.file.write( 'end\n' )
		return True
	
	# create a filename that is matching our file format.
	# overwrite if you need to tickle with the file title.
	def getExportFilename( self, filename ):
		return filename[: -( len( filename.split( '.', -1 )[ -1 ] ) + 1 ) ] \
			+ '_' + self.oldAction.getName().replace( '.', '_' ) \
			+ '.' + self.getExportExtension()
	
	# protected export to file function
	def safeExport( self ):
		# write file
		Blender.Window.DrawProgressBar( 0.0, 'Writing bones' )
		if not self.prepareBones():
			return False
		# write file
		Blender.Window.DrawProgressBar( 0.25, 'Writing animation' )
		if not self.writeAnimation( self.oldAction ):
			return False
		# finished
		Blender.Window.DrawProgressBar( 1.0, 'Finished' )
		return True	



# Animation all exporter
# ----------------------
# This is the same as the animation epxporter
# but it does export all animations using a
# base file.
###############################################
class HLExporterAnimationAll( HLExporterAnimation ):
	
	# constructor
	def __init__( self ):
		HLExporterAnimation.__init__( self )
	
	# build list of moves to export
	def scanMoves( self ):
		actions = Blender.Armature.NLA.GetActions()
		# prepare re programs
		reIgnore = re.compile( '.*\.ignore$' )
		# determine actions to use
		for action in actions.values():
			# check if this action has moves
			ipos = action.getAllChannelIpos()
			if len( ipos ) == 0:
				continue
			# check if the move is marked to be ignored
			if reIgnore.match( action.getName() ):
				continue
			# add move
			self.moves.append( DEMove( action ) )
		return True
	
	# write animations
	def writeAnimations( self ):
		# we export all actions into their own smd files. the stub filename
		# has already been put aside by us.
		countMoves = len( self.moves )
		progressCounter = 0
		for move in self.moves:
			# open the file. we append the move name to the stub filename
			# but replace all dots by a underscore
			filename = self.filename + '_' + move.name.replace( '.', '_' ) + '.smd'
			# update blender feedback
			Blender.Window.DrawProgressBar( float( progressCounter ) / float( countMoves ),
				'Writing Sequence "' + move.name + '" ...' )
			if debugLevel > 0: print '- move', move.name, 'filename', filename
			# open the file
			self.file = open( filename, "w" )
			# write the animation
			if not self.writeAnimation( move.action ):
				return False
			# close the file
			self.file.close()
			self.file = None
			# debug infos
			progressCounter = progressCounter + 1
		return True
	
	# create a filename that is matching our file format.
	# overwrite if you need to tickle with the file title.
	def getExportFilename( self, filename ):
		return filename[: -len( filename.split( '.', -1 )[ -1 ] ) ] + self.getExportExtension()
	
	# protected export to file function
	def safeExport( self ):
		# write file
		if not self.prepareBones() or not self.scanMoves() or not self.writeAnimations():
			return False
		Blender.Window.DrawProgressBar( 1.0, 'Finished' )
		# finished
		return True	
	
	# export file
	def export( self, filename ):
		# we overwrite this function to avoid opening a stub file since
		# we want to use separate files for each animation.
		self.filename = filename[: -( len( filename.split( '.', -1 )[ -1 ] ) + 1 ) ]
		self.file = None # no file, this is the deal here
		try:
			result = self.safeExport()
		finally:
			self.restoreOldValues()
			if self.file: # to make sure we don't get an exception
				self.file.close()
		self.printInfos()
		return result



# output debugging informations
def outputDebug( message ):
	Blender.Draw.PupMenu( 'Half-Life 2 Export%t|' + message )

# alert user of something
def alertUser( message, title ):
	Blender.Draw.PupMenu( 'Half-Life 2 Export ' + title + '%t|' + message )

# helper function to convert matrix from blender to hl2
def convertMatrix( matrix ):
	axisX = Vector( [ -matrix[0][1], -matrix[0][0], matrix[0][2], 0 ] )
	axisX.normalize()
	axisY = Vector( [ -matrix[1][1], -matrix[1][0], matrix[1][2], 0 ] )
	axisY.normalize()
	axisZ = Vector( [ -matrix[2][1], -matrix[2][0], matrix[2][2], 0 ] )
	axisZ.normalize()
	pos = Vector( [ -matrix[3][1], -matrix[3][0], matrix[3][2], 1 ] )
	return Matrix( axisY, axisX, axisZ, pos )

# helper function calculating the euler angles
# from a given converted matrix
def matrixToEuler( matrix ):
	euler = matrix.toEuler()
	return Vector( -euler.x, euler.y, -euler.z )

# helper function to transform a vector by a matrix
def vector_by_matrix( m, p ):
  return Vector( [ p[0] * m[0][0] + p[1] * m[1][0] + p[2] * m[2][0] + m[3][0],
          p[0] * m[0][1] + p[1] * m[1][1] + p[2] * m[2][1] + m[3][1],
          p[0] * m[0][2] + p[1] * m[1][2] + p[2] * m[2][2] + m[3][2] ] )

# helper function to transform an axis vector by a matrix
def axis_by_matrix( m, p ):
  return Vector( [ p[0] * m[0][0] + p[1] * m[1][0] + p[2] * m[2][0],
          p[0] * m[0][1] + p[1] * m[1][1] + p[2] * m[2][1],
          p[0] * m[0][2] + p[1] * m[1][2] + p[2] * m[2][2] ] )

# callback for the file dialog
def fileDialogCallback( filename ):
	if not filename.endswith( '.' + exporter.getExportExtension() ):
		filename = filename + '.' + exporter.getExportExtension()
	exporter.export( filename )



# debugging informations
# 0 = no debug infos
# 1 = a bit of debug infos
# 2 = even more debug infos
# 3 = lots of debug infos, makes your console explode ;=)
debugLevel = 1

# entry point.
args = __script__[ 'arg' ]
exporter = None

if args == 'export_static':
	exporter = HLExporterStaticMesh()
elif args == 'export_anim':
	exporter = HLExporterAnimation()
elif args == 'export_anim_all':
	exporter = HLExporterAnimationAll()
else:
	alertUser( 'Now I am confuzzled. What shall I do with "%s"?!' % ( args ), 'Error' )

if exporter and exporter.readyForExport():
	Blender.Window.FileSelector( fileDialogCallback, exporter.getExportTitle(),
		exporter.getExportFilename( Blender.Get( 'filename' ) ) )
