Final merge of HEAD (bf-blender) into the orange branch.
[blender.git] / release / scripts / lightwave_export.py
1 #!BPY
2
3 """
4 Name: 'LightWave (.lwo)...'
5 Blender: 232
6 Group: 'Export'
7 Tooltip: 'Export selected meshes to LightWave File Format (.lwo)'
8 """
9
10 __author__ = "Anthony D'Agostino (Scorpius)"
11 __url__ = ("blender", "elysiun",
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 # | Released under the Blender Artistic Licence (BAL)       |
50 # | Import Export Suite v0.5                                |
51 # +---------------------------------------------------------+
52 # | Read and write LightWave Object File Format (*.lwo)     |
53 # +---------------------------------------------------------+
54
55 import Blender, meshtools
56 import struct, chunk, os, cStringIO, time, operator
57
58 # ==============================
59 # === Write LightWave Format ===
60 # ==============================
61 def write(filename):
62         start = time.clock()
63         file = open(filename, "wb")
64
65         objects = Blender.Object.GetSelected()
66         objects.sort(lambda a,b: cmp(a.name, b.name))
67         if not objects:
68                 meshtools.print_boxed("No mesh objects are selected.")
69                 return
70
71         if len(objects) > 20 and meshtools.show_progress:
72                 meshtools.show_progress = 0
73
74         text = generate_text()
75         desc = generate_desc()
76         icon = "" #generate_icon()
77
78         material_names = get_used_material_names(objects)
79         tags = generate_tags(material_names)
80         surfs = generate_surfs(material_names)
81         chunks = [text, desc, icon, tags]
82
83         meshdata = cStringIO.StringIO()
84         layer_index = 0
85         for object in objects:
86                 objname = object.name
87                 meshname = object.data.name
88                 mesh = Blender.NMesh.GetRaw(meshname)
89                 #mesh = Blender.NMesh.GetRawFromObject(meshname)        # for SubSurf
90                 obj = Blender.Object.Get(objname)
91                 if not mesh: continue
92
93                 layr = generate_layr(objname, layer_index)
94                 pnts = generate_pnts(mesh, obj.matrix)
95                 bbox = generate_bbox(mesh)
96                 pols = generate_pols(mesh)
97                 ptag = generate_ptag(mesh, material_names)
98                 clip = generate_clip(mesh, material_names)
99
100                 if mesh.hasFaceUV():
101                         vmad_uv = generate_vmad_uv(mesh)  # per face
102
103                 if meshtools.has_vertex_colors(mesh):
104                         if meshtools.average_vcols:
105                                 vmap_vc = generate_vmap_vc(mesh)  # per vert
106                         else:
107                                 vmad_vc = generate_vmad_vc(mesh)  # per face
108
109                 write_chunk(meshdata, "LAYR", layr); chunks.append(layr)
110                 write_chunk(meshdata, "PNTS", pnts); chunks.append(pnts)
111                 write_chunk(meshdata, "BBOX", bbox); chunks.append(bbox)
112                 write_chunk(meshdata, "POLS", pols); chunks.append(pols)
113                 write_chunk(meshdata, "PTAG", ptag); chunks.append(ptag)
114
115                 if meshtools.has_vertex_colors(mesh):
116                         if meshtools.average_vcols:
117                                 write_chunk(meshdata, "VMAP", vmap_vc)
118                                 chunks.append(vmap_vc)
119                         else:
120                                 write_chunk(meshdata, "VMAD", vmad_vc)
121                                 chunks.append(vmad_vc)
122
123                 if mesh.hasFaceUV():
124                         write_chunk(meshdata, "VMAD", vmad_uv)
125                         chunks.append(vmad_uv)
126                         write_chunk(meshdata, "CLIP", clip)
127                         chunks.append(clip)
128                 
129                 layer_index += 1
130
131         for surf in surfs:
132                 chunks.append(surf)
133
134         write_header(file, chunks)
135         write_chunk(file, "ICON", icon)
136         write_chunk(file, "TEXT", text)
137         write_chunk(file, "DESC", desc)
138         write_chunk(file, "TAGS", tags)
139         file.write(meshdata.getvalue()); meshdata.close()
140         for surf in surfs:
141                 write_chunk(file, "SURF", surf)
142         write_chunk(file, "DATE", "August 19, 2005")
143
144         Blender.Window.DrawProgressBar(1.0, "")    # clear progressbar
145         file.close()
146         print '\a\r',
147         end = time.clock()
148         seconds = " in %.2f %s" % (end-start, "seconds")
149         message = "Successfully exported " + os.path.basename(filename) + seconds
150         meshtools.print_boxed(message)
151
152 # =======================================
153 # === Generate Null-Terminated String ===
154 # =======================================
155 def generate_nstring(string):
156         if len(string)%2 == 0:  # even
157                 string += "\0\0"
158         else:                                   # odd
159                 string += "\0"
160         return string
161
162 # ===============================
163 # === Get Used Material Names ===
164 # ===============================
165 def get_used_material_names(objects):
166         matnames = {}
167         for object in objects:
168                 objname = object.name
169                 meshname = object.data.name
170                 mesh = Blender.NMesh.GetRaw(meshname)
171                 if not mesh: continue
172                 if (not mesh.materials) and (meshtools.has_vertex_colors(mesh)):
173                         # vcols only
174                         if meshtools.average_vcols:
175                                 matnames["\251 Per-Vert Vertex Colors"] = None
176                         else:
177                                 matnames["\251 Per-Face Vertex Colors"] = None
178                 elif (mesh.materials) and (not meshtools.has_vertex_colors(mesh)):
179                         # materials only
180                         for material in mesh.materials:
181                                 matnames[material.name] = None
182                 elif (not mesh.materials) and (not meshtools.has_vertex_colors(mesh)):
183                         # neither
184                         matnames["\251 Blender Default"] = None
185                 else:
186                         # both
187                         for material in mesh.materials:
188                                 matnames[material.name] = None
189         return matnames
190
191 # =========================================
192 # === Generate Tag Strings (TAGS Chunk) ===
193 # =========================================
194 def generate_tags(material_names):
195         material_names = map(generate_nstring, material_names.keys())
196         tags_data = reduce(operator.add, material_names)
197         return tags_data
198
199 # ========================
200 # === Generate Surface ===
201 # ========================
202 def generate_surface(name, mesh):
203         if name.find("\251 Per-") == 0:
204                 return generate_vcol_surf(mesh)
205         elif name == "\251 Blender Default":
206                 return generate_default_surf()
207         else:
208                 return generate_surf(name)
209
210 # ======================
211 # === Generate Surfs ===
212 # ======================
213 def generate_surfs(material_names):
214         keys = material_names.keys()
215         values = material_names.values()
216         surfaces = map(generate_surface, keys, values)
217         return surfaces
218
219 # ===================================
220 # === Generate Layer (LAYR Chunk) ===
221 # ===================================
222 def generate_layr(name, idx):
223         data = cStringIO.StringIO()
224         data.write(struct.pack(">h", idx))          # layer number
225         data.write(struct.pack(">h", 0))            # flags
226         data.write(struct.pack(">fff", 0, 0, 0))    # pivot
227         data.write(generate_nstring(name))                      # name
228         return data.getvalue()
229
230 # ===================================
231 # === Generate Verts (PNTS Chunk) ===
232 # ===================================
233 def generate_pnts(mesh, matrix):
234         data = cStringIO.StringIO()
235         for i in range(len(mesh.verts)):
236                 if not i%100 and meshtools.show_progress:
237                         Blender.Window.DrawProgressBar(float(i)/len(mesh.verts), "Writing Verts")
238                 x, y, z = meshtools.apply_transform(mesh.verts[i].co, matrix)
239                 data.write(struct.pack(">fff", x, z, y))
240         return data.getvalue()
241
242 # ==========================================
243 # === Generate Bounding Box (BBOX Chunk) ===
244 # ==========================================
245 def generate_bbox(mesh):
246         data = cStringIO.StringIO()
247         # need to transform verts here
248         nv = map(getattr, mesh.verts, ["co"]*len(mesh.verts))
249         xx = map(operator.getitem, nv, [0]*len(nv))
250         yy = map(operator.getitem, nv, [1]*len(nv))
251         zz = map(operator.getitem, nv, [2]*len(nv))
252         data.write(struct.pack(">6f", min(xx), min(zz), min(yy), max(xx), max(zz), max(yy)))
253         return data.getvalue()
254
255 # ========================================
256 # === Average All Vertex Colors (Fast) ===
257 # ========================================
258 def average_vertexcolors(mesh):
259         vertexcolors = {}
260         vcolor_add = lambda u, v: [u[0]+v[0], u[1]+v[1], u[2]+v[2], u[3]+v[3]]
261         vcolor_div = lambda u, s: [u[0]/s, u[1]/s, u[2]/s, u[3]/s]
262         for i in range(len(mesh.faces)):        # get all vcolors that share this vertex
263                 if not i%100 and meshtools.show_progress:
264                         Blender.Window.DrawProgressBar(float(i)/len(mesh.verts), "Finding Shared VColors")
265                 for j in range(len(mesh.faces[i].v)):
266                         index = mesh.faces[i].v[j].index
267                         color = mesh.faces[i].col[j]
268                         r,g,b,a = color.r, color.g, color.b, color.a
269                         vertexcolors.setdefault(index, []).append([r,g,b,a])
270         for i in range(len(vertexcolors)):      # average them
271                 if not i%100 and meshtools.show_progress:
272                         Blender.Window.DrawProgressBar(float(i)/len(mesh.verts), "Averaging Vertex Colors")
273                 vcolor = [0,0,0,0]      # rgba
274                 for j in range(len(vertexcolors[i])):
275                         vcolor = vcolor_add(vcolor, vertexcolors[i][j])
276                 shared = len(vertexcolors[i])
277                 vertexcolors[i] = vcolor_div(vcolor, shared)
278         return vertexcolors
279
280 # ====================================================
281 # === Generate Per-Vert Vertex Colors (VMAP Chunk) ===
282 # ====================================================
283 def generate_vmap_vc(mesh):
284         data = cStringIO.StringIO()
285         data.write("RGB ")                                      # type
286         data.write(struct.pack(">H", 3))                        # dimension
287         data.write(generate_nstring("Blender's Vertex Colors")) # name
288         vertexcolors = average_vertexcolors(mesh)
289         for i in range(len(vertexcolors)):
290                 r, g, b, a = vertexcolors[i]
291                 data.write(struct.pack(">H", i)) # vertex index
292                 data.write(struct.pack(">fff", r/255.0, g/255.0, b/255.0))
293         return data.getvalue()
294
295 # ====================================================
296 # === Generate Per-Face Vertex Colors (VMAD Chunk) ===
297 # ====================================================
298 def generate_vmad_vc(mesh):
299         data = cStringIO.StringIO()
300         data.write("RGB ")                                      # type
301         data.write(struct.pack(">H", 3))                        # dimension
302         data.write(generate_nstring("Blender's Vertex Colors")) # name
303         for i in range(len(mesh.faces)):
304                 if not i%100 and meshtools.show_progress:
305                         Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing Vertex Colors")
306                 numfaceverts = len(mesh.faces[i].v)
307                 for j in range(numfaceverts-1, -1, -1):                         # Reverse order
308                         r = mesh.faces[i].col[j].r
309                         g = mesh.faces[i].col[j].g
310                         b = mesh.faces[i].col[j].b
311                         v = mesh.faces[i].v[j].index
312                         data.write(struct.pack(">H", v)) # vertex index
313                         data.write(struct.pack(">H", i)) # face index
314                         data.write(struct.pack(">fff", r/255.0, g/255.0, b/255.0))
315         return data.getvalue()
316
317 # ================================================
318 # === Generate Per-Face UV Coords (VMAD Chunk) ===
319 # ================================================
320 def generate_vmad_uv(mesh):
321         data = cStringIO.StringIO()
322         data.write("TXUV")                                       # type
323         data.write(struct.pack(">H", 2))                         # dimension
324         data.write(generate_nstring("Blender's UV Coordinates")) # name
325         for i in range(len(mesh.faces)):
326                 if not i%100 and meshtools.show_progress:
327                         Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing UV Coordinates")
328                 numfaceverts = len(mesh.faces[i].v)
329                 for j in range(numfaceverts-1, -1, -1):                         # Reverse order
330                         U,V = mesh.faces[i].uv[j]
331                         v = mesh.faces[i].v[j].index
332                         data.write(struct.pack(">H", v)) # vertex index
333                         data.write(struct.pack(">H", i)) # face index
334                         data.write(struct.pack(">ff", U, V))
335         return data.getvalue()
336
337 # ======================================
338 # === Generate Variable-Length Index ===
339 # ======================================
340 def generate_vx(index):
341         if index < 0xFF00:
342                 value = struct.pack(">H", index)                 # 2-byte index
343         else:
344                 value = struct.pack(">L", index | 0xFF000000)    # 4-byte index
345         return value
346
347 # ===================================
348 # === Generate Faces (POLS Chunk) ===
349 # ===================================
350 def generate_pols(mesh):
351         data = cStringIO.StringIO()
352         data.write("FACE")  # polygon type
353         for i in range(len(mesh.faces)):
354                 if not i%100 and meshtools.show_progress:
355                         Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing Faces")
356                 data.write(struct.pack(">H", len(mesh.faces[i].v))) # numfaceverts
357                 numfaceverts = len(mesh.faces[i].v)
358                 for j in range(numfaceverts-1, -1, -1):                         # Reverse order
359                         index = mesh.faces[i].v[j].index
360                         data.write(generate_vx(index))
361         return data.getvalue()
362
363 # =================================================
364 # === Generate Polygon Tag Mapping (PTAG Chunk) ===
365 # =================================================
366 def generate_ptag(mesh, material_names):
367         data = cStringIO.StringIO()
368         data.write("SURF")  # polygon tag type
369         for i in range(len(mesh.faces)): # numfaces
370                 if not i%100 and meshtools.show_progress:
371                         Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing Surface Indices")
372                 data.write(generate_vx(i))
373                 if (not mesh.materials) and (meshtools.has_vertex_colors(mesh)):                # vcols only
374                         if meshtools.average_vcols:
375                                 name = "\251 Per-Vert Vertex Colors"
376                         else:
377                                 name = "\251 Per-Face Vertex Colors"
378                 elif (mesh.materials) and (not meshtools.has_vertex_colors(mesh)):              # materials only
379                         idx = mesh.faces[i].mat #erialIndex
380                         name = mesh.materials[idx].name
381                 elif (not mesh.materials) and (not meshtools.has_vertex_colors(mesh)):  # neither
382                         name = "\251 Blender Default"
383                 else:                                                                                                                                           # both
384                         idx = mesh.faces[i].mat
385                         name = mesh.materials[idx].name
386                 names = material_names.keys()
387                 surfidx = names.index(name)
388                 data.write(struct.pack(">H", surfidx)) # surface index
389         return data.getvalue()
390
391 # ===================================================
392 # === Generate VC Surface Definition (SURF Chunk) ===
393 # ===================================================
394 def generate_vcol_surf(mesh):
395         data = cStringIO.StringIO()
396         if meshtools.average_vcols and meshtools.has_vertex_colors(mesh):
397                 surface_name = generate_nstring("\251 Per-Vert Vertex Colors")
398         else:
399                 surface_name = generate_nstring("\251 Per-Face Vertex Colors")
400         data.write(surface_name)
401         data.write("\0\0")
402
403         data.write("COLR")
404         data.write(struct.pack(">H", 14))
405         data.write(struct.pack(">fffH", 1, 1, 1, 0))
406
407         data.write("DIFF")
408         data.write(struct.pack(">H", 6))
409         data.write(struct.pack(">fH", 0.0, 0))
410
411         data.write("LUMI")
412         data.write(struct.pack(">H", 6))
413         data.write(struct.pack(">fH", 1.0, 0))
414
415         data.write("VCOL")
416         data.write(struct.pack(">H", 34))
417         data.write(struct.pack(">fH4s", 1.0, 0, "RGB "))  # intensity, envelope, type
418         data.write(generate_nstring("Blender's Vertex Colors")) # name
419
420         data.write("CMNT")  # material comment
421         comment = "Vertex Colors: Exported from Blender\256 " + meshtools.blender_version_str
422         comment = generate_nstring(comment)
423         data.write(struct.pack(">H", len(comment)))
424         data.write(comment)
425         return data.getvalue()
426
427 # ================================================
428 # === Generate Surface Definition (SURF Chunk) ===
429 # ================================================
430 def generate_surf(material_name):
431         data = cStringIO.StringIO()
432         data.write(generate_nstring(material_name))
433         data.write("\0\0")
434
435         material = Blender.Material.Get(material_name)
436         R,G,B = material.R, material.G, material.B
437         data.write("COLR")
438         data.write(struct.pack(">H", 14))
439         data.write(struct.pack(">fffH", R, G, B, 0))
440
441         data.write("DIFF")
442         data.write(struct.pack(">H", 6))
443         data.write(struct.pack(">fH", material.ref, 0))
444
445         data.write("LUMI")
446         data.write(struct.pack(">H", 6))
447         data.write(struct.pack(">fH", material.emit, 0))
448
449         data.write("SPEC")
450         data.write(struct.pack(">H", 6))
451         data.write(struct.pack(">fH", material.spec, 0))
452
453         data.write("GLOS")
454         data.write(struct.pack(">H", 6))
455         gloss = material.hard / (255/2.0)
456         gloss = round(gloss, 1)
457         data.write(struct.pack(">fH", gloss, 0))
458
459         data.write("CMNT")  # material comment
460         comment = material_name + ": Exported from Blender\256 " + meshtools.blender_version_str
461         comment = generate_nstring(comment)
462         data.write(struct.pack(">H", len(comment)))
463         data.write(comment)
464
465         # Check if the material contains any image maps
466         mtextures = material.getTextures()                                                                      # Get a list of textures linked to the material
467         for mtex in mtextures:
468                 if (mtex) and (mtex.tex.type == Blender.Texture.Types.IMAGE):   # Check if the texture is of type "IMAGE"
469                         data.write("BLOK")                  # Surface BLOK header
470                         data.write(struct.pack(">H", 104))  # Hardcoded and ugly! Will only handle 1 image per material
471
472                         # IMAP subchunk (image map sub header)
473                         data.write("IMAP")                  
474                         data_tmp = cStringIO.StringIO()
475                         data_tmp.write(struct.pack(">H", 0))  # Hardcoded - not sure what it represents
476                         data_tmp.write("CHAN")
477                         data_tmp.write(struct.pack(">H", 4))
478                         data_tmp.write("COLR")
479                         data_tmp.write("OPAC")                # Hardcoded texture layer opacity
480                         data_tmp.write(struct.pack(">H", 8))
481                         data_tmp.write(struct.pack(">H", 0))
482                         data_tmp.write(struct.pack(">f", 1.0))
483                         data_tmp.write(struct.pack(">H", 0))
484                         data_tmp.write("ENAB")
485                         data_tmp.write(struct.pack(">HH", 2, 1))  # 1 = texture layer enabled
486                         data_tmp.write("NEGA")
487                         data_tmp.write(struct.pack(">HH", 2, 0))  # Disable negative image (1 = invert RGB values)
488                         data_tmp.write("AXIS")
489                         data_tmp.write(struct.pack(">HH", 2, 1))
490                         data.write(struct.pack(">H", len(data_tmp.getvalue())))
491                         data.write(data_tmp.getvalue())
492
493                         # IMAG subchunk
494                         data.write("IMAG")
495                         data.write(struct.pack(">HH", 2, 1))
496                         data.write("PROJ")
497                         data.write(struct.pack(">HH", 2, 5)) # UV projection
498
499                         data.write("VMAP")
500                         uvname = generate_nstring("Blender's UV Coordinates")
501                         data.write(struct.pack(">H", len(uvname)))
502                         data.write(uvname)
503
504         return data.getvalue()
505
506 # =============================================
507 # === Generate Default Surface (SURF Chunk) ===
508 # =============================================
509 def generate_default_surf():
510         data = cStringIO.StringIO()
511         material_name = "\251 Blender Default"
512         data.write(generate_nstring(material_name))
513         data.write("\0\0")
514
515         data.write("COLR")
516         data.write(struct.pack(">H", 14))
517         data.write(struct.pack(">fffH", 1, 1, 1, 0))
518
519         data.write("DIFF")
520         data.write(struct.pack(">H", 6))
521         data.write(struct.pack(">fH", 0.8, 0))
522
523         data.write("LUMI")
524         data.write(struct.pack(">H", 6))
525         data.write(struct.pack(">fH", 0, 0))
526
527         data.write("SPEC")
528         data.write(struct.pack(">H", 6))
529         data.write(struct.pack(">fH", 0.5, 0))
530
531         data.write("GLOS")
532         data.write(struct.pack(">H", 6))
533         gloss = 50 / (255/2.0)
534         gloss = round(gloss, 1)
535         data.write(struct.pack(">fH", gloss, 0))
536
537         data.write("CMNT")  # material comment
538         comment = material_name + ": Exported from Blender\256 " + meshtools.blender_version_str
539
540         # vals = map(chr, range(164,255,1))
541         # keys = range(164,255,1)
542         # keys = map(lambda x: `x`, keys)
543         # comment = map(None, keys, vals)
544         # comment = reduce(operator.add, comment)
545         # comment = reduce(operator.add, comment)
546
547         comment = generate_nstring(comment)
548         data.write(struct.pack(">H", len(comment)))
549         data.write(comment)
550         return data.getvalue()
551
552 # ============================================
553 # === Generate Object Comment (TEXT Chunk) ===
554 # ============================================
555 def generate_text():
556         comment  = "Lightwave Export Script for Blender "
557         comment +=      meshtools.blender_version_str + "\n"
558         comment += "by Anthony D'Agostino\n"
559         comment += "scorpius@netzero.com\n"
560         comment += "http://ourworld.compuserve.com/homepages/scorpius\n"
561         return generate_nstring(comment)
562
563 # ==============================================
564 # === Generate Description Line (DESC Chunk) ===
565 # ==============================================
566 def generate_desc():
567         comment = "Copyright 2002 Scorpius Entertainment"
568         return generate_nstring(comment)
569
570 # ==================================================
571 # === Generate Thumbnail Icon Image (ICON Chunk) ===
572 # ==================================================
573 def generate_icon():
574         data = cStringIO.StringIO()
575         file = open("f:/obj/radiosity/lwo2_icon.tga", "rb") # 60x60 uncompressed TGA
576         file.read(18)
577         icon_data = file.read(3600) # ?
578         file.close()
579         data.write(struct.pack(">HH", 0, 60))
580         data.write(icon_data)
581         #print len(icon_data)
582         return data.getvalue()
583
584 # ===============================================
585 # === Generate CLIP chunk with STIL subchunks ===
586 # ===============================================
587 def generate_clip(mesh, material_names):
588         data = cStringIO.StringIO()
589         clipid = 1
590         for i in range(len(mesh.materials)):                                                                    # Run through list of materials used by mesh
591                 material = Blender.Material.Get(mesh.materials[i].name)
592                 mtextures = material.getTextures()                                                                      # Get a list of textures linked to the material
593                 for mtex in mtextures:
594                         if (mtex) and (mtex.tex.type == Blender.Texture.Types.IMAGE):   # Check if the texture is of type "IMAGE"
595                                 pathname = mtex.tex.image.filename                                                      # If full path is needed use filename in place of name
596                                 pathname = pathname[0:2] + pathname.replace("\\", "/")[3:]  # Convert to Modo standard path
597                                 imagename = generate_nstring(pathname)
598                                 data.write(struct.pack(">L", clipid))                       # CLIP sequence/id
599                                 data.write("STIL")                                          # STIL image
600                                 data.write(struct.pack(">H", len(imagename)))               # Size of image name
601                                 data.write(imagename)
602                                 clipid += 1
603         return data.getvalue()
604
605 # ===================
606 # === Write Chunk ===
607 # ===================
608 def write_chunk(file, name, data):
609         file.write(name)
610         file.write(struct.pack(">L", len(data)))
611         file.write(data)
612
613 # =============================
614 # === Write LWO File Header ===
615 # =============================
616 def write_header(file, chunks):
617         chunk_sizes = map(len, chunks)
618         chunk_sizes = reduce(operator.add, chunk_sizes)
619         form_size = chunk_sizes + len(chunks)*8 + len("FORM")
620         file.write("FORM")
621         file.write(struct.pack(">L", form_size))
622         file.write("LWO2")
623
624 def fs_callback(filename):
625         if filename.find('.lwo', -4) <= 0: filename += '.lwo'
626         write(filename)
627
628 Blender.Window.FileSelector(fs_callback, "Export LWO")