~ubuntu-branches/ubuntu/gutsy/blender/gutsy-security

« back to all changes in this revision

Viewing changes to release/scripts/uvcalc_lightmap.py

  • Committer: Bazaar Package Importer
  • Author(s): Florian Ernst
  • Date: 2007-05-17 11:47:59 UTC
  • mfrom: (1.2.6 upstream)
  • Revision ID: james.westby@ubuntu.com-20070517114759-yp4ybrnhp2u7pk66
Tags: 2.44-1
* New upstream release.
* Drop debian/patches/01_64bits_stupidity, not needed anymore: as of this
  version blender is 64 bits safe again. Adjust README.Debian accordingly.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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"
 
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
                if PREF_SEL_ONLY:
 
226
                        faces = [f for f in me.faces if f.sel]
 
227
                else:
 
228
                        faces = list(me.faces)
 
229
                
 
230
                if PREF_PACK_IN_ONE:
 
231
                        face_groups[0].extend(faces)
 
232
                else:
 
233
                        face_groups.append(faces)
 
234
                
 
235
                if PREF_NEW_UVLAYER:
 
236
                        me.addUVLayer('lightmap')
 
237
                        me.activeUVLayer = 'lightmap'
 
238
        
 
239
        for face_sel in face_groups:
 
240
                print "\nStarting unwrap"
 
241
                
 
242
                if len(face_sel) <4:
 
243
                        print '\tWarning, less then 4 faces, skipping'
 
244
                        continue
 
245
                
 
246
                pretty_faces = [prettyface(f) for f in face_sel if len(f) == 4]
 
247
                
 
248
                
 
249
                # Do we have any tri's
 
250
                if len(pretty_faces) != len(face_sel):
 
251
                        
 
252
                        # Now add tri's, not so simple because we need to pair them up.
 
253
                        def trylens(f):
 
254
                                # f must be a tri
 
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]
 
257
                                
 
258
                                lens_min = lens.index(min(lens))
 
259
                                lens_max = lens.index(max(lens))
 
260
                                for i in xrange(3):
 
261
                                        if i != lens_min and i!= lens_max:
 
262
                                                lens_mid = i
 
263
                                                break
 
264
                                lens_order = lens_min, lens_mid, lens_max
 
265
                                
 
266
                                return f, lens, lens_order
 
267
                                
 
268
                        tri_lengths = [trylens(f) for f in face_sel if len(f) == 3]
 
269
                        del trylens
 
270
                        
 
271
                        def trilensdiff(t1,t2):
 
272
                                return\
 
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]])
 
276
                        
 
277
                        while tri_lengths:
 
278
                                tri1 = tri_lengths.pop()
 
279
                                
 
280
                                if not tri_lengths:
 
281
                                        pretty_faces.append(prettyface((tri1, None)))
 
282
                                        break
 
283
                                
 
284
                                best_tri_index = -1
 
285
                                best_tri_diff  = 100000000.0
 
286
                                
 
287
                                for i, tri2 in enumerate(tri_lengths):
 
288
                                        diff = trilensdiff(tri1, tri2)
 
289
                                        if diff < best_tri_diff:
 
290
                                                best_tri_index = i
 
291
                                                best_tri_diff = diff
 
292
                                
 
293
                                pretty_faces.append(prettyface((tri1, tri_lengths.pop(best_tri_index))))
 
294
                
 
295
                
 
296
                # Get the min, max and total areas
 
297
                max_area = 0.0
 
298
                min_area = 100000000.0
 
299
                tot_area = 0
 
300
                for f in face_sel:
 
301
                        area = f.area
 
302
                        if area > max_area:             max_area = area
 
303
                        if area < min_area:             min_area = area
 
304
                        tot_area += area
 
305
                        
 
306
                max_len = sqrt(max_area)
 
307
                min_len = sqrt(min_area)
 
308
                side_len = sqrt(tot_area) 
 
309
                
 
310
                # Build widths
 
311
                
 
312
                curr_len = max_len
 
313
                
 
314
                print '\tGenerating lengths...',
 
315
                
 
316
                lengths = []
 
317
                while curr_len > min_len:
 
318
                        lengths.append(curr_len) 
 
319
                        curr_len = curr_len/2
 
320
                        
 
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:
 
325
                                break
 
326
                
 
327
                # convert into ints
 
328
                lengths_to_ints = {}
 
329
                
 
330
                l_int = 1
 
331
                for l in reversed(lengths):
 
332
                        lengths_to_ints[l] = l_int
 
333
                        l_int*=2
 
334
                
 
335
                lengths_to_ints = lengths_to_ints.items()
 
336
                lengths_to_ints.sort()
 
337
                print 'done'
 
338
                
 
339
                # apply quantized values.
 
340
                
 
341
                for pf in pretty_faces:
 
342
                        w = pf.width
 
343
                        h = pf.height
 
344
                        bestw_diff = 1000000000.0
 
345
                        besth_diff = 1000000000.0
 
346
                        new_w = 0.0
 
347
                        new_h = 0.0
 
348
                        for l, i in lengths_to_ints:
 
349
                                d = abs(l - w)
 
