Merged revision(s) 57423-57498 from trunk/blender into soc-2013-dingto
[blender-staging.git] / release / scripts / startup / bl_operators / uvcalc_lightmap.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 import bpy
22 from bpy.types import Operator
23 import mathutils
24
25
26 class prettyface(object):
27     __slots__ = ("uv",
28                  "width",
29                  "height",
30                  "children",
31                  "xoff",
32                  "yoff",
33                  "has_parent",
34                  "rot",
35                  )
36
37     def __init__(self, data):
38         self.has_parent = False
39         self.rot = False  # only used for triangles
40         self.xoff = 0
41         self.yoff = 0
42
43         if type(data) == list:  # list of data
44             self.uv = None
45
46             # join the data
47             if len(data) == 2:
48                 # 2 vertical blocks
49                 data[1].xoff = data[0].width
50                 self.width = data[0].width * 2
51                 self.height = data[0].height
52
53             elif len(data) == 4:
54                 # 4 blocks all the same size
55                 d = data[0].width  # dimension x/y are the same
56
57                 data[1].xoff += d
58                 data[2].yoff += d
59
60                 data[3].xoff += d
61                 data[3].yoff += d
62
63                 self.width = self.height = d * 2
64
65             #else:
66             #    print(len(data), data)
67             #    raise "Error"
68
69             for pf in data:
70                 pf.has_parent = True
71
72             self.children = data
73
74         elif type(data) == tuple:
75             # 2 blender faces
76             # f, (len_min, len_mid, len_max)
77             self.uv = data
78
79             f1, lens1, lens1ord = data[0]
80             if data[1]:
81                 f2, lens2, lens2ord = data[1]
82                 self.width = (lens1[lens1ord[0]] + lens2[lens2ord[0]]) / 2.0
83                 self.height = (lens1[lens1ord[1]] + lens2[lens2ord[1]]) / 2.0
84             else:  # 1 tri :/
85                 self.width = lens1[0]
86                 self.height = lens1[1]
87
88             self.children = []
89
90         else:  # blender face
91             uv_layer = data.id_data.uv_layers.active.data
92             self.uv = [uv_layer[i].uv for i in data.loop_indices]
93
94             # cos = [v.co for v in data]
95             cos = [data.id_data.vertices[v].co for v in data.vertices]  # XXX25
96
97             self.width = ((cos[0] - cos[1]).length + (cos[2] - cos[3]).length) / 2.0
98             self.height = ((cos[1] - cos[2]).length + (cos[0] - cos[3]).length) / 2.0
99
100             self.children = []
101
102     def spin(self):
103         if self.uv and len(self.uv) == 4:
104             self.uv = self.uv[1], self.uv[2], self.uv[3], self.uv[0]
105
106         self.width, self.height = self.height, self.width
107         self.xoff, self.yoff = self.yoff, self.xoff  # not needed?
108         self.rot = not self.rot  # only for tri pairs.
109         # print("spinning")
110         for pf in self.children:
111             pf.spin()
112
113     def place(self, xoff, yoff, xfac, yfac, margin_w, margin_h):
114         from math import pi
115
116         xoff += self.xoff
117         yoff += self.yoff
118
119         for pf in self.children:
120             pf.place(xoff, yoff, xfac, yfac, margin_w, margin_h)
121
122         uv = self.uv
123         if not uv:
124             return
125
126         x1 = xoff
127         y1 = yoff
128         x2 = xoff + self.width
129         y2 = yoff + self.height
130
131         # Scale the values
132         x1 = x1 / xfac + margin_w
133         x2 = x2 / xfac - margin_w
134         y1 = y1 / yfac + margin_h
135         y2 = y2 / yfac - margin_h
136
137         # 2 Tri pairs
138         if len(uv) == 2:
139             # match the order of angle sizes of the 3d verts with the UV angles and rotate.
140             def get_tri_angles(v1, v2, v3):
141                 a1 = (v2 - v1).angle(v3 - v1, pi)
142                 a2 = (v1 - v2).angle(v3 - v2, pi)
143                 a3 = pi - (a1 + a2)  # a3= (v2 - v3).angle(v1 - v3)
144
145                 return [(a1, 0), (a2, 1), (a3, 2)]
146
147             def set_uv(f, p1, p2, p3):
148
149                 # cos =
150                 #v1 = cos[0]-cos[1]
151                 #v2 = cos[1]-cos[2]
152                 #v3 = cos[2]-cos[0]
153
154                 # angles_co = get_tri_angles(*[v.co for v in f])
155                 angles_co = get_tri_angles(*[f.id_data.vertices[v].co for v in f.vertices])  # XXX25
156
157                 angles_co.sort()
158                 I = [i for a, i in angles_co]
159
160                 uv_layer = f.id_data.uv_layers.active.data
161                 fuv = [uv_layer[i].uv for i in f.loop_indices]
162
163                 if self.rot:
164                     fuv[I[2]][:] = p1
165                     fuv[I[1]][:] = p2
166                     fuv[I[0]][:] = p3
167                 else:
168                     fuv[I[2]][:] = p1
169                     fuv[I[0]][:] = p2
170                     fuv[I[1]][:] = p3
171
172             f, lens, lensord = uv[0]
173
174             set_uv(f, (x1, y1), (x1, y2 - margin_h), (x2 - margin_w, y1))
175
176             if uv[1]:
177                 f, lens, lensord = uv[1]
178                 set_uv(f, (x2, y2), (x2, y1 + margin_h), (x1 + margin_w, y2))
179
180         else:  # 1 QUAD
181             uv[1][:] = x1, y1
182             uv[2][:] = x1, y2
183             uv[3][:] = x2, y2
184             uv[0][:] = x2, y1
185
186     def __hash__(self):
187         # None unique hash
188         return self.width, self.height
189
190
191 def lightmap_uvpack(meshes,
192                     PREF_SEL_ONLY=True,
193                     PREF_NEW_UVLAYER=False,
194                     PREF_PACK_IN_ONE=False,
195                     PREF_APPLY_IMAGE=False,
196                     PREF_IMG_PX_SIZE=512,
197                     PREF_BOX_DIV=8,
198                     PREF_MARGIN_DIV=512
199                     ):
200     """
201     BOX_DIV if the maximum division of the UV map that
202     a box may be consolidated into.
203     Basically, a lower value will be slower but waist less space
204     and a higher value will have more clumpy boxes but more wasted space
205     """
206     import time
207     from math import sqrt
208
209     if not meshes:
210         return
211
212     t = time.time()
213
214     if PREF_PACK_IN_ONE:
215         if PREF_APPLY_IMAGE:
216             image = bpy.data.images.new(name="lightmap", width=PREF_IMG_PX_SIZE, height=PREF_IMG_PX_SIZE, alpha=False)
217         face_groups = [[]]
218     else:
219         face_groups = []
220
221     for me in meshes:
222         if PREF_SEL_ONLY:
223             faces = [f for f in me.polygons if f.select]
224         else:
225             faces = me.polygons[:]
226
227         if PREF_PACK_IN_ONE:
228             face_groups[0].extend(faces)
229         else:
230             face_groups.append(faces)
231
232         if PREF_NEW_UVLAYER:
233             me.uv_textures.new()
234
235         # Add face UV if it does not exist.
236         # All new faces are selected.
237         if not me.uv_textures:
238             me.uv_textures.new()
239
240     for face_sel in face_groups:
241         print("\nStarting unwrap")
242
243         if len(face_sel) < 4:
244             print("\tWarning, less then 4 faces, skipping")
245             continue
246
247         pretty_faces = [prettyface(f) for f in face_sel if f.loop_total == 4]
248
249         # Do we have any triangles?
250         if len(pretty_faces) != len(face_sel):
251
252             # Now add triangles, not so simple because we need to pair them up.
253             def trylens(f):
254                 # f must be a tri
255
256                 # cos = [v.co for v in f]
257                 cos = [f.id_data.vertices[v].co for v in f.vertices]  # XXX25
258
259                 lens = [(cos[0] - cos[1]).length, (cos[1] - cos[2]).length, (cos[2] - cos[0]).length]
260
261                 lens_min = lens.index(min(lens))
262                 lens_max = lens.index(max(lens))
263                 for i in range(3):
264                     if i != lens_min and i != lens_max:
265                         lens_mid = i
266                         break
267                 lens_order = lens_min, lens_mid, lens_max
268
269                 return f, lens, lens_order
270
271             tri_lengths = [trylens(f) for f in face_sel if f.loop_total == 3]
272             del trylens
273
274             def trilensdiff(t1, t2):
275                 return (abs(t1[1][t1[2][0]] - t2[1][t2[2][0]]) +
276                         abs(t1[1][t1[2][1]] - t2[1][t2[2][1]]) +
277                         abs(t1[1][t1[2][2]] - t2[1][t2[2][2]]))
278
279             while tri_lengths:
280                 tri1 = tri_lengths.pop()
281
282                 if not tri_lengths:
283                     pretty_faces.append(prettyface((tri1, None)))
284                     break
285
286                 best_tri_index = -1
287                 best_tri_diff = 100000000.0
288
289                 for i, tri2 in enumerate(tri_lengths):
290                     diff = trilensdiff(tri1, tri2)
291                     if diff < best_tri_diff:
292                         best_tri_index = i
293                         best_tri_diff = diff
294
295                 pretty_faces.append(prettyface((tri1, tri_lengths.pop(best_tri_index))))
296
297         # Get the min, max and total areas
298         max_area = 0.0
299         min_area = 100000000.0
300         tot_area = 0
301         for f in face_sel:
302             area = f.area
303             if area > max_area:
304                 max_area = area
305             if area < min_area:
306                 min_area = area
307             tot_area += area
308
309         max_len = sqrt(max_area)
310         min_len = sqrt(min_area)
311         side_len = sqrt(tot_area)
312
313         # Build widths
314
315         curr_len = max_len
316
317         print("\tGenerating lengths...", end="")
318
319         lengths = []
320         while curr_len > min_len:
321             lengths.append(curr_len)
322             curr_len = curr_len / 2.0
323
324             # Don't allow boxes smaller then the margin
325             # since we contract on the margin, boxes that are smaller will create errors
326             # print(curr_len, side_len/MARGIN_DIV)
327             if curr_len / 4.0 < side_len / PREF_MARGIN_DIV:
328                 break
329
330         if not lengths:
331             lengths.append(curr_len)
332
333         # convert into ints
334         lengths_to_ints = {}
335
336         l_int = 1
337         for l in reversed(lengths):
338             lengths_to_ints[l] = l_int
339             l_int *= 2
340
341         lengths_to_ints = list(lengths_to_ints.items())
342         lengths_to_ints.sort()
343         print("done")
344
345         # apply quantized values.
346
347         for pf in pretty_faces:
348             w = pf.width
349             h = pf.height
350             bestw_diff = 1000000000.0
351             besth_diff = 1000000000.0
352             new_w = 0.0
353             new_h = 0.0
354             for l, i in lengths_to_ints:
355                 d = abs(l - w)
356                 if d < bestw_diff:
357                     bestw_diff = d
358                     new_w = i  # assign the int version
359
360                 d = abs(l - h)
361                 if d < besth_diff:
362                     besth_diff = d
363                     new_h = i  # ditto
364
365             pf.width = new_w
366             pf.height = new_h
367
368             if new_w > new_h:
369                 pf.spin()
370
371         print("...done")
372
373         # Since the boxes are sized in powers of 2, we can neatly group them into bigger squares
374         # this is done hierarchically, so that we may avoid running the pack function
375         # on many thousands of boxes, (under 1k is best) because it would get slow.
376         # Using an off and even dict us useful because they are packed differently
377         # where w/h are the same, their packed in groups of 4
378         # where they are different they are packed in pairs
379         #
380         # After this is done an external pack func is done that packs the whole group.
381
382         print("\tConsolidating Boxes...", end="")
383         even_dict = {}  # w/h are the same, the key is an int (w)
384         odd_dict = {}  # w/h are different, the key is the (w,h)
385
386         for pf in pretty_faces:
387             w, h = pf.width, pf.height
388             if w == h:
389                 even_dict.setdefault(w, []).append(pf)
390             else:
391                 odd_dict.setdefault((w, h), []).append(pf)
392
393         # Count the number of boxes consolidated, only used for stats.
394         c = 0
395
396         # This is tricky. the total area of all packed boxes, then sqrt() that to get an estimated size
397         # this is used then converted into out INT space so we can compare it with
398         # the ints assigned to the boxes size
399         # and divided by BOX_DIV, basically if BOX_DIV is 8
400         # ...then the maximum box consolidation (recursive grouping) will have a max width & height
401         # ...1/8th of the UV size.
402         # ...limiting this is needed or you end up with bug unused texture spaces
403         # ...however if its too high, box-packing is way too slow for high poly meshes.
404         float_to_int_factor = lengths_to_ints[0][0]
405         if float_to_int_factor > 0:
406             max_int_dimension = int(((side_len / float_to_int_factor)) / PREF_BOX_DIV)
407             ok = True
408         else:
409             max_int_dimension = 0.0  # wont be used
410             ok = False
411
412         # RECURSIVE pretty face grouping
413         while ok:
414             ok = False
415
416             # Tall boxes in groups of 2
417             for d, boxes in list(odd_dict.items()):
418                 if d[1] < max_int_dimension:
419                     #\boxes.sort(key = lambda a: len(a.children))
420                     while len(boxes) >= 2:
421                         # print("foo", len(boxes))
422                         ok = True
423                         c += 1
424                         pf_parent = prettyface([boxes.pop(), boxes.pop()])
425                         pretty_faces.append(pf_parent)
426
427                         w, h = pf_parent.width, pf_parent.height
428
429                         if w > h:
430                             raise "error"
431
432                         if w == h:
433                             even_dict.setdefault(w, []).append(pf_parent)
434                         else:
435                             odd_dict.setdefault((w, h), []).append(pf_parent)
436
437             # Even boxes in groups of 4
438             for d, boxes in list(even_dict.items()):
439                 if d < max_int_dimension:
440                     boxes.sort(key=lambda a: len(a.children))
441
442                     while len(boxes) >= 4:
443                         # print("bar", len(boxes))
444                         ok = True
445                         c += 1
446
447                         pf_parent = prettyface([boxes.pop(), boxes.pop(), boxes.pop(), boxes.pop()])
448                         pretty_faces.append(pf_parent)
449                         w = pf_parent.width  # width and weight are the same
450                         even_dict.setdefault(w, []).append(pf_parent)
451
452         del even_dict
453         del odd_dict
454
455         # orig = len(pretty_faces)
456
457         pretty_faces = [pf for pf in pretty_faces if not pf.has_parent]
458
459         # spin every second pretty-face
460         # if there all vertical you get less efficiently used texture space
461         i = len(pretty_faces)
462         d = 0
463         while i:
464             i -= 1
465             pf = pretty_faces[i]
466             if pf.width != pf.height:
467                 d += 1
468                 if d % 2:  # only pack every second
469                     pf.spin()
470                     # pass
471
472         print("Consolidated", c, "boxes, done")
473         # print("done", orig, len(pretty_faces))
474
475         # boxes2Pack.append([islandIdx, w,h])
476         print("\tPacking Boxes", len(pretty_faces), end="...")
477         boxes2Pack = [[0.0, 0.0, pf.width, pf.height, i] for i, pf in enumerate(pretty_faces)]
478         packWidth, packHeight = mathutils.geometry.box_pack_2d(boxes2Pack)
479
480         # print(packWidth, packHeight)
481
482         packWidth = float(packWidth)
483         packHeight = float(packHeight)
484
485         margin_w = ((packWidth) / PREF_MARGIN_DIV) / packWidth
486         margin_h = ((packHeight) / PREF_MARGIN_DIV) / packHeight
487
488         # print(margin_w, margin_h)
489         print("done")
490
491         # Apply the boxes back to the UV coords.
492         print("\twriting back UVs", end="")
493         for i, box in enumerate(boxes2Pack):
494             pretty_faces[i].place(box[0], box[1], packWidth, packHeight, margin_w, margin_h)
495             # pf.place(box[1][1], box[1][2], packWidth, packHeight, margin_w, margin_h)
496         print("done")
497
498         if PREF_APPLY_IMAGE:
499             if not PREF_PACK_IN_ONE:
500                 image = bpy.data.images.new(name="lightmap",
501                                             width=PREF_IMG_PX_SIZE,
502                                             height=PREF_IMG_PX_SIZE,
503                                             )
504
505             for f in face_sel:
506                 # f.image = image
507                 f.id_data.uv_textures.active.data[f.index].image = image  # XXX25
508
509     for me in meshes:
510         me.update()
511
512     print("finished all %.2f " % (time.time() - t))
513
514     # Window.RedrawAll()
515
516
517 def unwrap(operator, context, **kwargs):
518
519     is_editmode = (context.object.mode == 'EDIT')
520     if is_editmode:
521         bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
522
523     PREF_ACT_ONLY = kwargs.pop("PREF_ACT_ONLY")
524
525     meshes = []
526     if PREF_ACT_ONLY:
527         obj = context.scene.objects.active
528         if obj and obj.type == 'MESH':
529             meshes = [obj.data]
530     else:
531         meshes = list({me for obj in context.selected_objects if obj.type == 'MESH' for me in (obj.data,) if me.polygons and me.library is None})
532
533     if not meshes:
534         operator.report({'ERROR'}, "No mesh object")
535         return {'CANCELLED'}
536
537     lightmap_uvpack(meshes, **kwargs)
538
539     if is_editmode:
540         bpy.ops.object.mode_set(mode='EDIT', toggle=False)
541
542     return {'FINISHED'}
543
544 from bpy.props import BoolProperty, FloatProperty, IntProperty
545
546
547 class LightMapPack(Operator):
548     """Follow UVs from active quads along continuous face loops"""
549     bl_idname = "uv.lightmap_pack"
550     bl_label = "Lightmap Pack"
551
552     # Disable REGISTER flag for now because this operator might create new
553     # images. This leads to non-proper operator redo because current undo
554     # stack is local for edit mode and can not remove images created by this
555     # operator.
556     # Proper solution would be to make undo stack aware of such things,
557     # but for now just disable redo. Keep undo here so unwanted changes to uv
558     # coords might be undone.
559     # This fixes infinite image creation reported there [#30968] (sergey)
560     bl_options = {'UNDO'}
561
562     PREF_CONTEXT = bpy.props.EnumProperty(
563             name="Selection",
564             items=(('SEL_FACES', "Selected Faces", "Space all UVs evenly"),
565                    ('ALL_FACES', "All Faces", "Average space UVs edge length of each loop"),
566                    ('ALL_OBJECTS', "Selected Mesh Object", "Average space UVs edge length of each loop")
567                    ),
568             )
569
570     # Image & UVs...
571     PREF_PACK_IN_ONE = BoolProperty(
572             name="Share Tex Space",
573             description=("Objects Share texture space, map all objects "
574                          "into 1 uvmap"),
575             default=True,
576             )
577     PREF_NEW_UVLAYER = BoolProperty(
578             name="New UV Map",
579             description="Create a new UV map for every mesh packed",
580             default=False,
581             )
582     PREF_APPLY_IMAGE = BoolProperty(
583             name="New Image",
584             description=("Assign new images for every mesh (only one if "
585                          "shared tex space enabled)"),
586             default=False,
587             )
588     PREF_IMG_PX_SIZE = IntProperty(
589             name="Image Size",
590             description="Width and Height for the new image",
591             min=64, max=5000,
592             default=512,
593             )
594     # UV Packing...
595     PREF_BOX_DIV = IntProperty(
596             name="Pack Quality",
597             description="Pre Packing before the complex boxpack",
598             min=1, max=48,
599             default=12,
600             )
601     PREF_MARGIN_DIV = FloatProperty(
602             name="Margin",
603             description="Size of the margin as a division of the UV",
604             min=0.001, max=1.0,
605             default=0.1,
606             )
607
608     def execute(self, context):
609         kwargs = self.as_keywords()
610         PREF_CONTEXT = kwargs.pop("PREF_CONTEXT")
611
612         if PREF_CONTEXT == 'SEL_FACES':
613             kwargs["PREF_ACT_ONLY"] = True
614             kwargs["PREF_SEL_ONLY"] = True
615         elif PREF_CONTEXT == 'ALL_FACES':
616             kwargs["PREF_ACT_ONLY"] = True
617             kwargs["PREF_SEL_ONLY"] = False
618         elif PREF_CONTEXT == 'ALL_OBJECTS':
619             kwargs["PREF_ACT_ONLY"] = False
620             kwargs["PREF_SEL_ONLY"] = False
621         else:
622             raise Exception("invalid context")
623
624         kwargs["PREF_MARGIN_DIV"] = int(1.0 / (kwargs["PREF_MARGIN_DIV"] / 100.0))
625
626         return unwrap(self, context, **kwargs)
627
628     def invoke(self, context, event):
629         wm = context.window_manager
630         return wm.invoke_props_dialog(self)