=== Patch ===
[blender.git] / release / scripts / uv_export.py
1 #!BPY
2
3 """
4 Name: 'Save UV Face Layout...'
5 Blender: 242
6 Group: 'UV'
7 Tooltip: 'Export the UV face layout of the selected object to a .TGA or .SVG file'
8 """ 
9
10 __author__ = "Martin 'theeth' Poirier"
11 __url__ = ("http://www.blender.org", "http://blenderartists.org/")
12 __version__ = "2.4"
13
14 __bpydoc__ = """\
15 This script exports the UV face layout of the selected mesh object to
16 a TGA image file or a SVG vector file.  Then you can, for example, paint details
17 in this image using an external 2d paint program of your choice and bring it back
18 to be used as a texture for the mesh.
19
20 Usage:
21
22 Open this script from UV/Image Editor's "UVs" menu, make sure there is a mesh
23 selected, define size and wire size parameters and push "Export" button.
24
25 There are more options to configure, like setting export path, if image should
26 use object's name and more.
27
28 Notes:<br>See change logs in scripts for a list of contributors.
29 """
30
31
32 # $Id$
33 #
34 # --------------------------------------------------------------------------
35 # ***** BEGIN GPL LICENSE BLOCK *****
36 #
37 # Copyright (C) 2003: Martin Poirier, theeth@yahoo.com
38 #
39 # This program is free software; you can redistribute it and/or
40 # modify it under the terms of the GNU General Public License
41 # as published by the Free Software Foundation; either version 2
42 # of the License, or (at your option) any later version.
43 #
44 # This program is distributed in the hope that it will be useful,
45 # but WITHOUT ANY WARRANTY; without even the implied warranty of
46 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
47 # GNU General Public License for more details.
48 #
49 # You should have received a copy of the GNU General Public License
50 # along with this program; if not, write to the Free Software Foundation,
51 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
52 #
53 # ***** END GPL LICENCE BLOCK *****
54 # --------------------------------------------------------------------------
55 # thanks to jms for the tga functions:
56 # Writetga and buffer functions
57 # (c) 2002-2004 J-M Soler released under GPL licence
58 # Official Page :
59 # http://jmsoler.free.fr/didacticiel/blender/tutor/write_tga_pic.htm
60 # Communicate problems and errors on:
61 # http://www.zoo-logique.org/3D.Blender/newsportal/thread.php?group=3D.Blender 
62 # --------------------------
63 #        Version 1.1            
64 # Clear a bug that crashed the script when UV coords overlapped in the same faces
65 # --------------------------
66 #        Version 1.2
67 # Now with option to use the object's name as filename
68 # --------------------------
69 #        Version 1.3 Updates by Zaz from Elysiun.com
70 # Default path is now the directory of the last saved .blend
71 # New options: Work on selected face only & Scale image when face wraps
72 # --------------------------
73 #        Version 1.3a
74 # Corrected a minor typo and added the tga extension to both export call
75 # --------------------------
76 #        Version 1.4 Updates by Macouno from Elysiun.com
77 # Fixed rounding error that can cause breaks in lines.
78 # --------------------------
79 #        Version 2.0
80 # New interface using PupBlock and FileSelector
81 # Save/Load config to Registry
82 # Edit in external program
83 # --------------------------
84 #        Version 2.1 Updates by Jarod from blenderartists.org
85 # New exportformat SVG
86 # simple memory optimations,    two third less memory usage
87 # tga filewriting speed improvements, 3times faster now
88 # --------------------------
89 #       Version 2.2
90 # Cleanup code
91 # Filename handling enhancement and bug fixes
92 # --------------------------
93 #       Version 2.3
94 # Added check for excentric UVs (only affects TGA)
95 # --------------------------
96 #       Version 2.4
97 # Port from NMesh to Mesh by Daniel Salazar (zanqdo)
98 # --------------------------
99
100
101 FullPython = False
102
103 import Blender
104 import bpy
105 import BPyMessages
106
107 try:
108         import os
109         FullPython = True
110 except:
111         pass
112
113 from math import *
114
115 def ExportConfig():
116         conf = {}
117         
118         conf["SIZE"] = bSize.val
119         conf["WSIZE"] = bWSize.val
120         conf["OBFILE"] = bObFile.val
121         conf["WRAP"] = bWrap.val
122         conf["ALLFACES"] = bAllFaces.val
123         conf["EDIT"] = bEdit.val
124         conf["EXTERNALEDITOR"] = bEditPath.val
125         conf["UVFORMATSVG"] = bSVG.val
126         conf["SVGFILL"] = bSVGFill.val
127         
128         Blender.Registry.SetKey("UVEXPORT", conf, True)
129
130 def ImportConfig():
131         global bSize, bWSize, bObFile, bWrap, bAllFaces
132         
133         conf = Blender.Registry.GetKey("UVEXPORT", True)
134         
135         if not conf:
136                 return
137         
138         try:
139                 bSize.val = conf["SIZE"]
140                 bWSize.val = conf["WSIZE"]
141                 bObFile.val = conf["OBFILE"]
142                 bWrap.val = conf["WRAP"]
143                 bAllFaces.val = conf["ALLFACES"]
144                 bEdit.val = conf["EDIT"]
145                 editor = conf["EXTERNALEDITOR"]
146                 bSVG.val = conf["UVFORMATSVG"]
147                 bSVGFill.val = conf["SVGFILL"]
148                 if editor:
149                         bEditPath.val = editor
150         except KeyError:
151                 # If one of the key is not in the dict, don't worry, it'll use the defaults
152                 pass
153                         
154         
155 def PrintConfig():
156         print
157         print         "Imagesize: %ipx" % bSize.val
158         print         "Wiresize : %ipx" % bWSize.val
159         
160         if bWrap.val:
161                 print "Wrap     : yes"
162         else:
163                 print "Wrap     : no"
164                 
165         if bAllFaces.val:
166                 print "AllFaces : yes"
167         else:
168                 print "AllFaces : no"
169                 
170         if bSVG.val:
171                 print "Format   : *.svg"
172         else:
173                 print "Format   : *.tga"
174
175
176 def ExportCallback(f):
177         obj = Blender.Scene.GetCurrent().objects.active
178         
179         time1= Blender.sys.time()
180
181         if obj.type != "Mesh":
182                 BPyMessages.Error_NoMeshActive()
183                 return
184
185         mesh = obj.getData(mesh=1)
186         if not mesh.faceUV:
187                 BPyMessages.Error_NoMeshUvActive()
188                 return
189
190         # just for information...
191         PrintConfig()
192         
193         # taking care of filename
194         if bObFile.val:
195                 name = AddExtension(f, obj.name)
196         else:
197                 name = AddExtension(f, None)
198                 
199         
200         print "Target   :", name
201         print
202         
203         UVFaces = ExtractUVFaces(mesh, bAllFaces.val)
204         
205         if not bSVG.val:
206                 print "TGA export is running..."
207                 UV_Export_TGA(UVFaces, bSize.val, bWSize.val, bWrap.val, name)
208         else:
209                 print "SVG export is running..."
210                 if bSVGFill.val:
211                         SVGFillColor="#F2DAF2"
212                 else:
213                         SVGFillColor="none"
214                 
215                 UV_Export_SVG(UVFaces, bSize.val, bWSize.val, bWrap.val, name, obj.name, SVGFillColor)
216         
217         print
218         print "     ...finished exporting in %.4f sec." % (Blender.sys.time()-time1)
219         
220         if FullPython and bEdit.val and bEditPath.val:
221                 filepath = os.path.realpath(name)
222                 print filepath
223                 os.spawnl(os.P_NOWAIT, bEditPath.val, "", filepath)
224         
225
226 def GetExtension():
227         if bSVG.val:
228                 ext = "svg"
229         else:
230                 ext = "tga"
231         
232         return ext
233         
234 def AddExtension(filename, object_name):
235         ext = "." + GetExtension()
236         
237         hasExtension = (ext in filename or ext.upper() in filename)
238         
239         if object_name and hasExtension:
240                 filename = filename.replace(ext, "")
241                 hasExtension = False
242                 
243         if object_name:
244                 filename += "_" + object_name
245                 
246         if not hasExtension:
247                 filename += ext
248                 
249         return filename
250         
251 def GetDefaultFilename():
252         filename = Blender.Get("filename")
253         
254         filename = filename.replace(".blend", "")
255         filename += "." + GetExtension()
256         
257         return filename
258
259 def ExtractUVFaces(mesh, allface):
260         
261         if allface: return [f.uv for f in mesh.faces]
262         else:  return [f.uv for f in mesh.faces if f.sel]
263
264 def Buffer(height=16, width=16, profondeur=1,rvb=255 ):  
265         """  
266         reserve l'espace memoire necessaire  
267         """  
268         p=[rvb]  
269         myb=height*width*profondeur
270         print"Memory  : %ikB" % (myb/1024)
271         b=p*myb
272         return b
273
274 def write_tgafile(loc2,bitmap,width,height,profondeur):  
275         
276         f=open(loc2,'wb')
277
278         Origine_en_haut_a_gauche=32
279         Origine_en_bas_a_gauche=0
280
281         Data_Type_2=2
282         RVB=profondeur*8
283         RVBA=32
284         entete0=[]
285         for t in range(18):
286           entete0.append(chr(0))
287
288         entete0[2]=chr(Data_Type_2)
289         entete0[13]=chr(width/256)
290         entete0[12]=chr(width % 256)
291         entete0[15]=chr(height/256)
292         entete0[14]=chr(height % 256)
293         entete0[16]=chr(RVB)
294         entete0[17]=chr(Origine_en_bas_a_gauche)
295
296         # Origine_en_haut_a_gauche
297         print"  ...writing tga..."
298         for t in entete0:
299           f.write(t)
300         
301         redpx=chr(0) + chr(0) + chr(255)
302         blackpx=chr(0) + chr(0) + chr(0)
303         whitepx=chr(255) + chr(255) + chr(255)
304         
305         for t in bitmap:
306                 if t==255:
307                         f.write(whitepx)
308                 elif t==0:
309                         f.write(blackpx)
310                 else:
311                         f.write(redpx)
312                 
313         f.close()
314         
315         
316 def UV_Export_TGA(vList, size, wsize, wrap, file):
317         extreme_warning = False
318         
319         minx = 0
320         miny = 0
321         scale = 1.0
322         
323         step = 0
324
325         img = Buffer(size+1,size+1)
326
327         if wrap:
328                 wrapSize = size
329         else:
330                 wrapSize = size
331                 maxx = -100000
332                 maxy = -100000
333                 for f in vList:
334                         for v in f:
335                                 x = int(v[0] * size)
336                                 maxx = max (x, maxx)
337                                 minx = min (x, minx)
338                                 
339                                 y = int(v[1] * size)
340                                 maxy = max (y, maxy)
341                                 miny = min (y, miny)
342                 wrapSize = max (maxx - minx + 1, maxy - miny + 1)
343                 scale = float (size) / float (wrapSize)
344
345         fnum = 0
346         fcnt = len (vList)
347
348         for f in vList:
349                 fnum = fnum + 1
350                 if not fnum % 100:
351                         print "%i of %i Faces completed" % (fnum, fcnt)
352                         
353                 for index in range(len(f)):
354                         co1 = f[index]
355                         if index < len(f) - 1:
356                                 co2 = f[index + 1]
357                         else:
358                                 co2 = f[0]
359
360                         step = int(ceil(size*sqrt((co1[0]-co2[0])**2+(co1[1]-co2[1])**2)))
361                         if step:
362                                 try:
363                                         for t in xrange(step):
364                                                         x = int(floor((co1[0] + t*(co2[0]-co1[0])/step) * size))
365                                                         y = int(floor((co1[1] + t*(co2[1]-co1[1])/step) * size))
366                 
367                                                         if wrap:
368                                                                 x = x % wrapSize
369                                                                 y = y % wrapSize
370                                                         else:
371                                                                 x = int ((x - minx) * scale)
372                                                                 y = int ((y - miny) * scale)
373                                                                 
374                                                         co = x * 1 + y * 1 * size;
375                                                         
376                                                         img[co] = 0
377                                                         if wsize > 1:
378                                                                 for x in range(-1*wsize + 1,wsize):
379                                                                         for y in range(-1*wsize,wsize):
380                                                                                 img[co + 1 * x + y * 1 * size] = 0
381                                 except OverflowError:
382                                         if not extreme_warning:
383                                                 print "Skipping extremely long UV edges, check your layout for excentric values"
384                                                 extreme_warning = True
385                 
386                 for v in f:
387                         x = int(v[0] * size)
388                         y = int(v[1] * size)
389
390                         if wrap:
391                                 x = x % wrapSize
392                                 y = y % wrapSize
393                         else:
394                                 x = int ((x - minx) * scale)
395                                 y = int ((y - miny) * scale)
396
397                         co = x * 1 + y * 1 * size
398                         img[co] = 1
399         
400         
401         write_tgafile(file,img,size,size,3)
402
403 def UV_Export_SVG(vList, size, wsize, wrap, file, objname, facesfillcolor):
404         fl=open(file,'wb')      
405         fl.write('<?xml version="1.0"?>\r\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\r\n')
406         fl.write('<svg width="' + str(size) + 'px" height="' + str(size) + 'px" viewBox="0 0 ' + str(size) + ' ' + str(size) + '" xmlns="http://www.w3.org/2000/svg" version="1.1">\r\n')
407         fl.write('<desc>UV-Map from Object: ' + str(objname) +'. Exported from Blender3D with UV Exportscript</desc>\r\n')
408         fl.write('<rect x="0" y="0" width="' + str(size) + '" height="' + str(size) + '" fill="none" stroke="blue" stroke-width="' + str(wsize) + 'px" />\r\n')
409         fl.write('<g style="fill:' + str(facesfillcolor) + '; stroke:black; stroke-width:' + str(wsize) + 'px;">\r\n')
410
411         fnum = 0
412         fcnt = len (vList)
413         fnumv = (long) (fcnt/10)
414         
415         for f in vList:
416                 fnum = fnum + 1
417
418                 if fnum == fnumv:
419                         print ".",
420                         fnumv = fnumv + ((long) (fcnt/10))
421                         
422                 fl.write('<polygon points="')
423                 for index in range(len(f)):
424                         co = f[index]
425                         fl.write("%.3f,%.3f " % (co[0]*size, size-co[1]*size))
426                 fl.write('" />\r\n')
427                 
428         print "%i Faces completed." % fnum
429         fl.write('</g>\r\n')
430         fl.write('</svg>')
431         fl.close()
432         
433 def SetEditorAndExportCallback(f):
434         global bEditPath
435         bEditPath.val = f
436         
437         ExportConfig()
438         
439         Export()
440         
441 def Export():
442         Blender.Window.FileSelector(ExportCallback, "Save UV (%s)" % GetExtension(), GetDefaultFilename())
443         
444 def SetEditorAndExport():
445         Blender.Window.FileSelector(SetEditorAndExportCallback, "Select Editor")
446         
447 # ###################################### MAIN SCRIPT BODY ###############################
448
449 # Create user values and fill with defaults
450 bSize = Blender.Draw.Create(512)
451 bWSize = Blender.Draw.Create(1)
452 bObFile = Blender.Draw.Create(1)
453 bWrap = Blender.Draw.Create(1)
454 bAllFaces = Blender.Draw.Create(1)
455 bEdit = Blender.Draw.Create(0)
456 bEditPath = Blender.Draw.Create("")
457 bSVG = Blender.Draw.Create(0)
458 bSVGFill = Blender.Draw.Create(1)
459
460 # Import saved configurations
461 ImportConfig()
462
463
464 Block = []
465
466 Block.append(("Size: ", bSize, 64, 16384, "Size of the exported image"))
467 Block.append(("Wire: ", bWSize, 1, 9, "Size of the wire of the faces"))
468 Block.append(("Wrap", bWrap, "Wrap to image size, scale otherwise"))
469 Block.append(("All Faces", bAllFaces, "Export all or only selected faces"))
470 Block.append(("Object", bObFile, "Use object name in filename"))
471 Block.append(("SVG", bSVG, "save as *.svg instead of *.tga"))
472 Block.append(("Fill SVG faces", bSVGFill, "SVG faces will be filled, none filled otherwise"))
473
474 if FullPython:
475         Block.append(("Edit", bEdit, "Edit resulting file in an external program"))
476         Block.append(("Editor: ", bEditPath, 0, 399, "Path to external editor (leave blank to select a new one)"))
477
478 retval = Blender.Draw.PupBlock("UV Image Export", Block)
479
480 if retval:
481         ExportConfig()
482                 
483         if bEdit.val and not bEditPath.val:
484                 SetEditorAndExport()
485         else:
486                 Export()