#!BPY
 
"""
Name: 'morph .tri (.tri)...'
Blender: 249
Group: 'Export'
Tooltip: 'Write a morph TRI file.'
"""

__author__= "Core script by kapaer, modvertice support by deedes"
__version__= "0.2.1"

__bpydoc__= """\
This script exports a morph TRI file from Blender.

Usage:
Run this script from "File->Export" menu and then write the desired TRI file.
Only one object can be exported at once.
Note, this script uses "_[number]" to export regular morphs and "_(number)" to export modifiers.

deedes:
Please realize that this is a very crude attempt at getting .tri files to work correctly.
While this script works for me I can not guarantee that it is failsave. I'll try to fix bugs if they are found - but do not expect me to.
"""

# ***** BEGIN GPL LICENSE BLOCK *****
#
#	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 3 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, see <http://www.gnu.org/licenses/>
#
# ***** END GPL LICENCE BLOCK *****
# --------------------------------------------------------------------------

import bpy
import Blender
from Blender import Mesh, Draw, Window, Texture, Material, sys

import BPyMesh
import BPyMessages

import re
from itertools import izip

try:
	from struct import pack
except:
	pack= None

VERSION_STRING = 'FRTRI003'
INT_LEN = 4
FLOAT_LEN = 4
SHORT_LEN = 2

# Header
class fileHeader:
	str				= ''	#Header with version number / magic number
	vertexNum		= 0		#number of vertices on the base mesh
	faceNum			= 0		#number of faces on the base mesh
	uvNum			= 0		#number of Vectors for the UV on the base mesh
	morphNum		= 0		#number of regular morphs
	addMorphNum		= 0		#number of modifier morphs
	addVertexNum	= 0		#number of additional vertices for modifier morphs

	def __init__(self):
		self.str			= ''
		self.vertexNum		= 0
		self.faceNum		= 0
		self.uvNum			= 0
		self.morphNum		= 0
		self.addMorphNum	= 0
		self.addVertexNum	= 0

	def dump(self):
		print 'Header str: ', self.str
		print 'vertexNum: ', self.vertexNum
		print 'faceNum: ', self.faceNum
		print 'uvNum: ', self.uvNum
		print 'morphNum: ', self.morphNum
		print 'addMorphNum: ', self.addMorphNum
		print 'addVerterxNum: ', self.addVertexNum
		
	def writeFile(self):
		return pack('<8s14I',	self.str,
								self.vertexNum,
								self.faceNum,
								0,0,0,
								self.uvNum,
								1,
								self.morphNum,
								self.addMorphNum,
								self.addVertexNum,
								0,0,0,0)

def veckey2d(v):
	return round(v.x, 6), round(v.y, 6)


# write objects
def write_mesh(filename):

	header				= fileHeader()						#construct an empty header
	scn					= bpy.data.scenes.active			#source of the mesh
	ob					= scn.objects.active				#grab the object
	mesh				= ob.getData(mesh=True)				#get its mesh data

	p					= re.compile('(.*) \\[\\d+\\]$')	#needed to extract the morph's name
	q					= re.compile('(.*) \\(\\d+\\)$')	#needed to extract the modifier's name

	shapeKeys			= mesh.key	#shape key object
	
	modHeaderPacked		= ''	#string to collect the header data
	modVerticePacked	= ''	#string to collect the vertice data
	morphKeysPacked		= ''	#string to collect the morph key data to write
	
	if shapeKeys:
		print "Found Shape Keys. Gathering Data..."
		shapeKeys = mesh.key.blocks
		verts = shapeKeys[0].getData()
		for i in range(len(shapeKeys) - 1):		#goes thru all shape keys
			morphName = shapeKeys[i+1].name		#gets the key's name
			m = p.match(morphName)		#grabs the name and looks if it matches a regular morph
			n = q.match(morphName)		#<the same, just for modifiers
			shape_verts =  shapeKeys[i+1].getData()	#gets the shape key's data
			verts_diff = []		#list to save vertices to
			if m:	#if the match was sucessful, the morph will be processed
				header.morphNum += 1
				morphName = m.group(1)
				max_diff = 0
				morphKeysPacked += pack('<I'+str(len(morphName))+'sx', len(morphName)+1, morphName)
				for bv, nv in izip(verts, shape_verts):
					data = [nv.x - bv.x, nv.y - bv.y, nv.z - bv.z]	#creates a vector
					#looks for the size of the vector and gets the biggest distance
					if abs(data[0]) > max_diff:
						max_diff = abs(data[0])
					if abs(data[1]) > max_diff:
						max_diff = abs(data[1])
					if abs(data[2]) > max_diff:
						max_diff = abs(data[2])

					verts_diff.append(data)

				if ROTATE_X90:	#rotate the mesh
					verts_diff[:] = [(v[0], v[2], -v[1]) for v in verts_diff]

				diff_base = max_diff / 0x7fff
				if diff_base == 0:	#check if the morph is identical to the base mesh.. 
					diff_base = 1
					print "  "+morphName+" is identical to the base morph."
				morphKeysPacked += pack('<f', diff_base)	#add the multiplier to the string buffer for the morphs
	
				for v in verts_diff:	#add the vectors to the string buffer for the morphs
					morphKeysPacked += pack('<3h', int(v[0]/diff_base), int(v[1]/diff_base), int(v[2]/diff_base))

