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