Logo Search packages:      
Sourcecode: blender version File versions

lightwave_export.py

#!BPY

"""
Name: 'LightWave (.lwo)...'
Blender: 232
Group: 'Export'
Tooltip: 'Export selected meshes to LightWave File Format (.lwo)'
"""

__author__ = "Anthony D'Agostino (Scorpius)"
__url__ = ("blender", "elysiun",
"Author's homepage, http://www.redrival.com/scorpius")
__version__ = "Part of IOSuite 0.5"

__bpydoc__ = """\
This script exports meshes to LightWave file format.

LightWave is a full-featured commercial modeling and rendering
application. The lwo file format is composed of 'chunks,' is well
defined, and easy to read and write. It is similar in structure to the
trueSpace cob format.

Usage:<br>
      Select meshes to be exported and run this script from "File->Export" menu.

Supported:<br>
      UV Coordinates, Meshes, Materials, Material Indices, Specular
Highlights, and Vertex Colors. For added functionality, each object is
placed on its own layer.

Missing:<br>
      Not too much, I hope! :).

Known issues:<br>
      Empty objects crash has been fixed.

Notes:<br>
      For compatibility reasons, it also reads lwo files in the old LW
v5.5 format.
"""

# $Id: lightwave_export.py,v 1.5 2004/11/30 02:27:46 ianwill Exp $
#
# +---------------------------------------------------------+
# | Copyright (c) 2002 Anthony D'Agostino                   |
# | http://www.redrival.com/scorpius                        |
# | scorpius@netzero.com                                    |
# | April 21, 2002                                          |
# | Released under the Blender Artistic Licence (BAL)       |
# | Import Export Suite v0.5                                |
# +---------------------------------------------------------+
# | Read and write LightWave Object File Format (*.lwo)     |
# +---------------------------------------------------------+

import Blender, mod_meshtools
import struct, chunk, os, cStringIO, time, operator

# ==============================
# === Write LightWave Format ===
# ==============================
def write(filename):
      start = time.clock()
      file = open(filename, "wb")

      objects = Blender.Object.GetSelected()
      objects.sort(lambda a,b: cmp(a.name, b.name))
      if not objects:
            mod_meshtools.print_boxed("No mesh objects are selected.")
            return

      if len(objects) > 20 and mod_meshtools.show_progress:
            mod_meshtools.show_progress = 0

      text = generate_text()
      desc = generate_desc()
      icon = "" #generate_icon()

      material_names = get_used_material_names(objects)
      tags = generate_tags(material_names)
      surfs = generate_surfs(material_names)
      chunks = [text, desc, icon, tags]

      meshdata = cStringIO.StringIO()
      layer_index = 0
      for object in objects:
            objname = object.name
            meshname = object.data.name
            mesh = Blender.NMesh.GetRaw(meshname)
            #mesh = Blender.NMesh.GetRawFromObject(meshname)      # for SubSurf
            obj = Blender.Object.Get(objname)
            if not mesh: continue

            layr = generate_layr(objname, layer_index)
            pnts = generate_pnts(mesh, obj.matrix)
            bbox = generate_bbox(mesh)
            pols = generate_pols(mesh)
            ptag = generate_ptag(mesh, material_names)

            if mesh.hasFaceUV():
                  vmad_uv = generate_vmad_uv(mesh)  # per face

            if mod_meshtools.has_vertex_colors(mesh):
                  if mod_meshtools.average_vcols:
                        vmap_vc = generate_vmap_vc(mesh)  # per vert
                  else:
                        vmad_vc = generate_vmad_vc(mesh)  # per face

            write_chunk(meshdata, "LAYR", layr); chunks.append(layr)
            write_chunk(meshdata, "PNTS", pnts); chunks.append(pnts)
            write_chunk(meshdata, "BBOX", bbox); chunks.append(bbox)
            write_chunk(meshdata, "POLS", pols); chunks.append(pols)
            write_chunk(meshdata, "PTAG", ptag); chunks.append(ptag)

            if mesh.hasFaceUV():
                  write_chunk(meshdata, "VMAD", vmad_uv)
                  chunks.append(vmad_uv)

            if mod_meshtools.has_vertex_colors(mesh):
                  if mod_meshtools.average_vcols:
                        write_chunk(meshdata, "VMAP", vmap_vc)
                        chunks.append(vmap_vc)
                  else:
                        write_chunk(meshdata, "VMAD", vmad_vc)
                        chunks.append(vmad_vc)
            layer_index += 1

      for surf in surfs:
            chunks.append(surf)

      write_header(file, chunks)
      write_chunk(file, "ICON", icon)
      write_chunk(file, "TEXT", text)
      write_chunk(file, "DESC", desc)
      write_chunk(file, "TAGS", tags)
      file.write(meshdata.getvalue()); meshdata.close()
      for surf in surfs:
            write_chunk(file, "SURF", surf)

      Blender.Window.DrawProgressBar(1.0, "")    # clear progressbar
      file.close()
      print '\a\r',
      end = time.clock()
      seconds = " in %.2f %s" % (end-start, "seconds")
      message = "Successfully exported " + os.path.basename(filename) + seconds
      mod_meshtools.print_boxed(message)

