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)
225 # Add face UV if it does not exist.
226 # All new faces are selected.
230 faces
= [f
for f
in me
.faces
if f
.sel
]
232 faces
= list(me
.faces
)
235 face_groups
[0].extend(faces
)
237 face_groups
.append(faces
)
240 me
.addUVLayer('lightmap')
241 me
.activeUVLayer
= 'lightmap'
243 for face_sel
in face_groups
:
244 print "\nStarting unwrap"
247 print '\tWarning, less then 4 faces, skipping'
250 pretty_faces
= [prettyface(f
) for f
in face_sel
if len(f
) == 4]
253 # Do we have any tri's
254 if len(pretty_faces
) != len(face_sel
):
256 # Now add tri's, not so simple because we need to pair them up.
259 cos
= [v
.co
for v
in f
]
260 lens
= [(cos
[0] - cos
[1]).length
, (cos
[1] - cos
[2]).length
, (cos
[2] - cos
[0]).length
]
262 lens_min
= lens
.index(min(lens
))
263 lens_max
= lens
.index(max(lens
))
265 if i
!= lens_min
and i
!= lens_max
:
268 lens_order
= lens_min
, lens_mid
, lens_max
270 return f
, lens
, lens_order
272 tri_lengths
= [trylens(f
) for f
in face_sel
if len(f
) == 3]
275 def trilensdiff(t1
,t2
):
277 abs(t1
[1][t1
[2][0]]-t2
[1][t2
[2][0]])+\
278 abs(t1
[1][t1
[2][1]]-t2
[1][t2
[2][1]])+\
279 abs(t1
[1][t1
[2][2]]-t2
[1][t2
[2][2]])
282 tri1
= tri_lengths
.pop()
285 pretty_faces
.append(prettyface((tri1
, None)))
289 best_tri_diff
= 100000000.0
291 for i
, tri2
in enumerate(tri_lengths
):
292 diff
= trilensdiff(tri1
, tri2
)
293 if diff
< best_tri_diff
:
297 pretty_faces
.append(prettyface((tri1
, tri_lengths
.pop(best_tri_index
))))
300 # Get the min, max and total areas
302 min_area
= 100000000.0
306 if area
> max_area
: max_area
= area
307 if area
< min_area
: min_area
= area
310 max_len
= sqrt(max_area
)
311 min_len
= sqrt(min_area
)
312 side_len
= sqrt(tot_area
)
318 print '\tGenerating lengths...',
321 while curr_len
> min_len
:
322 lengths
.append(curr_len
)
323 curr_len
= curr_len
/2
325 # Dont allow boxes smaller then the margin
326 # since we contract on the margin, boxes that are smaller will create errors
327 # print curr_len, side_len/MARGIN_DIV
328 if curr_len
/4 < side_len
/PREF_MARGIN_DIV
:
335 for l
in reversed(lengths
):
336 lengths_to_ints
[l
] = l_int
339 lengths_to_ints
= lengths_to_ints
.items()
340 lengths_to_ints
.sort()
343 # apply quantized values.
345 for pf
in pretty_faces
:
348 bestw_diff
= 1000000000.0
349 besth_diff
= 1000000000.0
352 for l
, i
in lengths_to_ints
:
356 new_w
= i
# assign the int version
372 # Since the boxes are sized in powers of 2, we can neatly group them into bigger squares
373 # this is done hierarchily, so that we may avoid running the pack function
374 # on many thousands of boxes, (under 1k is best) because it would get slow.
375 # Using an off and even dict us usefull because they are packed differently
376 # where w/h are the same, their packed in groups of 4
377 # where they are different they are packed in pairs
379 # After this is done an external pack func is done that packs the whole group.
381 print '\tConsolidating Boxes...',
382 even_dict
= {} # w/h are the same, the key is an int (w)
383 odd_dict
= {} # w/h are different, the key is the (w,h)
385 for pf
in pretty_faces
:
386 w
,h
= pf
.width
, pf
.height
387 if w
==h
: even_dict
.setdefault(w
, []).append( pf
)
388 else: odd_dict
.setdefault((w
,h
), []).append( pf
)
390 # Count the number of boxes consolidated, only used for stats.
393 # This is tricky. the total area of all packed boxes, then squt that to get an estimated size
394 # this is used then converted into out INT space so we can compare it with
395 # the ints assigned to the boxes size
396 # and divided by BOX_DIV, basicly if BOX_DIV is 8
397 # ...then the maximum box consolidataion (recursive grouping) will have a max width & height
398 # ...1/8th of the UV size.
399 # ...limiting this is needed or you end up with bug unused texture spaces
400 # ...however if its too high, boxpacking is way too slow for high poly meshes.
401 float_to_int_factor
= lengths_to_ints
[0][0]
402 max_int_dimension
= int(((side_len
/ float_to_int_factor
)) / PREF_BOX_DIV
)
405 # RECURSIVE prettyface grouping
410 # Tall boxes in groups of 2
411 for d
, boxes
in odd_dict
.items():
412 if d
[1] < max_int_dimension
:
413 #\boxes.sort(key = lambda a: len(a.children))
414 while len(boxes
) >= 2:
415 # print "foo", len(boxes)
418 pf_parent
= prettyface([boxes
.pop(), boxes
.pop()])
419 pretty_faces
.append(pf_parent
)
421 w
,h
= pf_parent
.width
, pf_parent
.height
423 if w
>h
: raise "error"
426 even_dict
.setdefault(w
, []).append(pf_parent
)
428 odd_dict
.setdefault((w
,h
), []).append(pf_parent
)
430 # Even boxes in groups of 4
431 for d
, boxes
in even_dict
.items():
432 if d
< max_int_dimension
:
433 boxes
.sort(key
= lambda a
: len(a
.children
))
434 while len(boxes
) >= 4:
435 # print "bar", len(boxes)
439 pf_parent
= prettyface([boxes
.pop(), boxes
.pop(), boxes
.pop(), boxes
.pop()])
440 pretty_faces
.append(pf_parent
)
441 w
= pf_parent
.width
# width and weight are the same
442 even_dict
.setdefault(w
, []).append(pf_parent
)
447 orig
= len(pretty_faces
)
449 pretty_faces
= [pf
for pf
in pretty_faces
if not pf
.has_parent
]
451 # spin every second prettyface
452 # if there all vertical you get less efficiently used texture space
453 i
= len(pretty_faces
)
458 if pf
.width
!= pf
.height
:
460 if d
% 2: # only pack every second
464 print 'Consolidated', c
, 'boxes, done'
465 # print 'done', orig, len(pretty_faces)
468 # boxes2Pack.append([islandIdx, w,h])
469 print '\tPacking Boxes', len(pretty_faces
), '...',
470 boxes2Pack
= [ [0.0, 0.0, pf
.width
, pf
.height
, i
] for i
, pf
in enumerate(pretty_faces
)]
471 packWidth
, packHeight
= Geometry
.BoxPack2D(boxes2Pack
)
473 # print packWidth, packHeight
475 packWidth
= float(packWidth
)
476 packHeight
= float(packHeight
)
478 margin_w
= ((packWidth
) / PREF_MARGIN_DIV
)/ packWidth
479 margin_h
= ((packHeight
) / PREF_MARGIN_DIV
) / packHeight
481 # print margin_w, margin_h
484 # Apply the boxes back to the UV coords.
485 print '\twriting back UVs',
486 for i
, box
in enumerate(boxes2Pack
):
487 pretty_faces
[i
].place(box
[0], box
[1], packWidth
, packHeight
, margin_w
, margin_h
)
488 # pf.place(box[1][1], box[1][2], packWidth, packHeight, margin_w, margin_h)
493 if not PREF_PACK_IN_ONE
:
494 image
= Image
.New('lightmap', PREF_IMG_PX_SIZE
, PREF_IMG_PX_SIZE
, 24)
502 print 'finished all %.2f ' % (sys
.time() - t
)
507 scn
= bpy
.data
.scenes
.active
509 PREF_ACT_ONLY
= Draw
.Create(1)
510 PREF_SEL_ONLY
= Draw
.Create(1)
511 PREF_NEW_UVLAYER
= Draw
.Create(0)
512 PREF_PACK_IN_ONE
= Draw
.Create(0)
513 PREF_APPLY_IMAGE
= Draw
.Create(0)
514 PREF_IMG_PX_SIZE
= Draw
.Create(512)
515 PREF_BOX_DIV
= Draw
.Create(12)
516 PREF_MARGIN_DIV
= Draw
.Create(0.1)
518 if not Draw
.PupBlock('Lightmap Pack', [\
520 ('Active Object', PREF_ACT_ONLY
, 'If disabled, use all objects for packing the lightmap.'),\
521 ('Selected Faces', PREF_SEL_ONLY
, 'Use only selected faces from all selected meshes.'),\
523 ('Share Tex Space', PREF_PACK_IN_ONE
, 'Objects Share texture space, map all objects into 1 uvmap'),\
524 ('New UV Layer', PREF_NEW_UVLAYER
, 'Create a new UV layer for every mesh packed'),\
525 ('New Image', PREF_APPLY_IMAGE
, 'Assign new images for every mesh (only one if shared tex space enabled)'),\
526 ('Image Size', PREF_IMG_PX_SIZE
, 64, 5000, 'Width and Height for the new image'),\
528 ('Pack Quality: ', PREF_BOX_DIV
, 1, 48, 'Pre Packing before the complex boxpack'),\
529 ('Margin: ', PREF_MARGIN_DIV
, 0.001, 1.0, 'Size of the margin as a division of the UV')\
534 if PREF_ACT_ONLY
.val
:
535 ob
= scn
.objects
.active
536 if ob
== None or ob
.type != 'Mesh':
537 Draw
.PupMenu('Error%t|No mesh object.')
539 meshes
= [ ob
.getData(mesh
=1) ]
541 meshes
= dict([ (me
.name
, me
) for ob
in scn
.objects
.context
for me
in (ob
.getData(mesh
=1),) if not me
.lib
])
542 meshes
= meshes
.values()
544 Draw
.PupMenu('Error%t|No mesh objects selected.')
548 lightmap_uvpack(meshes
,\
550 PREF_NEW_UVLAYER
.val
,\
551 PREF_PACK_IN_ONE
.val
,\
552 PREF_APPLY_IMAGE
.val
,\
553 PREF_IMG_PX_SIZE
.val
,\
555 int(1/(PREF_MARGIN_DIV
.val
/100)))
559 if __name__
== '__main__':