#!BPY
################
#author: Daniel Jungmann (dsj at gmx dot net)
#version: 2.5
################
"""
Name: 'Eternal-Lands files (.e3d)'
Blender: 243
Group: 'Import'
Tip: 'Import from Eternal-Lands 3d object files.'
"""
import Blender
from Blender import NMesh, Image, Object, Material, Texture, Mesh
from Blender.BGL import *
from Blender.Draw import *
from Blender.Window import *
from Blender.Image import *
from Blender.Mathutils import Vector
import sys, struct, string
from types import *
import os
from os import path
import md5
import gzip
from tempfile import gettempdir

has_normal = 0
has_tangent = 0
has_extra_uv = 0
default_uv_layer = "UVTex"
extra_uv_layer = "Extra UVTex"
alpha_uv_layer = "Alpha UVTex"

def vertex_hash(v):
	return struct.pack("3f", v[0], v[1], v[2])

def normals_hash(n):
	return struct.pack("3i", n[0]*32768, n[1]*32768, n[2]*32768)

class el3d_vertex:
	vertexes = []
	normals = []
	textures = []
	tangents = []
	extra_uvs = []
	binary_format_vertex = "3f"
	binary_format_normal = "3f"
	binary_format_tangent = "3f"
	binary_format_texture = "2f"
	binary_format_extra_uv = "2f"
	def __init__(self):
        	self.vertexes = [0]*3
        	self.normals = [0]*3
		self.textures = [0]*2
        	self.tangents = [0]*3
		self.extra_uvs = [0]*2
	def load(self, file):
		global has_normal, has_tangent, has_extra_uv
		data = file.read(struct.calcsize(self.binary_format_texture))
		self.textures = struct.unpack(self.binary_format_texture, data)
		if (has_extra_uv):
			data = file.read(struct.calcsize(self.binary_format_extra_uv))
			self.extra_uvs = struct.unpack(self.binary_format_extra_uv, data)
		if (has_normal):
			data = file.read(struct.calcsize(self.binary_format_normal))
			self.normals = struct.unpack(self.binary_format_normal, data)
		if (has_tangent):
			data = file.read(struct.calcsize(self.binary_format_tangent))
			self.tangents = struct.unpack(self.binary_format_tangent, data)
		data = file.read(struct.calcsize(self.binary_format_vertex))
		self.vertexes = struct.unpack(self.binary_format_vertex, data)
	def size(self):
		global has_normal, has_tangent, has_extra_uv
		ret = struct.calcsize(self.binary_format_vertex)
		ret += struct.calcsize(self.binary_format_texture)
		if (has_normal):
			ret += struct.calcsize(self.binary_format_normal)
		if (has_tangent):
			ret += struct.calcsize(self.binary_format_tangent)
		if (has_extra_uv):		
			ret += struct.calcsize(self.binary_format_extra_uv)
		return ret

class el3d_index:
	index_val = 0
	binary_format = "1i"
	def load(self, file):
		data = file.read(self.size())
		self.index_val = struct.unpack(self.binary_format, data)[0]
	def size(self):
		return struct.calcsize(self.binary_format)


class el3d_material:
	options = 0
	start = 0
	count = 0
	min_x = 0
	min_y = 0
	min_z = 0
	max_x = 0
	max_y = 0
	max_z = 0
	min_index = 0
	max_index = 0
	material_name = ""
	extra_material_name = ""
	alpha_material_name = ""
	binary_format_material = "1i128s6f4i"
	binary_format_extra_material = "128s"
	binary_format_alpha_material = "128s"
	def load(self, file):
		data = file.read(struct.calcsize(self.binary_format_material))
		data = struct.unpack(self.binary_format_material, data)
		self.options = data[0]
		self.material_name = string.strip(data[1], '\0')
		self.min_x = data[2]
		self.min_y = data[3]
		self.min_z = data[4]
		self.max_x = data[5]
		self.max_y = data[6]
		self.max_z = data[7]
		self.min_index = data[8]
		self.max_index = data[9]
		self.start = data[10]
		self.count = data[11]
		if (has_extra_uv):
			data = file.read(struct.calcsize(self.binary_format_extra_material))
			data = struct.unpack(self.binary_format_extra_material, data)
			self.extra_material_name = string.strip(data[0], '\0')
			data = file.read(struct.calcsize(self.binary_format_alpha_material))
			data = struct.unpack(self.binary_format_alpha_material, data)
			self.alpha_material_name = string.strip(data[0], '\0')
	def size(self):
		global has_normal, has_tangent, has_extra_uv
		ret = struct.calcsize(self.binary_format_material)
		if (has_extra_uv):
			ret += struct.calcsize(self.binary_format_extra_material)
			ret += struct.calcsize(self.binary_format_alpha_material)
		return ret 