# =======================================
# === Generate Null-Terminated String ===
# =======================================
def generate_nstring(string):
      if len(string)%2 == 0:  # even
            string += "\0\0"
      else:                         # odd
            string += "\0"
      return string

# ===============================
# === Get Used Material Names ===
# ===============================
def get_used_material_names(objects):
      matnames = {}
      for object in objects:
            objname = object.name
            meshname = object.data.name
            mesh = Blender.NMesh.GetRaw(meshname)
            if not mesh: continue
            if (not mesh.materials) and (mod_meshtools.has_vertex_colors(mesh)):
                  # vcols only
                  if mod_meshtools.average_vcols:
                        matnames["\251 Per-Vert Vertex Colors"] = None
                  else:
                        matnames["\251 Per-Face Vertex Colors"] = None
            elif (mesh.materials) and (not mod_meshtools.has_vertex_colors(mesh)):
                  # materials only
                  for material in mesh.materials:
                        matnames[material.name] = None
            elif (not mesh.materials) and (not mod_meshtools.has_vertex_colors(mesh)):
                  # neither
                  matnames["\251 Blender Default"] = None
            else:
                  # both
                  for material in mesh.materials:
                        matnames[material.name] = None
      return matnames

# =========================================
# === Generate Tag Strings (TAGS Chunk) ===
# =========================================
def generate_tags(material_names):
      material_names = map(generate_nstring, material_names.keys())
      tags_data = reduce(operator.add, material_names)
      return tags_data

# ========================
# === Generate Surface ===
# ========================
def generate_surface(name, mesh):
      if name.find("\251 Per-") == 0:
            return generate_vcol_surf(mesh)
      elif name == "\251 Blender Default":
            return generate_default_surf()
      else:
            return generate_surf(name)

# ======================
# === Generate Surfs ===
# ======================
def generate_surfs(material_names):
      keys = material_names.keys()
      values = material_names.values()
      surfaces = map(generate_surface, keys, values)
      return surfaces

# ===================================
# === Generate Layer (LAYR Chunk) ===
# ===================================
def generate_layr(name, idx):
      data = cStringIO.StringIO()
      data.write(struct.pack(">h", idx))          # layer number
      data.write(struct.pack(">h", 0))            # flags
      data.write(struct.pack(">fff", 0, 0, 0))    # pivot
      data.write(generate_nstring(name))              # name
      return data.getvalue()

# ===================================
# === Generate Verts (PNTS Chunk) ===
# ===================================
def generate_pnts(mesh, matrix):
      data = cStringIO.StringIO()
      for i in range(len(mesh.verts)):
            if not i%100 and mod_meshtools.show_progress:
                  Blender.Window.DrawProgressBar(float(i)/len(mesh.verts), "Writing Verts")
            x, y, z = mod_meshtools.apply_transform(mesh.verts[i].co, matrix)
            data.write(struct.pack(">fff", x, z, y))
      return data.getvalue()

# ==========================================
# === Generate Bounding Box (BBOX Chunk) ===
# ==========================================
def generate_bbox(mesh):
      data = cStringIO.StringIO()
      # need to transform verts here
      nv = map(getattr, mesh.verts, ["co"]*len(mesh.verts))
      xx = map(operator.getitem, nv, [0]*len(nv))
      yy = map(operator.getitem, nv, [1]*len(nv))
      zz = map(operator.getitem, nv, [2]*len(nv))
      data.write(struct.pack(">6f", min(xx), min(zz), min(yy), max(xx), max(zz), max(yy)))
      return data.getvalue()

