#!BPY

""" Registration info for Blender menus
Name: 'Normal Based Mapping'
Blender: 248
Group: 'UV'
Tooltip: 'creates mapping coordinates based on the normals of a mapping help object'
"""

__author__= "Sammler Rene"
__url__ = ("http://www.blender.org", "http://www.sammler-mediengestaltung.com")
__version__ = "v070807"	

__bpydoc__ = """\
***** BEGIN GPL LICENSE BLOCK *****

This program is free software; you can redistribute it and/or<br>
modify it under the terms of the GNU General Public License<br>
as published by the Free Software Foundation; either version 2<br>
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, 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

***** END GPL LICENCE BLOCK *****
--------------------------------------------------------------------------

Usage:

First create a mapping help object (it has to be a mesh object) and set the rotation (the size and
location don't matter). Than select all the objects you want to map. At last select the mapping help 
object, make your settings on the script GUI and press 'Do Mapping'.
(The different mapping groups based on the normals of the polygons of the mapping help object.)

WARNING for the option 'create material groups':<br>
  - there can be only 16 material groups for 1 object, all remaining polygones will set to the<br> 
    material group 16<br>
  - any polygons which can't set to a mapping group will be inserted to a seperate material group<br>
    on material index 1<br>
"""

import Blender
from Blender.Draw import *
from Blender.BGL import *
from Blender.Mathutils import *
from Blender.Types import *
from Blender import Object, Window, Material
from math import *


class Projection_Group:
	
	def __init__(self):
		self._normal = Vector(0,0,0)
		self._face_lst = []
		self._min_x = 1000000.0
		self._max_x = -1000000.0
		self._min_y = 1000000.0
		self._max_y = -1000000.0
		self._is_material = False




