1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
17 # ##### END GPL LICENSE BLOCK #####
19 # TODO <pep8 compliant>
21 from mathutils import Matrix, Vector, geometry
23 from bpy.types import Operator
25 DEG_TO_RAD = 0.017453292519943295 # pi/180.0
26 SMALL_NUM = 0.000000001
29 global USER_FILL_HOLES
30 global USER_FILL_HOLES_QUALITY
31 USER_FILL_HOLES = None
32 USER_FILL_HOLES_QUALITY = None
34 def pointInTri2D(v, v1, v2, v3):
35 key = v1.x, v1.y, v2.x, v2.y, v3.x, v3.y
37 # Commented because its slower to do the bounds check, we should realy cache the bounds info for each face.
58 if x<xmin or x>xmax or y < ymin or y > ymax:
60 # Done with bounds check
63 mtx = dict_matrix[key]
70 nor = side1.cross(side2)
72 mtx = Matrix((side1, side2, nor))
74 # Zero area 2d tri, even tho we throw away zerop area faces
75 # the projection UV can result in a zero area UV.
76 if not mtx.determinant():
77 dict_matrix[key] = None
82 dict_matrix[key] = mtx
85 return 0 <= uvw[0] and 0 <= uvw[1] and uvw[0] + uvw[1] <= 1
88 def boundsIsland(faces):
89 minx = maxx = faces[0].uv[0][0] # Set initial bounds.
90 miny = maxy = faces[0].uv[0][1]
91 # print len(faces), minx, maxx, miny , maxy
101 return minx, miny, maxx, maxy
104 def boundsEdgeLoop(edges):
105 minx = maxx = edges[0][0] # Set initial bounds.
106 miny = maxy = edges[0][1]
107 # print len(faces), minx, maxx, miny , maxy
118 return minx, miny, maxx, maxy
121 # Turns the islands into a list of unpordered edges (Non internal)
123 # only returns outline edges for intersection tests. and unique points.
125 def island2Edge(island):
133 f_uvkey= map(tuple, f.uv)
136 for vIdx, edkey in enumerate(f.edge_keys):
137 unique_points[f_uvkey[vIdx]] = f.uv[vIdx]
139 if f.v[vIdx].index > f.v[vIdx-1].index:
144 try: edges[ f_uvkey[i1], f_uvkey[i2] ] *= 0 # sets eny edge with more then 1 user to 0 are not returned.
145 except: edges[ f_uvkey[i1], f_uvkey[i2] ] = (f.uv[i1] - f.uv[i2]).length,
147 # If 2 are the same then they will be together, but full [a,b] order is not correct.
152 length_sorted_edges = [(Vector(key[0]), Vector(key[1]), value) for key, value in edges.items() if value != 0]
154 try: length_sorted_edges.sort(key = lambda A: -A[2]) # largest first
155 except: length_sorted_edges.sort(lambda A, B: cmp(B[2], A[2]))
157 # Its okay to leave the length in there.
158 #for e in length_sorted_edges:
161 # return edges and unique points
162 return length_sorted_edges, [v.to_3d() for v in unique_points.values()]
164 # ========================= NOT WORKING????
165 # Find if a points inside an edge loop, un-orderd.
167 # edges are a non ordered loop of edges.
168 # #offsets are the edge x and y offset.
170 def pointInEdges(pt, edges):
175 # Point to the left of this line.
180 xi, yi = lineIntersection2D(x1,y1, x2,y2, ed[0][0], ed[0][1], ed[1][0], ed[1][1])
181 if xi != None: # Is there an intersection.
184 return intersectCount % 2
187 def pointInIsland(pt, island):
188 vec1, vec2, vec3 = Vector(), Vector(), Vector()
190 vec1.x, vec1.y = f.uv[0]
191 vec2.x, vec2.y = f.uv[1]
192 vec3.x, vec3.y = f.uv[2]
194 if pointInTri2D(pt, vec1, vec2, vec3):
198 vec1.x, vec1.y = f.uv[0]
199 vec2.x, vec2.y = f.uv[2]
200 vec3.x, vec3.y = f.uv[3]
201 if pointInTri2D(pt, vec1, vec2, vec3):
206 # box is (left,bottom, right, top)
207 def islandIntersectUvIsland(source, target, SourceOffset):
208 # Is 1 point in the box, inside the vertLoops
209 edgeLoopsSource = source[6] # Pretend this is offset
210 edgeLoopsTarget = target[6]
212 # Edge intersect test
213 for ed in edgeLoopsSource:
214 for seg in edgeLoopsTarget:
215 i = geometry.intersect_line_line_2d(\
216 seg[0], seg[1], SourceOffset+ed[0], SourceOffset+ed[1])
218 return 1 # LINE INTERSECTION
220 # 1 test for source being totally inside target
221 SourceOffset.resize_3d()
223 if pointInIsland(pv+SourceOffset, target[0]):
224 return 2 # SOURCE INSIDE TARGET
226 # 2 test for a part of the target being totaly inside the source.
228 if pointInIsland(pv-SourceOffset, source[0]):
229 return 3 # PART OF TARGET INSIDE SOURCE.
231 return 0 # NO INTERSECTION
236 # Returns the X/y Bounds of a list of vectors.
237 def testNewVecLs2DRotIsBetter(vecs, mat=-1, bestAreaSoFar = -1):
239 # UV's will never extend this far.
240 minx = miny = BIG_NUM
241 maxx = maxy = -BIG_NUM
243 for i, v in enumerate(vecs):
245 # Do this allong the way
247 v = vecs[i] = mat * v
255 # Spesific to this algo, bail out if we get bigger then the current area
256 if bestAreaSoFar != -1 and (maxx-minx) * (maxy-miny) > bestAreaSoFar:
257 return (BIG_NUM, None), None
260 return (w*h, w,h), vecs # Area, vecs
262 def optiRotateUvIsland(faces):
266 def best2dRotation(uvVecs, MAT1, MAT2):
269 newAreaPos, newfaceProjectionGroupListPos =\
270 testNewVecLs2DRotIsBetter(uvVecs[:], MAT1, currentArea[0])
273 # Why do I use newpos here? May as well give the best area to date for an early bailout
274 # some slight speed increase in this.
275 # If the new rotation is smaller then the existing, we can
276 # avoid copying a list and overwrite the old, crappy one.
278 if newAreaPos[0] < currentArea[0]:
279 newAreaNeg, newfaceProjectionGroupListNeg =\
280 testNewVecLs2DRotIsBetter(uvVecs, MAT2, newAreaPos[0]) # Reuse the old bigger list.
282 newAreaNeg, newfaceProjectionGroupListNeg =\
283 testNewVecLs2DRotIsBetter(uvVecs[:], MAT2, currentArea[0]) # Cant reuse, make a copy.
286 # Now from the 3 options we need to discover which to use
287 # we have cerrentArea/newAreaPos/newAreaNeg
288 bestArea = min(currentArea[0], newAreaPos[0], newAreaNeg[0])
290 if currentArea[0] == bestArea:
292 elif newAreaPos[0] == bestArea:
293 uvVecs = newfaceProjectionGroupListPos
294 currentArea = newAreaPos
295 elif newAreaNeg[0] == bestArea:
296 uvVecs = newfaceProjectionGroupListNeg
297 currentArea = newAreaNeg
302 # Serialized UV coords to Vectors
303 uvVecs = [uv for f in faces for uv in f.uv]
305 # Theres a small enough number of these to hard code it
306 # rather then a loop.
308 # Will not modify anything
309 currentArea, dummy =\
310 testNewVecLs2DRotIsBetter(uvVecs)
314 newAreaPos, newfaceProjectionGroupListPos = testNewVecLs2DRotIsBetter(uvVecs[:], ROTMAT_2D_POS_45D, currentArea[0])
316 if newAreaPos[0] < currentArea[0]:
317 uvVecs = newfaceProjectionGroupListPos
318 currentArea = newAreaPos
321 # Testcase different rotations and find the onfe that best fits in a square
322 for ROTMAT in RotMatStepRotation:
323 uvVecs = best2dRotation(uvVecs, ROTMAT[0], ROTMAT[1])
325 # Only if you want it, make faces verticle!
326 if currentArea[1] > currentArea[2]:
328 # Work directly on the list, no need to return a value.
329 testNewVecLs2DRotIsBetter(uvVecs, ROTMAT_2D_POS_90D)
332 # Now write the vectors back to the face UV's
333 i = 0 # count the serialized uv/vectors
335 #f.uv = [uv for uv in uvVecs[i:len(f)+i] ]
336 for j, k in enumerate(range(i, len(f.v)+i)):
337 f.uv[j][:] = uvVecs[k]
341 # Takes an island list and tries to find concave, hollow areas to pack smaller islands into.
342 def mergeUvIslands(islandList):
343 global USER_FILL_HOLES
344 global USER_FILL_HOLES_QUALITY
347 # Pack islands to bottom LHS
350 #islandTotFaceArea = [] # A list of floats, each island area
351 #islandArea = [] # a list of tuples ( area, w,h)
354 decoratedIslandList = []
356 islandIdx = len(islandList)
359 minx, miny, maxx, maxy = boundsIsland(islandList[islandIdx])
360 w, h = maxx-minx, maxy-miny
363 offset= Vector((minx, miny))
364 for f in islandList[islandIdx]:
368 totFaceArea += f.area
370 islandBoundsArea = w*h
371 efficiency = abs(islandBoundsArea - totFaceArea)
373 # UV Edge list used for intersections as well as unique points.
374 edges, uniqueEdgePoints = island2Edge(islandList[islandIdx])
376 decoratedIslandList.append([islandList[islandIdx], totFaceArea, efficiency, islandBoundsArea, w,h, edges, uniqueEdgePoints])
379 # Sort by island bounding box area, smallest face area first.
380 # no.. chance that to most simple edge loop first.
381 decoratedIslandListAreaSort =decoratedIslandList[:]
383 decoratedIslandListAreaSort.sort(key = lambda A: A[3])
385 # sort by efficiency, Least Efficient first.
386 decoratedIslandListEfficSort = decoratedIslandList[:]
387 # decoratedIslandListEfficSort.sort(lambda A, B: cmp(B[2], A[2]))
389 decoratedIslandListEfficSort.sort(key = lambda A: -A[2])
391 # ================================================== THESE CAN BE TWEAKED.
392 # This is a quality value for the number of tests.
393 # from 1 to 4, generic quality value is from 1 to 100
394 USER_STEP_QUALITY = ((USER_FILL_HOLES_QUALITY - 1) / 25.0) + 1
396 # If 100 will test as long as there is enough free space.
397 # this is rarely enough, and testing takes a while, so lower quality speeds this up.
399 # 1 means they have the same quality
400 USER_FREE_SPACE_TO_TEST_QUALITY = 1 + (((100 - USER_FILL_HOLES_QUALITY)/100.0) *5)
402 #print 'USER_STEP_QUALITY', USER_STEP_QUALITY
403 #print 'USER_FREE_SPACE_TO_TEST_QUALITY', USER_FREE_SPACE_TO_TEST_QUALITY
408 ctrl = Window.Qual.CTRL
410 while areaIslandIdx < len(decoratedIslandListAreaSort) and not BREAK:
411 sourceIsland = decoratedIslandListAreaSort[areaIslandIdx]
413 if not sourceIsland[0]:
417 while efficIslandIdx < len(decoratedIslandListEfficSort) and not BREAK:
419 if Window.GetKeyQualifiers() & ctrl:
423 # Now we have 2 islands, is the efficience of the islands lowers theres an
424 # increasing likely hood that we can fit merge into the bigger UV island.
425 # this ensures a tight fit.
427 # Just use figures we have about user/unused area to see if they might fit.
429 targetIsland = decoratedIslandListEfficSort[efficIslandIdx]
432 if sourceIsland[0] == targetIsland[0] or\
433 not targetIsland[0] or\
438 # ([island, totFaceArea, efficiency, islandArea, w,h])
439 # Waisted space on target is greater then UV bounding island area.
442 # if targetIsland[3] > (sourceIsland[2]) and\ #
443 # print USER_FREE_SPACE_TO_TEST_QUALITY
444 if targetIsland[2] > (sourceIsland[1] * USER_FREE_SPACE_TO_TEST_QUALITY) and\
445 targetIsland[4] > sourceIsland[4] and\
446 targetIsland[5] > sourceIsland[5]:
448 # DEBUG # print '%.10f %.10f' % (targetIsland[3], sourceIsland[1])
450 # These enough spare space lets move the box until it fits
452 # How many times does the source fit into the target x/y
453 blockTestXUnit = targetIsland[4]/sourceIsland[4]
454 blockTestYUnit = targetIsland[5]/sourceIsland[5]
459 # Distllllance we can move between whilst staying inside the targets bounds.
460 testWidth = targetIsland[4] - sourceIsland[4]
461 testHeight = targetIsland[5] - sourceIsland[5]
463 # Increment we move each test. x/y
464 xIncrement = (testWidth / (blockTestXUnit * ((USER_STEP_QUALITY/50)+0.1)))
465 yIncrement = (testHeight / (blockTestYUnit * ((USER_STEP_QUALITY/50)+0.1)))
467 # Make sure were not moving less then a 3rg of our width/height
468 if xIncrement<sourceIsland[4]/3:
469 xIncrement= sourceIsland[4]
470 if yIncrement<sourceIsland[5]/3:
471 yIncrement= sourceIsland[5]
474 boxLeft = 0 # Start 1 back so we can jump into the loop.
475 boxBottom= 0 #-yIncrement
479 while boxBottom <= testHeight:
480 # Should we use this? - not needed for now.
481 #if Window.GetKeyQualifiers() & ctrl:
486 #print 'Testing intersect'
487 Intersect = islandIntersectUvIsland(sourceIsland, targetIsland, Vector((boxLeft, boxBottom)))
488 #print 'Done', Intersect
489 if Intersect == 1: # Line intersect, dont bother with this any more
492 if Intersect == 2: # Source inside target
494 We have an intersection, if we are inside the target
495 then move us 1 whole width accross,
496 Its possible this is a bad idea since 2 skinny Angular faces
497 could join without 1 whole move, but its a lot more optimal to speed this up
498 since we have already tested for it.
500 It gives about 10% speedup with minimal errors.
503 # Move the test allong its width + SMALL_NUM
504 #boxLeft += sourceIsland[4] + SMALL_NUM
505 boxLeft += sourceIsland[4]
506 elif Intersect == 0: # No intersection?? Place it.
509 #XXX Window.DrawProgressBar(0.0, 'Merged: %i islands, Ctrl to finish early.' % removedCount)
511 # Move faces into new island and offset
512 targetIsland[0].extend(sourceIsland[0])
513 offset= Vector((boxLeft, boxBottom))
515 for f in sourceIsland[0]:
519 sourceIsland[0][:] = [] # Empty
522 # Move edge loop into new and offset.
523 # targetIsland[6].extend(sourceIsland[6])
524 #while sourceIsland[6]:
525 targetIsland[6].extend( [ (\
526 (e[0]+offset, e[1]+offset, e[2])\
527 ) for e in sourceIsland[6] ] )
529 sourceIsland[6][:] = [] # Empty
531 # Sort by edge length, reverse so biggest are first.
533 try: targetIsland[6].sort(key = lambda A: A[2])
534 except: targetIsland[6].sort(lambda B,A: cmp(A[2], B[2] ))
537 targetIsland[7].extend(sourceIsland[7])
538 offset= Vector((boxLeft, boxBottom, 0.0))
539 for p in sourceIsland[7]:
542 sourceIsland[7][:] = []
545 # Decrement the efficiency
546 targetIsland[1]+=sourceIsland[1] # Increment totFaceArea
547 targetIsland[2]-=sourceIsland[1] # Decrement efficiency
548 # IF we ever used these again, should set to 0, eg
549 sourceIsland[2] = 0 # No area if anyone wants to know
554 # INCREMENR NEXT LOCATION
555 if boxLeft > testWidth:
556 boxBottom += yIncrement
559 boxLeft += xIncrement
565 # Remove empty islands
569 if not islandList[i]:
570 del islandList[i] # Can increment islands removed here.
572 # Takes groups of faces. assumes face groups are UV groups.
573 def getUvIslands(faceGroups, me):
575 # Get seams so we dont cross over seams
576 edge_seams = {} # shoudl be a set
579 edge_seams[ed.key] = None # dummy var- use sets!
585 #XXX Window.DrawProgressBar(0.0, 'Splitting %d projection groups into UV islands:' % len(faceGroups))
586 #print '\tSplitting %d projection groups into UV islands:' % len(faceGroups),
589 faceGroupIdx = len(faceGroups)
593 faces = faceGroups[faceGroupIdx]
601 for i, f in enumerate(faces):
602 for ed_key in f.edge_keys:
603 if ed_key in edge_seams: # DELIMIT SEAMS! ;)
604 edge_users[ed_key] = [] # so as not to raise an error
606 try: edge_users[ed_key].append(i)
607 except: edge_users[ed_key] = [i]
610 # 0 - face not yet touched.
611 # 1 - added to island list, and need to search
612 # 2 - touched and searched - dont touch again.
613 face_modes = [0] * len(faces) # initialize zero - untested.
615 face_modes[0] = 1 # start the search with face 1
619 newIsland.append(faces[0])
628 for i in range(len(faces)):
629 if face_modes[i] == 1: # search
630 for ed_key in faces[i].edge_keys:
631 for ii in edge_users[ed_key]:
632 if i != ii and face_modes[ii] == 0:
633 face_modes[ii] = ok = 1 # mark as searched
634 newIsland.append(faces[ii])
636 # mark as searched, dont look again.
639 islandList.append(newIsland)
642 for i in range(len(faces)):
643 if face_modes[i] == 0:
645 newIsland.append(faces[i])
647 face_modes[i] = ok = 1
649 # if not ok will stop looping
651 #XXX Window.DrawProgressBar(0.1, 'Optimizing Rotation for %i UV Islands' % len(islandList))
653 for island in islandList:
654 optiRotateUvIsland(island)
659 def packIslands(islandList):
661 #XXX Window.DrawProgressBar(0.1, 'Merging Islands (Ctrl: skip merge)...')
662 mergeUvIslands(islandList) # Modify in place
665 # Now we have UV islands, we need to pack them.
667 # Make a synchronised list with the islands
668 # so we can box pak the islands.
671 # Keep a list of X/Y offset so we can save time by writing the
672 # uv's and packed data in one pass.
673 islandOffsetList = []
677 while islandIdx < len(islandList):
678 minx, miny, maxx, maxy = boundsIsland(islandList[islandIdx])
680 w, h = maxx-minx, maxy-miny
682 if USER_ISLAND_MARGIN:
683 minx -= USER_ISLAND_MARGIN# *w
684 miny -= USER_ISLAND_MARGIN# *h
685 maxx += USER_ISLAND_MARGIN# *w
686 maxy += USER_ISLAND_MARGIN# *h
688 # recalc width and height
689 w, h = maxx-minx, maxy-miny
691 if w < 0.00001 or h < 0.00001:
692 del islandList[islandIdx]
696 '''Save the offset to be applied later,
697 we could apply to the UVs now and allign them to the bottom left hand area
698 of the UV coords like the box packer imagines they are
699 but, its quicker just to remember their offset and
700 apply the packing and offset in 1 pass '''
701 islandOffsetList.append((minx, miny))
703 # Add to boxList. use the island idx for the BOX id.
704 packBoxes.append([0, 0, w, h])
707 # Now we have a list of boxes to pack that syncs
710 #print '\tPacking UV Islands...'
711 #XXX Window.DrawProgressBar(0.7, 'Packing %i UV Islands...' % len(packBoxes) )
713 # time1 = time.time()
714 packWidth, packHeight = geometry.box_pack_2d(packBoxes)
716 # print 'Box Packing Time:', time.time() - time1
718 #if len(pa ckedLs) != len(islandList):
719 # raise "Error packed boxes differes from original length"
721 #print '\tWriting Packed Data to faces'
722 #XXX Window.DrawProgressBar(0.8, 'Writing Packed Data to faces')
724 # Sort by ID, so there in sync again
725 islandIdx = len(islandList)
726 # Having these here avoids devide by 0
729 if USER_STRETCH_ASPECT:
730 # Maximize to uv area?? Will write a normalize function.
731 xfactor = 1.0 / packWidth
732 yfactor = 1.0 / packHeight
735 xfactor = yfactor = 1.0 / max(packWidth, packHeight)
739 # Write the packed values to the UV's
741 xoffset = packBoxes[islandIdx][0] - islandOffsetList[islandIdx][0]
742 yoffset = packBoxes[islandIdx][1] - islandOffsetList[islandIdx][1]
744 for f in islandList[islandIdx]: # Offsetting the UV's so they fit in there packed box
746 uv.x= (uv.x+xoffset) * xfactor
747 uv.y= (uv.y+yoffset) * yfactor
751 vec = vec.normalized()
752 return vec.to_track_quat('Z', 'X' if abs(vec.x) > 0.5 else 'Y').inverted()
755 class thickface(object):
756 __slost__= 'v', 'uv', 'no', 'area', 'edge_keys'
757 def __init__(self, face, uvface, mesh_verts):
758 self.v = [mesh_verts[i] for i in face.vertices]
760 self.uv = uvface.uv1, uvface.uv2, uvface.uv3, uvface.uv4
762 self.uv = uvface.uv1, uvface.uv2, uvface.uv3
764 self.no = face.normal
765 self.area = face.area
766 self.edge_keys = face.edge_keys
770 from math import radians
772 global ROTMAT_2D_POS_90D
773 global ROTMAT_2D_POS_45D
774 global RotMatStepRotation
776 ROTMAT_2D_POS_90D = Matrix.Rotation( radians(90.0), 2)
777 ROTMAT_2D_POS_45D = Matrix.Rotation( radians(45.0), 2)
779 RotMatStepRotation = []
780 rot_angle = 22.5 #45.0/2
781 while rot_angle > 0.1:
782 RotMatStepRotation.append([\
783 Matrix.Rotation( radians(rot_angle), 2),\
784 Matrix.Rotation( radians(-rot_angle), 2)])
786 rot_angle = rot_angle/2.0
796 global USER_FILL_HOLES
797 global USER_FILL_HOLES_QUALITY
798 global USER_STRETCH_ASPECT
799 global USER_ISLAND_MARGIN
809 # Takes a list of faces that make up a UV island and rotate
810 # until they optimally fit inside a square.
811 global ROTMAT_2D_POS_90D
812 global ROTMAT_2D_POS_45D
813 global RotMatStepRotation
816 # TODO, all selected meshes
818 # objects = context.selected_editable_objects
821 # we can will tag them later.
822 obList = [ob for ob in objects if ob.type == 'MESH']
824 # Face select object may not be selected.
825 ob = context.active_object
827 if ob and (not ob.select) and ob.type == 'MESH':
834 obList = [ob for ob in [context.active_object] if ob and ob.type == 'MESH']
837 raise('error, no selected mesh objects')
839 # Create the variables.
840 USER_PROJECTION_LIMIT = projection_limit
841 USER_ONLY_SELECTED_FACES = (1)
842 USER_SHARE_SPACE = (1) # Only for hole filling.
843 USER_STRETCH_ASPECT = (1) # Only for hole filling.
844 USER_ISLAND_MARGIN = island_margin # Only for hole filling.
845 USER_FILL_HOLES = (0)
846 USER_FILL_HOLES_QUALITY = (50) # Only for hole filling.
847 USER_VIEW_INIT = (0) # Only for hole filling.
851 ob = "Unwrap %i Selected Mesh"
853 ob = "Unwrap %i Selected Meshes"
855 # HACK, loop until mouse is lifted.
857 while Window.GetMouseButtons() != 0:
861 #XXX if not Draw.PupBlock(ob % len(obList), pup_block):
865 # Convert from being button types
867 USER_PROJECTION_LIMIT_CONVERTED = cos(USER_PROJECTION_LIMIT * DEG_TO_RAD)
868 USER_PROJECTION_LIMIT_HALF_CONVERTED = cos((USER_PROJECTION_LIMIT/2) * DEG_TO_RAD)
872 is_editmode = (context.active_object.mode == 'EDIT')
874 bpy.ops.object.mode_set(mode='OBJECT')
875 # Assume face select mode! an annoying hack to toggle face select mode because Mesh dosent like faceSelectMode.
878 # Sort by data name so we get consistant results
879 obList.sort(key = lambda ob: ob.data.name)
880 collected_islandList= []
882 #XXX Window.WaitCursor(1)
886 # Tag as False se we dont operate on the same mesh twice.
887 #XXX bpy.data.meshes.tag = False
888 for me in bpy.data.meshes:
895 if me.tag or me.library:
901 if not me.uv_textures: # Mesh has no UV Coords, dont bother.
904 uv_layer = me.uv_textures.active.data
905 me_verts = list(me.vertices)
907 if USER_ONLY_SELECTED_FACES:
908 meshFaces = [thickface(f, uv_layer[i], me_verts) for i, f in enumerate(me.faces) if f.select]
910 # meshFaces = map(thickface, me.faces)
915 #XXX Window.DrawProgressBar(0.1, 'SmartProj UV Unwrapper, mapping "%s", %i faces.' % (me.name, len(meshFaces)))
918 # Generate a projection list from face normals, this is ment to be smart :)
920 # make a list of face props that are in sync with meshFaces
921 # Make a Face List that is sorted by area.
924 # meshFaces.sort( lambda a, b: cmp(b.area , a.area) ) # Biggest first.
925 meshFaces.sort( key = lambda a: -a.area )
927 # remove all zero area faces
928 while meshFaces and meshFaces[-1].area <= SMALL_NUM:
929 # Set their UV's to 0,0
930 for uv in meshFaces[-1].uv:
934 # Smallest first is slightly more efficient, but if the user cancels early then its better we work on the larger data.
936 # Generate Projection Vecs
941 # Initialize projectVecs
943 # Generate Projection
944 projectVecs = [Vector(Window.GetViewVector()) * ob.matrix_world.inverted().to_3x3()] # We add to this allong the way
948 newProjectVec = meshFaces[0].no
949 newProjectMeshFaces = [] # Popping stuffs it up.
952 # Predent that the most unique angke is ages away to start the loop off
953 mostUniqueAngle = -1.0
956 tempMeshFaces = meshFaces[:]
960 # This while only gathers projection vecs, faces are assigned later on.
962 # If theres none there then start with the largest face
964 # add all the faces that are close.
965 for fIdx in range(len(tempMeshFaces)-1, -1, -1):
966 # Use half the angle limit so we dont overweight faces towards this
967 # normal and hog all the faces.
968 if newProjectVec.dot(tempMeshFaces[fIdx].no) > USER_PROJECTION_LIMIT_HALF_CONVERTED:
969 newProjectMeshFaces.append(tempMeshFaces.pop(fIdx))
971 # Add the average of all these faces normals as a projectionVec
972 averageVec = Vector((0.0, 0.0, 0.0))
973 if user_area_weight == 0.0:
974 for fprop in newProjectMeshFaces:
975 averageVec += fprop.no
976 elif user_area_weight == 1.0:
977 for fprop in newProjectMeshFaces:
978 averageVec += fprop.no * fprop.area
980 for fprop in newProjectMeshFaces:
981 averageVec += fprop.no * ((fprop.area * user_area_weight) + (1.0 - user_area_weight))
983 if averageVec.x != 0 or averageVec.y != 0 or averageVec.z != 0: # Avoid NAN
984 projectVecs.append(averageVec.normalized())
988 # Pick the face thats most different to all existing angles :)
989 mostUniqueAngle = 1.0 # 1.0 is 0d. no difference.
990 mostUniqueIndex = 0 # dummy
992 for fIdx in range(len(tempMeshFaces)-1, -1, -1):
993 angleDifference = -1.0 # 180d difference.
995 # Get the closest vec angle we are to.
996 for p in projectVecs:
997 temp_angle_diff= p.dot(tempMeshFaces[fIdx].no)
999 if angleDifference < temp_angle_diff:
1000 angleDifference= temp_angle_diff
1002 if angleDifference < mostUniqueAngle:
1003 # We have a new most different angle
1004 mostUniqueIndex = fIdx
1005 mostUniqueAngle = angleDifference
1007 if mostUniqueAngle < USER_PROJECTION_LIMIT_CONVERTED:
1008 #print 'adding', mostUniqueAngle, USER_PROJECTION_LIMIT, len(newProjectMeshFaces)
1009 # Now weight the vector to all its faces, will give a more direct projection
1010 # if the face its self was not representive of the normal from surrounding faces.
1012 newProjectVec = tempMeshFaces[mostUniqueIndex].no
1013 newProjectMeshFaces = [tempMeshFaces.pop(mostUniqueIndex)]
1017 if len(projectVecs) >= 1: # Must have at least 2 projections
1021 # If there are only zero area faces then its possible
1022 # there are no projectionVecs
1023 if not len(projectVecs):
1024 Draw.PupMenu('error, no projection vecs where generated, 0 area faces can cause this.')
1027 faceProjectionGroupList =[[] for i in range(len(projectVecs)) ]
1029 # MAP and Arrange # We know there are 3 or 4 faces here
1031 for fIdx in range(len(meshFaces)-1, -1, -1):
1032 fvec = meshFaces[fIdx].no
1033 i = len(projectVecs)
1036 bestAng = fvec.dot(projectVecs[0])
1039 # Cycle through the remaining, first already done
1043 newAng = fvec.dot(projectVecs[i])
1044 if newAng > bestAng: # Reverse logic for dotvecs
1048 # Store the area for later use.
1049 faceProjectionGroupList[bestAngIdx].append(meshFaces[fIdx])
1051 # Cull faceProjectionGroupList,
1054 # Now faceProjectionGroupList is full of faces that face match the project Vecs list
1055 for i in range(len(projectVecs)):
1056 # Account for projectVecs having no faces.
1057 if not faceProjectionGroupList[i]:
1060 # Make a projection matrix from a unit length vector.
1061 MatQuat = VectoQuat(projectVecs[i])
1063 # Get the faces UV's from the projected vertex.
1064 for f in faceProjectionGroupList[i]:
1066 for j, v in enumerate(f.v):
1067 # XXX - note, between mathutils in 2.4 and 2.5 the order changed.
1068 f_uv[j][:] = (MatQuat * v.co).xy
1071 if USER_SHARE_SPACE:
1072 # Should we collect and pack later?
1073 islandList = getUvIslands(faceProjectionGroupList, me)
1074 collected_islandList.extend(islandList)
1077 # Should we pack the islands for this 1 object?
1078 islandList = getUvIslands(faceProjectionGroupList, me)
1079 packIslands(islandList)
1082 # update the mesh here if we need to.
1084 # We want to pack all in 1 go, so pack now
1085 if USER_SHARE_SPACE:
1086 #XXX Window.DrawProgressBar(0.9, "Box Packing for all objects...")
1087 packIslands(collected_islandList)
1089 print("Smart Projection time: %.2f" % (time.time() - time1))
1090 # Window.DrawProgressBar(0.9, "Smart Projections done, time: %.2f sec." % (time.time() - time1))
1093 bpy.ops.object.mode_set(mode='EDIT')
1097 #XXX Window.DrawProgressBar(1.0, "")
1098 #XXX Window.WaitCursor(0)
1099 #XXX Window.RedrawAll()
1104 ('Selected Faces Only', USER_ONLY_SELECTED_FACES, 'Use only selected faces from all selected meshes.'),\
1105 ('Init from view', USER_VIEW_INIT, 'The first projection will be from the view vector.'),\
1108 ('Share Tex Space', USER_SHARE_SPACE, 'Objects Share texture space, map all objects into 1 uvmap.'),\
1109 ('Stretch to bounds', USER_STRETCH_ASPECT, 'Stretch the final output to texture bounds.'),\
1110 * ('Island Margin:', USER_ISLAND_MARGIN, 0.0, 0.5, ''),\
1111 'Fill in empty areas',\
1112 ('Fill Holes', USER_FILL_HOLES, 'Fill in empty areas reduced texture waistage (slow).'),\
1113 ('Fill Quality:', USER_FILL_HOLES_QUALITY, 1, 100, 'Depends on fill holes, how tightly to fill UV holes, (higher is slower)'),\
1117 from bpy.props import FloatProperty
1120 class SmartProject(Operator):
1121 '''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.'''
1122 bl_idname = "uv.smart_project"
1123 bl_label = "Smart UV Project"
1124 bl_options = {'REGISTER', 'UNDO'}
1126 angle_limit = FloatProperty(name="Angle Limit",
1127 description="lower for more projection groups, higher for less distortion",
1128 default=66.0, min=1.0, max=89.0)
1130 island_margin = FloatProperty(name="Island Margin",
1131 description="Margin to reduce bleed from adjacent islands",
1132 default=0.0, min=0.0, max=1.0)
1134 user_area_weight = FloatProperty(name="Area Weight",
1135 description="Weight projections vector by faces with larger areas",
1136 default=0.0, min=0.0, max=1.0)
1139 def poll(cls, context):
1140 return context.active_object != None
1142 def execute(self, context):
1146 self.user_area_weight,
1150 def invoke(self, context, event):
1151 wm = context.window_manager
1152 return wm.invoke_props_dialog(self)