Skip to content
Snippets Groups Projects
uvcalc_smart_project.py 32.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000
    #!BPY
    
    """ Registration info for Blender menus: <- these words are ignored
    Name: 'Unwrap (smart projections)'
    Blender: 240
    Group: 'UVCalculation'
    Tooltip: 'UV Unwrap mesh faces for all select mesh objects'
    """
    
    
    __author__ = "Campbell Barton"
    __url__ = ("blender", "blenderartists.org")
    __version__ = "1.1 12/18/05"
    
    __bpydoc__ = """\
    This script projection unwraps the selected faces of a mesh.
    
    it operates on all selected mesh objects, and can be used unwrap
    selected faces, or all faces.
    """
    
    # -------------------------------------------------------------------------- 
    # Smart Projection UV Projection Unwrapper v1.1 by Campbell Barton (AKA Ideasman) 
    # -------------------------------------------------------------------------- 
    # ***** 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 ***** 
    # -------------------------------------------------------------------------- 
    
    
    from Blender import Object, Draw, Window, sys, Mesh, Geometry
    from Blender.Mathutils import Matrix, Vector, RotationMatrix
    import bpy
    from math import cos
    
    DEG_TO_RAD = 0.017453292519943295 # pi/180.0
    SMALL_NUM = 0.000000001
    BIG_NUM = 1e15
    
    global USER_FILL_HOLES
    global USER_FILL_HOLES_QUALITY
    USER_FILL_HOLES = None
    USER_FILL_HOLES_QUALITY = None
    
    dict_matrix = {}
    
    def pointInTri2D(v, v1, v2, v3):
    	global dict_matrix
    	
    	key = v1.x, v1.y, v2.x, v2.y, v3.x, v3.y
    	
    	# Commented because its slower to do teh bounds check, we should realy cache the bounds info for each face.
    	'''
    	# BOUNDS CHECK
    	xmin= 1000000
    	ymin= 1000000
    	
    	xmax= -1000000
    	ymax= -1000000
    	
    	for i in (0,2,4):
    		x= key[i]
    		y= key[i+1]
    		
    		if xmax<x:	xmax= x
    		if ymax<y:	ymax= y
    		if xmin>x:	xmin= x
    		if ymin>y:	ymin= y	
    	
    	x= v.x
    	y= v.y
    	
    	if x<xmin or x>xmax or y < ymin or y > ymax:
    		return False
    	# Done with bounds check
    	'''
    	try:
    		mtx = dict_matrix[key]
    		if not mtx:
    			return False
    	except:
    		side1 = v2 - v1
    		side2 = v3 - v1
    		
    		nor = side1.cross(side2)
    		
    		l1 = [side1[0], side1[1], side1[2]]
    		l2 = [side2[0], side2[1], side2[2]]
    		l3 = [nor[0], nor[1], nor[2]]
    		
    		mtx = Matrix(l1, l2, l3)
    		
    		# Zero area 2d tri, even tho we throw away zerop area faces
    		# the projection UV can result in a zero area UV.
    		if not mtx.determinant():
    			dict_matrix[key] = None
    			return False
    		
    		mtx.invert()
    		
    		dict_matrix[key] = mtx
    	
    	uvw = (v - v1) * mtx
    	return 0 <= uvw[0] and 0 <= uvw[1] and uvw[0] + uvw[1] <= 1
    
    	
    def boundsIsland(faces):
    	minx = maxx = faces[0].uv[0][0] # Set initial bounds.
    	miny = maxy = faces[0].uv[0][1]
    	# print len(faces), minx, maxx, miny , maxy
    	for f in faces:
    		for uv in f.uv:
    			x= uv.x
    			y= uv.y
    			if x<minx: minx= x
    			if y<miny: miny= y
    			if x>maxx: maxx= x
    			if y>maxy: maxy= y
    	
    	return minx, miny, maxx, maxy
    
    """
    def boundsEdgeLoop(edges):
    	minx = maxx = edges[0][0] # Set initial bounds.
    	miny = maxy = edges[0][1]
    	# print len(faces), minx, maxx, miny , maxy
    	for ed in edges:
    		for pt in ed:
    			print 'ass'
    			x= pt[0]
    			y= pt[1]
    			if x<minx: x= minx
    			if y<miny: y= miny
    			if x>maxx: x= maxx
    			if y>maxy: y= maxy
    	
    	return minx, miny, maxx, maxy
    """
    
    # Turns the islands into a list of unpordered edges (Non internal)
    # Onlt for UV's
    # only returns outline edges for intersection tests. and unique points.
    
    def island2Edge(island):
    	
    	# Vert index edges
    	edges = {}
    	
    	unique_points= {}
    	
    	for f in island:
    		f_uvkey= map(tuple, f.uv)
    		
    		
    		for vIdx, edkey in enumerate(f.edge_keys):
    			unique_points[f_uvkey[vIdx]] = f.uv[vIdx]
    			
    			if f.v[vIdx].index > f.v[vIdx-1].index:
    				i1= vIdx-1;	i2= vIdx
    			else:		
    				i1= vIdx;	i2= vIdx-1
    			
    			try:	edges[ f_uvkey[i1], f_uvkey[i2] ] *= 0 # sets eny edge with more then 1 user to 0 are not returned.
    			except:	edges[ f_uvkey[i1], f_uvkey[i2] ] = (f.uv[i1] - f.uv[i2]).length, 
    	
    	# If 2 are the same then they will be together, but full [a,b] order is not correct.
    	
    	# Sort by length
    	
    		
    	length_sorted_edges = [(Vector(key[0]), Vector(key[1]), value) for key, value in edges.iteritems() if value != 0]
    	
    	try:	length_sorted_edges.sort(key = lambda A: -A[2]) # largest first
    	except:	length_sorted_edges.sort(lambda A, B: cmp(B[2], A[2]))
    	
    	# Its okay to leave the length in there.
    	#for e in length_sorted_edges:
    	#	e.pop(2)
    	
    	# return edges and unique points
    	return length_sorted_edges, [v.__copy__().resize3D() for v in unique_points.itervalues()]
    	
    # ========================= NOT WORKING????
    # Find if a points inside an edge loop, un-orderd.
    # pt is and x/y
    # edges are a non ordered loop of edges.
    # #offsets are the edge x and y offset.
    """
    def pointInEdges(pt, edges):
    	#
    	x1 = pt[0] 
    	y1 = pt[1]
    	
    	# Point to the left of this line.
    	x2 = -100000
    	y2 = -10000
    	intersectCount = 0
    	for ed in edges:
    		xi, yi = lineIntersection2D(x1,y1, x2,y2, ed[0][0], ed[0][1], ed[1][0], ed[1][1])
    		if xi != None: # Is there an intersection.
    			intersectCount+=1
    	
    	return intersectCount % 2
    """
    
    def pointInIsland(pt, island):
    	vec1 = Vector(); vec2 = Vector(); vec3 = Vector()	
    	for f in island:
    		vec1.x, vec1.y = f.uv[0]
    		vec2.x, vec2.y = f.uv[1]
    		vec3.x, vec3.y = f.uv[2]
    
    		if pointInTri2D(pt, vec1, vec2, vec3):
    			return True
    		
    		if len(f.v) == 4:
    			vec1.x, vec1.y = f.uv[0]
    			vec2.x, vec2.y = f.uv[2]
    			vec3.x, vec3.y = f.uv[3]			
    			if pointInTri2D(pt, vec1, vec2, vec3):
    				return True
    	return False
    
    
    # box is (left,bottom, right, top)
    def islandIntersectUvIsland(source, target, SourceOffset):
    	# Is 1 point in the box, inside the vertLoops
    	edgeLoopsSource = source[6] # Pretend this is offset
    	edgeLoopsTarget = target[6]
    	
    	# Edge intersect test	
    	for ed in edgeLoopsSource:
    		for seg in edgeLoopsTarget:
    			i = Geometry.LineIntersect2D(\
    			seg[0], seg[1], SourceOffset+ed[0], SourceOffset+ed[1])
    			if i:
    				return 1 # LINE INTERSECTION
    	
    	# 1 test for source being totally inside target
    	SourceOffset.resize3D()
    	for pv in source[7]:
    		if pointInIsland(pv+SourceOffset, target[0]):
    			return 2 # SOURCE INSIDE TARGET
    	
    	# 2 test for a part of the target being totaly inside the source.
    	for pv in target[7]:
    		if pointInIsland(pv-SourceOffset, source[0]):
    			return 3 # PART OF TARGET INSIDE SOURCE.
    
    	return 0 # NO INTERSECTION
    
    
    
    
    # Returns the X/y Bounds of a list of vectors.
    def testNewVecLs2DRotIsBetter(vecs, mat=-1, bestAreaSoFar = -1):
    	
    	# UV's will never extend this far.
    	minx = miny = BIG_NUM
    	maxx = maxy = -BIG_NUM
    	
    	for i, v in enumerate(vecs):
    		
    		# Do this allong the way
    		if mat != -1:
    			v = vecs[i] = v*mat
    			x= v.x
    			y= v.y
    			if x<minx: minx= x
    			if y<miny: miny= y
    			if x>maxx: maxx= x
    			if y>maxy: maxy= y
    		
    		# Spesific to this algo, bail out if we get bigger then the current area
    		if bestAreaSoFar != -1 and (maxx-minx) * (maxy-miny) > bestAreaSoFar:
    			return (BIG_NUM, None), None
    	w = maxx-minx
    	h = maxy-miny
    	return (w*h, w,h), vecs # Area, vecs
    	
    # Takes a list of faces that make up a UV island and rotate
    # until they optimally fit inside a square.
    ROTMAT_2D_POS_90D = RotationMatrix( 90, 2)
    ROTMAT_2D_POS_45D = RotationMatrix( 45, 2)
    
    RotMatStepRotation = []
    rot_angle = 22.5 #45.0/2
    while rot_angle > 0.1:
    	RotMatStepRotation.append([\
    	 RotationMatrix( rot_angle, 2),\
    	 RotationMatrix( -rot_angle, 2)])
    	
    	rot_angle = rot_angle/2.0
    	
    
    def optiRotateUvIsland(faces):
    	global currentArea
    	
    	# Bestfit Rotation
    	def best2dRotation(uvVecs, MAT1, MAT2):
    		global currentArea
    		
    		newAreaPos, newfaceProjectionGroupListPos =\
    		testNewVecLs2DRotIsBetter(uvVecs[:], MAT1, currentArea[0])
    		
    		
    		# Why do I use newpos here? May as well give the best area to date for an early bailout
    		# some slight speed increase in this.
    		# If the new rotation is smaller then the existing, we can 
    		# avoid copying a list and overwrite the old, crappy one.
    		
    		if newAreaPos[0] < currentArea[0]:
    			newAreaNeg, newfaceProjectionGroupListNeg =\
    			testNewVecLs2DRotIsBetter(uvVecs, MAT2, newAreaPos[0])  # Reuse the old bigger list.
    		else:
    			newAreaNeg, newfaceProjectionGroupListNeg =\
    			testNewVecLs2DRotIsBetter(uvVecs[:], MAT2, currentArea[0])  # Cant reuse, make a copy.
    		
    		
    		# Now from the 3 options we need to discover which to use
    		# we have cerrentArea/newAreaPos/newAreaNeg
    		bestArea = min(currentArea[0], newAreaPos[0], newAreaNeg[0])
    		
    		if currentArea[0] == bestArea:
    			return uvVecs
    		elif newAreaPos[0] == bestArea:
    			uvVecs = newfaceProjectionGroupListPos
    			currentArea = newAreaPos		
    		elif newAreaNeg[0] == bestArea:
    			uvVecs = newfaceProjectionGroupListNeg
    			currentArea = newAreaNeg
    		
    		return uvVecs
    		
    	
    	# Serialized UV coords to Vectors
    	uvVecs = [uv for f in faces  for uv in f.uv]
    	
    	# Theres a small enough number of these to hard code it
    	# rather then a loop.
    	
    	# Will not modify anything
    	currentArea, dummy =\
    	testNewVecLs2DRotIsBetter(uvVecs)
    	
    	
    	# Try a 45d rotation
    	newAreaPos, newfaceProjectionGroupListPos = testNewVecLs2DRotIsBetter(uvVecs[:], ROTMAT_2D_POS_45D, currentArea[0])
    	
    	if newAreaPos[0] < currentArea[0]:
    		uvVecs = newfaceProjectionGroupListPos
    		currentArea = newAreaPos
    	# 45d done
    	
    	# Testcase different rotations and find the onfe that best fits in a square
    	for ROTMAT in RotMatStepRotation:
    		uvVecs = best2dRotation(uvVecs, ROTMAT[0], ROTMAT[1])
    	
    	# Only if you want it, make faces verticle!
    	if currentArea[1] > currentArea[2]:
    		# Rotate 90d
    		# Work directly on the list, no need to return a value.
    		testNewVecLs2DRotIsBetter(uvVecs, ROTMAT_2D_POS_90D)
    	
    	
    	# Now write the vectors back to the face UV's
    	i = 0 # count the serialized uv/vectors
    	for f in faces:
    		#f.uv = [uv for uv in uvVecs[i:len(f)+i] ]
    		for j, k in enumerate(xrange(i, len(f.v)+i)):
    			f.uv[j][:] = uvVecs[k]
    		i += len(f.v)
    
    
    # Takes an island list and tries to find concave, hollow areas to pack smaller islands into.
    def mergeUvIslands(islandList):
    	global USER_FILL_HOLES
    	global USER_FILL_HOLES_QUALITY
    	
    	
    	# Pack islands to bottom LHS
    	# Sync with island
    	
    	#islandTotFaceArea = [] # A list of floats, each island area
    	#islandArea = [] # a list of tuples ( area, w,h)
    	
    	
    	decoratedIslandList = []
    	
    	islandIdx = len(islandList)
    	while islandIdx:
    		islandIdx-=1
    		minx, miny, maxx, maxy = boundsIsland(islandList[islandIdx])
    		w, h = maxx-minx, maxy-miny
    		
    		totFaceArea = 0
    		offset= Vector(minx, miny)
    		for f in islandList[islandIdx]:
    			for uv in f.uv:
    				uv -= offset
    			
    			totFaceArea += f.area
    		
    		islandBoundsArea = w*h
    		efficiency = abs(islandBoundsArea - totFaceArea)
    		
    		# UV Edge list used for intersections as well as unique points.
    		edges, uniqueEdgePoints = island2Edge(islandList[islandIdx])
    		
    		decoratedIslandList.append([islandList[islandIdx], totFaceArea, efficiency, islandBoundsArea, w,h, edges, uniqueEdgePoints]) 
    		
    	
    	# Sort by island bounding box area, smallest face area first.
    	# no.. chance that to most simple edge loop first.
    	decoratedIslandListAreaSort =decoratedIslandList[:]
    	
    	try:	decoratedIslandListAreaSort.sort(key = lambda A: A[3])
    	except:	decoratedIslandListAreaSort.sort(lambda A, B: cmp(A[3], B[3]))
    	
    	
    	# sort by efficiency, Least Efficient first.
    	decoratedIslandListEfficSort = decoratedIslandList[:]
    	# decoratedIslandListEfficSort.sort(lambda A, B: cmp(B[2], A[2]))
    
    	try:	decoratedIslandListEfficSort.sort(key = lambda A: -A[2])
    	except:	decoratedIslandListEfficSort.sort(lambda A, B: cmp(B[2], A[2]))
    
    	# ================================================== THESE CAN BE TWEAKED.
    	# This is a quality value for the number of tests.
    	# from 1 to 4, generic quality value is from 1 to 100
    	USER_STEP_QUALITY =   ((USER_FILL_HOLES_QUALITY - 1) / 25.0) + 1
    	
    	# If 100 will test as long as there is enough free space.
    	# this is rarely enough, and testing takes a while, so lower quality speeds this up.
    	
    	# 1 means they have the same quality 
    	USER_FREE_SPACE_TO_TEST_QUALITY = 1 + (((100 - USER_FILL_HOLES_QUALITY)/100.0) *5)
    	
    	#print 'USER_STEP_QUALITY', USER_STEP_QUALITY
    	#print 'USER_FREE_SPACE_TO_TEST_QUALITY', USER_FREE_SPACE_TO_TEST_QUALITY
    	
    	removedCount = 0
    	
    	areaIslandIdx = 0
    	ctrl = Window.Qual.CTRL
    	BREAK= False
    	while areaIslandIdx < len(decoratedIslandListAreaSort) and not BREAK:
    		sourceIsland = decoratedIslandListAreaSort[areaIslandIdx]
    		# Alredy packed?
    		if not sourceIsland[0]:
    			areaIslandIdx+=1
    		else:
    			efficIslandIdx = 0
    			while efficIslandIdx < len(decoratedIslandListEfficSort) and not BREAK:
    				
    				if Window.GetKeyQualifiers() & ctrl:
    					BREAK= True
    					break
    				
    				# Now we have 2 islands, is the efficience of the islands lowers theres an
    				# increasing likely hood that we can fit merge into the bigger UV island.
    				# this ensures a tight fit.
    				
    				# Just use figures we have about user/unused area to see if they might fit.
    				
    				targetIsland = decoratedIslandListEfficSort[efficIslandIdx]
    				
    				
    				if sourceIsland[0] == targetIsland[0] or\
    				not targetIsland[0] or\
    				not sourceIsland[0]:
    					pass
    				else:
    					
    					# ([island, totFaceArea, efficiency, islandArea, w,h])
    					# Waisted space on target is greater then UV bounding island area.
    					
    					
    					# if targetIsland[3] > (sourceIsland[2]) and\ #
    					# print USER_FREE_SPACE_TO_TEST_QUALITY, 'ass'
    					if targetIsland[2] > (sourceIsland[1] * USER_FREE_SPACE_TO_TEST_QUALITY) and\
    					targetIsland[4] > sourceIsland[4] and\
    					targetIsland[5] > sourceIsland[5]:
    						
    						# DEBUG # print '%.10f  %.10f' % (targetIsland[3], sourceIsland[1])
    						
    						# These enough spare space lets move the box until it fits
    						
    						# How many times does the source fit into the target x/y
    						blockTestXUnit = targetIsland[4]/sourceIsland[4]
    						blockTestYUnit = targetIsland[5]/sourceIsland[5]
    						
    						boxLeft = 0
    						
    						
    						# Distllllance we can move between whilst staying inside the targets bounds.
    						testWidth = targetIsland[4] - sourceIsland[4]
    						testHeight = targetIsland[5] - sourceIsland[5]
    						
    						# Increment we move each test. x/y
    						xIncrement = (testWidth / (blockTestXUnit * ((USER_STEP_QUALITY/50)+0.1)))
    						yIncrement = (testHeight / (blockTestYUnit * ((USER_STEP_QUALITY/50)+0.1)))
    
    						# Make sure were not moving less then a 3rg of our width/height
    						if xIncrement<sourceIsland[4]/3:
    							xIncrement= sourceIsland[4]
    						if yIncrement<sourceIsland[5]/3:
    							yIncrement= sourceIsland[5]
    						
    						
    						boxLeft = 0 # Start 1 back so we can jump into the loop.
    						boxBottom= 0 #-yIncrement
    						
    						##testcount= 0
    						
    						while boxBottom <= testHeight:
    							# Should we use this? - not needed for now.
    							#if Window.GetKeyQualifiers() & ctrl:
    							#	BREAK= True
    							#	break
    							
    							##testcount+=1
    							#print 'Testing intersect'
    							Intersect = islandIntersectUvIsland(sourceIsland, targetIsland, Vector(boxLeft, boxBottom))
    							#print 'Done', Intersect
    							if Intersect == 1:  # Line intersect, dont bother with this any more
    								pass
    							
    							if Intersect == 2:  # Source inside target
    								'''
    								We have an intersection, if we are inside the target 
    								then move us 1 whole width accross,
    								Its possible this is a bad idea since 2 skinny Angular faces
    								could join without 1 whole move, but its a lot more optimal to speed this up
    								since we have alredy tested for it.
    								
    								It gives about 10% speedup with minimal errors.
    								'''
    								#print 'ass'
    								# Move the test allong its width + SMALL_NUM
    								#boxLeft += sourceIsland[4] + SMALL_NUM
    								boxLeft += sourceIsland[4]
    							elif Intersect == 0: # No intersection?? Place it.
    								# Progress
    								removedCount +=1
    								Window.DrawProgressBar(0.0, 'Merged: %i islands, Ctrl to finish early.' % removedCount)
    								
    								# Move faces into new island and offset
    								targetIsland[0].extend(sourceIsland[0])
    								offset= Vector(boxLeft, boxBottom)
    								
    								for f in sourceIsland[0]:
    									for uv in f.uv:
    										uv+= offset
    								
    								sourceIsland[0][:] = [] # Empty
    								
    
    								# Move edge loop into new and offset.
    								# targetIsland[6].extend(sourceIsland[6])
    								#while sourceIsland[6]:
    								targetIsland[6].extend( [ (\
    									 (e[0]+offset, e[1]+offset, e[2])\
    								) for e in sourceIsland[6] ] )
    								
    								sourceIsland[6][:] = [] # Empty
    								
    								# Sort by edge length, reverse so biggest are first.
    								
    								try: 	targetIsland[6].sort(key = lambda A: A[2])
    								except:	targetIsland[6].sort(lambda B,A: cmp(A[2], B[2] ))
    								
    								
    								targetIsland[7].extend(sourceIsland[7])
    								offset= Vector(boxLeft, boxBottom, 0)
    								for p in sourceIsland[7]:
    									p+= offset
    								
    								sourceIsland[7][:] = []
    								
    								
    								# Decrement the efficiency
    								targetIsland[1]+=sourceIsland[1] # Increment totFaceArea
    								targetIsland[2]-=sourceIsland[1] # Decrement efficiency
    								# IF we ever used these again, should set to 0, eg
    								sourceIsland[2] = 0 # No area if anyone wants to know
    								
    								break
    							
    							
    							# INCREMENR NEXT LOCATION
    							if boxLeft > testWidth:
    								boxBottom += yIncrement
    								boxLeft = 0.0
    							else:
    								boxLeft += xIncrement
    						##print testcount
    				
    				efficIslandIdx+=1
    		areaIslandIdx+=1
    	
    	# Remove empty islands
    	i = len(islandList)
    	while i:
    		i-=1
    		if not islandList[i]:
    			del islandList[i] # Can increment islands removed here.
    
    # Takes groups of faces. assumes face groups are UV groups.
    def getUvIslands(faceGroups, me):
    	
    	# Get seams so we dont cross over seams
    	edge_seams = {} # shoudl be a set
    	SEAM = Mesh.EdgeFlags.SEAM
    	for ed in me.edges:
    		if ed.flag & SEAM:
    			edge_seams[ed.key] = None # dummy var- use sets!			
    	# Done finding seams
    	
    	
    	islandList = []
    	
    	Window.DrawProgressBar(0.0, 'Splitting %d projection groups into UV islands:' % len(faceGroups))
    	#print '\tSplitting %d projection groups into UV islands:' % len(faceGroups),
    	# Find grouped faces
    	
    	faceGroupIdx = len(faceGroups)
    	
    	while faceGroupIdx:
    		faceGroupIdx-=1
    		faces = faceGroups[faceGroupIdx]
    		
    		if not faces:
    			continue
    		
    		# Build edge dict
    		edge_users = {}
    		
    		for i, f in enumerate(faces):
    			for ed_key in f.edge_keys:
    				if edge_seams.has_key(ed_key): # DELIMIT SEAMS! ;)
    					edge_users[ed_key] = [] # so as not to raise an error
    				else:
    					try:		edge_users[ed_key].append(i)
    					except:		edge_users[ed_key] = [i]
    		
    		# Modes
    		# 0 - face not yet touched.
    		# 1 - added to island list, and need to search
    		# 2 - touched and searched - dont touch again.
    		face_modes = [0] * len(faces) # initialize zero - untested.
    		
    		face_modes[0] = 1 # start the search with face 1
    		
    		newIsland = []
    		
    		newIsland.append(faces[0])
    		
    		
    		ok = True
    		while ok:
    			
    			ok = True
    			while ok:
    				ok= False
    				for i in xrange(len(faces)):
    					if face_modes[i] == 1: # search
    						for ed_key in faces[i].edge_keys:
    							for ii in edge_users[ed_key]:
    								if i != ii and face_modes[ii] == 0:
    									face_modes[ii] = ok = 1 # mark as searched
    									newIsland.append(faces[ii])
    								
    						# mark as searched, dont look again.
    						face_modes[i] = 2
    			
    			islandList.append(newIsland)
    			
    			ok = False
    			for i in xrange(len(faces)):
    				if face_modes[i] == 0:
    					newIsland = []
    					newIsland.append(faces[i])
    					
    					face_modes[i] = ok = 1
    					break
    			# if not ok will stop looping
    	
    	Window.DrawProgressBar(0.1, 'Optimizing Rotation for %i UV Islands' % len(islandList))
    	
    	for island in islandList:
    		optiRotateUvIsland(island)
    	
    	return islandList
    	
    
    def packIslands(islandList):
    	if USER_FILL_HOLES:
    		Window.DrawProgressBar(0.1, 'Merging Islands (Ctrl: skip merge)...')
    		mergeUvIslands(islandList) # Modify in place
    		
    	
    	# Now we have UV islands, we need to pack them.
    	
    	# Make a synchronised list with the islands
    	# so we can box pak the islands.
    	packBoxes = []
    	
    	# Keep a list of X/Y offset so we can save time by writing the 
    	# uv's and packed data in one pass.
    	islandOffsetList = [] 
    	
    	islandIdx = 0
    	
    	while islandIdx < len(islandList):
    		minx, miny, maxx, maxy = boundsIsland(islandList[islandIdx])
    		
    		w, h = maxx-minx, maxy-miny
    		
    		if USER_ISLAND_MARGIN:
    			minx -= USER_ISLAND_MARGIN# *w
    			miny -= USER_ISLAND_MARGIN# *h
    			maxx += USER_ISLAND_MARGIN# *w
    			maxy += USER_ISLAND_MARGIN# *h
    		
    			# recalc width and height
    			w, h = maxx-minx, maxy-miny
    		
    		if w < 0.00001 or h < 0.00001:
    			del islandList[islandIdx]
    			islandIdx -=1
    			continue
    		
    		'''Save the offset to be applied later,
    		we could apply to the UVs now and allign them to the bottom left hand area
    		of the UV coords like the box packer imagines they are
    		but, its quicker just to remember their offset and
    		apply the packing and offset in 1 pass '''
    		islandOffsetList.append((minx, miny))
    		
    		# Add to boxList. use the island idx for the BOX id.
    		packBoxes.append([0, 0, w, h])
    		islandIdx+=1
    	
    	# Now we have a list of boxes to pack that syncs
    	# with the islands.
    	
    	#print '\tPacking UV Islands...'
    	Window.DrawProgressBar(0.7, 'Packing %i UV Islands...' % len(packBoxes) )
    	
    	time1 = sys.time()
    	packWidth, packHeight = Geometry.BoxPack2D(packBoxes)
    	
    	# print 'Box Packing Time:', sys.time() - time1
    	
    	#if len(pa	ckedLs) != len(islandList):
    	#	raise "Error packed boxes differes from original length"
    	
    	#print '\tWriting Packed Data to faces'
    	Window.DrawProgressBar(0.8, 'Writing Packed Data to faces')
    	
    	# Sort by ID, so there in sync again
    	islandIdx = len(islandList)
    	# Having these here avoids devide by 0
    	if islandIdx:
    		
    		if USER_STRETCH_ASPECT:
    			# Maximize to uv area?? Will write a normalize function.
    			xfactor = 1.0 / packWidth
    			yfactor = 1.0 / packHeight	
    		else:
    			# Keep proportions.
    			xfactor = yfactor = 1.0 / max(packWidth, packHeight)
    	
    	while islandIdx:
    		islandIdx -=1
    		# Write the packed values to the UV's
    		
    		xoffset = packBoxes[islandIdx][0] - islandOffsetList[islandIdx][0]
    		yoffset = packBoxes[islandIdx][1] - islandOffsetList[islandIdx][1]
    		
    		for f in islandList[islandIdx]: # Offsetting the UV's so they fit in there packed box
    			for uv in f.uv:
    				uv.x= (uv.x+xoffset) * xfactor
    				uv.y= (uv.y+yoffset) * yfactor
    			
    			
    
    def VectoMat(vec):
    	a3 = vec.__copy__().normalize()
    	
    	up = Vector(0,0,1)
    	if abs(a3.dot(up)) == 1.0:
    		up = Vector(0,1,0)
    	
    	a1 = a3.cross(up).normalize()
    	a2 = a3.cross(a1)
    	return Matrix([a1[0], a1[1], a1[2]], [a2[0], a2[1], a2[2]], [a3[0], a3[1], a3[2]])
    
    
    
    class thickface(object):
    	__slost__= 'v', 'uv', 'no', 'area', 'edge_keys'
    	def __init__(self, face):
    		self.v = face.v
    		self.uv = face.uv
    		self.no = face.no
    		self.area = face.area
    		self.edge_keys = face.edge_keys
    
    global ob
    ob = None
    def main():
    	global USER_FILL_HOLES
    	global USER_FILL_HOLES_QUALITY
    	global USER_STRETCH_ASPECT
    	global USER_ISLAND_MARGIN
    	
    	objects= bpy.data.scenes.active.objects
    	
    	# we can will tag them later.
    	obList =  [ob for ob in objects.context if ob.type == 'Mesh']
    	
    	# Face select object may not be selected.
    	ob = objects.active
    	if ob and ob.sel == 0 and ob.type == 'Mesh':
    		# Add to the list
    		obList =[ob]
    	del objects
    	
    	if not obList:
    		Draw.PupMenu('error, no selected mesh objects')
    		return
    	
    	# Create the variables.
    	USER_PROJECTION_LIMIT = Draw.Create(66)
    	USER_ONLY_SELECTED_FACES = Draw.Create(1)
    	USER_SHARE_SPACE = Draw.Create(1) # Only for hole filling.
    	USER_STRETCH_ASPECT = Draw.Create(1) # Only for hole filling.
    	USER_ISLAND_MARGIN = Draw.Create(0.0) # Only for hole filling.
    	USER_FILL_HOLES = Draw.Create(0)
    	USER_FILL_HOLES_QUALITY = Draw.Create(50) # Only for hole filling.
    	USER_VIEW_INIT = Draw.Create(0) # Only for hole filling.
    	USER_AREA_WEIGHT = Draw.Create(1) # Only for hole filling.
    	
    	
    	pup_block = [\
    	'Projection',\
    	('Angle Limit:', USER_PROJECTION_LIMIT, 1, 89, 'lower for more projection groups, higher for less distortion.'),\
    	('Selected Faces Only', USER_ONLY_SELECTED_FACES, 'Use only selected faces from all selected meshes.'),\
    	('Init from view', USER_VIEW_INIT, 'The first projection will be from the view vector.'),\
    	('Area Weight', USER_AREA_WEIGHT, 'Weight projections vector by face area.'),\
    	'',\
    	'',\
    	'',\
    	'UV Layout',\
    	('Share Tex Space', USER_SHARE_SPACE, 'Objects Share texture space, map all objects into 1 uvmap.'),\
    	('Stretch to bounds', USER_STRETCH_ASPECT, 'Stretch the final output to texture bounds.'),\
    	('Island Margin:', USER_ISLAND_MARGIN, 0.0, 0.5, 'Margin to reduce bleed from adjacent islands.'),\
    	'Fill in empty areas',\
    	('Fill Holes', USER_FILL_HOLES, 'Fill in empty areas reduced texture waistage (slow).'),\
    	('Fill Quality:', USER_FILL_HOLES_QUALITY, 1, 100, 'Depends on fill holes, how tightly to fill UV holes, (higher is slower)'),\
    	]
    	
    	# Reuse variable
    	if len(obList) == 1:
    		ob = "Unwrap %i Selected Mesh"
    	else:
    		ob = "Unwrap %i Selected Meshes"
    	
    	# HACK, loop until mouse is lifted.
    	'''
    	while Window.GetMouseButtons() != 0:
    		sys.sleep(10)
    	'''
    	
    	if not Draw.PupBlock(ob % len(obList), pup_block):
    		return
    	del ob
    	
    	# Convert from being button types
    	USER_PROJECTION_LIMIT = USER_PROJECTION_LIMIT.val
    	USER_ONLY_SELECTED_FACES = USER_ONLY_SELECTED_FACES.val
    	USER_SHARE_SPACE = USER_SHARE_SPACE.val
    	USER_STRETCH_ASPECT = USER_STRETCH_ASPECT.val
    	USER_ISLAND_MARGIN = USER_ISLAND_MARGIN.val
    	USER_FILL_HOLES = USER_FILL_HOLES.val
    	USER_FILL_HOLES_QUALITY = USER_FILL_HOLES_QUALITY.val
    	USER_VIEW_INIT = USER_VIEW_INIT.val
    	USER_AREA_WEIGHT = USER_AREA_WEIGHT.val
    	
    	USER_PROJECTION_LIMIT_CONVERTED = cos(USER_PROJECTION_LIMIT * DEG_TO_RAD)
    	USER_PROJECTION_LIMIT_HALF_CONVERTED = cos((USER_PROJECTION_LIMIT/2) * DEG_TO_RAD)
    	
    	
    	# Toggle Edit mode
    	is_editmode = Window.EditMode()
    	if is_editmode:
    		Window.EditMode(0)
    	# Assume face select mode! an annoying hack to toggle face select mode because Mesh dosent like faceSelectMode.
    	
    	if USER_SHARE_SPACE:
    		# Sort by data name so we get consistant results
    		try:	obList.sort(key = lambda ob: ob.getData(name_only=1))
    		except:	obList.sort(lambda ob1, ob2: cmp( ob1.getData(name_only=1), ob2.getData(name_only=1) ))
    		
    		collected_islandList= []
    	
    	Window.WaitCursor(1)
    	
    	time1 = sys.time()
    	
    	# Tag as False se we dont operate on teh same mesh twice.
    	bpy.data.meshes.tag = False 
    	
    	for ob in obList:
    		me = ob.getData(mesh=1)
    		
    		if me.tag or me.lib:
    			continue
    		
    		# Tag as used
    		me.tag = True
    		
    		if not me.faceUV: # Mesh has no UV Coords, dont bother.
    			me.faceUV= True
    		
    		if USER_ONLY_SELECTED_FACES:
    			meshFaces = [thickface(f) for f in me.faces if f.sel]
    		else:
    			meshFaces = map(thickface, me.faces)
    		
    		if not meshFaces:
    			continue
    		
    		Window.DrawProgressBar(0.1, 'SmartProj UV Unwrapper, mapping "%s", %i faces.' % (me.name, len(meshFaces)))
    		
    		# =======
    		# Generate a projection list from face normals, this is ment to be smart :)
    		
    		# make a list of face props that are in sync with meshFaces		
    		# Make a Face List that is sorted by area.
    		# meshFaces = []
    		
    		# meshFaces.sort( lambda a, b: cmp(b.area , a.area) ) # Biggest first.
    		try:	meshFaces.sort( key = lambda a: -a.area ) 
    		except:	meshFaces.sort( lambda a, b: cmp(b.area , a.area) )
    			
    		# remove all zero area faces
    		while meshFaces and meshFaces[-1].area <= SMALL_NUM:
    			# Set their UV's to 0,0
    			for uv in meshFaces[-1].uv:
    				uv.zero()
    			meshFaces.pop()
    		
    		# Smallest first is slightly more efficient, but if the user cancels early then its better we work on the larger data.
    		
    		# Generate Projection Vecs
    		# 0d is   1.0
    		# 180 IS -0.59846
    		
    		
    		# Initialize projectVecs
    		if USER_VIEW_INIT:
    			# Generate Projection
    			projectVecs = [Vector(Window.GetViewVector()) * ob.matrixWorld.copy().invert().rotationPart()] # We add to this allong the way
    		else:
    			projectVecs = []
    		
    		newProjectVec = meshFaces[0].no
    		newProjectMeshFaces = []	# Popping stuffs it up.
    		
    		
    		# Predent that the most unique angke is ages away to start the loop off
    		mostUniqueAngle = -1.0
    		
    		# This is popped
    		tempMeshFaces = meshFaces[:]
    		
    		
    		
    		# This while only gathers projection vecs, faces are assigned later on.
    		while 1:
    			# If theres none there then start with the largest face
    			
    			# add all the faces that are close.