#				if not (-32768 <= int(v[0]/diff_base) <= 32767):
#					print "over"
#				if not (-32768 <= int(v[1]/diff_base) <= 32767):
#					print "over"
#				if not (-32768 <= int(v[2]/diff_base) <= 32767):
#					print "over"

				print "  "+morphName+" ["+str(header.morphNum)+"] loaded... "

			elif n:		#if the match was sucessful, the modifier will be processed
				header.addMorphNum += 1
				morphName = n.group(1)
				#store the name in the string buffer
				modHeaderPacked += pack('<I'+str(len(morphName))+'sx', len(morphName)+1, morphName)
				verticeCount 	= 0	#keeps track of the number of vertices
				verticeAdded	= 0	#keeps track of the number of vertices which were actually added to the additional vertex list
				for nv, mv in izip(shape_verts, verts): #go thru all the vertices,
					div = abs(nv.x - mv.x) + abs(nv.y - mv.y) + abs(nv.z - mv.z) / 3
					if div > 0.001:		#filter out the vertices which are too similiar to the base mesh
						data = [nv.x, nv.y, nv.z, verticeCount]
						verts_diff.append(data)	#add them to the list
						header.addVertexNum += 1
						verticeAdded += 1
					verticeCount += 1

				if ROTATE_X90:
					verts_diff[:]	= [(v[0], v[2], -v[1]) for v in verts_diff]

				modHeaderPacked		+= pack('<I', verticeAdded)
				for v in verts_diff:	#write all vertices into the string buffer
					modHeaderPacked	+= pack('<I', int(v[3]))
					modVerticePacked	+= pack('<3f', v[0], v[1], v[2])
				print "  "+morphName+" ("+str(header.addMorphNum)+") loaded...\n\t"+str(len(verts_diff))+" Vertices kept."

			else:	# the name of the shape key did not match a morph or modifier so the user will be informed to rename the shapekey
				raise ValueError("All shape Keys need a Index number in Brackets \"[]\" (regular Morphs) or Parentheses \"()\" (deform Morphs) "+morphName+" did not meet this conditions.")
	else:
		print "no Shapekeys found. Just writing the Base mesh."
		verts = mesh.verts

	# obtain the face data
	faces = mesh.faces

	header.vertexNum = len(verts)
	header.faceNum = len(faces)
	header.str = VERSION_STRING

	# check double UV

	uvDataPacked = ''
	uv_face_mapping = [[0,0,0,0] for f in faces]
	uv_dict = {}
	for f_index, f in enumerate(faces):
		for uv_index, uv in enumerate(f.uv):
			uvkey = veckey2d(uv)
			try:
				uv_face_mapping[f_index][uv_index] = uv_dict[uvkey]
			except:
				uv_face_mapping[f_index][uv_index] = uv_dict[uvkey] = len(uv_dict)
				uvDataPacked += pack('<2f', uv.x, uv.y)

	header.uvNum = len(uv_dict)
	del uv, uvkey, uv_dict, f_index, uv_index

	print "Preparing primary mesh ..."
	# vertex packing
	vertexDataPacked = ''
	for v in verts:
		if shapeKeys:
			vv = [v.x, v.y, v.z]
		else:
			vv = [v.co[0], v.co[1], v.co[2]]
		if ROTATE_X90:
			tmp_data = pack('<3f', vv[0], vv[2], -vv[1])
		else:
			tmp_data = pack('<3f', vv[0], vv[1], vv[2])
		vertexDataPacked += tmp_data
	
	# face packing
	faceDataPacked = ''
	for f in faces:
		faceDataPacked += pack('<3I', f.v[0].index, f.v[1].index, f.v[2].index)

	faceNumDataPacked = ''
	for i in range(header.faceNum):
		faceNumDataPacked +=  pack('<3I', uv_face_mapping[i][0], uv_face_mapping[i][1], uv_face_mapping[i][2])
	del uv_face_mapping

	# start writing...
	file = open(filename,'wb')
	print "Writing file ..."
	file.write(header.writeFile()
					+ vertexDataPacked
					+ modVerticePacked
					+ faceDataPacked
					+ uvDataPacked
					+ faceNumDataPacked
					+ morphKeysPacked
					+ modHeaderPacked)
	file.close()
	del header, vertexDataPacked, modVerticePacked, faceDataPacked, uvDataPacked, faceNumDataPacked, morphKeysPacked, modHeaderPacked

# menu
def exportMenu():
	# IMPORT_AS_INSTANCE= Blender.Draw.Create(0)
	global ROTATE_X90
	ROTATE_X90 = Blender.Draw.Create(1)
	ROTATE_X90.val = 0
	
	# Get USER Options
	pup_block= [\
	('Rotate X90', ROTATE_X90, 'Rotate X 90')\
	]
	
	if not Blender.Draw.PupBlock('Import Tri...', pup_block):
		return
	
	Blender.Window.WaitCursor(1)
	
	ROTATE_X90 = ROTATE_X90.val


# export
def write_tri(filename):

	if not filename.lower().endswith('.tri'):
		filename += '.tri'
	
	if not BPyMessages.Warning_SaveOver(filename):
		return
	
	print '\n\nExporting tri: "%s"' % (Blender.sys.expandpath(filename))

	# options
	exportMenu()

	write_mesh(filename)
	print "... finished"

# main
scn = bpy.data.scenes.active
if len(scn.objects.selected) != 1:
	raise ValueError("select one object only.")
if __name__=='__main__':
	if pack == None:
		Blender.Draw.PupMenu('Error%t|a full python installation not found')
	else:
		Blender.Window.FileSelector(write_tri, 'Export tri', sys.makename(ext='.tri'))


