4
Name: 'LightWave (.lwo)...'
7
Tooltip: 'Export selected meshes to LightWave File Format (.lwo)'
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"
16
This script exports meshes to LightWave file format.
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
24
Select meshes to be exported and run this script from "File->Export" menu.
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.
32
Not too much, I hope! :).
35
Empty objects crash has been fixed.
38
For compatibility reasons, it also reads lwo files in the old LW
42
# $Id: lightwave_export.py,v 1.6 2005/03/21 05:26:51 ianwill Exp $
44
# +---------------------------------------------------------+
45
# | Copyright (c) 2002 Anthony D'Agostino |
46
# | http://www.redrival.com/scorpius |
47
# | scorpius@netzero.com |
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
# +---------------------------------------------------------+
55
import Blender, meshtools
56
import struct, chunk, os, cStringIO, time, operator
58
# ==============================
59
# === Write LightWave Format ===
60
# ==============================
63
file = open(filename, "wb")
65
objects = Blender.Object.GetSelected()
66
objects.sort(lambda a,b: cmp(a.name, b.name))
68
meshtools.print_boxed("No mesh objects are selected.")
71
if len(objects) > 20 and meshtools.show_progress:
72
meshtools.show_progress = 0
74
text = generate_text()
75
desc = generate_desc()
76
icon = "" #generate_icon()
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]
83
meshdata = cStringIO.StringIO()
85
for object in objects:
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)
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)
100
vmad_uv = generate_vmad_uv(mesh) # per face
102
if meshtools.has_vertex_colors(mesh):
103
if meshtools.average_vcols:
104
vmap_vc = generate_vmap_vc(mesh) # per vert
106
vmad_vc = generate_vmad_vc(mesh) # per face
108
write_chunk(meshdata, "LAYR", layr); chunks.append(layr)
109
write_chunk(meshdata, "PNTS", pnts); chunks.append(pnts)
110
write_chunk(meshdata, "BBOX", bbox); chunks.append(bbox)
111
write_chunk(meshdata, "POLS", pols); chunks.append(pols)
112
write_chunk(meshdata, "PTAG", ptag); chunks.append(ptag)
115
write_chunk(meshdata, "VMAD", vmad_uv)
116
chunks.append(vmad_uv)
118
if meshtools.has_vertex_colors(mesh):
119
if meshtools.average_vcols:
120
write_chunk(meshdata, "VMAP", vmap_vc)
121
chunks.append(vmap_vc)
123
write_chunk(meshdata, "VMAD", vmad_vc)
124
chunks.append(vmad_vc)
130
write_header(file, chunks)
131
write_chunk(file, "ICON", icon)
132
write_chunk(file, "TEXT", text)
133
write_chunk(file, "DESC", desc)
134
write_chunk(file, "TAGS", tags)
135
file.write(meshdata.getvalue()); meshdata.close()
137
write_chunk(file, "SURF", surf)
139
Blender.Window.DrawProgressBar(1.0, "") # clear progressbar
143
seconds = " in %.2f %s" % (end-start, "seconds")
144
message = "Successfully exported " + os.path.basename(filename) + seconds
145
meshtools.print_boxed(message)
147
# =======================================
148
# === Generate Null-Terminated String ===
149
# =======================================
150
def generate_nstring(string):
151
if len(string)%2 == 0: # even
157
# ===============================
158
# === Get Used Material Names ===
159
# ===============================
160
def get_used_material_names(objects):
162
for object in objects:
163
objname = object.name
164
meshname = object.data.name
165
mesh = Blender.NMesh.GetRaw(meshname)
166
if not mesh: continue
167
if (not mesh.materials) and (meshtools.has_vertex_colors(mesh)):
169
if meshtools.average_vcols:
170
matnames["\251 Per-Vert Vertex Colors"] = None
172
matnames["\251 Per-Face Vertex Colors"] = None
173
elif (mesh.materials) and (not meshtools.has_vertex_colors(mesh)):
175
for material in mesh.materials:
176
matnames[material.name] = None
177
elif (not mesh.materials) and (not meshtools.has_vertex_colors(mesh)):
179
matnames["\251 Blender Default"] = None
182
for material in mesh.materials:
183
matnames[material.name] = None
186
# =========================================
187
# === Generate Tag Strings (TAGS Chunk) ===
188
# =========================================
189
def generate_tags(material_names):
190
material_names = map(generate_nstring, material_names.keys())
191
tags_data = reduce(operator.add, material_names)
194
# ========================
195
# === Generate Surface ===
196
# ========================
197
def generate_surface(name, mesh):
198
if name.find("\251 Per-") == 0:
199
return generate_vcol_surf(mesh)
200
elif name == "\251 Blender Default":
201
return generate_default_surf()
203
return generate_surf(name)
205
# ======================
206
# === Generate Surfs ===
207
# ======================
208
def generate_surfs(material_names):
209
keys = material_names.keys()
210
values = material_names.values()
211
surfaces = map(generate_surface, keys, values)
214
# ===================================
215
# === Generate Layer (LAYR Chunk) ===
216
# ===================================
217
def generate_layr(name, idx):
218
data = cStringIO.StringIO()
219
data.write(struct.pack(">h", idx)) # layer number
220
data.write(struct.pack(">h", 0)) # flags
221
data.write(struct.pack(">fff", 0, 0, 0)) # pivot
222
data.write(generate_nstring(name)) # name
223
return data.getvalue()
225
# ===================================
226
# === Generate Verts (PNTS Chunk) ===
227
# ===================================
228
def generate_pnts(mesh, matrix):
229
data = cStringIO.StringIO()
230
for i in range(len(mesh.verts)):
231
if not i%100 and meshtools.show_progress:
232
Blender.Window.DrawProgressBar(float(i)/len(mesh.verts), "Writing Verts")
233
x, y, z = meshtools.apply_transform(mesh.verts[i].co, matrix)
234
data.write(struct.pack(">fff", x, z, y))
235
return data.getvalue()
237
# ==========================================
238
# === Generate Bounding Box (BBOX Chunk) ===
239
# ==========================================
240
def generate_bbox(mesh):
241
data = cStringIO.StringIO()
242
# need to transform verts here
243
nv = map(getattr, mesh.verts, ["co"]*len(mesh.verts))
244
xx = map(operator.getitem, nv, [0]*len(nv))
245
yy = map(operator.getitem, nv, [1]*len(nv))
246
zz = map(operator.getitem, nv, [2]*len(nv))
247
data.write(struct.pack(">6f", min(xx), min(zz), min(yy), max(xx), max(zz), max(yy)))
248
return data.getvalue()
250
# ========================================
251
# === Average All Vertex Colors (Fast) ===
252
# ========================================
253
def average_vertexcolors(mesh):
255
vcolor_add = lambda u, v: [u[0]+v[0], u[1]+v[1], u[2]+v[2], u[3]+v[3]]
256
vcolor_div = lambda u, s: [u[0]/s, u[1]/s, u[2]/s, u[3]/s]
257
for i in range(len(mesh.faces)): # get all vcolors that share this vertex
258
if not i%100 and meshtools.show_progress:
259
Blender.Window.DrawProgressBar(float(i)/len(mesh.verts), "Finding Shared VColors")
260
for j in range(len(mesh.faces[i].v)):
261
index = mesh.faces[i].v[j].index
262
color = mesh.faces[i].col[j]
263
r,g,b,a = color.r, color.g, color.b, color.a
264
vertexcolors.setdefault(index, []).append([r,g,b,a])
265
for i in range(len(vertexcolors)): # average them
266
if not i%100 and meshtools.show_progress:
267
Blender.Window.DrawProgressBar(float(i)/len(mesh.verts), "Averaging Vertex Colors")
268
vcolor = [0,0,0,0] # rgba
269
for j in range(len(vertexcolors[i])):
270
vcolor = vcolor_add(vcolor, vertexcolors[i][j])
271
shared = len(vertexcolors[i])
272
vertexcolors[i] = vcolor_div(vcolor, shared)
275
# ====================================================
276
# === Generate Per-Vert Vertex Colors (VMAP Chunk) ===
277
# ====================================================
278
def generate_vmap_vc(mesh):
279
data = cStringIO.StringIO()
280
data.write("RGB ") # type
281
data.write(struct.pack(">H", 3)) # dimension
282
data.write(generate_nstring("Blender's Vertex Colors")) # name
283
vertexcolors = average_vertexcolors(mesh)
284
for i in range(len(vertexcolors)):
285
r, g, b, a = vertexcolors[i]
286
data.write(struct.pack(">H", i)) # vertex index
287
data.write(struct.pack(">fff", r/255.0, g/255.0, b/255.0))
288
return data.getvalue()
290
# ====================================================
291
# === Generate Per-Face Vertex Colors (VMAD Chunk) ===
292
# ====================================================
293
def generate_vmad_vc(mesh):
294
data = cStringIO.StringIO()
295
data.write("RGB ") # type
296
data.write(struct.pack(">H", 3)) # dimension
297
data.write(generate_nstring("Blender's Vertex Colors")) # name
298
for i in range(len(mesh.faces)):
299
if not i%100 and meshtools.show_progress:
300
Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing Vertex Colors")
301
numfaceverts = len(mesh.faces[i].v)
302
for j in range(numfaceverts-1, -1, -1): # Reverse order
303
r = mesh.faces[i].col[j].r
304
g = mesh.faces[i].col[j].g
305
b = mesh.faces[i].col[j].b
306
v = mesh.faces[i].v[j].index
307
data.write(struct.pack(">H", v)) # vertex index
308
data.write(struct.pack(">H", i)) # face index
309
data.write(struct.pack(">fff", r/255.0, g/255.0, b/255.0))
310
return data.getvalue()
312
# ================================================
313
# === Generate Per-Face UV Coords (VMAD Chunk) ===
314
# ================================================
315
def generate_vmad_uv(mesh):
316
data = cStringIO.StringIO()
317
data.write("TXUV") # type
318
data.write(struct.pack(">H", 2)) # dimension
319
data.write(generate_nstring("Blender's UV Coordinates")) # name
320
for i in range(len(mesh.faces)):
321
if not i%100 and meshtools.show_progress:
322
Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing UV Coordinates")
323
numfaceverts = len(mesh.faces[i].v)
324
for j in range(numfaceverts-1, -1, -1): # Reverse order
325
U,V = mesh.faces[i].uv[j]
326
v = mesh.faces[i].v[j].index
327
data.write(struct.pack(">H", v)) # vertex index
328
data.write(struct.pack(">H", i)) # face index
329
data.write(struct.pack(">ff", U, V))
330
return data.getvalue()
332
# ======================================
333
# === Generate Variable-Length Index ===
334
# ======================================
335
def generate_vx(index):
337
value = struct.pack(">H", index) # 2-byte index
339
value = struct.pack(">L", index | 0xFF000000) # 4-byte index
342
# ===================================
343
# === Generate Faces (POLS Chunk) ===
344
# ===================================
345
def generate_pols(mesh):
346
data = cStringIO.StringIO()
347
data.write("FACE") # polygon type
348
for i in range(len(mesh.faces)):
349
if not i%100 and meshtools.show_progress:
350
Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing Faces")
351
data.write(struct.pack(">H", len(mesh.faces[i].v))) # numfaceverts
352
numfaceverts = len(mesh.faces[i].v)
353
for j in range(numfaceverts-1, -1, -1): # Reverse order
354
index = mesh.faces[i].v[j].index
355
data.write(generate_vx(index))
356
return data.getvalue()
358
# =================================================
359
# === Generate Polygon Tag Mapping (PTAG Chunk) ===
360
# =================================================
361
def generate_ptag(mesh, material_names):
362
data = cStringIO.StringIO()
363
data.write("SURF") # polygon tag type
364
for i in range(len(mesh.faces)): # numfaces
365
if not i%100 and meshtools.show_progress:
366
Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing Surface Indices")
367
data.write(generate_vx(i))
368
if (not mesh.materials) and (meshtools.has_vertex_colors(mesh)): # vcols only
369
if meshtools.average_vcols:
370
name = "\251 Per-Vert Vertex Colors"
372
name = "\251 Per-Face Vertex Colors"
373
elif (mesh.materials) and (not meshtools.has_vertex_colors(mesh)): # materials only
374
idx = mesh.faces[i].mat #erialIndex
375
name = mesh.materials[idx].name
376
elif (not mesh.materials) and (not meshtools.has_vertex_colors(mesh)): # neither
377
name = "\251 Blender Default"
379
idx = mesh.faces[i].mat
380
name = mesh.materials[idx].name
381
names = material_names.keys()
382
surfidx = names.index(name)
383
data.write(struct.pack(">H", surfidx)) # surface index
384
return data.getvalue()
386
# ===================================================
387
# === Generate VC Surface Definition (SURF Chunk) ===
388
# ===================================================
389
def generate_vcol_surf(mesh):
390
data = cStringIO.StringIO()
391
if meshtools.average_vcols and meshtools.has_vertex_colors(mesh):
392
surface_name = generate_nstring("\251 Per-Vert Vertex Colors")
394
surface_name = generate_nstring("\251 Per-Face Vertex Colors")
395
data.write(surface_name)
399
data.write(struct.pack(">H", 14))
400
data.write(struct.pack(">fffH", 1, 1, 1, 0))
403
data.write(struct.pack(">H", 6))
404
data.write(struct.pack(">fH", 0.0, 0))
407
data.write(struct.pack(">H", 6))
408
data.write(struct.pack(">fH", 1.0, 0))
411
data.write(struct.pack(">H", 34))
412
data.write(struct.pack(">fH4s", 1.0, 0, "RGB ")) # intensity, envelope, type
413
data.write(generate_nstring("Blender's Vertex Colors")) # name
415
data.write("CMNT") # material comment
416
comment = "Vertex Colors: Exported from Blender\256 " + meshtools.blender_version_str
417
comment = generate_nstring(comment)
418
data.write(struct.pack(">H", len(comment)))
420
return data.getvalue()
422
# ================================================
423
# === Generate Surface Definition (SURF Chunk) ===
424
# ================================================
425
def generate_surf(material_name):
426
data = cStringIO.StringIO()
427
data.write(generate_nstring(material_name))
430
material = Blender.Material.Get(material_name)
431
R,G,B = material.R, material.G, material.B
433
data.write(struct.pack(">H", 14))
434
data.write(struct.pack(">fffH", R, G, B, 0))
437
data.write(struct.pack(">H", 6))
438
data.write(struct.pack(">fH", material.ref, 0))
441
data.write(struct.pack(">H", 6))
442
data.write(struct.pack(">fH", material.emit, 0))
445
data.write(struct.pack(">H", 6))
446
data.write(struct.pack(">fH", material.spec, 0))
449
data.write(struct.pack(">H", 6))
450
gloss = material.hard / (255/2.0)
451
gloss = round(gloss, 1)
452
data.write(struct.pack(">fH", gloss, 0))
454
data.write("CMNT") # material comment
455
comment = material_name + ": Exported from Blender\256 " + meshtools.blender_version_str
456
comment = generate_nstring(comment)
457
data.write(struct.pack(">H", len(comment)))
459
return data.getvalue()
461
# =============================================
462
# === Generate Default Surface (SURF Chunk) ===
463
# =============================================
464
def generate_default_surf():
465
data = cStringIO.StringIO()
466
material_name = "\251 Blender Default"
467
data.write(generate_nstring(material_name))
471
data.write(struct.pack(">H", 14))
472
data.write(struct.pack(">fffH", 1, 1, 1, 0))
475
data.write(struct.pack(">H", 6))
476
data.write(struct.pack(">fH", 0.8, 0))
479
data.write(struct.pack(">H", 6))
480
data.write(struct.pack(">fH", 0, 0))
483
data.write(struct.pack(">H", 6))
484
data.write(struct.pack(">fH", 0.5, 0))
487
data.write(struct.pack(">H", 6))
488
gloss = 50 / (255/2.0)
489
gloss = round(gloss, 1)
490
data.write(struct.pack(">fH", gloss, 0))
492
data.write("CMNT") # material comment
493
comment = material_name + ": Exported from Blender\256 " + meshtools.blender_version_str
495
# vals = map(chr, range(164,255,1))
496
# keys = range(164,255,1)
497
# keys = map(lambda x: `x`, keys)
498
# comment = map(None, keys, vals)
499
# comment = reduce(operator.add, comment)
500
# comment = reduce(operator.add, comment)
502
comment = generate_nstring(comment)
503
data.write(struct.pack(">H", len(comment)))
505
return data.getvalue()
507
# ============================================
508
# === Generate Object Comment (TEXT Chunk) ===
509
# ============================================
511
comment = "Lightwave Export Script for Blender "
512
comment += meshtools.blender_version_str + "\n"
513
comment += "by Anthony D'Agostino\n"
514
comment += "scorpius@netzero.com\n"
515
comment += "http://ourworld.compuserve.com/homepages/scorpius\n"
516
return generate_nstring(comment)
518
# ==============================================
519
# === Generate Description Line (DESC Chunk) ===
520
# ==============================================
522
comment = "Copyright 2002 Scorpius Entertainment"
523
return generate_nstring(comment)
525
# ==================================================
526
# === Generate Thumbnail Icon Image (ICON Chunk) ===
527
# ==================================================
529
data = cStringIO.StringIO()
530
file = open("f:/obj/radiosity/lwo2_icon.tga", "rb") # 60x60 uncompressed TGA
532
icon_data = file.read(3600) # ?
534
data.write(struct.pack(">HH", 0, 60))
535
data.write(icon_data)
536
#print len(icon_data)
537
return data.getvalue()
539
# ===================
540
# === Write Chunk ===
541
# ===================
542
def write_chunk(file, name, data):
544
file.write(struct.pack(">L", len(data)))
547
# =============================
548
# === Write LWO File Header ===
549
# =============================
550
def write_header(file, chunks):
551
chunk_sizes = map(len, chunks)
552
chunk_sizes = reduce(operator.add, chunk_sizes)
553
form_size = chunk_sizes + len(chunks)*8 + len("FORM")
555
file.write(struct.pack(">L", form_size))
558
def fs_callback(filename):
559
if filename.find('.lwo', -4) <= 0: filename += '.lwo'
562
Blender.Window.FileSelector(fs_callback, "Export LWO")