= line ending fix =
[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://www.elysiun.com")
12 __version__ = "2.1"
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
98 FullPython = False
99
100 import Blender
101
102 try:
103         import os
104         FullPython = True
105 except:
106         pass
107
108 from math import *
109
110
111 def ExportConfig():
112         conf = {}
113         
114         conf["SIZE"] = bSize.val
115         conf["WSIZE"] = bWSize.val
116         conf["OBFILE"] = bObFile.val
117         conf["WRAP"] = bWrap.val
118         conf["ALLFACES"] = bAllFaces.val
119         conf["EDIT"] = bEdit.val
120         conf["EXTERNALEDITOR"] = bEditPath.val
121         conf["UVFORMATSVG"] = bSVG.val
122         conf["SVGFILL"] = bSVGFill.val
123         
124         Blender.Registry.SetKey("UVEXPORT", conf, True)
125
126 def ImportConfig():
127         global bSize, bWSize, bObFile, bWrap, bAllFaces
128         
129         conf = Blender.Registry.GetKey("UVEXPORT", True)
130         
131         if not conf:
132                 return
133         
134         try:
135                 bSize.val = conf["SIZE"]
136                 bWSize.val = conf["WSIZE"]
137                 bObFile.val = conf["OBFILE"]
138                 bWrap.val = conf["WRAP"]
139                 bAllFaces.val = conf["ALLFACES"]
140                 bEdit.val = conf["EDIT"]
141                 editor = conf["EXTERNALEDITOR"]
142                 bSVG.val = conf["UVFORMATSVG"]
143                 bSVGFill.val = conf["SVGFILL"]
144                 if editor:
145                         bEditPath.val = editor
146         except KeyError:
147                 # If one of the key is not in the dict, don't worry, it'll use the defaults
148                 pass
149                         
150         
151 def PrintConfig():
152         print
153         print         "Imagesize: %ipx" % bSize.val
154         print         "Wiresize : %ipx" % bWSize.val
155         
156         if bWrap.val:
157                 print "Wrap     : yes"
158         else:
159                 print "Wrap     : no"
160                 
161         if bAllFaces.val:
162                 print "AllFaces : yes"
163         else:
164                 print "AllFaces : no"
165                 
166         if bSVG.val:
167                 print "Format   : *.svg"
168         else:
169                 print "Format   : *.tga"
170
171
172 def ExportCallback(f):
173         obj = Blender.Scene.GetCurrent().objects.active
174         
175         time1= Blender.sys.time()
176         
177         if not obj:
178                 Blender.Draw.PupMenu("ERROR%t|No Active Object!")
179                 return
180
181         if obj.type != "Mesh":
182                 Blender.Draw.PupMenu("ERROR%t|Not a Mesh!")
183                 return
184
185         mesh = obj.getData()
186         if not mesh.hasFaceUV():
187                 Blender.Draw.PupMenu("ERROR%t|No UV coordinates!")
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         FaceList = []
261         
262         if allface:
263                 faces = mesh.faces
264         else:
265                 faces = mesh.getSelectedFaces()
266
267         for f in faces:
268                 FaceList.append(f.uv)
269         
270         return FaceList
271
272
273 def Buffer(height=16, width=16, profondeur=1,rvb=255 ):  
274         """  
275         reserve l'espace memoire necessaire  
276         """  
277         p=[rvb]  
278         myb=height*width*profondeur
279         print"Memory  : %ikB" % (myb/1024)
280         b=p*myb
281         return b
282
283 def write_tgafile(loc2,bitmap,width,height,profondeur):  
284         
285         f=open(loc2,'wb')
286
287         Origine_en_haut_a_gauche=32
288         Origine_en_bas_a_gauche=0
289
290         Data_Type_2=2
291         RVB=profondeur*8
292         RVBA=32
293         entete0=[]
294         for t in range(18):
295           entete0.append(chr(0))
296
297         entete0[2]=chr(Data_Type_2)
298         entete0[13]=chr(width/256)
299         entete0[12]=chr(width % 256)
300         entete0[15]=chr(height/256)
301         entete0[14]=chr(height % 256)
302         entete0[16]=chr(RVB)
303         entete0[17]=chr(Origine_en_bas_a_gauche)
304
305         # Origine_en_haut_a_gauche
306         print"  ...writing tga..."
307         for t in entete0:
308           f.write(t)
309         
310         redpx=chr(0) + chr(0) + chr(255)
311         blackpx=chr(0) + chr(0) + chr(0)
312         whitepx=chr(255) + chr(255) + chr(255)
313         
314         for t in bitmap:
315                 if t==255:
316                         f.write(whitepx)
317                 elif t==0:
318                         f.write(blackpx)
319                 else:
320                         f.write(redpx)
321                 
322         f.close()
323         
324         
325 def UV_Export_TGA(vList, size, wsize, wrap, file):
326         minx = 0
327         miny = 0
328         scale = 1.0
329         
330         step = 0
331
332         img = Buffer(size+1,size+1)
333
334         if wrap:
335                 wrapSize = size
336         else:
337                 wrapSize = size
338                 maxx = -100000
339                 maxy = -100000
340                 for f in vList:
341                         for v in f:
342                                 x = int(v[0] * size)
343                                 maxx = max (x, maxx)
344                                 minx = min (x, minx)
345                                 
346                                 y = int(v[1] * size)
347                                 maxy = max (y, maxy)
348                                 miny = min (y, miny)
349                 wrapSize = max (maxx - minx + 1, maxy - miny + 1)
350                 scale = float (size) / float (wrapSize)
351
352         fnum = 0
353         fcnt = len (vList)
354
355         for f in vList:
356                 fnum = fnum + 1
357                 if not fnum % 100:
358                         print "%i of %i Faces completed" % (fnum, fcnt)
359                         
360                 for index in range(len(f)):
361                         co1 = f[index]
362                         if index < len(f) - 1:
363                                 co2 = f[index + 1]
364                         else:
365                                 co2 = f[0]
366
367                         step = int(ceil(size*sqrt((co1[0]-co2[0])**2+(co1[1]-co2[1])**2)))
368                         if step:
369                                 for t in range(step):
370                                         x = int(floor((co1[0] + t*(co2[0]-co1[0])/step) * size))
371                                         y = int(floor((co1[1] + t*(co2[1]-co1[1])/step) * size))
372
373                                         if wrap:
374                                                 x = x % wrapSize
375                                                 y = y % wrapSize
376                                         else:
377                                                 x = int ((x - minx) * scale)
378                                                 y = int ((y - miny) * scale)
379                                                 
380                                         co = x * 1 + y * 1 * size;
381                                         
382                                         img[co] = 0
383                                         if wsize > 1:
384                                                 for x in range(-1*wsize + 1,wsize):
385                                                         for y in range(-1*wsize,wsize):
386                                                                 img[co + 1 * x + y * 1 * size] = 0
387         
388                 for v in f:
389                         x = int(v[0] * size)
390                         y = int(v[1] * size)
391
392                         if wrap:
393                                 x = x % wrapSize
394                                 y = y % wrapSize
395                         else:
396                                 x = int ((x - minx) * scale)
397                                 y = int ((y - miny) * scale)
398
399                         co = x * 1 + y * 1 * size
400                         img[co] = 1
401         
402         
403         write_tgafile(file,img,size,size,3)
404
405 def UV_Export_SVG(vList, size, wsize, wrap, file, objname, facesfillcolor):
406         fl=open(file,'wb')      
407         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')
408         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')
409         fl.write('<desc>UV-Map from Object: ' + str(objname) +'. Exported from Blender3D with UV Exportscript</desc>\r\n')
410         fl.write('<rect x="0" y="0" width="' + str(size) + '" height="' + str(size) + '" fill="none" stroke="blue" stroke-width="' + str(wsize) + 'px" />\r\n')
411         fl.write('<g style="fill:' + str(facesfillcolor) + '; stroke:black; stroke-width:' + str(wsize) + 'px;">\r\n')
412
413         fnum = 0
414         fcnt = len (vList)
415         fnumv = (long) (fcnt/10)
416         
417         for f in vList:
418                 fnum = fnum + 1
419
420                 if fnum == fnumv:
421                         print ".",
422                         fnumv = fnumv + ((long) (fcnt/10))
423                         
424                 fl.write('<polygon points="')
425                 for index in range(len(f)):
426                         co = f[index]
427                         fl.write("%.3f,%.3f " % (co[0]*size, size-co[1]*size))
428                 fl.write('" />\r\n')
429                 
430         print "%i Faces completed." % fnum
431         fl.write('</g>\r\n')
432         fl.write('</svg>')
433         fl.close()
434         
435 def SetEditorAndExportCallback(f):
436         global bEditPath
437         bEditPath.val = f
438         
439         ExportConfig()
440         
441         Export()
442         
443 def Export():
444         Blender.Window.FileSelector(ExportCallback, "Save UV (%s)" % GetExtension(), GetDefaultFilename())
445         
446 def SetEditorAndExport():
447         Blender.Window.FileSelector(SetEditorAndExportCallback, "Select Editor")
448         
449 # ###################################### MAIN SCRIPT BODY ###############################
450
451 # Create user values and fill with defaults
452 bSize = Blender.Draw.Create(512)
453 bWSize = Blender.Draw.Create(1)
454 bObFile = Blender.Draw.Create(1)
455 bWrap = Blender.Draw.Create(1)
456 bAllFaces = Blender.Draw.Create(1)
457 bEdit = Blender.Draw.Create(0)
458 bEditPath = Blender.Draw.Create("")
459 bSVG = Blender.Draw.Create(0)
460 bSVGFill = Blender.Draw.Create(1)
461
462 # Import saved configurations
463 ImportConfig()
464
465
466 Block = []
467
468 Block.append(("Size: ", bSize, 64, 16384, "Size of the exported image"))
469 Block.append(("Wire: ", bWSize, 1, 9, "Size of the wire of the faces"))
470 Block.append(("Wrap", bWrap, "Wrap to image size, scale otherwise"))
471 Block.append(("All Faces", bAllFaces, "Export all or only selected faces"))
472 Block.append(("Object", bObFile, "Use object name in filename"))
473 Block.append(("SVG", bSVG, "save as *.svg instead of *.tga"))
474 Block.append(("Fill SVG faces", bSVGFill, "SVG faces will be filled, none filled otherwise"))
475
476 if FullPython:
477         Block.append(("Edit", bEdit, "Edit resulting file in an external program"))
478         Block.append(("Editor: ", bEditPath, 0, 399, "Path to external editor (leave blank to select a new one)"))
479
480 retval = Blender.Draw.PupBlock("UV Image Export", Block)
481
482 if retval:
483         ExportConfig()
484                 
485         if bEdit.val and not bEditPath.val:
486                 SetEditorAndExport()
487         else:
488                 Export()