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