# ========================================
# === Average All Vertex Colors (Fast) ===
# ========================================
def average_vertexcolors(mesh):
      vertexcolors = {}
      vcolor_add = lambda u, v: [u[0]+v[0], u[1]+v[1], u[2]+v[2], u[3]+v[3]]
      vcolor_div = lambda u, s: [u[0]/s, u[1]/s, u[2]/s, u[3]/s]
      for i in range(len(mesh.faces)):    # get all vcolors that share this vertex
            if not i%100 and mod_meshtools.show_progress:
                  Blender.Window.DrawProgressBar(float(i)/len(mesh.verts), "Finding Shared VColors")
            for j in range(len(mesh.faces[i].v)):
                  index = mesh.faces[i].v[j].index
                  color = mesh.faces[i].col[j]
                  r,g,b,a = color.r, color.g, color.b, color.a
                  vertexcolors.setdefault(index, []).append([r,g,b,a])
      for i in range(len(vertexcolors)):  # average them
            if not i%100 and mod_meshtools.show_progress:
                  Blender.Window.DrawProgressBar(float(i)/len(mesh.verts), "Averaging Vertex Colors")
            vcolor = [0,0,0,0]      # rgba
            for j in range(len(vertexcolors[i])):
                  vcolor = vcolor_add(vcolor, vertexcolors[i][j])
            shared = len(vertexcolors[i])
            vertexcolors[i] = vcolor_div(vcolor, shared)
      return vertexcolors

# ====================================================
# === Generate Per-Vert Vertex Colors (VMAP Chunk) ===
# ====================================================
def generate_vmap_vc(mesh):
      data = cStringIO.StringIO()
      data.write("RGB ")                                      # type
      data.write(struct.pack(">H", 3))                        # dimension
      data.write(generate_nstring("Blender's Vertex Colors")) # name
      vertexcolors = average_vertexcolors(mesh)
      for i in range(len(vertexcolors)):
            r, g, b, a = vertexcolors[i]
            data.write(struct.pack(">H", i)) # vertex index
            data.write(struct.pack(">fff", r/255.0, g/255.0, b/255.0))
      return data.getvalue()

# ====================================================
# === Generate Per-Face Vertex Colors (VMAD Chunk) ===
# ====================================================
def generate_vmad_vc(mesh):
      data = cStringIO.StringIO()
      data.write("RGB ")                                      # type
      data.write(struct.pack(">H", 3))                        # dimension
      data.write(generate_nstring("Blender's Vertex Colors")) # name
      for i in range(len(mesh.faces)):
            if not i%100 and mod_meshtools.show_progress:
                  Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing Vertex Colors")
            numfaceverts = len(mesh.faces[i].v)
            for j in range(numfaceverts-1, -1, -1):               # Reverse order
                  r = mesh.faces[i].col[j].r
                  g = mesh.faces[i].col[j].g
                  b = mesh.faces[i].col[j].b
                  v = mesh.faces[i].v[j].index
                  data.write(struct.pack(">H", v)) # vertex index
                  data.write(struct.pack(">H", i)) # face index
                  data.write(struct.pack(">fff", r/255.0, g/255.0, b/255.0))
      return data.getvalue()

# ================================================
# === Generate Per-Face UV Coords (VMAD Chunk) ===
# ================================================
def generate_vmad_uv(mesh):
      data = cStringIO.StringIO()
      data.write("TXUV")                                       # type
      data.write(struct.pack(">H", 2))                         # dimension
      data.write(generate_nstring("Blender's UV Coordinates")) # name
      for i in range(len(mesh.faces)):
            if not i%100 and mod_meshtools.show_progress:
                  Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing UV Coordinates")
            numfaceverts = len(mesh.faces[i].v)
            for j in range(numfaceverts-1, -1, -1):               # Reverse order
                  U,V = mesh.faces[i].uv[j]
                  v = mesh.faces[i].v[j].index
                  data.write(struct.pack(">H", v)) # vertex index
                  data.write(struct.pack(">H", i)) # face index
                  data.write(struct.pack(">ff", U, V))
      return data.getvalue()