class Normal_B_Map:
	
	def __init__(self):
		self.__projection_groups = []
		
		self.__mapping_gizmo = None
		self.__gizmo_mesh = None
		self.__mapping_obj = None
		self.__obj_mesh = None
		
		self._threshold = 0.0
		
		self.__materialGroups = []
		self.__material = None

	def __clear(self):
		self.__projection_groups = []
		self.__mapping_gizmo = None
		self.__gizmo_mesh = None		
		self.__mapping_obj = None
		self.__obj_mesh = None
		self.__materialGroups = []
		
	def __create_projection_groups(self):
		obj_matrix = Matrix(self.__mapping_obj.getMatrix())
		gizmo_matrix = Matrix(self.__mapping_gizmo.getMatrix())
		
		rot_obj = Matrix(obj_matrix.rotationPart())
		rot_gizmo = Matrix(gizmo_matrix.rotationPart())
		rot_obj.invert()
		rotation = rot_gizmo * rot_obj
		
		mapping_group = Projection_Group()
		mapping_group._normal = None
		
		self.__projection_groups.append(mapping_group)
		
		face_lst = self.__gizmo_mesh.faces
		
		for face in face_lst:
			tmp = Vector(face.no) * rotation
			if tmp.length != 0:
				mapping_group = Projection_Group()
				mapping_group._normal = tmp
				self.__projection_groups.append(mapping_group)
	
	def __fill_projection_groups(self, flags = [False, False]):
		threshold = 180 * self._threshold
		tmp = [180, 0]
		face_lst = self.__obj_mesh.faces
		for i in range (0, len(face_lst)):
			if not flags[0] or face_lst[i].sel:	
				if len(self.__projection_groups) > 1:
					for j in range(1, len(self.__projection_groups)):
						tmp_no = Vector(face_lst[i].no)
						if tmp_no.length != 0:
							angle = AngleBetweenVecs(tmp_no, self.__projection_groups[j]._normal)
							if tmp[0] > angle:
								tmp[0] = angle
								tmp[1] = j
						else:
							tmp = [180, 0]
				if  tmp[0] <= threshold and len(self.__projection_groups) > 1:
					self.__projection_groups[tmp[1]]._face_lst.append(i)
				else: 
					if flags[1]:
						if not self.__projection_groups[0]._is_material:
							self.__projection_groups[0]._is_material = True
							self.__materialGroups.append(self.__material)							
						face_lst[i].materialIndex = 0
						
					self.__projection_groups[0]._face_lst.append(i)
					for k in range(0, len(face_lst[i].v)):
						try:
							self.__projection_groups[0]._min_x = min(face_lst[i].uv[k][0], self.__projection_groups[0]._min_x)
							self.__projection_groups[0]._max_x = max(face_lst[i].uv[k][0], self.__projection_groups[0]._max_x)
							self.__projection_groups[0]._min_y = min(face_lst[i].uv[k][1], self.__projection_groups[0]._min_y)
							self.__projection_groups[0]._max_y = max(face_lst[i].uv[k][1], self.__projection_groups[0]._max_y)
						except:
							face_lst[i].uv.append((face_lst[i].v[k].co[0], face_lst[i].v[k].co[1]))
							self.__projection_groups[0]._min_x = min(face_lst[i].uv[k][0], self.__projection_groups[0]._min_x)
							self.__projection_groups[0]._max_x = max(face_lst[i].uv[k][0], self.__projection_groups[0]._max_x)
							self.__projection_groups[0]._min_y = min(face_lst[i].uv[k][1], self.__projection_groups[0]._min_y)
							self.__projection_groups[0]._max_y = max(face_lst[i].uv[k][1], self.__projection_groups[0]._max_y)

				tmp = [180, 0] 	
			
	def __map_face(self, face, rot_mat):
		x = [1000000,-1000000]
		y = [1000000,-1000000]
		for i in range(0, len(face.v)):
			tmp = face.v[i].co * rot_mat
			try:
				face.uv[i] = (tmp[0],tmp[1])
			except:
				face.uv.append((tmp[0],tmp[1]))
			x[0] = min(x[0],tmp[0])
			x[1] = max(x[1],tmp[0])
			y[0] = min(y[0],tmp[1])
			y[1] = max(y[1],tmp[1])
		return x, y

	def __set_uv_coords(self, face, scale, base_x, base_y, min_x, max_y):
		for i in range(0, len(face.v)):
			if len(face.uv) == len(face.v):
				tmp = face.uv[i]
				tmp_1 = (((tmp[0] * 100) * scale) / 100) + (base_x - min_x)
				tmp_2 = (((tmp[1] * 100) * scale) / 100) + (base_y - max_y)
				face.uv[i] = (tmp_1, tmp_2)

	def __arrange_uv_groups(self, anz):
		line_count = ceil(sqrt(anz))
		tmp_max_width = -1
		tmp__max_height = 0
		tmp_width = 0
		tmp_height = -1
		tmp_count = line_count
		for mapping_group in self.__projection_groups:
			if len(mapping_group._face_lst) > 0:
				tmp_width += abs(mapping_group._max_x - mapping_group._min_x)
				tmp_height = max(tmp_height, abs(mapping_group._max_y - mapping_group._min_y))
				tmp_count -= 1
				if tmp_count == 0:
					tmp_count = line_count
					if tmp_max_width < tmp_width: tmp_max_width = tmp_width
					tmp__max_height += tmp_height
					tmp_height = -1
					tmp_width = 0
		scale_x = 1 / tmp_max_width
		scale_y = 1 / tmp__max_height
		scale = min(scale_x, scale_y)
		tmp_count = line_count
		base_pos = [0,1]
		tmp_height = -1
		for mapping_group in self.__projection_groups:
			if len(mapping_group._face_lst) > 0:
				mapping_group._min_x = ((mapping_group._min_x * 100) * scale) / 100
				mapping_group._max_x = ((mapping_group._max_x * 100) * scale) / 100
				mapping_group._min_y = ((mapping_group._min_y * 100) * scale) / 100
				mapping_group._max_y = ((mapping_group._max_y * 100) * scale) / 100
				face_lst = self.__obj_mesh.faces
				for i in mapping_group._face_lst:
					self.__set_uv_coords(face_lst[i], scale, base_pos[0], base_pos[1], mapping_group._min_x, mapping_group._max_y)
				tmp_count -= 1
				tmp_height = max(tmp_height, abs(mapping_group._max_y - mapping_group._min_y))
				if tmp_count == 0:
					base_pos[0] = 0
					base_pos[1] -= tmp_height
					tmp_height = -1
					tmp_count = line_count
				else:
					base_pos[0] += abs(mapping_group._max_x - mapping_group._min_x)

	def create_mapping(self, obj, gizmo, flags = [False, False]):
		ret = ""
		self.__clear()
		self.__material = Material.New('Material')
		self.__mapping_gizmo = gizmo
		self.__gizmo_mesh = Blender.NMesh.GetRawFromObject(self.__mapping_gizmo.name)
		self.__mapping_obj = obj
		self.__obj_mesh = Blender.NMesh.GetRawFromObject(self.__mapping_obj.name)
		
		if self.__gizmo_mesh and self.__obj_mesh:
			self.__create_projection_groups()
			self.__fill_projection_groups(flags)	
			face_lst = self.__obj_mesh.faces
			tmp = 0	
			tmp_x = None
			tmp_y = None
			for map_group in self.__projection_groups:
				if len(map_group._face_lst) > 0: tmp += 1
				if map_group._normal:
					rot_angle = AngleBetweenVecs(Vector(0,0,1), map_group._normal) * -1
					rot_axis = CrossVecs(Vector(0,0,1), map_group._normal)
					if rot_axis == Vector(0.0,0.0,0.0):
						rot_axis = Vector(0,1,0)
					rot_mat = RotationMatrix(rot_angle, 3, "r", rot_axis)
					for i in map_group._face_lst:
						tmp_x, tmp_y = self.__map_face(face_lst[i], rot_mat)
						map_group._min_x = min(map_group._min_x, tmp_x[0])
						map_group._max_x = max(map_group._max_x, tmp_x[1])
						map_group._min_y = min(map_group._min_y, tmp_y[0])
						map_group._max_y = max(map_group._max_y, tmp_y[1])
						if flags[1]:
							if not map_group._is_material:
								map_group._is_material = True
								if len(self.__materialGroups) < 16:
									self.__materialGroups.append(self.__material)
								else: 
									ret = "WARNING: To many material groups!"
									print "WARNING: To many material groups!"
							face_lst[i].materialIndex = len(self.__materialGroups)-1						
			self.__arrange_uv_groups(tmp)
			
			if flags[1]:
				self.__mapping_obj.setMaterials(self.__materialGroups)
				self.__mapping_obj.colbits = int(pow(2, len(self.__materialGroups)) - 1)
				print self.__mapping_obj.colbits
				print int(pow(2, len(self.__materialGroups))-1)
			
			Blender.NMesh.PutRaw(self.__obj_mesh, self.__obj_mesh.name, 0 , 0)
		return ret			





