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