# ======================================
# === Generate Variable-Length Index ===
# ======================================
def generate_vx(index):
      if index < 0xFF00:
            value = struct.pack(">H", index)                 # 2-byte index
      else:
            value = struct.pack(">L", index | 0xFF000000)    # 4-byte index
      return value

# ===================================
# === Generate Faces (POLS Chunk) ===
# ===================================
def generate_pols(mesh):
      data = cStringIO.StringIO()
      data.write("FACE")  # polygon type
      for i in range(len(mesh.faces)):
            if not i%100 and mod_meshtools.show_progress:
                  Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing Faces")
            data.write(struct.pack(">H", len(mesh.faces[i].v))) # numfaceverts
            numfaceverts = len(mesh.faces[i].v)
            for j in range(numfaceverts-1, -1, -1):               # Reverse order
                  index = mesh.faces[i].v[j].index
                  data.write(generate_vx(index))
      return data.getvalue()

# =================================================
# === Generate Polygon Tag Mapping (PTAG Chunk) ===
# =================================================
def generate_ptag(mesh, material_names):
      data = cStringIO.StringIO()
      data.write("SURF")  # polygon tag type
      for i in range(len(mesh.faces)): # numfaces
            if not i%100 and mod_meshtools.show_progress:
                  Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing Surface Indices")
            data.write(generate_vx(i))
            if (not mesh.materials) and (mod_meshtools.has_vertex_colors(mesh)):          # vcols only
                  if mod_meshtools.average_vcols:
                        name = "\251 Per-Vert Vertex Colors"
                  else:
                        name = "\251 Per-Face Vertex Colors"
            elif (mesh.materials) and (not mod_meshtools.has_vertex_colors(mesh)):        # materials only
                  idx = mesh.faces[i].mat #erialIndex
                  name = mesh.materials[idx].name
            elif (not mesh.materials) and (not mod_meshtools.has_vertex_colors(mesh)):    # neither
                  name = "\251 Blender Default"
            else:                                                                                                       # both
                  idx = mesh.faces[i].mat
                  name = mesh.materials[idx].name
            names = material_names.keys()
            surfidx = names.index(name)
            data.write(struct.pack(">H", surfidx)) # surface index
      return data.getvalue()

# ===================================================
# === Generate VC Surface Definition (SURF Chunk) ===
# ===================================================
def generate_vcol_surf(mesh):
      data = cStringIO.StringIO()
      if mod_meshtools.average_vcols and mod_meshtools.has_vertex_colors(mesh):
            surface_name = generate_nstring("\251 Per-Vert Vertex Colors")
      else:
            surface_name = generate_nstring("\251 Per-Face Vertex Colors")
      data.write(surface_name)
      data.write("\0\0")

      data.write("COLR")
      data.write(struct.pack(">H", 14))
      data.write(struct.pack(">fffH", 1, 1, 1, 0))

      data.write("DIFF")
      data.write(struct.pack(">H", 6))
      data.write(struct.pack(">fH", 0.0, 0))

      data.write("LUMI")
      data.write(struct.pack(">H", 6))
      data.write(struct.pack(">fH", 1.0, 0))

      data.write("VCOL")
      data.write(struct.pack(">H", 34))
      data.write(struct.pack(">fH4s", 1.0, 0, "RGB "))  # intensity, envelope, type
      data.write(generate_nstring("Blender's Vertex Colors")) # name

      data.write("CMNT")  # material comment
      comment = "Vertex Colors: Exported from Blender\256 " + mod_meshtools.blender_version_str
      comment = generate_nstring(comment)
      data.write(struct.pack(">H", len(comment)))
      data.write(comment)
      return data.getvalue()

