#!BPY

""" Registration info for Blender menus: <- these words are ignored
Name: 'Projection UV Mapper'
Blender: 248
Group: 'UV'
Tooltip: 'Progection UV Mapper - Uses selected cameras with a mesh.'
"""
# -------------------------------------------------------------------------- 
# ***** 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 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
# 
# ***** END GPL LICENCE BLOCK ***** 
# -------------------------------------------------------------------------- 

import Blender
Vector = Blender.Mathutils.Vector
Matrix = Blender.Mathutils.Matrix
AngleBetweenVecs = Blender.Mathutils.AngleBetweenVecs

#==================#
# Apply Transform  #
#==================#

def vecApplyMatrix(vec, mat):
	"""Applies a transformation matrix to a vector.
		 Assumes the vector is an XYZ vector.
	"""
	return\
	Vector(\
	vec[0]*mat[0][0] + vec[1]*mat[1][0] + vec[2]*mat[2][0] + mat[3][0],\
	vec[0]*mat[0][1] + vec[1]*mat[1][1] + vec[2]*mat[2][1] + mat[3][1],\
	vec[0]*mat[0][2] + vec[1]*mat[1][2] + vec[2]*mat[2][2] + mat[3][2],\
	)


def main():
	scn = Blender.Scene.GetCurrent()
	
	camObLs = [\
	  (ob, data) for ob, data in\
	    [(ob, ob.data) for ob in scn.objects if ob.getType()=='Camera' ] \
	  if data.getType()==1\
	] # CamType == 1: Ortho Camera objects used for projection
	
	if not camObLs:
		if not Blender.bylink:
			Blender.Draw.PupMenu('error, no orthographic cameras, add a property named "image" to the camera you want to use for projection')
		return
	if Blender.bylink:
		# Script link
		meObLs = [(ob, ob.getData(mesh=1) ) for ob in scn.getChildren() if ob.getType() == 'Mesh' if ob.Layers & scn.Layers]
	else:
		meObLs = [(ob, ob.getData(mesh=1) ) for ob in Blender.Object.GetSelected() if ob.getType() == 'Mesh']
		
		
	meDoneDict = {}
	
	if not meObLs:
		if not Blender.bylink:		
			Blender.Draw.PupMenu('error, no meshes in selection')
		return
	
	
	# Gather image/camera data
	# This is a dict storing images names as keys, each image links to a list of camera objects.
	imageCameras = {}
	for cam, camdata in camObLs:
		cammatrix_invert = Matrix(cam.matrixWorld); cammatrix_invert.invert()
		
		camDataList = (cam, camdata, 1/camdata.scale, Matrix(cam.matrixWorld), cammatrix_invert)
		
		for imageName in [p.data for p in cam.getAllProperties() if p.name.startswith('image')]:
			try: imageCameras[imageName].append( camDataList )
			except: imageCameras[imageName] = [camDataList]
				
	
	offsetMid = Vector(0.5, 0.5, 0.0)
	zVector = Vector(0,0,1)
	zeroVec = Vector(0,0,0)
	
	for meOb, mapMesh in meObLs:
		
		# Record how many faces are donm of this mesh
		try:
			meDoneDict[mapMesh.name]
			continue # We have done this mesh before, dont bother again.
			
		except KeyError:
			# Init the mesh TotDone Faces
			meDoneDict[mapMesh.name] = 0
		
		
		mesh_mat = meOb.matrixWorld
		
		for f in mapMesh.faces:
			try:
				# May fail if no image, of the image name is not linked to a camera.
				camList = imageCameras[f.image.name]
				
				# Only 1 camera- dont search for the best match.
				if len(camList) == 1:
					cam, camdata, cam_scale_inv, cammat, cammat_invert = camList[0]
					f.uv = tuple([\
					(vecApplyMatrix( v.co, mesh_mat * cammat_invert) *\
					cam_scale_inv) + offsetMid\
					for v in f.v])
					
				else: # More then 1 camera
					bestAng = 361
					bestCamItem = None
					
					for camDataItem in camList:
						cam, camdata, cam_scale_inv, cammat, cammat_invert = camDataItem
						
						camVec = zVector*cammat # Rotate the z axis by the cameras 3x3 matrix so we can get its direction.
						
						try:
							ang = AngleBetweenVecs(camVec, f.no * mesh_mat)
							# So totaly flipped faces still get mapping from the same direction.
							if ang > 90: ang = abs(ang-180) 
							
						except:
							ang = 180
						
						if ang < bestAng:
							bestAng = ang
							bestCamItem = camDataItem
							
					# We have 
					cam, camdata, cam_scale_inv, cammat, cammat_invert = bestCamItem
					f.uv = tuple([\
					(vecApplyMatrix( v.co, mesh_mat * cammat_invert) *\
					cam_scale_inv) + offsetMid\
					for v in f.v])
			
			except:
				# No image attached to camera.
				pass
		
		#mapMesh.update()
		
	# If we are not a script link
	if not Blender.bylink:
		Blender.Window.RedrawAll()
		
main()
	