class Normal_B_Map_GUI:
	
	def __init__(self, x, y, norm_b_map = None):
		self.x = x
		self.y = y
		
		self.__norm_b_map = norm_b_map
		
		self.__btn_do_mapping = Create(1)
		self.__btn_do_mapping_vals = {"name":"Do Mapping","evt":100, "x":self.x+5, "y":self.y+35, "width":110, "height":20, "tt":"creates the uv mapping"}
		self.__btn_exit = Create(1)
		self.__btn_exit_vals = {"name":"Exit","evt":101 ,"x": self.__btn_do_mapping_vals['x']+self.__btn_do_mapping_vals['width']+5, "y":self.__btn_do_mapping_vals['y'], "width":110, "height":20, "tt":"exit the script"}
			
		self.__number_threshold = Create(0.0)
		self.__number_threshold_vals = {"name":"threshold","evt":0 ,"x": self.__btn_do_mapping_vals['x'], "y":self.__btn_do_mapping_vals['y'] + self.__btn_do_mapping_vals['height'] + 12, "width":225, "height":20,  "min":0.0, "max":1.0, "tt":"limit of tolerance for the different mapping groups in percent"}			

		self.__toggle_mat_groups = Create(0)
		self.__toggle_mat_groups_vals = {"name":"Create MatGroups","evt":150 ,"x": self.__number_threshold_vals['x'], "y":self.__number_threshold_vals['y'] + self.__number_threshold_vals['height'] + 5, "width":110, "height":20, "tt":"creates a material group for every mapping group"}
		self.__toggle_only_sel = Create(0)
		self.__toggle_only_sel_vals = {"name":"Only Sel. Faces","evt":151 ,"x": self.__toggle_mat_groups_vals['x'] + self.__toggle_mat_groups_vals['width'] + 5, "y":self.__toggle_mat_groups_vals['y'], "width":110, "height":20, "tt":"creates the mapping only for selected faces"}	
		
		self.__lines_vals = {"width":235, "height":2, "x":self.x, "y":\
			[self.__btn_do_mapping_vals['y']+self.__btn_do_mapping_vals['height']+3,\
			self.__toggle_mat_groups_vals['y']+self.__toggle_mat_groups_vals['height']+3]}		
		
		self.__text_vals = {"x":[self.x + 15, self.x+5,self.x+5],\
			 "y":[self.y+5,\
				self.__toggle_mat_groups_vals['y']+self.__toggle_mat_groups_vals['height']+10,\
				self.__toggle_mat_groups_vals['y']+self.__toggle_mat_groups_vals['height']+30],\
				"text":["created by Rene Sammler (sammler-mediengestaltung.com)", "mapping settings", "Normal Based Mapping (v 07 08 07)"],\
				"size":["tiny", "small", "normal"]}

		self.__FAILUR = ""
		
		self._import_config()

	def _import_config(self):
		conf = Blender.Registry.GetKey("NORMBASEMAP", True)		
		if not conf:
			return		
		try:
			self.__number_threshold.val = conf["threshold"]
			self.__toggle_mat_groups.val = conf["MATGRP"]
			self.__toggle_only_sel.val = conf["SEL"]
		except KeyError:
			pass

	def _export_config(self):
		conf = {}
		
		conf["threshold"] = self.__number_threshold.val
		conf["MATGRP"] = self.__toggle_mat_groups.val
		conf["SEL"] = self.__toggle_only_sel.val
		
		Blender.Registry.SetKey("NORMBASEMAP", conf, True)

	def __print_failur(self):
		if self.__FAILUR != "":
			print self.__FAILUR
	
	def __type_check(self, obj_lst):
		ret = True
		self.__FAILUR = ""
		if len(obj_lst) < 2: 
			self.__FAILUR = "ERROR: Select two Meshobjects first!!"
			ret = False
		else:
			for i in obj_lst:
				if type(i.getData()) != NMeshType: 
					self.__FAILUR = "ERROR: '" + i.name + "' isn't a mesh object!"
					ret = False
					return ret
		return ret
	
	def __GUI (self):
		
		glColor3f(0.7,0.7,0.7)
		glRecti(self.x,self.y+15, self.__btn_exit_vals['x'] + self.__btn_exit_vals['width'] + 5, self.__toggle_mat_groups_vals['y']+self.__toggle_mat_groups_vals['height']+25)
		glColor3f(0.9,0.9,0.9)
		glRecti(self.x,self.y+15, self.__btn_exit_vals['x'] + self.__btn_exit_vals['width'] + 5, self.y+30)
		glColor3f(0.65,0.65,0.65)
		glRecti(self.x, self.__toggle_mat_groups_vals['y']+self.__toggle_mat_groups_vals['height']+25, self.__btn_exit_vals['x'] + self.__btn_exit_vals['width'] + 5, self.__toggle_mat_groups_vals['y']+self.__toggle_mat_groups_vals['height']+40)		

		tmp = self.__btn_do_mapping_vals	
		self.__btn_do_mapping = Button(tmp['name'], tmp['evt'], tmp['x'], tmp['y'], tmp['width'], tmp['height'], tmp['tt'])
		tmp = self.__btn_exit_vals	
		self.__btn_exit = Button(tmp['name'], tmp['evt'], tmp['x'], tmp['y'], tmp['width'], tmp['height'], tmp['tt'])

		tmp = self.__toggle_mat_groups_vals	
		self.__toggle_mat_groups = Toggle(tmp['name'], tmp['evt'], tmp['x'], tmp['y'], tmp['width'], tmp['height'], self.__toggle_mat_groups.val, tmp['tt'])		
		tmp = self.__toggle_only_sel_vals	
		self.__toggle_only_sel = Toggle(tmp['name'], tmp['evt'], tmp['x'], tmp['y'], tmp['width'], tmp['height'], self.__toggle_only_sel.val, tmp['tt'])		
		
		tmp = self.__number_threshold_vals	
		self.__number_threshold = Number(tmp['name'], tmp['evt'], tmp['x'], tmp['y'], tmp['width'], tmp['height'], self.__number_threshold.val, tmp['min'], tmp['max'], tmp['tt'])		
	
		glColor3f(0.15,0.15,0.15)
		for tmp in self.__lines_vals['y']:		
			glRecti(self.__lines_vals['x'],tmp,self.__lines_vals['x']+self.__lines_vals['width'],tmp+self.__lines_vals['height'])

		for tmp in range(0, len(self.__text_vals['y'])):
			if tmp == len(self.__text_vals['y']) - 1:
				glColor3f(0.0,0.0,0.0)	
			glRasterPos2f(self.__text_vals['x'][tmp],self.__text_vals['y'][tmp])
			Text(self.__text_vals['text'][tmp], self.__text_vals['size'][tmp])

		glColor3f(1.0,0.0,0.0)
		glRasterPos2f(self.x+5, self.y+20)
		Text(self.__FAILUR)
		
	def __Event(self, evt, val):
		if evt == QKEY:
			self._export_config()
			Exit()
			return	
				
	def __BEvent(self,evt):
		if evt == self.__btn_do_mapping_vals['evt']:
			self.__FAILUR = ""
			if self.__type_check(Blender.Object.GetSelected()):
				gizmo = Blender.Object.GetSelected()[0]
				for i in range (1, len(Blender.Object.GetSelected())):
					obj = Blender.Object.GetSelected()[i]
					self.__norm_b_map._threshold = self.__number_threshold.val
					self.__FAILUR = self.__norm_b_map.create_mapping(obj, gizmo, [self.__toggle_only_sel.val, self.__toggle_mat_groups.val])
					print "Mapping for '" + obj.name + "' is complete."
					if self.__FAILUR == "":
						self.__FAILUR = "object %d of %d is done." % (i , len(Blender.Object.GetSelected())-1)
					self.__print_failur()
			else:
				self.__print_failur()
			print "-----------------------------------------------------"
		elif evt == self.__btn_exit_vals['evt']:
			self._export_config()
			Exit()
			return	
		Blender.Redraw(1)			
	
	def run(self):
		Register(self.__GUI, self.__Event, self.__BEvent)
		Blender.Redraw(1)


if __name__ == '__main__':
	mapping_obj = Normal_B_Map()
	script = Normal_B_Map_GUI(10,10,mapping_obj)
	script.run()