350
                                if d < bestw_diff:
 
351
                                        bestw_diff = d
 
352
                                        new_w = i # assign the int version
 
353
                                
 
354
                                d = abs(l - h)
 
355
                                if d < besth_diff:
 
356
                                        besth_diff = d
 
357
                                        new_h = i # ditto
 
358
                        
 
359
                        pf.width = new_w
 
360
                        pf.height = new_h
 
361
                        
 
362
                        if new_w > new_h:
 
363
                                pf.spin()
 
364
                        
 
365
                print '...done'
 
366
                
 
367
                
 
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
 
374
                #
 
375
                # After this is done an external pack func is done that packs the whole group.
 
376
                
 
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)
 
380
                
 
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 )
 
385
                
 
386
                # Count the number of boxes consolidated, only used for stats.
 
387
                c = 0
 
388
                
 
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)
 
399
                
 
400
                
 
401
                # RECURSIVE prettyface grouping
 
402
                ok = True
 
403
                while ok:
 
404
                        ok = False
 
405
                        
 
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)
 
412
                                                ok = True
 
413
                                                c += 1
 
414
                                                pf_parent = prettyface([boxes.pop(), boxes.pop()])
 
415
                                                pretty_faces.append(pf_parent)
 
416
                                                
 
417
                                                w,h = pf_parent.width, pf_parent.height
 
418
                                                
 
419
                                                if w>h: raise "error"
 
420
                                                
 
421
                                                if w==h:
 
422
                                                        even_dict.setdefault(w, []).append(pf_parent)
 
423
                                                else:
 
424
                                                        odd_dict.setdefault((w,h), []).append(pf_parent)
 
425
                                        
 
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)
 
432
                                                ok = True
 
433
                                                c += 1
 
434
                                                
 
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)
 
439
                
 
440
                del even_dict
 
441
                del odd_dict
 
442
                
 
443
                orig = len(pretty_faces)
 
444
                
 
445
                pretty_faces = [pf for pf in pretty_faces if not pf.has_parent]
 
446
                
 
447
                # spin every second prettyface
 
448
                # if there all vertical you get less efficiently used texture space
 
449
                i = len(pretty_faces)
 
450
                d = 0
 
451
                while i:
 
452
                        i -=1
 
453
                        pf = pretty_faces[i]
 
454
                        if pf.width != pf.height:
 
455
                                d += 1
 
456
                                if d % 2: # only pack every second
 
457
                                        pf.spin()
 
458
                                        # pass
 
459
                
 
460
                print 'Consolidated', c, 'boxes, done'
 
461
                # print 'done', orig, len(pretty_faces)
 
462
                
 
463
                
 
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)
 
468
                
 
469
                # print packWidth, packHeight
 
470
                
 
471
                packWidth = float(packWidth)
 
472
                packHeight = float(packHeight)
 
473
                
 
474
                margin_w = ((packWidth) / PREF_MARGIN_DIV)/ packWidth
 
475
                margin_h = ((packHeight) / PREF_MARGIN_DIV) / packHeight
 
476
                
 
477
                # print margin_w, margin_h
 
478
                print 'done'
 
479
                
 
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)
 
485
                print 'done'
 
486
                
 
487
                
 
488
                if PREF_APPLY_IMAGE:
 
489
                        if not PREF_PACK_IN_ONE:
 
490
                                image = Image.New('lightmap', PREF_IMG_PX_SIZE, PREF_IMG_PX_SIZE, 24)
 
491
                                
 
492
                        for f in face_sel:
 
493
                                f.image = image
 
494
                
 
495
        for me in meshes:
 
496
                me.update()
 
497
        
 
498
        print 'finished all %.2f ' % (sys.time() - t)
 
499
        
 
500
        Window.RedrawAll()
 
501
 
 
502
def main():
 
503
        scn = bpy.data.scenes.active
 
504
        
 
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)
 
513
        
 
514
        if not Draw.PupBlock('Lightmap Pack', [\
 
515
        'Context...',
 
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.'),\
 
518
        'Image & UVs...',
 
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'),\
 
523
        'UV Packing...',
 
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')\
 
526
        ]):
 
527
                return
 
528
        
 
529
        
 
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.')
 
534
                        return
 
535
                meshes = [ ob.getData(mesh=1) ]
 
536
        else:
 
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()
 
539
                if not meshes:
 
540
                        Draw.PupMenu('Error%t|No mesh objects selected.')
 
541
                        return
 
542
        
 
543
        Window.WaitCursor(1)
 
544
        lightmap_uvpack(meshes,\
 
545
                        PREF_SEL_ONLY.val,\
 
546
                        PREF_NEW_UVLAYER.val,\
 
547
                        PREF_PACK_IN_ONE.val,\
 
548
                        PREF_APPLY_IMAGE.val,\
 
549
                        PREF_IMG_PX_SIZE.val,\
 
550
                        PREF_BOX_DIV.val,\
 
551
                        int(1/(PREF_MARGIN_DIV.val/100)))
 
552
        
 
553
        Window.WaitCursor(0)
 
554
 
 
555
if __name__ == '__main__':
 
556
        main()