# ================================================
# === Generate Surface Definition (SURF Chunk) ===
# ================================================
def generate_surf(material_name):
      data = cStringIO.StringIO()
      data.write(generate_nstring(material_name))
      data.write("\0\0")

      material = Blender.Material.Get(material_name)
      R,G,B = material.R, material.G, material.B
      data.write("COLR")
      data.write(struct.pack(">H", 14))
      data.write(struct.pack(">fffH", R, G, B, 0))

      data.write("DIFF")
      data.write(struct.pack(">H", 6))
      data.write(struct.pack(">fH", material.ref, 0))

      data.write("LUMI")
      data.write(struct.pack(">H", 6))
      data.write(struct.pack(">fH", material.emit, 0))

      data.write("SPEC")
      data.write(struct.pack(">H", 6))
      data.write(struct.pack(">fH", material.spec, 0))

      data.write("GLOS")
      data.write(struct.pack(">H", 6))
      gloss = material.hard / (255/2.0)
      gloss = round(gloss, 1)
      data.write(struct.pack(">fH", gloss, 0))

      data.write("CMNT")  # material comment
      comment = material_name + ": Exported from Blender\256 " + mod_meshtools.blender_version_str
      comment = generate_nstring(comment)
      data.write(struct.pack(">H", len(comment)))
      data.write(comment)
      return data.getvalue()

# =============================================
# === Generate Default Surface (SURF Chunk) ===
# =============================================
def generate_default_surf():
      data = cStringIO.StringIO()
      material_name = "\251 Blender Default"
      data.write(generate_nstring(material_name))
      data.write("\0\0")

      data.write("COLR")
      data.write(struct.pack(">H", 14))
      data.write(struct.pack(">fffH", 1, 1, 1, 0))

      data.write("DIFF")
      data.write(struct.pack(">H", 6))
      data.write(struct.pack(">fH", 0.8, 0))

      data.write("LUMI")
      data.write(struct.pack(">H", 6))
      data.write(struct.pack(">fH", 0, 0))

      data.write("SPEC")
      data.write(struct.pack(">H", 6))
      data.write(struct.pack(">fH", 0.5, 0))

      data.write("GLOS")
      data.write(struct.pack(">H", 6))
      gloss = 50 / (255/2.0)
      gloss = round(gloss, 1)
      data.write(struct.pack(">fH", gloss, 0))

      data.write("CMNT")  # material comment
      comment = material_name + ": Exported from Blender\256 " + mod_meshtools.blender_version_str

      # vals = map(chr, range(164,255,1))
      # keys = range(164,255,1)
      # keys = map(lambda x: `x`, keys)
      # comment = map(None, keys, vals)
      # comment = reduce(operator.add, comment)
      # comment = reduce(operator.add, comment)

      comment = generate_nstring(comment)
      data.write(struct.pack(">H", len(comment)))
      data.write(comment)
      return data.getvalue()

# ============================================
# === Generate Object Comment (TEXT Chunk) ===
# ============================================
def generate_text():
      comment  = "Lightwave Export Script for Blender "
      comment +=  mod_meshtools.blender_version_str + "\n"
      comment += "by Anthony D'Agostino\n"
      comment += "scorpius@netzero.com\n"
      comment += "http://ourworld.compuserve.com/homepages/scorpius\n"
      return generate_nstring(comment)

# ==============================================
# === Generate Description Line (DESC Chunk) ===
# ==============================================
def generate_desc():
      comment = "Copyright 2002 Scorpius Entertainment"
      return generate_nstring(comment)

# ==================================================
# === Generate Thumbnail Icon Image (ICON Chunk) ===
# ==================================================
def generate_icon():
      data = cStringIO.StringIO()
      file = open("f:/obj/radiosity/lwo2_icon.tga", "rb") # 60x60 uncompressed TGA
      file.read(18)
      icon_data = file.read(3600) # ?
      file.close()
      data.write(struct.pack(">HH", 0, 60))
      data.write(icon_data)
      #print len(icon_data)
      return data.getvalue()

# ===================
# === Write Chunk ===
# ===================
def write_chunk(file, name, data):
      file.write(name)
      file.write(struct.pack(">L", len(data)))
      file.write(data)

# =============================
# === Write LWO File Header ===
# =============================
def write_header(file, chunks):
      chunk_sizes = map(len, chunks)
      chunk_sizes = reduce(operator.add, chunk_sizes)
      form_size = chunk_sizes + len(chunks)*8 + len("FORM")
      file.write("FORM")
      file.write(struct.pack(">L", form_size))
      file.write("LWO2")

def fs_callback(filename):
      if filename.find('.lwo', -4) <= 0: filename += '.lwo'
      write(filename)

Blender.Window.FileSelector(fs_callback, "Export LWO")

Generated by  Doxygen 1.6.0   Back to index