1
# --------------------------------------------------------------------------
2
# Smart Projection UV Projection Unwrapper v1.2 by Campbell Barton (AKA Ideasman)
3
# --------------------------------------------------------------------------
4
# ***** BEGIN GPL LICENSE BLOCK *****
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
# ***** END GPL LICENCE BLOCK *****
21
# --------------------------------------------------------------------------
25
from mathutils import Matrix, Vector, RotationMatrix
29
from math import cos, radians
31
DEG_TO_RAD = 0.017453292519943295 # pi/180.0
32
SMALL_NUM = 0.000000001
35
global USER_FILL_HOLES
36
global USER_FILL_HOLES_QUALITY
37
USER_FILL_HOLES = None
38
USER_FILL_HOLES_QUALITY = None
42
def pointInTri2D(v, v1, v2, v3):
45
key = v1.x, v1.y, v2.x, v2.y, v3.x, v3.y
47
# Commented because its slower to do teh bounds check, we should realy cache the bounds info for each face.
68
if x<xmin or x>xmax or y < ymin or y > ymax:
70
# Done with bounds check
73
mtx = dict_matrix[key]
80
nor = side1.cross(side2)
82
l1 = [side1[0], side1[1], side1[2]]
83
l2 = [side2[0], side2[1], side2[2]]
84
l3 = [nor[0], nor[1], nor[2]]
86
mtx = Matrix(l1, l2, l3)
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
96
dict_matrix[key] = mtx
99
return 0 <= uvw[0] and 0 <= uvw[1] and uvw[0] + uvw[1] <= 1
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
115
return minx, miny, maxx, maxy
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
132
return minx, miny, maxx, maxy
135
# Turns the islands into a list of unpordered edges (Non internal)
137
# only returns outline edges for intersection tests. and unique points.
139
def island2Edge(island):
147
f_uvkey= map(tuple, f.uv)
150
for vIdx, edkey in enumerate(f.edge_keys):
151
unique_points[f_uvkey[vIdx]] = f.uv[vIdx]
153
if f.v[vIdx].index > f.v[vIdx-1].index:
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,
161
# If 2 are the same then they will be together, but full [a,b] order is not correct.
166
length_sorted_edges = [(Vector(key[0]), Vector(key[1]), value) for key, value in edges.items() if value != 0]
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]))
171
# Its okay to leave the length in there.
172
#for e in length_sorted_edges:
175
# return edges and unique points
176
return length_sorted_edges, [v.__copy__().resize3D() for v in unique_points.values()]
178
# ========================= NOT WORKING????
179
# Find if a points inside an edge loop, un-orderd.
181
# edges are a non ordered loop of edges.
182
# #offsets are the edge x and y offset.
184
def pointInEdges(pt, edges):
189
# Point to the left of this line.
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.
198
return intersectCount % 2
201
def pointInIsland(pt, island):
202
vec1, vec2, vec3 = Vector(), Vector(), Vector()
204
vec1.x, vec1.y = f.uv[0]
205
vec2.x, vec2.y = f.uv[1]
206
vec3.x, vec3.y = f.uv[2]
208
if pointInTri2D(pt, vec1, vec2, vec3):
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):
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]
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])
232
return 1 # LINE INTERSECTION
234
# 1 test for source being totally inside target
235
SourceOffset.resize3D()
237
if pointInIsland(pv+SourceOffset, target[0]):
238
return 2 # SOURCE INSIDE TARGET
240
# 2 test for a part of the target being totaly inside the source.
242
if pointInIsland(pv-SourceOffset, source[0]):
243
return 3 # PART OF TARGET INSIDE SOURCE.
245
return 0 # NO INTERSECTION
250
# Returns the X/y Bounds of a list of vectors.
251
def testNewVecLs2DRotIsBetter(vecs, mat=-1, bestAreaSoFar = -1):
253
# UV's will never extend this far.
254
minx = miny = BIG_NUM
255
maxx = maxy = -BIG_NUM
257
for i, v in enumerate(vecs):
259
# Do this allong the way
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
274
return (w*h, w,h), vecs # Area, vecs
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)
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)])
288
rot_angle = rot_angle/2.0
291
def optiRotateUvIsland(faces):
295
def best2dRotation(uvVecs, MAT1, MAT2):
298
newAreaPos, newfaceProjectionGroupListPos =\
299
testNewVecLs2DRotIsBetter(uvVecs[:], MAT1, currentArea[0])
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.
307
if newAreaPos[0] < currentArea[0]:
308
newAreaNeg, newfaceProjectionGroupListNeg =\
309
testNewVecLs2DRotIsBetter(uvVecs, MAT2, newAreaPos[0]) # Reuse the old bigger list.
311
newAreaNeg, newfaceProjectionGroupListNeg =\
312
testNewVecLs2DRotIsBetter(uvVecs[:], MAT2, currentArea[0]) # Cant reuse, make a copy.
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])
319
if currentArea[0] == bestArea:
321
elif newAreaPos[0] == bestArea:
322
uvVecs = newfaceProjectionGroupListPos
323
currentArea = newAreaPos
324
elif newAreaNeg[0] == bestArea:
325
uvVecs = newfaceProjectionGroupListNeg
326
currentArea = newAreaNeg
331
# Serialized UV coords to Vectors
332
uvVecs = [uv for f in faces for uv in f.uv]
334
# Theres a small enough number of these to hard code it
335
# rather then a loop.
337
# Will not modify anything
338
currentArea, dummy =\
339
testNewVecLs2DRotIsBetter(uvVecs)
343
newAreaPos, newfaceProjectionGroupListPos = testNewVecLs2DRotIsBetter(uvVecs[:], ROTMAT_2D_POS_45D, currentArea[0])
345
if newAreaPos[0] < currentArea[0]:
346
uvVecs = newfaceProjectionGroupListPos
347
currentArea = newAreaPos
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])
354
# Only if you want it, make faces verticle!
355
if currentArea[1] > currentArea[2]:
357
# Work directly on the list, no need to return a value.
358
testNewVecLs2DRotIsBetter(uvVecs, ROTMAT_2D_POS_90D)
361
# Now write the vectors back to the face UV's
362
i = 0 # count the serialized uv/vectors
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]
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
376
# Pack islands to bottom LHS
379
#islandTotFaceArea = [] # A list of floats, each island area
380
#islandArea = [] # a list of tuples ( area, w,h)
383
decoratedIslandList = []
385
islandIdx = len(islandList)
388
minx, miny, maxx, maxy = boundsIsland(islandList[islandIdx])
389
w, h = maxx-minx, maxy-miny
392
offset= Vector((minx, miny))
393
for f in islandList[islandIdx]:
397
totFaceArea += f.area
399
islandBoundsArea = w*h
400
efficiency = abs(islandBoundsArea - totFaceArea)
402
# UV Edge list used for intersections as well as unique points.
403
edges, uniqueEdgePoints = island2Edge(islandList[islandIdx])
405
decoratedIslandList.append([islandList[islandIdx], totFaceArea, efficiency, islandBoundsArea, w,h, edges, uniqueEdgePoints])
408
# Sort by island bounding box area, smallest face area first.
409
# no.. chance that to most simple edge loop first.
410
decoratedIslandListAreaSort =decoratedIslandList[:]
412
decoratedIslandListAreaSort.sort(key = lambda A: A[3])
414
# sort by efficiency, Least Efficient first.
415
decoratedIslandListEfficSort = decoratedIslandList[:]
416
# decoratedIslandListEfficSort.sort(lambda A, B: cmp(B[2], A[2]))
418
decoratedIslandListEfficSort.sort(key = lambda A: -A[2])
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
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.
428
# 1 means they have the same quality
429
USER_FREE_SPACE_TO_TEST_QUALITY = 1 + (((100 - USER_FILL_HOLES_QUALITY)/100.0) *5)
431
#print 'USER_STEP_QUALITY', USER_STEP_QUALITY
432
#print 'USER_FREE_SPACE_TO_TEST_QUALITY', USER_FREE_SPACE_TO_TEST_QUALITY
437
ctrl = Window.Qual.CTRL
439
while areaIslandIdx < len(decoratedIslandListAreaSort) and not BREAK:
440
sourceIsland = decoratedIslandListAreaSort[areaIslandIdx]
442
if not sourceIsland[0]:
446
while efficIslandIdx < len(decoratedIslandListEfficSort) and not BREAK:
448
if Window.GetKeyQualifiers() & ctrl:
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.
456
# Just use figures we have about user/unused area to see if they might fit.
458
targetIsland = decoratedIslandListEfficSort[efficIslandIdx]
461
if sourceIsland[0] == targetIsland[0] or\
462
not targetIsland[0] or\
467
# ([island, totFaceArea, efficiency, islandArea, w,h])
468
# Waisted space on target is greater then UV bounding island area.
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]:
477
# DEBUG # print '%.10f %.10f' % (targetIsland[3], sourceIsland[1])
479
# These enough spare space lets move the box until it fits
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]
488
# Distllllance we can move between whilst staying inside the targets bounds.
489
testWidth = targetIsland[4] - sourceIsland[4]
490
testHeight = targetIsland[5] - sourceIsland[5]
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)))
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]
503
boxLeft = 0 # Start 1 back so we can jump into the loop.
504
boxBottom= 0 #-yIncrement
508
while boxBottom <= testHeight:
509
# Should we use this? - not needed for now.
510
#if Window.GetKeyQualifiers() & ctrl:
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
521
if Intersect == 2: # Source inside target
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 already tested for it.
529
It gives about 10% speedup with minimal errors.
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.
538
#XXX Window.DrawProgressBar(0.0, 'Merged: %i islands, Ctrl to finish early.' % removedCount)
540
# Move faces into new island and offset
541
targetIsland[0].extend(sourceIsland[0])
542
offset= Vector((boxLeft, boxBottom))
544
for f in sourceIsland[0]:
548
sourceIsland[0][:] = [] # Empty
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] ] )
558
sourceIsland[6][:] = [] # Empty
560
# Sort by edge length, reverse so biggest are first.
562
try: targetIsland[6].sort(key = lambda A: A[2])
563
except: targetIsland[6].sort(lambda B,A: cmp(A[2], B[2] ))
566
targetIsland[7].extend(sourceIsland[7])
567
offset= Vector((boxLeft, boxBottom, 0.0))
568
for p in sourceIsland[7]:
571
sourceIsland[7][:] = []
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
583
# INCREMENR NEXT LOCATION
584
if boxLeft > testWidth:
585
boxBottom += yIncrement
588
boxLeft += xIncrement
594
# Remove empty islands
598
if not islandList[i]:
599
del islandList[i] # Can increment islands removed here.
601
# Takes groups of faces. assumes face groups are UV groups.
602
def getUvIslands(faceGroups, me):
604
# Get seams so we dont cross over seams
605
edge_seams = {} # shoudl be a set
608
edge_seams[ed.key] = None # dummy var- use sets!
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),
618
faceGroupIdx = len(faceGroups)
622
faces = faceGroups[faceGroupIdx]
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
635
try: edge_users[ed_key].append(i)
636
except: edge_users[ed_key] = [i]
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.
644
face_modes[0] = 1 # start the search with face 1
648
newIsland.append(faces[0])
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])
665
# mark as searched, dont look again.
668
islandList.append(newIsland)
671
for i in range(len(faces)):
672
if face_modes[i] == 0:
674
newIsland.append(faces[i])
676
face_modes[i] = ok = 1
678
# if not ok will stop looping
680
#XXX Window.DrawProgressBar(0.1, 'Optimizing Rotation for %i UV Islands' % len(islandList))
682
for island in islandList:
683
optiRotateUvIsland(island)
688
def packIslands(islandList):
690
#XXX Window.DrawProgressBar(0.1, 'Merging Islands (Ctrl: skip merge)...')
691
mergeUvIslands(islandList) # Modify in place
694
# Now we have UV islands, we need to pack them.
696
# Make a synchronised list with the islands
697
# so we can box pak the islands.
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 = []
706
while islandIdx < len(islandList):
707
minx, miny, maxx, maxy = boundsIsland(islandList[islandIdx])
709
w, h = maxx-minx, maxy-miny
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
717
# recalc width and height
718
w, h = maxx-minx, maxy-miny
720
if w < 0.00001 or h < 0.00001:
721
del islandList[islandIdx]
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))
732
# Add to boxList. use the island idx for the BOX id.
733
packBoxes.append([0, 0, w, h])
736
# Now we have a list of boxes to pack that syncs
739
#print '\tPacking UV Islands...'
740
#XXX Window.DrawProgressBar(0.7, 'Packing %i UV Islands...' % len(packBoxes) )
743
packWidth, packHeight = geometry.BoxPack2D(packBoxes)
745
# print 'Box Packing Time:', time.time() - time1
747
#if len(pa ckedLs) != len(islandList):
748
# raise "Error packed boxes differes from original length"
750
#print '\tWriting Packed Data to faces'
751
#XXX Window.DrawProgressBar(0.8, 'Writing Packed Data to faces')
753
# Sort by ID, so there in sync again
754
islandIdx = len(islandList)
755
# Having these here avoids devide by 0
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
764
xfactor = yfactor = 1.0 / max(packWidth, packHeight)
768
# Write the packed values to the UV's
770
xoffset = packBoxes[islandIdx][0] - islandOffsetList[islandIdx][0]
771
yoffset = packBoxes[islandIdx][1] - islandOffsetList[islandIdx][1]
773
for f in islandList[islandIdx]: # Offsetting the UV's so they fit in there packed box
775
uv.x= (uv.x+xoffset) * xfactor
776
uv.y= (uv.y+yoffset) * yfactor
781
a3 = vec.__copy__().normalize()
783
up = Vector((0.0, 0.0, 1.0))
784
if abs(a3.dot(up)) == 1.0:
785
up = Vector((0.0, 1.0, 0.0))
787
a1 = a3.cross(up).normalize()
789
return Matrix([a1[0], a1[1], a1[2]], [a2[0], a2[1], a2[2]], [a3[0], a3[1], a3[2]])
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]
797
self.uv = uvface.uv1, uvface.uv2, uvface.uv3, uvface.uv4
799
self.uv = uvface.uv1, uvface.uv2, uvface.uv3
801
self.no = face.normal
802
self.area = face.area
803
self.edge_keys = face.edge_keys
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
813
#XXX objects= bpy.data.scenes.active.objects
814
objects = context.selected_editable_objects
817
# we can will tag them later.
818
obList = [ob for ob in objects if ob.type == 'MESH']
820
# Face select object may not be selected.
821
#XXX ob = objects.active
824
if ob and (not ob.select) and ob.type == 'MESH':
830
raise('error, no selected mesh objects')
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.
845
ob = "Unwrap %i Selected Mesh"
847
ob = "Unwrap %i Selected Meshes"
849
# HACK, loop until mouse is lifted.
851
while Window.GetMouseButtons() != 0:
855
#XXX if not Draw.PupBlock(ob % len(obList), pup_block):
859
# Convert from being button types
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)
866
is_editmode = (context.active_object.mode == 'EDIT')
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.
872
# Sort by data name so we get consistant results
873
obList.sort(key = lambda ob: ob.data.name)
874
collected_islandList= []
876
#XXX Window.WaitCursor(1)
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:
889
if me.tag or me.library:
895
if len(me.uv_textures)==0: # Mesh has no UV Coords, dont bother.
898
uv_layer = me.active_uv_texture.data
899
me_verts = list(me.verts)
901
if USER_ONLY_SELECTED_FACES:
902
meshFaces = [thickface(f, uv_layer[i], me_verts) for i, f in enumerate(me.faces) if f.select]
904
# meshFaces = map(thickface, me.faces)
909
#XXX Window.DrawProgressBar(0.1, 'SmartProj UV Unwrapper, mapping "%s", %i faces.' % (me.name, len(meshFaces)))
912
# Generate a projection list from face normals, this is ment to be smart :)
914
# make a list of face props that are in sync with meshFaces
915
# Make a Face List that is sorted by area.
918
# meshFaces.sort( lambda a, b: cmp(b.area , a.area) ) # Biggest first.
919
meshFaces.sort( key = lambda a: -a.area )
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:
928
# Smallest first is slightly more efficient, but if the user cancels early then its better we work on the larger data.
930
# Generate Projection Vecs
935
# Initialize projectVecs
937
# Generate Projection
938
projectVecs = [Vector(Window.GetViewVector()) * ob.matrix_world.copy().invert().rotation_part()] # We add to this allong the way
942
newProjectVec = meshFaces[0].no
943
newProjectMeshFaces = [] # Popping stuffs it up.
946
# Predent that the most unique angke is ages away to start the loop off
947
mostUniqueAngle = -1.0
950
tempMeshFaces = meshFaces[:]
954
# This while only gathers projection vecs, faces are assigned later on.
956
# If theres none there then start with the largest face
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))
965
# Add the average of all these faces normals as a projectionVec
966
averageVec = Vector((0.0, 0.0, 0.0))
968
for fprop in newProjectMeshFaces:
969
averageVec += (fprop.no * fprop.area)
971
for fprop in newProjectMeshFaces:
972
averageVec += fprop.no
974
if averageVec.x != 0 or averageVec.y != 0 or averageVec.z != 0: # Avoid NAN
975
projectVecs.append(averageVec.normalize())
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
983
for fIdx in range(len(tempMeshFaces)-1, -1, -1):
984
angleDifference = -1.0 # 180d difference.
986
# Get the closest vec angle we are to.
987
for p in projectVecs:
988
temp_angle_diff= p.dot(tempMeshFaces[fIdx].no)
990
if angleDifference < temp_angle_diff:
991
angleDifference= temp_angle_diff
993
if angleDifference < mostUniqueAngle:
994
# We have a new most different angle
995
mostUniqueIndex = fIdx
996
mostUniqueAngle = angleDifference
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.
1003
newProjectVec = tempMeshFaces[mostUniqueIndex].no
1004
newProjectMeshFaces = [tempMeshFaces.pop(mostUniqueIndex)]
1008
if len(projectVecs) >= 1: # Must have at least 2 projections
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.')
1018
faceProjectionGroupList =[[] for i in range(len(projectVecs)) ]
1020
# MAP and Arrange # We know there are 3 or 4 faces here
1022
for fIdx in range(len(meshFaces)-1, -1, -1):
1023
fvec = meshFaces[fIdx].no
1024
i = len(projectVecs)
1027
bestAng = fvec.dot(projectVecs[0])
1030
# Cycle through the remaining, first already done
1034
newAng = fvec.dot(projectVecs[i])
1035
if newAng > bestAng: # Reverse logic for dotvecs
1039
# Store the area for later use.
1040
faceProjectionGroupList[bestAngIdx].append(meshFaces[fIdx])
1042
# Cull faceProjectionGroupList,
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]:
1051
# Make a projection matrix from a unit length vector.
1052
MatProj = VectoMat(projectVecs[i])
1054
# Get the faces UV's from the projected vertex.
1055
for f in faceProjectionGroupList[i]:
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]
1062
if USER_SHARE_SPACE:
1063
# Should we collect and pack later?
1064
islandList = getUvIslands(faceProjectionGroupList, me)
1065
collected_islandList.extend(islandList)
1068
# Should we pack the islands for this 1 object?
1069
islandList = getUvIslands(faceProjectionGroupList, me)
1070
packIslands(islandList)
1073
# update the mesh here if we need to.
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)
1080
print("Smart Projection time: %.2f" % (time.time() - time1))
1081
# Window.DrawProgressBar(0.9, "Smart Projections done, time: %.2f sec." % (time.time() - time1))
1084
bpy.ops.object.mode_set(mode='EDIT')
1086
#XXX Window.DrawProgressBar(1.0, "")
1087
#XXX Window.WaitCursor(0)
1088
#XXX Window.RedrawAll()
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.'),\
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)'),\
1110
from bpy.props import *
1113
class SmartProject(bpy.types.Operator):
1114
'''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.'''
1115
bl_idname = "uv.smart_project"
1116
bl_label = "Smart UV Project"
1117
bl_options = {'REGISTER', 'UNDO'}
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)
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)
1127
def poll(self, context):
1128
return context.active_object != None
1130
def execute(self, context):
1131
main(context, self.properties.island_margin, self.properties.angle_limit)
1136
menu_func = (lambda self, context: self.layout.operator(SmartProject.bl_idname,
1137
text="Smart Project"))
1141
bpy.types.register(SmartProject)
1142
bpy.types.VIEW3D_MT_uv_map.append(menu_func)
1146
bpy.types.unregister(SmartProject)
1147
bpy.types.VIEW3D_MT_uv_map.remove(menu_func)
1149
if __name__ == "__main__":