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