class el3d_header:
	magic = "e3dx"
	version = [1, 0, 0, 0]
	header_offset = 0
	index_no = 0
	index_size = 0
	index_offset = 0
	vertex_no = 0
	vertex_size = 0
	vertex_offset = 0
	material_no = 0
	material_size = 0
	material_offset = 0
	vertex_options = 0
	char_reserved_1 = 0
	char_reserved_2 = 0
	char_reserved_3 = 0
	digest = []
	md5_obj = md5.new()
	binary_format = "4c4b16c10i4b"
	binary_format_md5 = "9i4b"
	def check_magic(self, d0, d1, d2, d3):
		if ((self.magic[0] == d0) and (self.magic[1] == d1) and
			(self.magic[2] == d2) and (self.magic[3] == d3)):
			return True
		found_magic_string = str(d0) + str(d1) + str(d2) + str(d3)
		PupMenu("Error: File has wrong magic number! Found: \"" + \
			 found_magic_string + "\" Expected: \"" + self.magic + \
			"\"")
		return False
	def check_version(self, d0, d1, d2, d3):
		if ((self.version[0] == d0) and (self.version[1] == d1) and
			(self.version[2] == d2) and (self.version[3] == d3)):
			return True
		found_version_string = str(d0) + "." + str(d1) + "." + \
			str(d2) + "." + str(d3)
		expected_version_string = str(self.version[0]) + "." + \
			str(self.version[1]) + "." + str(self.version[2]) + \
			"." + str(self.version[3])
		ret = PupMenu("Warning: File has wrong version number!" + \
			" Found: " + found_version_string + \
			" expected: " + expected_version_string + \
			", continue? %t|Yes|No")
		if (ret == 1):
			return True
		else:
			return False
	def check_vertex_size(self):
		obj = el3d_vertex()
		tmp = obj.size()
		if (self.vertex_size == tmp):
			return True
		PupMenu("Error: File has wrong vertex size! Found: \"" + \
			str(self.vertex_size) + "\" Expected: \"" + str(tmp) \
			+ "\"")
		return False
	def check_index_size(self):
		obj = el3d_index()
		tmp = obj.size()
		if (self.index_size == tmp):
			return True
		PupMenu("Error: File has wrong index size! Found: \"" + \
			str(self.vertex_size) + "\" Expected: \"" + str(tmp) \
			+ "\"")
		return False
	def check_material_size(self):
		obj = el3d_material()
		tmp = obj.size()
		if (self.material_size == tmp):
			return True
		PupMenu("Error: File has wrong material size! Found: \"" + \
			str(self.vertex_size) + "\" Expected: \"" + str(tmp) \
			+ "\"")
		return False
	def check_md5(self):
		ret = True
		tmp = self.md5_obj.digest()
		for i in range(0, 16):
			if (tmp[i] != self.digest[i]):
				ret = False
				break
		if (not ret):
			PupMenu("Warning: File has wrong md5!")
		return ret
	def load(self, file):
		global has_normal, has_tangent, has_extra_uv
		data = file.read(self.size())
		data = struct.unpack(self.binary_format, data)
		if (not self.check_magic(data[0], data[1], data[2], data[3])):
			return False
		if (not self.check_version(data[4], data[5], data[6], data[7])):
			return False
		self.digest = [0] * 16
		self.digest[0] = data[8]
		self.digest[1] = data[9]
		self.digest[2] = data[10]
		self.digest[3] = data[11]
		self.digest[4] = data[12]
		self.digest[5] = data[13]
		self.digest[6] = data[14]
		self.digest[7] = data[15]
		self.digest[8] = data[16]
		self.digest[9] = data[17]
		self.digest[10] = data[18]
		self.digest[11] = data[19]
		self.digest[12] = data[20]
		self.digest[13] = data[21]
		self.digest[14] = data[22]
		self.digest[15] = data[23]
		self.header_offset = data[24]
		self.vertex_no = data[25]
		self.vertex_size = data[26]
		self.vertex_offset = data[27]
		self.index_no = data[28]
		self.index_size = data[29]
		self.index_offset = data[30]
		self.material_no = data[31]
		self.material_size = data[32]
		self.material_offset = data[33]
		self.vertex_options = data[34]
		self.char_reserved_1 = data[35]
		self.char_reserved_2 = data[36]
		self.char_reserved_3 = data[37]
		has_normal = (self.vertex_options & 1) == 0
		has_tangent = (self.vertex_options / 2) & 1
		has_extra_uv = (self.vertex_options / 4) & 1
		if (not self.check_vertex_size()):
			return False
		if (not self.check_index_size()):
			return False
		if (not self.check_material_size()):
			return False
		return True
	def size(self):
		return struct.calcsize(self.binary_format)
	def build_md5(self, file):
		self.md5_obj = md5.new()
		file.seek(self.header_offset)
		data = file.read()
		self.md5_obj.update(data)		

