Merge branch 'cb/fixs'
[plumiferos.git] / release / scripts / uvcalc_lightmap.py
bloba4b176bdb793ae6e6835b06eb18f236027854374
1 #!BPY
2 """
3 Name: 'Lightmap UVPack'
4 Blender: 242
5 Group: 'UVCalculation'
6 Tooltip: 'Give each face non overlapping space on a texture.'
7 """
8 __author__ = "Campbell Barton"
9 __url__ = ("blender", "elysiun")
10 __version__ = "1.0 2006/02/07"
12 __bpydoc__ = """\
13 """
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 # --------------------------------------------------------------------------
37 from Blender import *
38 import bpy
39 import BPyMesh
40 # reload(BPyMesh)
42 from math import sqrt
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
50 self.xoff = 0
51 self.yoff = 0
53 if type(data) == list: # list of data
54 self.uv = None
56 # join the data
57 if len(data) == 2:
58 # 2 vertical blocks
59 data[1].xoff = data[0].width
60 self.width = data[0].width * 2
61 self.height = data[0].height
63 elif len(data) == 4:
64 # 4 blocks all the same size
65 d = data[0].width # dimension x/y are the same
67 data[1].xoff += d
68 data[2].yoff += d
70 data[3].xoff += d
71 data[3].yoff += d
73 self.width = self.height = d*2
75 #else:
76 # print len(data), data
77 # raise "Error"
79 for pf in data:
80 pf.has_parent = True
83 self.children = data
85 elif type(data) == tuple:
86 # 2 blender faces
87 # f, (len_min, len_mid, len_max)
88 self.uv = data
90 f1, lens1, lens1ord = data[0]
91 if data[1]:
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
95 else: # 1 tri :/
96 self.width = lens1[0]
97 self.height = lens1[1]
99 self.children = []
102 else: # blender face
103 self.uv = data.uv
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
109 self.children = []
112 def spin(self):
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.
119 # print 'spinning'
120 for pf in self.children:
121 pf.spin()
124 def place(self, xoff, yoff, xfac, yfac, margin_w, margin_h):
126 xoff += self.xoff
127 yoff += self.yoff
129 for pf in self.children:
130 pf.place(xoff, yoff, xfac, yfac, margin_w, margin_h)
132 uv = self.uv
133 if not uv:
134 return
136 x1 = xoff
137 y1 = yoff
138 x2 = xoff + self.width
139 y2 = yoff + self.height
141 # Scale the values
142 x1 = x1/xfac + margin_w
143 x2 = x2/xfac - margin_w
144 y1 = y1/yfac + margin_h
145 y2 = y2/yfac - margin_h
147 # 2 Tri pairs
148 if len(uv) == 2:
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):
160 # cos =
161 #v1 = cos[0]-cos[1]
162 #v2 = cos[1]-cos[2]
163 #v3 = cos[2]-cos[0]
164 angles_co = get_tri_angles(*[v.co for v in f])
165 angles_co.sort()
166 I = [i for a,i in angles_co]
168 fuv = f.uv
169 if self.rot:
170 fuv[I[2]][:] = p1
171 fuv[I[1]][:] = p2
172 fuv[I[0]][:] = p3
173 else:
174 fuv[I[2]][:] = p1
175 fuv[I[0]][:] = p2
176 fuv[I[1]][:] = p3
178 f, lens, lensord = uv[0]
180 set_uv(f, (x1,y1), (x1, y2-margin_h), (x2-margin_w, y1))
182 if uv[1]:
183 f, lens, lensord = uv[1]
184 set_uv(f, (x2,y2), (x2, y1+margin_h), (x1+margin_w, y2))
186 else: # 1 QUAD
187 uv[1][:] = x1,y1
188 uv[2][:] = x1,y2
189 uv[3][:] = x2,y2
190 uv[0][:] = x2,y1
192 def __hash__(self):
193 # None unique hash
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,\
203 PREF_BOX_DIV= 8,\
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
212 if not meshes:
213 return
215 t = sys.time()
217 if PREF_PACK_IN_ONE:
218 if PREF_APPLY_IMAGE:
219 image = Image.New('lightmap', PREF_IMG_PX_SIZE, PREF_IMG_PX_SIZE, 24)
220 face_groups = [[]]
221 else:
222 face_groups = []
224 for me in meshes:
225 # Add face UV if it does not exist.
226 # All new faces are selected.
227 me.faceUV = True
229 if PREF_SEL_ONLY:
230 faces = [f for f in me.faces if f.sel]
231 else:
232 faces = list(me.faces)
234 if PREF_PACK_IN_ONE:
235 face_groups[0].extend(faces)
236 else:
237 face_groups.append(faces)
239 if PREF_NEW_UVLAYER:
240 me.addUVLayer('lightmap')
241 me.activeUVLayer = 'lightmap'
243 for face_sel in face_groups:
244 print "\nStarting unwrap"
246 if len(face_sel) <4:
247 print '\tWarning, less then 4 faces, skipping'
248 continue
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.
257 def trylens(f):
258 # f must be a tri
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))
264 for i in xrange(3):
265 if i != lens_min and i!= lens_max:
266 lens_mid = i
267 break
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]
273 del trylens
275 def trilensdiff(t1,t2):
276 return\
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]])
281 while tri_lengths:
282 tri1 = tri_lengths.pop()
284 if not tri_lengths:
285 pretty_faces.append(prettyface((tri1, None)))
286 break
288 best_tri_index = -1
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:
294 best_tri_index = i
295 best_tri_diff = diff
297 pretty_faces.append(prettyface((tri1, tri_lengths.pop(best_tri_index))))
300 # Get the min, max and total areas
301 max_area = 0.0
302 min_area = 100000000.0
303 tot_area = 0
304 for f in face_sel:
305 area = f.area
306 if area > max_area: max_area = area
307 if area < min_area: min_area = area
308 tot_area += area
310 max_len = sqrt(max_area)
311 min_len = sqrt(min_area)
312 side_len = sqrt(tot_area)
314 # Build widths
316 curr_len = max_len
318 print '\tGenerating lengths...',
320 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:
329 break
331 # convert into ints
332 lengths_to_ints = {}
334 l_int = 1
335 for l in reversed(lengths):
336 lengths_to_ints[l] = l_int
337 l_int*=2
339 lengths_to_ints = lengths_to_ints.items()
340 lengths_to_ints.sort()
341 print 'done'
343 # apply quantized values.
345 for pf in pretty_faces:
346 w = pf.width
347 h = pf.height
348 bestw_diff = 1000000000.0
349 besth_diff = 1000000000.0
350 new_w = 0.0
351 new_h = 0.0
352 for l, i in lengths_to_ints:
353 d = abs(l - w)
354 if d < bestw_diff:
355 bestw_diff = d
356 new_w = i # assign the int version
358 d = abs(l - h)
359 if d < besth_diff:
360 besth_diff = d
361 new_h = i # ditto
363 pf.width = new_w
364 pf.height = new_h
366 if new_w > new_h:
367 pf.spin()
369 print '...done'
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.
391 c = 0
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
406 ok = True
407 while ok:
408 ok = False
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)
416 ok = True
417 c += 1
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"
425 if w==h:
426 even_dict.setdefault(w, []).append(pf_parent)
427 else:
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)
436 ok = True
437 c += 1
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)
444 del even_dict
445 del odd_dict
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)
454 d = 0
455 while i:
456 i -=1
457 pf = pretty_faces[i]
458 if pf.width != pf.height:
459 d += 1
460 if d % 2: # only pack every second
461 pf.spin()
462 # pass
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
482 print 'done'
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)
489 print 'done'
492 if PREF_APPLY_IMAGE:
493 if not PREF_PACK_IN_ONE:
494 image = Image.New('lightmap', PREF_IMG_PX_SIZE, PREF_IMG_PX_SIZE, 24)
496 for f in face_sel:
497 f.image = image
499 for me in meshes:
500 me.update()
502 print 'finished all %.2f ' % (sys.time() - t)
504 Window.RedrawAll()
506 def main():
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', [\
519 'Context...',
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.'),\
522 'Image & UVs...',
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'),\
527 'UV Packing...',
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')\
531 return
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.')
538 return
539 meshes = [ ob.getData(mesh=1) ]
540 else:
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()
543 if not meshes:
544 Draw.PupMenu('Error%t|No mesh objects selected.')
545 return
547 Window.WaitCursor(1)
548 lightmap_uvpack(meshes,\
549 PREF_SEL_ONLY.val,\
550 PREF_NEW_UVLAYER.val,\
551 PREF_PACK_IN_ONE.val,\
552 PREF_APPLY_IMAGE.val,\
553 PREF_IMG_PX_SIZE.val,\
554 PREF_BOX_DIV.val,\
555 int(1/(PREF_MARGIN_DIV.val/100)))
557 Window.WaitCursor(0)
559 if __name__ == '__main__':
560 main()