Integrated Freestyle to rendering pipeline
[blender.git] / release / scripts / lightwave_export.py
1 #!BPY
2
3 """
4 Name: 'LightWave (.lwo)...'
5 Blender: 243
6 Group: 'Export'
7 Tooltip: 'Export selected meshes to LightWave File Format (.lwo)'
8 """
9
10 __author__ = "Anthony D'Agostino (Scorpius)"
11 __url__ = ("blender", "blenderartists.org",
12 "Author's homepage, http://www.redrival.com/scorpius")
13 __version__ = "Part of IOSuite 0.5"
14
15 __bpydoc__ = """\
16 This script exports meshes to LightWave file format.
17
18 LightWave is a full-featured commercial modeling and rendering
19 application. The lwo file format is composed of 'chunks,' is well
20 defined, and easy to read and write. It is similar in structure to the
21 trueSpace cob format.
22
23 Usage:<br>
24         Select meshes to be exported and run this script from "File->Export" menu.
25
26 Supported:<br>
27         UV Coordinates, Meshes, Materials, Material Indices, Specular
28 Highlights, and Vertex Colors. For added functionality, each object is
29 placed on its own layer. Someone added the CLIP chunk and imagename support.
30
31 Missing:<br>
32         Not too much, I hope! :).
33
34 Known issues:<br>
35         Empty objects crash has been fixed.
36
37 Notes:<br>
38         For compatibility reasons, it also reads lwo files in the old LW
39 v5.5 format.
40 """
41
42 # $Id$
43 #
44 # +---------------------------------------------------------+
45 # | Copyright (c) 2002 Anthony D'Agostino                   |
46 # | http://www.redrival.com/scorpius                        |
47 # | scorpius@netzero.com                                    |
48 # | April 21, 2002                                          |
49 # | Read and write LightWave Object File Format (*.lwo)     |
50 # +---------------------------------------------------------+
51
52 # ***** BEGIN GPL LICENSE BLOCK *****
53 #
54 # This program is free software; you can redistribute it and/or
55 # modify it under the terms of the GNU General Public License
56 # as published by the Free Software Foundation; either version 2
57 # of the License, or (at your option) any later version.
58 #
59 # This program is distributed in the hope that it will be useful,
60 # but WITHOUT ANY WARRANTY; without even the implied warranty of
61 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
62 # GNU General Public License for more details.
63 #
64 # You should have received a copy of the GNU General Public License
65 # along with this program; if not, write to the Free Software Foundation,
66 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
67 #
68 # ***** END GPL LICENCE BLOCK *****
69
70 import Blender
71 import BPyMesh
72 try: import struct
73 except: struct = None
74 try: import cStringIO
75 except: cStringIO = None
76 try: import operator
77 except: operator = None
78
79 VCOL_NAME = "\251 Per-Face Vertex Colors"
80 DEFAULT_NAME = "\251 Blender Default"
81 # ==============================
82 # === Write LightWave Format ===
83 # ==============================
84 def write(filename):
85         start = Blender.sys.time()
86         file = open(filename, "wb")
87         
88         scn = Blender.Scene.GetCurrent()
89         objects = list(scn.objects.context)
90         
91         if not objects:
92                 Blender.Draw.PupMenu('Error%t|No Objects selected')
93                 return
94         
95         try:    objects.sort( key = lambda a: a.name )
96         except: objects.sort(lambda a,b: cmp(a.name, b.name))
97
98         text = generate_text()
99         desc = generate_desc()
100         icon = "" #generate_icon()
101
102         meshes = []
103         mesh_object_name_lookup = {} # for name lookups only
104         
105         for obj in objects:
106                 mesh = BPyMesh.getMeshFromObject(obj, None, True, False, scn)
107                 if mesh:
108                         mesh.transform(obj.matrixWorld)
109                         meshes.append(mesh)
110                         mesh_object_name_lookup[mesh] = obj.name
111         del obj
112         
113         material_names = get_used_material_names(meshes)
114         tags = generate_tags(material_names)
115         surfs = generate_surfs(material_names)
116         chunks = [text, desc, icon, tags]
117
118         meshdata = cStringIO.StringIO()
119         
120         layer_index = 0
121         
122         for mesh in meshes:
123                 layr = generate_layr(mesh_object_name_lookup[mesh], layer_index)
124                 pnts = generate_pnts(mesh)
125                 bbox = generate_bbox(mesh)
126                 pols = generate_pols(mesh)
127                 ptag = generate_ptag(mesh, material_names)
128                 clip = generate_clip(mesh, material_names)
129
130                 if mesh.faceUV:
131                         vmad_uv = generate_vmad_uv(mesh)  # per face
132
133                 if mesh.vertexColors:
134                         #if meshtools.average_vcols:
135                         #       vmap_vc = generate_vmap_vc(mesh)  # per vert
136                         #else:
137                         vmad_vc = generate_vmad_vc(mesh)  # per face
138
139                 write_chunk(meshdata, "LAYR", layr); chunks.append(layr)
140                 write_chunk(meshdata, "PNTS", pnts); chunks.append(pnts)
141                 write_chunk(meshdata, "BBOX", bbox); chunks.append(bbox)
142                 write_chunk(meshdata, "POLS", pols); chunks.append(pols)
143                 write_chunk(meshdata, "PTAG", ptag); chunks.append(ptag)
144
145                 if mesh.vertexColors:
146                         #if meshtools.average_vcols:
147                         #       write_chunk(meshdata, "VMAP", vmap_vc)
148                         #       chunks.append(vmap_vc)
149                         #else:
150                         write_chunk(meshdata, "VMAD", vmad_vc)
151                         chunks.append(vmad_vc)
152
153                 if mesh.faceUV:
154                         write_chunk(meshdata, "VMAD", vmad_uv)
155                         chunks.append(vmad_uv)
156                         write_chunk(meshdata, "CLIP", clip)
157                         chunks.append(clip)
158                 
159                 layer_index += 1
160                 mesh.verts = None # save some ram
161         
162         del mesh_object_name_lookup
163         
164         for surf in surfs:
165                 chunks.append(surf)
166
167         write_header(file, chunks)
168         write_chunk(file, "ICON", icon)
169         write_chunk(file, "TEXT", text)
170         write_chunk(file, "DESC", desc)
171         write_chunk(file, "TAGS", tags)
172         file.write(meshdata.getvalue()); meshdata.close()
173         for surf in surfs:
174                 write_chunk(file, "SURF", surf)
175         write_chunk(file, "DATE", "August 19, 2005")
176
177         Blender.Window.DrawProgressBar(1.0, "")    # clear progressbar
178         file.close()
179         print '\a\r',
180         print "Successfully exported %s in %.3f seconds" % (filename.split('\\')[-1].split('/')[-1], Blender.sys.time() - start)
181         
182
183 # =======================================
184 # === Generate Null-Terminated String ===
185 # =======================================
186 def generate_nstring(string):
187         if len(string)%2 == 0:  # even
188                 string += "\0\0"
189         else:                                   # odd
190                 string += "\0"
191         return string
192
193 # ===============================
194 # === Get Used Material Names ===
195 # ===============================
196 def get_used_material_names(meshes):
197         matnames = {}
198         for mesh in meshes:
199                 if (not mesh.materials) and mesh.vertexColors:
200                         # vcols only
201                         matnames[VCOL_NAME] = None
202                         
203                 elif mesh.materials and (not mesh.vertexColors):
204                         # materials only
205                         for material in mesh.materials:
206                                 if material:
207                                         matnames[material.name] = None
208                 elif (not mesh.materials) and (not mesh.vertexColors):
209                         # neither
210                         matnames[DEFAULT_NAME] = None
211                 else:
212                         # both
213                         for material in mesh.materials:
214                                 if material:
215                                         matnames[material.name] = None
216         return matnames.keys()
217
218 # =========================================
219 # === Generate Tag Strings (TAGS Chunk) ===
220 # =========================================
221 def generate_tags(material_names):
222         if material_names:
223                 material_names = map(generate_nstring, material_names)
224                 tags_data = reduce(operator.add, material_names)
225         else:
226                 tags_data = generate_nstring('');
227         return tags_data
228
229 # ========================
230 # === Generate Surface ===
231 # ========================
232 def generate_surface(name):
233         #if name.find("\251 Per-") == 0:
234         #       return generate_vcol_surf(mesh)
235         if name == DEFAULT_NAME:
236                 return generate_default_surf()
237         else:
238                 return generate_surf(name)
239
240 # ======================
241 # === Generate Surfs ===
242 # ======================
243 def generate_surfs(material_names):
244         return map(generate_surface, material_names)
245
246 # ===================================
247 # === Generate Layer (LAYR Chunk) ===
248 # ===================================
249 def generate_layr(name, idx):
250         data = cStringIO.StringIO()
251         data.write(struct.pack(">h", idx))          # layer number
252         data.write(struct.pack(">h", 0))            # flags
253         data.write(struct.pack(">fff", 0, 0, 0))    # pivot
254         data.write(generate_nstring(name))                      # name
255         return data.getvalue()
256
257 # ===================================
258 # === Generate Verts (PNTS Chunk) ===
259 # ===================================
260 def generate_pnts(mesh):
261         data = cStringIO.StringIO()
262         for i, v in enumerate(mesh.verts):
263                 if not i%100:
264                         Blender.Window.DrawProgressBar(float(i)/len(mesh.verts), "Writing Verts")
265                 x, y, z = v.co
266                 data.write(struct.pack(">fff", x, z, y))
267         return data.getvalue()
268
269 # ==========================================
270 # === Generate Bounding Box (BBOX Chunk) ===
271 # ==========================================
272 def generate_bbox(mesh):
273         data = cStringIO.StringIO()
274         # need to transform verts here
275         if mesh.verts:
276                 nv = [v.co for v in mesh.verts]
277                 xx = [ co[0] for co in nv ]
278                 yy = [ co[1] for co in nv ]
279                 zz = [ co[2] for co in nv ]
280         else:
281                 xx = yy = zz = [0.0,]
282         
283         data.write(struct.pack(">6f", min(xx), min(zz), min(yy), max(xx), max(zz), max(yy)))
284         return data.getvalue()
285
286 # ========================================
287 # === Average All Vertex Colors (Fast) ===
288 # ========================================
289 '''
290 def average_vertexcolors(mesh):
291         vertexcolors = {}
292         vcolor_add = lambda u, v: [u[0]+v[0], u[1]+v[1], u[2]+v[2], u[3]+v[3]]
293         vcolor_div = lambda u, s: [u[0]/s, u[1]/s, u[2]/s, u[3]/s]
294         for i, f in enumerate(mesh.faces):      # get all vcolors that share this vertex
295                 if not i%100:
296                         Blender.Window.DrawProgressBar(float(i)/len(mesh.verts), "Finding Shared VColors")
297                 col = f.col
298                 for j in xrange(len(f)):
299                         index = f[j].index
300                         color = col[j]
301                         r,g,b = color.r, color.g, color.b
302                         vertexcolors.setdefault(index, []).append([r,g,b,255])
303         i = 0
304         for index, value in vertexcolors.iteritems():   # average them
305                 if not i%100:
306                         Blender.Window.DrawProgressBar(float(i)/len(mesh.verts), "Averaging Vertex Colors")
307                 vcolor = [0,0,0,0]      # rgba
308                 for v in value:
309                         vcolor = vcolor_add(vcolor, v)
310                 shared = len(value)
311                 value[:] = vcolor_div(vcolor, shared)
312                 i+=1
313         return vertexcolors
314 '''
315
316 # ====================================================
317 # === Generate Per-Vert Vertex Colors (VMAP Chunk) ===
318 # ====================================================
319 # Blender now has all vcols per face
320 """
321 def generate_vmap_vc(mesh):
322         data = cStringIO.StringIO()
323         data.write("RGB ")                                      # type
324         data.write(struct.pack(">H", 3))                        # dimension
325         data.write(generate_nstring("Blender's Vertex Colors")) # name
326         vertexcolors = average_vertexcolors(mesh)
327         for i in xrange(len(vertexcolors)):
328                 try:    r, g, b, a = vertexcolors[i] # has a face user
329                 except: r, g, b, a = 255,255,255,255
330                 data.write(struct.pack(">H", i)) # vertex index
331                 data.write(struct.pack(">fff", r/255.0, g/255.0, b/255.0))
332         return data.getvalue()
333 """
334
335 # ====================================================
336 # === Generate Per-Face Vertex Colors (VMAD Chunk) ===
337 # ====================================================
338 def generate_vmad_vc(mesh):
339         data = cStringIO.StringIO()
340         data.write("RGB ")                                      # type
341         data.write(struct.pack(">H", 3))                        # dimension
342         data.write(generate_nstring("Blender's Vertex Colors")) # name
343         for i, f in enumerate(mesh.faces):
344                 if not i%100:
345                         Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing Vertex Colors")
346                 col = f.col
347                 f_v = f.v
348                 for j in xrange(len(f)-1, -1, -1):                      # Reverse order
349                         r,g,b, dummy = tuple(col[j])
350                         data.write(struct.pack(">H", f_v[j].index)) # vertex index
351                         data.write(struct.pack(">H", i)) # face index
352                         data.write(struct.pack(">fff", r/255.0, g/255.0, b/255.0))
353         return data.getvalue()
354
355 # ================================================
356 # === Generate Per-Face UV Coords (VMAD Chunk) ===
357 # ================================================
358 def generate_vmad_uv(mesh):
359         layers = mesh.getUVLayerNames()
360         org_uv = mesh.activeUVLayer
361         for l in layers:
362                 mesh.activeUVLayer = l
363                 data = cStringIO.StringIO()
364                 data.write("TXUV")                                       # type
365                 data.write(struct.pack(">H", 2))                         # dimension
366                 data.write(generate_nstring(l)) # name
367                 for i, f in enumerate(mesh.faces):
368                         if not i%100:
369                                 Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing UV Coordinates")
370                         
371                         uv = f.uv
372                         f_v = f.v
373                         for j in xrange(len(f)-1, -1, -1):             # Reverse order
374                                 U,V = uv[j]
375                                 v = f_v[j].index
376                                 data.write(struct.pack(">H", v)) # vertex index
377                                 data.write(struct.pack(">H", i)) # face index
378                                 data.write(struct.pack(">ff", U, V))
379         
380         mesh.activeUVLayer = org_uv
381         return data.getvalue()
382
383 # ======================================
384 # === Generate Variable-Length Index ===
385 # ======================================
386 def generate_vx(index):
387         if index < 0xFF00:
388                 value = struct.pack(">H", index)                 # 2-byte index
389         else:
390                 value = struct.pack(">L", index | 0xFF000000)    # 4-byte index
391         return value
392
393 # ===================================
394 # === Generate Faces (POLS Chunk) ===
395 # ===================================
396 def generate_pols(mesh):
397         data = cStringIO.StringIO()
398         data.write("FACE")  # polygon type
399         for i,f in enumerate(mesh.faces):
400                 if not i%100:
401                         Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing Faces")
402                 data.write(struct.pack(">H", len(f))) # numfaceverts
403                 numfaceverts = len(f)
404                 f_v = f.v
405                 for j in xrange(numfaceverts-1, -1, -1):                        # Reverse order
406                         data.write(generate_vx(f_v[j].index))
407         return data.getvalue()
408
409 # =================================================
410 # === Generate Polygon Tag Mapping (PTAG Chunk) ===
411 # =================================================
412 def generate_ptag(mesh, material_names):
413         
414         def surf_indicies(mat):
415                 try:
416                         if mat:
417                                 return material_names.index(mat.name)
418                 except:
419                         pass
420                 
421                 return 0
422                 
423         
424         data = cStringIO.StringIO()
425         data.write("SURF")  # polygon tag type
426         mesh_materials = mesh.materials
427         mesh_surfindicies = [surf_indicies(mat) for mat in mesh_materials]
428         
429         try:    VCOL_NAME_SURF_INDEX = material_names.index(VCOL_NAME)
430         except: VCOL_NAME_SURF_INDEX = 0
431         
432         try:    DEFAULT_NAME_SURF_INDEX = material_names.index(DEFAULT_NAME)
433         except: DEFAULT_NAME_SURF_INDEX = 0
434         len_mat = len(mesh_materials)
435         for i, f in enumerate(mesh.faces): # numfaces
436                 f_mat = f.mat
437                 if f_mat >= len_mat: f_mat = 0 # Rare annoying eror
438                         
439                 
440                 if not i%100:
441                         Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing Surface Indices")
442                 
443                 data.write(generate_vx(i))
444                 if (not mesh_materials) and mesh.vertexColors:          # vcols only
445                         surfidx = VCOL_NAME_SURF_INDEX
446                 elif mesh_materials and not mesh.vertexColors:          # materials only
447                         surfidx = mesh_surfindicies[f_mat]
448                 elif (not mesh_materials) and (not mesh.vertexColors):  # neither
449                         surfidx = DEFAULT_NAME_SURF_INDEX
450                 else:                                                                                           # both
451                         surfidx = mesh_surfindicies[f_mat]
452                 
453                 data.write(struct.pack(">H", surfidx)) # surface index
454         return data.getvalue()
455
456 # ===================================================
457 # === Generate VC Surface Definition (SURF Chunk) ===
458 # ===================================================
459 def generate_vcol_surf(mesh):
460         data = cStringIO.StringIO()
461         if mesh.vertexColors:
462                 surface_name = generate_nstring(VCOL_NAME)
463         data.write(surface_name)
464         data.write("\0\0")
465
466         data.write("COLR")
467         data.write(struct.pack(">H", 14))
468         data.write(struct.pack(">fffH", 1, 1, 1, 0))
469
470         data.write("DIFF")
471         data.write(struct.pack(">H", 6))
472         data.write(struct.pack(">fH", 0.0, 0))
473
474         data.write("LUMI")
475         data.write(struct.pack(">H", 6))
476         data.write(struct.pack(">fH", 1.0, 0))
477
478         data.write("VCOL")
479         data.write(struct.pack(">H", 34))
480         data.write(struct.pack(">fH4s", 1.0, 0, "RGB "))  # intensity, envelope, type
481         data.write(generate_nstring("Blender's Vertex Colors")) # name
482
483         data.write("CMNT")  # material comment
484         comment = "Vertex Colors: Exported from Blender\256 243"
485         comment = generate_nstring(comment)
486         data.write(struct.pack(">H", len(comment)))
487         data.write(comment)
488         return data.getvalue()
489
490 # ================================================
491 # === Generate Surface Definition (SURF Chunk) ===
492 # ================================================
493 def generate_surf(material_name):
494         data = cStringIO.StringIO()
495         data.write(generate_nstring(material_name))
496         data.write("\0\0")
497         
498         try:
499                 material = Blender.Material.Get(material_name)
500                 R,G,B = material.R, material.G, material.B
501                 ref = material.ref
502                 emit = material.emit
503                 spec = material.spec
504                 hard = material.hard
505                 
506         except:
507                 material = None
508                 
509                 R=G=B = 1.0
510                 ref = 1.0
511                 emit = 0.0
512                 spec = 0.2
513                 hard = 0.0
514         
515                 
516         data.write("COLR")
517         data.write(struct.pack(">H", 14))
518         data.write(struct.pack(">fffH", R, G, B, 0))
519
520         data.write("DIFF")
521         data.write(struct.pack(">H", 6))
522         data.write(struct.pack(">fH", ref, 0))
523
524         data.write("LUMI")
525         data.write(struct.pack(">H", 6))
526         data.write(struct.pack(">fH", emit, 0))
527
528         data.write("SPEC")
529         data.write(struct.pack(">H", 6))
530         data.write(struct.pack(">fH", spec, 0))
531
532         data.write("GLOS")
533         data.write(struct.pack(">H", 6))
534         gloss = hard / (255/2.0)
535         gloss = round(gloss, 1)
536         data.write(struct.pack(">fH", gloss, 0))
537
538         data.write("CMNT")  # material comment
539         comment = material_name + ": Exported from Blender\256 243"
540         comment = generate_nstring(comment)
541         data.write(struct.pack(">H", len(comment)))
542         data.write(comment)
543         
544         # Check if the material contains any image maps
545         if material:
546                 mtextures = material.getTextures()                                                                      # Get a list of textures linked to the material
547                 for mtex in mtextures:
548                         if (mtex) and (mtex.tex.type == Blender.Texture.Types.IMAGE):   # Check if the texture is of type "IMAGE"
549                                 data.write("BLOK")                  # Surface BLOK header
550                                 data.write(struct.pack(">H", 104))  # Hardcoded and ugly! Will only handle 1 image per material
551
552                                 # IMAP subchunk (image map sub header)
553                                 data.write("IMAP")                  
554                                 data_tmp = cStringIO.StringIO()
555                                 data_tmp.write(struct.pack(">H", 0))  # Hardcoded - not sure what it represents
556                                 data_tmp.write("CHAN")
557                                 data_tmp.write(struct.pack(">H", 4))
558                                 data_tmp.write("COLR")
559                                 data_tmp.write("OPAC")                # Hardcoded texture layer opacity
560                                 data_tmp.write(struct.pack(">H", 8))
561                                 data_tmp.write(struct.pack(">H", 0))
562                                 data_tmp.write(struct.pack(">f", 1.0))
563                                 data_tmp.write(struct.pack(">H", 0))
564                                 data_tmp.write("ENAB")
565                                 data_tmp.write(struct.pack(">HH", 2, 1))  # 1 = texture layer enabled
566                                 data_tmp.write("NEGA")
567                                 data_tmp.write(struct.pack(">HH", 2, 0))  # Disable negative image (1 = invert RGB values)
568                                 data_tmp.write("AXIS")
569                                 data_tmp.write(struct.pack(">HH", 2, 1))
570                                 data.write(struct.pack(">H", len(data_tmp.getvalue())))
571                                 data.write(data_tmp.getvalue())
572
573                                 # IMAG subchunk
574                                 data.write("IMAG")
575                                 data.write(struct.pack(">HH", 2, 1))
576                                 data.write("PROJ")
577                                 data.write(struct.pack(">HH", 2, 5)) # UV projection
578
579                                 data.write("VMAP")
580                                 uvname = generate_nstring("Blender's UV Coordinates")
581                                 data.write(struct.pack(">H", len(uvname)))
582                                 data.write(uvname)
583
584         return data.getvalue()
585
586 # =============================================
587 # === Generate Default Surface (SURF Chunk) ===
588 # =============================================
589 def generate_default_surf():
590         data = cStringIO.StringIO()
591         material_name = DEFAULT_NAME
592         data.write(generate_nstring(material_name))
593         data.write("\0\0")
594
595         data.write("COLR")
596         data.write(struct.pack(">H", 14))
597         data.write(struct.pack(">fffH", 1, 1, 1, 0))
598
599         data.write("DIFF")
600         data.write(struct.pack(">H", 6))
601         data.write(struct.pack(">fH", 0.8, 0))
602
603         data.write("LUMI")
604         data.write(struct.pack(">H", 6))
605         data.write(struct.pack(">fH", 0, 0))
606
607         data.write("SPEC")
608         data.write(struct.pack(">H", 6))
609         data.write(struct.pack(">fH", 0.5, 0))
610
611         data.write("GLOS")
612         data.write(struct.pack(">H", 6))
613         gloss = 50 / (255/2.0)
614         gloss = round(gloss, 1)
615         data.write(struct.pack(">fH", gloss, 0))
616
617         data.write("CMNT")  # material comment
618         comment = material_name + ": Exported from Blender\256 243"
619
620         # vals = map(chr, xrange(164,255,1))
621         # keys = xrange(164,255,1)
622         # keys = map(lambda x: `x`, keys)
623         # comment = map(None, keys, vals)
624         # comment = reduce(operator.add, comment)
625         # comment = reduce(operator.add, comment)
626
627         comment = generate_nstring(comment)
628         data.write(struct.pack(">H", len(comment)))
629         data.write(comment)
630         return data.getvalue()
631
632 # ============================================
633 # === Generate Object Comment (TEXT Chunk) ===
634 # ============================================
635 def generate_text():
636         comment  = "Lightwave Export Script for Blender by Anthony D'Agostino"
637         return generate_nstring(comment)
638
639 # ==============================================
640 # === Generate Description Line (DESC Chunk) ===
641 # ==============================================
642 def generate_desc():
643         comment = "Copyright 2002 Scorpius Entertainment"
644         return generate_nstring(comment)
645
646 # ==================================================
647 # === Generate Thumbnail Icon Image (ICON Chunk) ===
648 # ==================================================
649 def generate_icon():
650         data = cStringIO.StringIO()
651         file = open("f:/obj/radiosity/lwo2_icon.tga", "rb") # 60x60 uncompressed TGA
652         file.read(18)
653         icon_data = file.read(3600) # ?
654         file.close()
655         data.write(struct.pack(">HH", 0, 60))
656         data.write(icon_data)
657         #print len(icon_data)
658         return data.getvalue()
659
660 # ===============================================
661 # === Generate CLIP chunk with STIL subchunks ===
662 # ===============================================
663 def generate_clip(mesh, material_names):
664         data = cStringIO.StringIO()
665         clipid = 1
666         for i, material in enumerate(mesh.materials):                                                                   # Run through list of materials used by mesh
667                 if material:
668                         mtextures = material.getTextures()                                                                      # Get a list of textures linked to the material
669                         for mtex in mtextures:
670                                 if (mtex) and (mtex.tex.type == Blender.Texture.Types.IMAGE):   # Check if the texture is of type "IMAGE"
671                                         pathname = mtex.tex.image.filename                                                      # If full path is needed use filename in place of name
672                                         pathname = pathname[0:2] + pathname.replace("\\", "/")[3:]  # Convert to Modo standard path
673                                         imagename = generate_nstring(pathname)
674                                         data.write(struct.pack(">L", clipid))                       # CLIP sequence/id
675                                         data.write("STIL")                                          # STIL image
676                                         data.write(struct.pack(">H", len(imagename)))               # Size of image name
677                                         data.write(imagename)
678                                         clipid += 1
679         return data.getvalue()
680
681 # ===================
682 # === Write Chunk ===
683 # ===================
684 def write_chunk(file, name, data):
685         file.write(name)
686         file.write(struct.pack(">L", len(data)))
687         file.write(data)
688
689 # =============================
690 # === Write LWO File Header ===
691 # =============================
692 def write_header(file, chunks):
693         chunk_sizes = map(len, chunks)
694         chunk_sizes = reduce(operator.add, chunk_sizes)
695         form_size = chunk_sizes + len(chunks)*8 + len("FORM")
696         file.write("FORM")
697         file.write(struct.pack(">L", form_size))
698         file.write("LWO2")
699
700 def fs_callback(filename):
701         if not filename.lower().endswith('.lwo'): filename += '.lwo'
702         write(filename)
703
704 if struct and cStringIO and operator:
705     Blender.Window.FileSelector(fs_callback, "Export LWO", Blender.sys.makename(ext='.lwo'))
706 else:
707     Blender.Draw.PupMenu("Error%t|This script requires a full python installation")