#!BPY

"""
Name: 'Smart_UV'
Blender: 248
Group: 'UV'
Tooltip: 'Rotates UV maps smartly'
"""

__author__ = "Nick Lawson"

__version__ = "1.0 (May 30th 2006)"

__bpydoc__ = """
Usage:

Select a mesh object, enter UV-Editing Mode, select the faces you want smart-uv'd, and run this script.

"""


# Id: SmartUV.py, v1.0 2006/05/05
#
# -------------------------------------------------------------------------- 
# SmartUV v1.0 by Nick Lawson
# -------------------------------------------------------------------------- 
#
# ***** 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
from Blender import *
from Blender import Window

remaining_faces = []
current_island = [] 

try:
	import psyco; psyco.full()
	print "Psyco ENABLED!"
except:
	pass

def UVsTouching(vert1,vert2):
	distvec = vert1 - vert2
	dotresult = Mathutils.DotVecs(distvec,distvec) 
	if (dotresult < 0.000001):
		return 1
	return 0

#is the uvcoord given in the same island?
def SameIsland(uvcoord,meshdata):
	global current_island
	for faceindex in current_island:
		for my_uv in meshdata.faces[faceindex].uv:
			vector1 = Mathutils.Vector(my_uv[0],my_uv[1])
			vector2 = Mathutils.Vector(uvcoord[0],uvcoord[1])
			if (UVsTouching(vector1,vector2)):
				return 1
	return 0

# create an island, given the remaining faces not yet worked on.
def CreateIsland(meshdata):
	global remaining_faces
	global current_island
	current_island = [] 
	
	#print " -- > Creating Island..."
	current_island.append(remaining_faces[0])
	del remaining_faces[0]
	listindex = int(0)
	changed = 1
	while (changed):
		changed = 0
		for faceindex in remaining_faces:
			for uvcoord in meshdata.faces[faceindex].uv:
				if (SameIsland(uvcoord,meshdata)):
					current_island.append(faceindex)
					remaining_faces.remove(faceindex)
					changed = 1
					break #break out of uvcoord check!
			listindex = listindex + 1
	#print " < -- Island Created, using ",len(current_island),"Faces."

def GetHorizontalRotationAngle(startpoint,endpoint):
	linevector = endpoint - startpoint
	linevector = linevector.normalize()
	horizon = Mathutils.Vector(1,0)
	anglebetween = Mathutils.AngleBetweenVecs(linevector,horizon)
	#print "Angle of ",linevector," is ",anglebetween
	return anglebetween

def GetEdgeLength(startpoint,endpoint):
	linevector = endpoint - startpoint
	return linevector.length

def AlreadyInEdgeList(edgelist,candidate):
	for element in edgelist:
		if (UVsTouching(element[0],candidate[0])):
			if (UVsTouching(element[1],candidate[1])):
				return 1
	return 0

# the all important scoring function
# this determines the best possible rotation angle
# given an angle, returns the score for it
# the score is a made up algorithm that seems to work really well

def GenerateRotationScore(angle,meshdata):
	#print "-->Computing score for angle: ",angle
	# do a virtual rotation of rotate each edge in the current island
	global current_island
	edgelist = []
	for faceindex in current_island:
		uvcoords = meshdata.faces[faceindex].uv
		for index in range(0,len(uvcoords)):
			startpoint = Mathutils.Vector(0,0)
			endpoint = Mathutils.Vector(0,0)
			if (index > 0):
				startpoint = Mathutils.Vector(uvcoords[index-1][0],uvcoords[index-1][1])
				endpoint = Mathutils.Vector(uvcoords[index][0],uvcoords[index][1])
			else:
				startpoint = Mathutils.Vector(uvcoords[len(uvcoords)-1][0],uvcoords[len(uvcoords)-1][1])
				endpoint = Mathutils.Vector(uvcoords[0][0],uvcoords[0][1])
				
			if (AlreadyInEdgeList(edgelist,[startpoint,endpoint])==0):
				edgelist.append([startpoint,endpoint])
				
	# we now have an edge list of form
	# [ [Start,End],[Start,End]...]
	
	total_score = float(0.0)
	rotmat = Mathutils.RotationMatrix(1.0 * angle,2)
	
	min_y = 20000.0
	max_y = -20000
	
	# compute the rotated shape.
	# also compute its size (vertically) for tie-breaking using height of longest line.
	for currentedge in edgelist:
		currentedge[0] = currentedge[0] * rotmat
		currentedge[1] = currentedge[1] * rotmat
		min_y = min(currentedge[0].y,min_y)
		min_y = min(currentedge[1].y,min_y)
		max_y = max(currentedge[0].y,max_y)
		max_y = max(currentedge[1].y,max_y)
		
	height_y = max_y - min_y
		
	for currentedge in edgelist:
		current_deflection = GetHorizontalRotationAngle(currentedge[0],currentedge[1])
		current_deflection = abs(current_deflection)
		
		# for tie-breaking, we also throw in the height in the UV map of the y
		yoffset_edge = (currentedge[0].y - min_y) / height_y
		yoffset_edge *= 0.00001
		
		#print "Current Deflection: ",current_deflection
		if (current_deflection < 0.025): #edge is horizontal - bonus
			total_score = total_score + (3.5 * GetEdgeLength(currentedge[0],currentedge[1]))
			total_score = total_score + (yoffset_edge * GetEdgeLength(currentedge[0],currentedge[1])) # tiebreak
		#elif (abs(90.0 - current_deflection) < 0.05):
		#	total_score = total_score + (1.0 * GetEdgeLength(currentedge[0],currentedge[1]))
		elif (abs(180.0 - current_deflection) < 0.05): # edge is horizontal - bonus
			total_score = total_score + (3.5 * GetEdgeLength(currentedge[0],currentedge[1]))
			total_score = total_score + (yoffset_edge * GetEdgeLength(currentedge[0],currentedge[1])) #tiebreak
		#elif (abs(270.0 - current_deflection) < 0.025):
		#	total_score = total_score + (1.0 * GetEdgeLength(currentedge[0],currentedge[1]))
		#else:
			#total_score = total_score - 0.5
	
	#print "<-- Total Score for angle ",angle, " is: ",total_score
	return total_score