def load_el3d(filename):
	vertex_list = []
	index_list = []
	header = el3d_header()
	compressed = True
	try:
		file = gzip.open(filename, 'rb')
		try:
			result = header.load(file)
			compressed = True
		except IOError:
			compressed = False
			file.close()
	except IOError:
		compressed = False
	if (not compressed):
		try:
			file = open(filename, 'rb')
		except IOError, (errno, strerror):
			errmsg = "IOError #%s: %s" % (errno, strerror)
			Blender.Draw.PupMenu(errmsg)
			Exit()
			return
		try:
			result = header.load(file)
		except IOError, (errno, strerror):
			errmsg = "IOError #%s: %s" % (errno, strerror)
			Blender.Draw.PupMenu(errmsg)
			Exit()
			return
	if (not result):
		Exit()
	header.build_md5(file)
	header.check_md5()
	file.seek(header.vertex_offset)
	#read vertices
	for x in range(0, header.vertex_no):
		vertex_list.append(el3d_vertex())
		vertex_list[x].load(file)
	
	fname = Blender.sys.basename(filename)
	name, ext = fname.rsplit('.', 1)
	if (ext == "gz"):
		fname = name
		name, ext = fname.rsplit('.', 1)
	mesh = Mesh.New(name)

	extend_list = []
	vert_dict = {}
	vert_no = 0
	#make the vertices
	for v in vertex_list:
		hash_value = vertex_hash(v.vertexes)
		if not vert_dict.has_key(hash_value):
			vert_dict[hash_value] = vert_no
			vert_no += 1
			extend_list.append(v.vertexes)

	mesh.verts.extend([Vector()])
	mesh.verts.extend(extend_list)
	
	file.seek(header.index_offset)
	#read indicies
	for x in range(0, header.index_no):
		index_list.append(el3d_index())
		index_list[x].load(file)
	
	file.seek(header.material_offset)
	#read materials
	faces = []
	for x in range(0, header.material_no):
		material = el3d_material();
		material.load(file)
		fname = Blender.sys.dirname(filename) + Blender.sys.dirsep + material.material_name
		try:
			img = Blender.Image.Load(fname)
			compress = False
		except IOError:
			compress = True
		if (compress):
			tmp_name = gettempdir() + Blender.sys.dirsep + material.material_name
			try:
				tmpfile = open(tmp_name, "wb")
				imgfile = gzip.open(fname + ".gz", "rb")
				data = imgfile.read()
				tmpfile.write(data)
				tmpfile.close()
				imgfile.close()
				img = Blender.Image.Load(tmp_name)
				os.remove(tmp_name)
			except IOError:			
				Blender.Draw.PupMenu("Error: Could not load image file: " + fname)
				remove(tmp_name)
				Exit()
				return
		if (has_extra_uv):
			fname = Blender.sys.dirname(filename) + Blender.sys.dirsep + material.extra_material_name
			try:
				img2 = Blender.Image.Load(fname)
				compress = False
			except IOError:
				compress = True
			if (compress):
				tmp_name = gettempdir() + Blender.sys.dirsep + material.extra_material_name
				try:
					tmpfile = open(tmp_name, "wb")
					imgfile = gzip.open(fname + ".gz", "rb")
					data = imgfile.read()
					tmpfile.write(data)
					tmpfile.close()
					imgfile.close()
					img2 = Blender.Image.Load(tmp_name)
					os.remove(tmp_name)
				except IOError:			
					Blender.Draw.PupMenu("Error: Could not load image file: " + fname)
					remove(tmp_name)
					Exit()
					return
			fname = Blender.sys.dirname(filename) + Blender.sys.dirsep + material.alpha_material_name
			try:
				img3 = Blender.Image.Load(fname)
				compress = False
			except IOError:
				compress = True
			if (compress):
				tmp_name = gettempdir() + Blender.sys.dirsep + material.alpha_material_name
				try:
					tmpfile = open(tmp_name, "wb")
					imgfile = gzip.open(fname + ".gz", "rb")
					data = imgfile.read()
					tmpfile.write(data)
					tmpfile.close()
					imgfile.close()
					img3 = Blender.Image.Load(tmp_name)
					os.remove(tmp_name)
				except IOError:			
					Blender.Draw.PupMenu("Error: Could not load image file: " + fname)
					remove(tmp_name)
					Exit()
					return
		else:
			img2 = 0
			img3 = 0

		i = material.start
		while (i < (material.start + material.count)):
			idx1 = index_list[i + 0].index_val
			idx2 = index_list[i + 1].index_val
			idx3 = index_list[i + 2].index_val
			hash_value = vertex_hash(vertex_list[idx1].vertexes)
			idx4 = vert_dict[hash_value]
			hash_value = vertex_hash(vertex_list[idx2].vertexes)
			idx5 = vert_dict[hash_value]
			hash_value = vertex_hash(vertex_list[idx3].vertexes)
			idx6 = vert_dict[hash_value]
			faces.append([[idx4 + 1, idx5 + 1, idx6 + 1], [idx1, idx2, idx3], [img, img2, img3]])
			i += 3
	file.close()
	face_mapping = mesh.faces.extend([f[0] for f in faces], indexList = True)
	mesh_faces = mesh.faces
	mesh.addUVLayer(default_uv_layer)
	if (has_extra_uv):
		mesh.addUVLayer(alpha_uv_layer)
		mesh.addUVLayer(extra_uv_layer)
	mesh.activeUVLayer = default_uv_layer
	mesh.faceUV = True
	for i, face in enumerate(faces):
		face_index_map = face_mapping[i]
		if (face_index_map != None): # None means the face wasnt added
			blender_face = mesh_faces[face_index_map]
			idx = face[1]
			index = [0] * len(idx)
			for i in range(0, len(idx)):
				blender_face.uv[i][0] = vertex_list[idx[i]].textures[0]
				blender_face.uv[i][1] = vertex_list[idx[i]].textures[1]
				index[i] = normals_hash(vertex_list[idx[i]].normals)
				if (has_extra_uv):
					mesh.activeUVLayer = extra_uv_layer
					blender_face.uv[i][0] = vertex_list[idx[i]].extra_uvs[0]
					blender_face.uv[i][1] = vertex_list[idx[i]].extra_uvs[1]
					mesh.activeUVLayer = alpha_uv_layer
					blender_face.uv[i][0] = vertex_list[idx[i]].extra_uvs[0]
					blender_face.uv[i][1] = vertex_list[idx[i]].extra_uvs[1]
					mesh.activeUVLayer = default_uv_layer
			if ((index[0] == index[1]) and (index[1] == index[2])):
				blender_face.smooth = False
			else:
				blender_face.smooth = True
			blender_face.image = face[2][0]
			if (has_extra_uv):
				mesh.activeUVLayer = extra_uv_layer
				blender_face.image = face[2][1]
				mesh.activeUVLayer = alpha_uv_layer
				blender_face.image = face[2][2]
				mesh.activeUVLayer = default_uv_layer
	mesh.verts.delete(0)
	scene = Blender.Scene.GetCurrent()
	scene.objects.new(mesh)
	Blender.Redraw()
	print "Done e3d import from file: ", filename

def select_load_el3d(filename):
	dir_name = os.path.dirname(filename)
	load_dir = Blender.Draw.PupMenu("Load all files in dir '" + dir_name + "' or file '" + filename + "'?%t|Load dir|Load file")
	if (load_dir == 1):
		file_list = os.listdir(dir_name)
		for file_name in file_list:
			if (os.path.isfile(os.path.join(dir_name, file_name))):
				fname = file_name
				name, ext = fname.rsplit('.', 1)
				if (ext == "gz"):
					fname = name
					name, ext = fname.rsplit('.', 1)
				if (ext == "e3d"):
					load_el3d(os.path.join(dir_name, file_name))
	else:
		load_el3d(filename)

Blender.Window.FileSelector(select_load_el3d, "Import e3d")
