3
Name: 'Lightmap UVPack'
6
Tooltip: 'Give each face non overlapping space on a texture.'
8
__author__ = "Campbell Barton"
9
__url__ = ("blender", "elysiun")
10
__version__ = "1.0 2006/02/07"
15
# ***** BEGIN GPL LICENSE BLOCK *****
17
# Script copyright (C) Campbell Barton
19
# This program is free software; you can redistribute it and/or
20
# modify it under the terms of the GNU General Public License
21
# as published by the Free Software Foundation; either version 2
22
# of the License, or (at your option) any later version.
24
# This program is distributed in the hope that it will be useful,
25
# but WITHOUT ANY WARRANTY; without even the implied warranty of
26
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
# GNU General Public License for more details.
29
# You should have received a copy of the GNU General Public License
30
# along with this program; if not, write to the Free Software Foundation,
31
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
33
# ***** END GPL LICENCE BLOCK *****
34
# --------------------------------------------------------------------------
44
class prettyface(object):
45
__slots__ = 'uv', 'width', 'height', 'children', 'xoff', 'yoff', 'has_parent', 'rot'
46
def __init__(self, data):
48
self.has_parent = False
49
self.rot = False # only used for triables
53
if type(data) == list: # list of data
59
data[1].xoff = data[0].width
60
self.width = data[0].width * 2
61
self.height = data[0].height
64
# 4 blocks all the same size
65
d = data[0].width # dimension x/y are the same
73
self.width = self.height = d*2
76
# print len(data), data
85
elif type(data) == tuple:
87
# f, (len_min, len_mid, len_max)
90
f1, lens1, lens1ord = data[0]
92
f2, lens2, lens2ord = data[1]
93
self.width = (lens1[lens1ord[0]] + lens2[lens2ord[0]])/2
94
self.height = (lens1[lens1ord[1]] + lens2[lens2ord[1]])/2
97
self.height = lens1[1]
105
cos = [v.co for v in data]
106
self.width = ((cos[0]-cos[1]).length + (cos[2]-cos[3]).length)/2
107
self.height = ((cos[1]-cos[2]).length + (cos[0]-cos[3]).length)/2
113
if self.uv and len(self.uv) == 4:
114
self.uv = self.uv[1], self.uv[2], self.uv[3], self.uv[0]
116
self.width, self.height = self.height, self.width
117
self.xoff, self.yoff = self.yoff, self.xoff # not needed?
118
self.rot = not self.rot # only for tri pairs.
120
for pf in self.children:
124
def place(self, xoff, yoff, xfac, yfac, margin_w, margin_h):
129
for pf in self.children:
130
pf.place(xoff, yoff, xfac, yfac, margin_w, margin_h)
138
x2 = xoff + self.width
139
y2 = yoff + self.height
142
x1 = x1/xfac + margin_w
143
x2 = x2/xfac - margin_w
144
y1 = y1/yfac + margin_h
145
y2 = y2/yfac - margin_h
149
# match the order of angle sizes of the 3d verts with the UV angles and rotate.
150
def get_tri_angles(v1,v2,v3):
151
a1= Mathutils.AngleBetweenVecs(v2-v1,v3-v1)
152
a2= Mathutils.AngleBetweenVecs(v1-v2,v3-v2)
153
a3 = 180 - (a1+a2) #a3= Mathutils.AngleBetweenVecs(v2-v3,v1-v3)
156
return [(a1,0),(a2,1),(a3,2)]
158
def set_uv(f, p1, p2, p3):
164
angles_co = get_tri_angles(*[v.co for v in f])
166
I = [i for a,i in angles_co]
178
f, lens, lensord = uv[0]
180
set_uv(f, (x1,y1), (x1, y2-margin_h), (x2-margin_w, y1))
183
f, lens, lensord = uv[1]
184
set_uv(f, (x2,y2), (x2, y1+margin_h), (x1+margin_w, y2))
194
return self.width, self.height
197
def lightmap_uvpack( meshes,\
198
PREF_SEL_ONLY= True,\
199
PREF_NEW_UVLAYER= False,\
200
PREF_PACK_IN_ONE= False,\
201
PREF_APPLY_IMAGE= False,\
202
PREF_IMG_PX_SIZE= 512,\
204
PREF_MARGIN_DIV= 512):
206
BOX_DIV if the maximum division of the UV map that
207
a box may be consolidated into.
208
Basicly, a lower value will be slower but waist less space
209
and a higher value will have more clumpy boxes but more waisted space
219
image = Image.New('lightmap', PREF_IMG_PX_SIZE, PREF_IMG_PX_SIZE, 24)
226
faces = [f for f in me.faces if f.sel]
228
faces = list(me.faces)
231
face_groups[0].extend(faces)
233
face_groups.append(faces)
236
me.addUVLayer('lightmap')
237
me.activeUVLayer = 'lightmap'
239
for face_sel in face_groups:
240
print "\nStarting unwrap"
243
print '\tWarning, less then 4 faces, skipping'
246
pretty_faces = [prettyface(f) for f in face_sel if len(f) == 4]
249
# Do we have any tri's
250
if len(pretty_faces) != len(face_sel):
252
# Now add tri's, not so simple because we need to pair them up.
255
cos = [v.co for v in f]
256
lens = [(cos[0] - cos[1]).length, (cos[1] - cos[2]).length, (cos[2] - cos[0]).length]
258
lens_min = lens.index(min(lens))
259
lens_max = lens.index(max(lens))
261
if i != lens_min and i!= lens_max:
264
lens_order = lens_min, lens_mid, lens_max
266
return f, lens, lens_order
268
tri_lengths = [trylens(f) for f in face_sel if len(f) == 3]
271
def trilensdiff(t1,t2):
273
abs(t1[1][t1[2][0]]-t2[1][t2[2][0]])+\
274
abs(t1[1][t1[2][1]]-t2[1][t2[2][1]])+\
275
abs(t1[1][t1[2][2]]-t2[1][t2[2][2]])
278
tri1 = tri_lengths.pop()
281
pretty_faces.append(prettyface((tri1, None)))
285
best_tri_diff = 100000000.0
287
for i, tri2 in enumerate(tri_lengths):
288
diff = trilensdiff(tri1, tri2)
289
if diff < best_tri_diff:
293
pretty_faces.append(prettyface((tri1, tri_lengths.pop(best_tri_index))))
296
# Get the min, max and total areas
298
min_area = 100000000.0
302
if area > max_area: max_area = area
303
if area < min_area: min_area = area
306
max_len = sqrt(max_area)
307
min_len = sqrt(min_area)
308
side_len = sqrt(tot_area)
314
print '\tGenerating lengths...',
317
while curr_len > min_len:
318
lengths.append(curr_len)
319
curr_len = curr_len/2
321
# Dont allow boxes smaller then the margin
322
# since we contract on the margin, boxes that are smaller will create errors
323
# print curr_len, side_len/MARGIN_DIV
324
if curr_len/4 < side_len/PREF_MARGIN_DIV:
331
for l in reversed(lengths):
332
lengths_to_ints[l] = l_int
335
lengths_to_ints = lengths_to_ints.items()
336
lengths_to_ints.sort()
339
# apply quantized values.
341
for pf in pretty_faces:
344
bestw_diff = 1000000000.0
345
besth_diff = 1000000000.0
348
for l, i in lengths_to_ints:
352
new_w = i # assign the int version
368
# Since the boxes are sized in powers of 2, we can neatly group them into bigger squares
369
# this is done hierarchily, so that we may avoid running the pack function
370
# on many thousands of boxes, (under 1k is best) because it would get slow.
371
# Using an off and even dict us usefull because they are packed differently
372
# where w/h are the same, their packed in groups of 4
373
# where they are different they are packed in pairs
375
# After this is done an external pack func is done that packs the whole group.
377
print '\tConsolidating Boxes...',
378
even_dict = {} # w/h are the same, the key is an int (w)
379
odd_dict = {} # w/h are different, the key is the (w,h)
381
for pf in pretty_faces:
382
w,h = pf.width, pf.height
383
if w==h: even_dict.setdefault(w, []).append( pf )
384
else: odd_dict.setdefault((w,h), []).append( pf )
386
# Count the number of boxes consolidated, only used for stats.
389
# This is tricky. the total area of all packed boxes, then squt that to get an estimated size
390
# this is used then converted into out INT space so we can compare it with
391
# the ints assigned to the boxes size
392
# and divided by BOX_DIV, basicly if BOX_DIV is 8
393
# ...then the maximum box consolidataion (recursive grouping) will have a max width & height
394
# ...1/8th of the UV size.
395
# ...limiting this is needed or you end up with bug unused texture spaces
396
# ...however if its too high, boxpacking is way too slow for high poly meshes.
397
float_to_int_factor = lengths_to_ints[0][0]
398
max_int_dimension = int(((side_len / float_to_int_factor)) / PREF_BOX_DIV)
401
# RECURSIVE prettyface grouping
406
# Tall boxes in groups of 2
407
for d, boxes in odd_dict.items():
408
if d[1] < max_int_dimension:
409
#\boxes.sort(key = lambda a: len(a.children))
410
while len(boxes) >= 2:
411
# print "foo", len(boxes)
414
pf_parent = prettyface([boxes.pop(), boxes.pop()])
415
pretty_faces.append(pf_parent)
417
w,h = pf_parent.width, pf_parent.height
419
if w>h: raise "error"
422
even_dict.setdefault(w, []).append(pf_parent)
424
odd_dict.setdefault((w,h), []).append(pf_parent)
426
# Even boxes in groups of 4
427
for d, boxes in even_dict.items():
428
if d < max_int_dimension:
429
boxes.sort(key = lambda a: len(a.children))
430
while len(boxes) >= 4:
431
# print "bar", len(boxes)
435
pf_parent = prettyface([boxes.pop(), boxes.pop(), boxes.pop(), boxes.pop()])
436
pretty_faces.append(pf_parent)
437
w = pf_parent.width # width and weight are the same
438
even_dict.setdefault(w, []).append(pf_parent)
443
orig = len(pretty_faces)
445
pretty_faces = [pf for pf in pretty_faces if not pf.has_parent]
447
# spin every second prettyface
448
# if there all vertical you get less efficiently used texture space
449
i = len(pretty_faces)
454
if pf.width != pf.height:
456
if d % 2: # only pack every second
460
print 'Consolidated', c, 'boxes, done'
461
# print 'done', orig, len(pretty_faces)
464
# boxes2Pack.append([islandIdx, w,h])
465
print '\tPacking Boxes', len(pretty_faces), '...',
466
boxes2Pack = [ [0.0, 0.0, pf.width, pf.height, i] for i, pf in enumerate(pretty_faces)]
467
packWidth, packHeight = Geometry.BoxPack2D(boxes2Pack)
469
# print packWidth, packHeight
471
packWidth = float(packWidth)
472
packHeight = float(packHeight)
474
margin_w = ((packWidth) / PREF_MARGIN_DIV)/ packWidth
475
margin_h = ((packHeight) / PREF_MARGIN_DIV) / packHeight
477
# print margin_w, margin_h
480
# Apply the boxes back to the UV coords.
481
print '\twriting back UVs',
482
for i, box in enumerate(boxes2Pack):
483
pretty_faces[i].place(box[0], box[1], packWidth, packHeight, margin_w, margin_h)
484
# pf.place(box[1][1], box[1][2], packWidth, packHeight, margin_w, margin_h)
489
if not PREF_PACK_IN_ONE:
490
image = Image.New('lightmap', PREF_IMG_PX_SIZE, PREF_IMG_PX_SIZE, 24)
498
print 'finished all %.2f ' % (sys.time() - t)
503
scn = bpy.data.scenes.active
505
PREF_ACT_ONLY = Draw.Create(1)
506
PREF_SEL_ONLY = Draw.Create(1)
507
PREF_NEW_UVLAYER = Draw.Create(0)
508
PREF_PACK_IN_ONE = Draw.Create(0)
509
PREF_APPLY_IMAGE = Draw.Create(0)
510
PREF_IMG_PX_SIZE = Draw.Create(512)
511
PREF_BOX_DIV = Draw.Create(12)
512
PREF_MARGIN_DIV = Draw.Create(0.1)
514
if not Draw.PupBlock('Lightmap Pack', [\
516
('Active Object', PREF_ACT_ONLY, 'If disabled, use all objects for packing the lightmap.'),\
517
('Selected Faces', PREF_SEL_ONLY, 'Use only selected faces from all selected meshes.'),\
519
('Share Tex Space', PREF_PACK_IN_ONE, 'Objects Share texture space, map all objects into 1 uvmap'),\
520
('New UV Layer', PREF_NEW_UVLAYER, 'Create a new UV layer for every mesh packed'),\
521
('New Image', PREF_APPLY_IMAGE, 'Assign new images for every mesh (only one if shared tex space enabled)'),\
522
('Image Size', PREF_IMG_PX_SIZE, 64, 5000, 'Width and Height for the new image'),\
524
('Pack Quality: ', PREF_BOX_DIV, 1, 48, 'Pre Packing before the complex boxpack'),\
525
('Margin: ', PREF_MARGIN_DIV, 0.001, 1.0, 'Size of the margin as a division of the UV')\
530
if PREF_ACT_ONLY.val:
531
ob = scn.objects.active
532
if ob == None or ob.type != 'Mesh':
533
Draw.PupMenu('Error%t|No mesh object.')
535
meshes = [ ob.getData(mesh=1) ]
537
meshes = dict([ (me.name, me) for ob in scn.objects.context for me in (ob.getData(mesh=1),) if not me.lib])
538
meshes = meshes.values()
540
Draw.PupMenu('Error%t|No mesh objects selected.')
544
lightmap_uvpack(meshes,\
546
PREF_NEW_UVLAYER.val,\
547
PREF_PACK_IN_ONE.val,\
548
PREF_APPLY_IMAGE.val,\
549
PREF_IMG_PX_SIZE.val,\
551
int(1/(PREF_MARGIN_DIV.val/100)))
555
if __name__ == '__main__':