def FindIslandCentroid(meshdata):
	num_gathered = float(0.0)
	additive_vec = Mathutils.Vector(0,0)
	for faceindex in current_island:
		uvcoords = meshdata.faces[faceindex].uv
		for index in range(0,len(uvcoords)):
			curvec = Mathutils.Vector(uvcoords[index][0],uvcoords[index][1])
			additive_vec = additive_vec + curvec
			num_gathered = num_gathered + 1.0
	additive_vec = additive_vec * (1.0 / num_gathered)
	return additive_vec
		

def RotateIsland(meshdata):
	# for each island we have found
	# we will go thru its face list
	# and go thru its vertex list
	# we will rotate each edge in turn, so that the edge is horizontal
	# then we will build a value to determine how nice this rotation is
	# in terms of all the other edges.
	# We will save the rotation
	# we will finalize it by actually rotating the object to the 'nicest' rotation.
	
	global current_island
	nicest_rotation = float(-1.0)
	highest_score = float(-1000.0)
	# current_island is a list of faceindices in meshdata.
	# we will only operate on those.
	for faceindex in current_island:
		uvcoords = meshdata.faces[faceindex].uv
		for index in range(1,len(uvcoords)):
			startpoint = Mathutils.Vector(uvcoords[index-1][0],uvcoords[index-1][1])
			endpoint = Mathutils.Vector(uvcoords[index][0],uvcoords[index][1])
			candidate_angle = GetHorizontalRotationAngle(startpoint,endpoint)
			# now generate the 'score' for that rogation
			newscore = GenerateRotationScore(candidate_angle,meshdata)
			if (newscore > highest_score ):
				highest_score = newscore
				nicest_rotation = candidate_angle
		
			# we also reverse the angle, just in case...
			newscore = GenerateRotationScore(candidate_angle * -1.0,meshdata)
			if (newscore > highest_score ):
				highest_score = newscore
				nicest_rotation = -1.0 * candidate_angle
				
			# we also flip it by 180 degrees, just in case...
			newscore = GenerateRotationScore(candidate_angle + 180,meshdata)
			if (newscore > highest_score ):
				highest_score = newscore
				nicest_rotation = candidate_angle + 180
	#print "Highest score: ",highest_score," with rotation angle: ",nicest_rotation
	#print "ROTATING..."
	
	# now we move the centroid such that it lines up with the grid (assuming power of two images used, which is most common)
	
	centroid = FindIslandCentroid(meshdata)
	final_centroid = centroid * 8
	final_centroid.x = float(int(final_centroid.x + 0.5))
	final_centroid.y = float(int(final_centroid.y + 0.5))
	final_centroid = final_centroid * (1.0 / 8.0)
	
	#this is where we actually make the change.  Beware.
	rotmat = Mathutils.RotationMatrix(1.0 * nicest_rotation,2)
	# actually rotate every uv coordinate in the current island by that much.
	for faceindex in current_island:
		uvcoords = meshdata.faces[faceindex].uv
		for index in range(0,len(uvcoords)):
			curvec = Mathutils.Vector(uvcoords[index][0],uvcoords[index][1])
			# move centroid to 0,0
			curvec = curvec - centroid
			# rotate
			curvec = curvec * rotmat
			# move to new centroid
			curvec = curvec + final_centroid
			meshdata.faces[faceindex].uv[index] = (curvec[0],curvec[1])

def GatherFaces(meshdata):
	#print " -- > Gathering faces..."
	global remaining_faces
	# we create remaining_faces list
	# [face index, face index, face index, face index]
	donelist = []
	SEL = NMesh.FaceFlags['SELECT']
	faces = meshdata.faces
	lenlist = len(faces)
	faceindex = int(0)
	uvindex = int(0)
	for curface in faces:
		if (curface.flag & SEL):
			remaining_faces.append(faceindex)
		faceindex = faceindex + 1
	#print " < -- Faces Gathered:", len(remaining_faces)
	
			
def SmartUV(meshdata):
	# first, gather all faces that are in this object
	global remaining_faces
	remaining_faces = []
	Window.DrawProgressBar(0.0,"Gathering UVFaces...")
	GatherFaces(meshdata)
	start_faces = float(len(remaining_faces))
	if (len(remaining_faces) > 0):
		while (len(remaining_faces)>0):
			float_remain = float(len(remaining_faces))
			fraction = float_remain / start_faces
			#print "Fraction remaining: ",fraction
			Window.DrawProgressBar(1.0 - fraction,"Smart-UV Working...")
			
			CreateIsland(meshdata)
			RotateIsland(meshdata)
		meshdata.update()

def Do_Action():
	selobjects = Object.GetSelected()
	if (not selobjects):
		print "No selected objects."
		return
	for objecta in selobjects:
		if (objecta.getType() == 'Mesh'):
			print "Performing SMART-UV on object: ",objecta.getName()
			SmartUV(objecta.getData())
	print "Smart UV complete."
	Window.DrawProgressBar(1.0,"Done")

print "----- SMART UV - Run begin --------------"
in_editmode = Window.EditMode()
if in_editmode:
	Window.EditMode(0)

Do_Action()

if in_editmode:
	Window.EditMode(1)

Window.RedrawAll()
Blender.Redraw()