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