uvcalc_lightmap would raise an error on meshes with no faces.
[blender.git] / release / scripts / uvcalc_lightmap.py
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 aka ideasman42"
9 __url__ = ("blender", "blenderartists.org")
10 __version__ = "1.0 2006/02/07"
11
12 __bpydoc__ = """\
13 """
14
15 # ***** BEGIN GPL LICENSE BLOCK *****
16 #
17 # Script copyright (C) Campbell Barton
18 #
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.
23 #
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.
28 #
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.
32 #
33 # ***** END GPL LICENCE BLOCK *****
34 # --------------------------------------------------------------------------
35
36
37 from Blender import *
38 import bpy
39 import BPyMesh
40 # reload(BPyMesh)
41
42 from math import sqrt
43
44 class prettyface(object):
45         __slots__ = 'uv', 'width', 'height', 'children', 'xoff', 'yoff', 'has_parent', 'rot'
46         def __init__(self, data):
47                 
48                 self.has_parent = False
49                 self.rot = False # only used for triables
50                 self.xoff = 0
51                 self.yoff = 0
52                 
53                 if type(data) == list: # list of data
54                         self.uv = None
55                         
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
62                         
63                         elif len(data) == 4:
64                                 # 4 blocks all the same size
65                                 d = data[0].width # dimension x/y are the same
66                                 
67                                 data[1].xoff += d
68                                 data[2].yoff += d
69                                 
70                                 data[3].xoff += d
71                                 data[3].yoff += d
72                                 
73                                 self.width = self.height = d*2
74                                 
75                         #else:
76                         #       print len(data), data
77                         #       raise "Error"
78                         
79                         for pf in data:
80                                 pf.has_parent = True
81                         
82                         
83                         self.children = data
84                         
85                 elif type(data) == tuple:
86                         # 2 blender faces
87                         # f, (len_min, len_mid, len_max)
88                         self.uv = data
89                         
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]
98                         
99                         self.children = []
100                         
101                         
102                 else: # blender face
103                         self.uv = data.uv
104                         
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
108                         
109                         self.children = []
110                 
111                 
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]
115                 
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()
122         
123         
124         def place(self, xoff, yoff, xfac, yfac, margin_w, margin_h):
125                 
126                 xoff += self.xoff
127                 yoff += self.yoff
128                 
129                 for pf in self.children:
130                         pf.place(xoff, yoff, xfac, yfac, margin_w, margin_h)
131                 
132                 uv = self.uv
133                 if not uv:
134                         return
135                 
136                 x1 = xoff
137                 y1 = yoff
138                 x2 = xoff + self.width
139                 y2 = yoff + self.height
140                 
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
146                 
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)
154                                 
155                                 
156                                 return [(a1,0),(a2,1),(a3,2)]
157                         
158                         def set_uv(f, p1, p2, p3):
159                                 
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]
167                                 
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
177                         
178                         f, lens, lensord = uv[0]
179                         
180                         set_uv(f,  (x1,y1),  (x1, y2-margin_h),  (x2-margin_w, y1))
181                         
182                         if uv[1]:
183                                 f, lens, lensord = uv[1]
184                                 set_uv(f,  (x2,y2),  (x2, y1+margin_h),  (x1+margin_w, y2))
185                         
186                 else: # 1 QUAD
187                         uv[1][:] = x1,y1
188                         uv[2][:] = x1,y2
189                         uv[3][:] = x2,y2
190                         uv[0][:] = x2,y1
191         
192         def __hash__(self):
193                 # None unique hash
194                 return self.width, self.height
195
196
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):
205         '''
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
210         '''
211         
212         if not meshes:
213                 return
214         
215         t = sys.time()
216         
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 = []
223         
224         for me in meshes:
225                 # Add face UV if it does not exist.
226                 # All new faces are selected.
227                 me.faceUV = True
228                         
229                 if PREF_SEL_ONLY:
230                         faces = [f for f in me.faces if f.sel]
231                 else:
232                         faces = list(me.faces)
233                 
234                 if PREF_PACK_IN_ONE:
235                         face_groups[0].extend(faces)
236                 else:
237                         face_groups.append(faces)
238                 
239                 if PREF_NEW_UVLAYER:
240                         me.addUVLayer('lightmap')
241                         me.activeUVLayer = 'lightmap'
242         
243         for face_sel in face_groups:
244                 print "\nStarting unwrap"
245                 
246                 if len(face_sel) <4:
247                         print '\tWarning, less then 4 faces, skipping'
248                         continue
249                 
250                 pretty_faces = [prettyface(f) for f in face_sel if len(f) == 4]
251                 
252                 
253                 # Do we have any tri's
254                 if len(pretty_faces) != len(face_sel):
255                         
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]
261                                 
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
269                                 
270                                 return f, lens, lens_order
271                                 
272                         tri_lengths = [trylens(f) for f in face_sel if len(f) == 3]
273                         del trylens
274                         
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]])
280                         
281                         while tri_lengths:
282                                 tri1 = tri_lengths.pop()
283                                 
284                                 if not tri_lengths:
285                                         pretty_faces.append(prettyface((tri1, None)))
286                                         break
287                                 
288                                 best_tri_index = -1
289                                 best_tri_diff  = 100000000.0
290                                 
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
296                                 
297                                 pretty_faces.append(prettyface((tri1, tri_lengths.pop(best_tri_index))))
298                 
299                 
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
309                         
310                 max_len = sqrt(max_area)
311                 min_len = sqrt(min_area)
312                 side_len = sqrt(tot_area) 
313                 
314                 # Build widths
315                 
316                 curr_len = max_len
317                 
318                 print '\tGenerating lengths...',
319                 
320                 lengths = []
321                 while curr_len > min_len:
322                         lengths.append(curr_len) 
323                         curr_len = curr_len/2
324                         
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
330                 
331                 # convert into ints
332                 lengths_to_ints = {}
333                 
334                 l_int = 1
335                 for l in reversed(lengths):
336                         lengths_to_ints[l] = l_int
337                         l_int*=2
338                 
339                 lengths_to_ints = lengths_to_ints.items()
340                 lengths_to_ints.sort()
341                 print 'done'
342                 
343                 # apply quantized values.
344                 
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
357                                 
358                                 d = abs(l - h)
359                                 if d < besth_diff:
360                                         besth_diff = d
361                                         new_h = i # ditto
362                         
363                         pf.width = new_w
364                         pf.height = new_h
365                         
366                         if new_w > new_h:
367                                 pf.spin()
368                         
369                 print '...done'
370                 
371                 
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
378                 #
379                 # After this is done an external pack func is done that packs the whole group.
380                 
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)
384                 
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 )
389                 
390                 # Count the number of boxes consolidated, only used for stats.
391                 c = 0
392                 
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)
403                 
404                 
405                 # RECURSIVE prettyface grouping
406                 ok = True
407                 while ok:
408                         ok = False
409                         
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)
420                                                 
421                                                 w,h = pf_parent.width, pf_parent.height
422                                                 
423                                                 if w>h: raise "error"
424                                                 
425                                                 if w==h:
426                                                         even_dict.setdefault(w, []).append(pf_parent)
427                                                 else:
428                                                         odd_dict.setdefault((w,h), []).append(pf_parent)
429                                         
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
438                                                 
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)
443                 
444                 del even_dict
445                 del odd_dict
446                 
447                 orig = len(pretty_faces)
448                 
449                 pretty_faces = [pf for pf in pretty_faces if not pf.has_parent]
450                 
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
463                 
464                 print 'Consolidated', c, 'boxes, done'
465                 # print 'done', orig, len(pretty_faces)
466                 
467                 
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)
472                 
473                 # print packWidth, packHeight
474                 
475                 packWidth = float(packWidth)
476                 packHeight = float(packHeight)
477                 
478                 margin_w = ((packWidth) / PREF_MARGIN_DIV)/ packWidth
479                 margin_h = ((packHeight) / PREF_MARGIN_DIV) / packHeight
480                 
481                 # print margin_w, margin_h
482                 print 'done'
483                 
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'
490                 
491                 
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)
495                                 
496                         for f in face_sel:
497                                 f.image = image
498                 
499         for me in meshes:
500                 me.update()
501         
502         print 'finished all %.2f ' % (sys.time() - t)
503         
504         Window.RedrawAll()
505
506 def main():
507         scn = bpy.data.scenes.active
508         
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)
517         
518         if not Draw.PupBlock('Lightmap Pack', [\
519         'Context...',
520         ('Active Object', PREF_ACT_ONLY, 'If disabled, include other selected 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')\
530         ]):
531                 return
532         
533         
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 if len(me.faces)])
542                 meshes = meshes.values()
543                 if not meshes:
544                         Draw.PupMenu('Error%t|No mesh objects selected.')
545                         return
546         
547         # Toggle Edit mode
548         is_editmode = Window.EditMode()
549         if is_editmode:
550                 Window.EditMode(0)
551
552
553         Window.WaitCursor(1)
554         lightmap_uvpack(meshes,\
555                         PREF_SEL_ONLY.val,\
556                         PREF_NEW_UVLAYER.val,\
557                         PREF_PACK_IN_ONE.val,\
558                         PREF_APPLY_IMAGE.val,\
559                         PREF_IMG_PX_SIZE.val,\
560                         PREF_BOX_DIV.val,\
561                         int(1/(PREF_MARGIN_DIV.val/100)))
562         
563         if is_editmode:
564                 Window.EditMode(1)
565         
566         Window.WaitCursor(0)
567
568 if __name__ == '__main__':
569         main()