Skip to content
Snippets Groups Projects
io_import_scene_lwo.py 42.8 KiB
Newer Older
Ken Nign's avatar
Ken Nign committed
# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>

bl_addon_info= {
    "name": "Import LightWave Objects",
    "author": "Ken Nign (Ken9)",
    "blender": (2, 5, 3),
    "location": "File > Import > LightWave Object (.lwo)",
    "description": "Imports a LWO file including any UV, Morph and Color maps. Can convert Skelegons to an Armature.",
    "warning": "",
    "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
        "Scripts/File_I-O/LightWave_Object",
Ken Nign's avatar
Ken Nign committed
    "tracker_url": "https://projects.blender.org/tracker/index.php?"\
        "func=detail&aid=23623&group_id=153&atid=469",
    "category": "Import/Export"}

Ken Nign's avatar
Ken Nign committed
# Copyright (c) Ken Nign 2010
# ken@virginpi.com
#
# Version 1.2 - Sep 7, 2010
Ken Nign's avatar
Ken Nign committed
#
# Loads a LightWave .lwo object file, including the vertex maps such as
# UV, Morph, Color and Weight maps.
#
# Will optionally create an Armature from an embedded Skelegon rig.
#
# Point orders are maintained so that .mdds can exchanged with other
# 3D programs.
#
#
# Notes:
# NGons, polygons with more than 4 points are supported, but are
# added (as triangles) after the vertex maps have been applied. Thus they
# won't contain all the vertex data that the original ngon had.
Ken Nign's avatar
Ken Nign committed
# Blender is limited to only 8 UV Texture and 8 Vertex Color maps,
# thus only the first 8 of each can be imported.
#
# History:
#
# 1.2 Added Absolute Morph and CC Edge Weight support.
#     Made edge creation safer.
# 1.0 First Release

Ken Nign's avatar
Ken Nign committed

import os
import io
import time
import struct
import chunk

import bpy
import mathutils
from mathutils.geometry import PolyFill
Ken Nign's avatar
Ken Nign committed


class _obj_layer(object):
    __slots__ = (
        "name",
        "index",
        "parent_index",
        "pivot",
        "pols",
        "bones",
        "bone_names",
        "bone_rolls",
        "pnts",
        "wmaps",
        "colmaps",
        "uvmaps",
        "morphs",
        "surf_tags",
        "has_subds",
        )
Ken Nign's avatar
Ken Nign committed
    def __init__(self):
        self.name= ""
        self.index= -1
        self.parent_index= -1
        self.pivot= [0, 0, 0]
Ken Nign's avatar
Ken Nign committed
        self.pols= []
        self.bones= []
        self.bone_names= {}
        self.bone_rolls= {}
        self.pnts= []
        self.wmaps= {}
        self.colmaps= {}
        self.uvmaps= {}
        self.morphs= {}
        self.edge_weights= {}
Ken Nign's avatar
Ken Nign committed
        self.surf_tags= {}
        self.has_subds= False


class _obj_surf(object):
    __slots__ = (
        "bl_mat",
        "name",
        "source_name",
        "colr",
        "diff",
        "lumi",
        "spec",
        "refl",
        "rblr",
        "tran",
        "rind",
        "tblr",
        "trnl",
        "glos",
        "shrp",
        "smooth",
        )

Ken Nign's avatar
Ken Nign committed
    def __init__(self):
        self.bl_mat= None
        self.name= "Default"
        self.source_name= ""
        self.colr= [1.0, 1.0, 1.0]
        self.diff= 1.0   # Diffuse
        self.lumi= 0.0   # Luminosity
        self.spec= 0.0   # Specular
        self.refl= 0.0   # Reflectivity
        self.rblr= 0.0   # Reflection Bluring
        self.tran= 0.0   # Transparency (the opposite of Blender's Alpha value)
        self.rind= 1.0   # RT Transparency IOR
        self.tblr= 0.0   # Refraction Bluring
        self.trnl= 0.0   # Translucency
        self.glos= 0.4   # Glossiness
        self.shrp= 0.0   # Diffuse Sharpness
        self.smooth= False  # Surface Smoothing
Ken Nign's avatar
Ken Nign committed

def load_lwo(filename,
             context,
             ADD_SUBD_MOD=True,
             LOAD_HIDDEN=False,
             SKEL_TO_ARM=True):
Ken Nign's avatar
Ken Nign committed
    '''Read the LWO file, hand off to version specific function.'''
    name, ext= os.path.splitext(os.path.basename(filename))
    file= open(filename, 'rb')
Ken Nign's avatar
Ken Nign committed
    try:
        header, chunk_size, chunk_name = struct.unpack(">4s1L4s", file.read(12))
    except:
        print("Error parsing file header!")
        file.close()
        return
Ken Nign's avatar
Ken Nign committed
    layers= []
    surfs= {}
    tags= []
    # Gather the object data using the version specific handler.
    if chunk_name == b'LWO2':
        read_lwo2(file, filename, layers, surfs, tags, ADD_SUBD_MOD, LOAD_HIDDEN, SKEL_TO_ARM)
    elif chunk_name == b'LWOB' or chunk_name == b'LWLO':
        # LWOB and LWLO are the old format, LWLO is a layered object.
Ken Nign's avatar
Ken Nign committed
        read_lwob(file, filename, layers, surfs, tags, ADD_SUBD_MOD)
    else:
        print("Not a supported file type!")
        file.close()
        return
Ken Nign's avatar
Ken Nign committed
    file.close()
Ken Nign's avatar
Ken Nign committed
    # With the data gathered, build the object(s).
    build_objects(layers, surfs, tags, name, ADD_SUBD_MOD, SKEL_TO_ARM)
Ken Nign's avatar
Ken Nign committed
    layers= None
    surfs.clear()
    tags= None


def read_lwo2(file, filename, layers, surfs, tags, add_subd_mod, load_hidden, skel_to_arm):
    '''Read version 2 file, LW 6+.'''
    handle_layer= True
    last_pols_count= 0
    just_read_bones= False
    print("Importing LWO: " + filename + "\nLWO v2 Format")
Ken Nign's avatar
Ken Nign committed
    while True:
        try:
            rootchunk = chunk.Chunk(file)
        except EOFError:
            break
Ken Nign's avatar
Ken Nign committed
        if rootchunk.chunkname == b'TAGS':
            read_tags(rootchunk.read(), tags)
        elif rootchunk.chunkname == b'LAYR':
            handle_layer= read_layr(rootchunk.read(), layers, load_hidden)
        elif rootchunk.chunkname == b'PNTS' and handle_layer:
            read_pnts(rootchunk.read(), layers)
        elif rootchunk.chunkname == b'VMAP' and handle_layer:
            vmap_type = rootchunk.read(4)
Ken Nign's avatar
Ken Nign committed
            if vmap_type == b'WGHT':
                read_weightmap(rootchunk.read(), layers)
            elif vmap_type == b'MORF':
                read_morph(rootchunk.read(), layers, False)
            elif vmap_type == b'SPOT':
                read_morph(rootchunk.read(), layers, True)
Ken Nign's avatar
Ken Nign committed
            elif vmap_type == b'TXUV':
                read_uvmap(rootchunk.read(), layers)
            elif vmap_type == b'RGB ' or vmap_type == b'RGBA':
                read_colmap(rootchunk.read(), layers)
            else:
                rootchunk.skip()
Ken Nign's avatar
Ken Nign committed
        elif rootchunk.chunkname == b'VMAD' and handle_layer:
            vmad_type= rootchunk.read(4)
Ken Nign's avatar
Ken Nign committed
            if vmad_type == b'TXUV':
                read_uv_vmad(rootchunk.read(), layers, last_pols_count)
            elif vmad_type == b'RGB ' or vmad_type == b'RGBA':
                read_color_vmad(rootchunk.read(), layers, last_pols_count)
            elif vmad_type == b'WGHT':
                # We only read the Edge Weight map if it's there.
                read_weight_vmad(rootchunk.read(), layers)
Ken Nign's avatar
Ken Nign committed
            else:
                rootchunk.skip()
Ken Nign's avatar
Ken Nign committed
        elif rootchunk.chunkname == b'POLS' and handle_layer:
            face_type = rootchunk.read(4)
            just_read_bones= False
            # PTCH is LW's Subpatches, SUBD is CatmullClark.
            if (face_type == b'FACE' or face_type == b'PTCH' or
                face_type == b'SUBD') and handle_layer:
Ken Nign's avatar
Ken Nign committed
                last_pols_count= read_pols(rootchunk.read(), layers)
                if face_type != b'FACE':
                    layers[-1].has_subds= True
            elif face_type == b'BONE' and handle_layer:
                read_bones(rootchunk.read(), layers)
                just_read_bones= True
            else:
                rootchunk.skip()
Ken Nign's avatar
Ken Nign committed
        elif rootchunk.chunkname == b'PTAG' and handle_layer:
            tag_type,= struct.unpack("4s", rootchunk.read(4))
Ken Nign's avatar
Ken Nign committed
            if tag_type == b'SURF' and not just_read_bones:
                # Ignore the surface data if we just read a bones chunk.
Ken Nign's avatar
Ken Nign committed
                read_surf_tags(rootchunk.read(), layers, last_pols_count)
Ken Nign's avatar
Ken Nign committed
            elif skel_to_arm:
                if tag_type == b'BNUP':
                    read_bone_tags(rootchunk.read(), layers, tags, 'BNUP')
                elif tag_type == b'BONE':
                    read_bone_tags(rootchunk.read(), layers, tags, 'BONE')
                else:
                    rootchunk.skip()
            else:
                rootchunk.skip()
        elif rootchunk.chunkname == b'SURF':
            read_surf(rootchunk.read(), surfs)
        else:
            #if handle_layer:
                #print("Skipping Chunk:", rootchunk.chunkname)
Ken Nign's avatar
Ken Nign committed
            rootchunk.skip()


def read_lwob(file, filename, layers, surfs, tags, add_subd_mod):
    '''Read version 1 file, LW < 6.'''
    last_pols_count= 0
    print("Importing LWO: " + filename + "\nLWO v1 Format")
Ken Nign's avatar
Ken Nign committed
    while True:
        try:
            rootchunk = chunk.Chunk(file)
        except EOFError:
            break
Ken Nign's avatar
Ken Nign committed
        if rootchunk.chunkname == b'SRFS':
            read_tags(rootchunk.read(), tags)
        elif rootchunk.chunkname == b'LAYR':
            read_layr_5(rootchunk.read(), layers)
        elif rootchunk.chunkname == b'PNTS':
            if len(layers) == 0:
                # LWOB files have no LAYR chunk to set this up.
Ken Nign's avatar
Ken Nign committed
                nlayer= _obj_layer()
                nlayer.name= "Layer 1"
                layers.append(nlayer)
            read_pnts(rootchunk.read(), layers)
        elif rootchunk.chunkname == b'POLS':
            last_pols_count= read_pols_5(rootchunk.read(), layers)
        elif rootchunk.chunkname == b'PCHS':
            last_pols_count= read_pols_5(rootchunk.read(), layers)
            layers[-1].has_subds= True
        elif rootchunk.chunkname == b'PTAG':
            tag_type,= struct.unpack("4s", rootchunk.read(4))
Ken Nign's avatar
Ken Nign committed
            if tag_type == b'SURF':
                read_surf_tags_5(rootchunk.read(), layers, last_pols_count)
            else:
                rootchunk.skip()
        elif rootchunk.chunkname == b'SURF':
            read_surf_5(rootchunk.read(), surfs)
        else:
            # For Debugging \/.
            #if handle_layer:
                #print("Skipping Chunk: ", rootchunk.chunkname)
Ken Nign's avatar
Ken Nign committed
            rootchunk.skip()


def read_lwostring(raw_name):
    '''Parse a zero-padded string.'''

    i = raw_name.find(b'\0')
    name_len = i + 1
    if name_len % 2 == 1:   # Test for oddness.
        name_len += 1
Ken Nign's avatar
Ken Nign committed
    if i > 0:
        # Some plugins put non-text strings in the tags chunk.
        name = raw_name[0:i].decode("utf-8", "ignore")
    else:
        name = ""

    return name, name_len

Ken Nign's avatar
Ken Nign committed
def read_vx(pointdata):
    '''Read a variable-length index.'''
    if pointdata[0] != 255:
        index= pointdata[0]*256 + pointdata[1]
        size= 2
    else:
        index= pointdata[1]*65536 + pointdata[2]*256 + pointdata[3]
        size= 4
Ken Nign's avatar
Ken Nign committed
    return index, size

Ken Nign's avatar
Ken Nign committed
def read_tags(tag_bytes, object_tags):
    '''Read the object's Tags chunk.'''
    offset= 0
    chunk_len= len(tag_bytes)

    while offset < chunk_len:
        tag, tag_len= read_lwostring(tag_bytes[offset:])
        offset+= tag_len
        object_tags.append(tag)


def read_layr(layr_bytes, object_layers, load_hidden):
    '''Read the object's layer data.'''
    new_layr= _obj_layer()
    new_layr.index, flags= struct.unpack(">HH", layr_bytes[0:4])
Ken Nign's avatar
Ken Nign committed
    if flags > 0 and not load_hidden:
        return False
Ken Nign's avatar
Ken Nign committed
    print("Reading Object Layer")
    offset= 4
    pivot= struct.unpack(">fff", layr_bytes[offset:offset+12])
    # Swap Y and Z to match Blender's pitch.
    new_layr.pivot= [pivot[0], pivot[2], pivot[1]]
Ken Nign's avatar
Ken Nign committed
    offset+= 12
    layr_name, name_len = read_lwostring(layr_bytes[offset:])
    offset+= name_len
Ken Nign's avatar
Ken Nign committed
    if layr_name:
        new_layr.name= layr_name
    else:
        new_layr.name= "Layer %d" % (new_layr.index + 1)
Ken Nign's avatar
Ken Nign committed
    if len(layr_bytes) == offset+2:
        new_layr.parent_index,= struct.unpack(">h", layr_bytes[offset:offset+2])
Ken Nign's avatar
Ken Nign committed
    object_layers.append(new_layr)
    return True


def read_layr_5(layr_bytes, object_layers):
    '''Read the object's layer data.'''
    # XXX: Need to check what these two exactly mean for a LWOB/LWLO file.
    new_layr= _obj_layer()
    new_layr.index, flags= struct.unpack(">HH", layr_bytes[0:4])
Ken Nign's avatar
Ken Nign committed
    print("Reading Object Layer")
    offset= 4
    layr_name, name_len = read_lwostring(layr_bytes[offset:])
    offset+= name_len
Ken Nign's avatar
Ken Nign committed
    if name_len > 2 and layr_name != 'noname':
        new_layr.name= layr_name
    else:
        new_layr.name= "Layer %d" % new_layr.index
Ken Nign's avatar
Ken Nign committed
    object_layers.append(new_layr)
Ken Nign's avatar
Ken Nign committed

def read_pnts(pnt_bytes, object_layers):
    '''Read the layer's points.'''
    print("\tReading Layer ("+object_layers[-1].name+") Points")
    offset= 0
    chunk_len= len(pnt_bytes)
Ken Nign's avatar
Ken Nign committed
    while offset < chunk_len:
        pnts= struct.unpack(">fff", pnt_bytes[offset:offset+12])
        offset+= 12
        # Re-order the points so that the mesh has the right pitch,
        # the pivot already has the correct order.
Ken Nign's avatar
Ken Nign committed
        pnts= [pnts[0] - object_layers[-1].pivot[0],\
               pnts[2] - object_layers[-1].pivot[1],\
               pnts[1] - object_layers[-1].pivot[2]]
Ken Nign's avatar
Ken Nign committed
        object_layers[-1].pnts.append(pnts)


def read_weightmap(weight_bytes, object_layers):
    '''Read a weight map's values.'''
    chunk_len= len(weight_bytes)
    offset= 2
    name, name_len= read_lwostring(weight_bytes[offset:])
    offset+= name_len
    weights= []
Ken Nign's avatar
Ken Nign committed
    while offset < chunk_len:
        pnt_id, pnt_id_len= read_vx(weight_bytes[offset:offset+4])
        offset+= pnt_id_len
        value,= struct.unpack(">f", weight_bytes[offset:offset+4])
        offset+= 4
        weights.append([pnt_id, value])
Ken Nign's avatar
Ken Nign committed
    object_layers[-1].wmaps[name]= weights


def read_morph(morph_bytes, object_layers, is_abs):
    '''Read an endomorph's relative or absolute displacement values.'''
Ken Nign's avatar
Ken Nign committed
    chunk_len= len(morph_bytes)
    offset= 2
    name, name_len= read_lwostring(morph_bytes[offset:])
    offset+= name_len
    deltas= []
Ken Nign's avatar
Ken Nign committed
    while offset < chunk_len:
        pnt_id, pnt_id_len= read_vx(morph_bytes[offset:offset+4])
        offset+= pnt_id_len
        pos= struct.unpack(">fff", morph_bytes[offset:offset+12])
Ken Nign's avatar
Ken Nign committed
        offset+= 12
        pnt= object_layers[-1].pnts[pnt_id]

        if is_abs:
            deltas.append([pnt_id, pos[0], pos[2], pos[1]])
        else:
            # Swap the Y and Z to match Blender's pitch.
            deltas.append([pnt_id, pnt[0]+pos[0], pnt[1]+pos[2], pnt[2]+pos[1]])

        object_layers[-1].morphs[name]= deltas

Ken Nign's avatar
Ken Nign committed

def read_colmap(col_bytes, object_layers):
    '''Read the RGB or RGBA color map.'''
    chunk_len= len(col_bytes)
    dia,= struct.unpack(">H", col_bytes[0:2])
    offset= 2
    name, name_len= read_lwostring(col_bytes[offset:])
    offset+= name_len
    colors= {}
Ken Nign's avatar
Ken Nign committed
    if dia == 3:
        while offset < chunk_len:
            pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
            offset+= pnt_id_len
            col= struct.unpack(">fff", col_bytes[offset:offset+12])
Ken Nign's avatar
Ken Nign committed
            offset+= 12
            colors[pnt_id]= (col[0], col[1], col[2])
    elif dia == 4:
        while offset < chunk_len:
            pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
            offset+= pnt_id_len
            col= struct.unpack(">ffff", col_bytes[offset:offset+16])
Ken Nign's avatar
Ken Nign committed
            offset+= 16
            colors[pnt_id]= (col[0], col[1], col[2])
Ken Nign's avatar
Ken Nign committed
    if name in object_layers[-1].colmaps:
        if "PointMap" in object_layers[-1].colmaps[name]:
            object_layers[-1].colmaps[name]["PointMap"].update(colors)
        else:
            object_layers[-1].colmaps[name]["PointMap"]= colors
    else:
        object_layers[-1].colmaps[name]= dict(PointMap=colors)
Ken Nign's avatar
Ken Nign committed


def read_color_vmad(col_bytes, object_layers, last_pols_count):
    '''Read the Discontinous (per-polygon) RGB values.'''
    chunk_len= len(col_bytes)
    dia,= struct.unpack(">H", col_bytes[0:2])
    offset= 2
    name, name_len= read_lwostring(col_bytes[offset:])
    offset+= name_len
    colors= {}
    abs_pid= len(object_layers[-1].pols) - last_pols_count
Ken Nign's avatar
Ken Nign committed
    if dia == 3:
        while offset < chunk_len:
            pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
            offset+= pnt_id_len
            pol_id, pol_id_len= read_vx(col_bytes[offset:offset+4])
            offset+= pol_id_len
Ken Nign's avatar
Ken Nign committed
            # The PolyID in a VMAD can be relative, this offsets it.
            pol_id+= abs_pid
            col= struct.unpack(">fff", col_bytes[offset:offset+12])
Ken Nign's avatar
Ken Nign committed
            offset+= 12
            if pol_id in colors:
                colors[pol_id][pnt_id]= (col[0], col[1], col[2])
            else:
                colors[pol_id]= dict({pnt_id: (col[0], col[1], col[2])})
    elif dia == 4:
        while offset < chunk_len:
            pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
            offset+= pnt_id_len
            pol_id, pol_id_len= read_vx(col_bytes[offset:offset+4])
            offset+= pol_id_len
Ken Nign's avatar
Ken Nign committed
            pol_id+= abs_pid
            col= struct.unpack(">ffff", col_bytes[offset:offset+16])
Ken Nign's avatar
Ken Nign committed
            offset+= 16
            if pol_id in colors:
                colors[pol_id][pnt_id]= (col[0], col[1], col[2])
            else:
                colors[pol_id]= dict({pnt_id: (col[0], col[1], col[2])})
Ken Nign's avatar
Ken Nign committed
    if name in object_layers[-1].colmaps:
        if "FaceMap" in object_layers[-1].colmaps[name]:
            object_layers[-1].colmaps[name]["FaceMap"].update(colors)
        else:
            object_layers[-1].colmaps[name]["FaceMap"]= colors
    else:
        object_layers[-1].colmaps[name]= dict(FaceMap=colors)

Ken Nign's avatar
Ken Nign committed

def read_uvmap(uv_bytes, object_layers):
    '''Read the simple UV coord values.'''
    chunk_len= len(uv_bytes)
    offset= 2
    name, name_len= read_lwostring(uv_bytes[offset:])
    offset+= name_len
    uv_coords= {}
Ken Nign's avatar
Ken Nign committed
    while offset < chunk_len:
        pnt_id, pnt_id_len= read_vx(uv_bytes[offset:offset+4])
        offset+= pnt_id_len
        pos= struct.unpack(">ff", uv_bytes[offset:offset+8])
Ken Nign's avatar
Ken Nign committed
        offset+= 8
        uv_coords[pnt_id]= (pos[0], pos[1])
Ken Nign's avatar
Ken Nign committed
    if name in object_layers[-1].uvmaps:
        if "PointMap" in object_layers[-1].uvmaps[name]:
            object_layers[-1].uvmaps[name]["PointMap"].update(uv_coords)
        else:
            object_layers[-1].uvmaps[name]["PointMap"]= uv_coords
    else:
        object_layers[-1].uvmaps[name]= dict(PointMap=uv_coords)
Ken Nign's avatar
Ken Nign committed


def read_uv_vmad(uv_bytes, object_layers, last_pols_count):
    '''Read the Discontinous (per-polygon) uv values.'''
    chunk_len= len(uv_bytes)
    offset= 2
    name, name_len= read_lwostring(uv_bytes[offset:])
    offset+= name_len
    uv_coords= {}
    abs_pid= len(object_layers[-1].pols) - last_pols_count
Ken Nign's avatar
Ken Nign committed
    while offset < chunk_len:
        pnt_id, pnt_id_len= read_vx(uv_bytes[offset:offset+4])
        offset+= pnt_id_len
        pol_id, pol_id_len= read_vx(uv_bytes[offset:offset+4])
        offset+= pol_id_len
Ken Nign's avatar
Ken Nign committed
        pol_id+= abs_pid
        pos= struct.unpack(">ff", uv_bytes[offset:offset+8])
Ken Nign's avatar
Ken Nign committed
        offset+= 8
        if pol_id in uv_coords:
            uv_coords[pol_id][pnt_id]= (pos[0], pos[1])
        else:
            uv_coords[pol_id]= dict({pnt_id: (pos[0], pos[1])})
Ken Nign's avatar
Ken Nign committed
    if name in object_layers[-1].uvmaps:
        if "FaceMap" in object_layers[-1].uvmaps[name]:
            object_layers[-1].uvmaps[name]["FaceMap"].update(uv_coords)
        else:
            object_layers[-1].uvmaps[name]["FaceMap"]= uv_coords
    else:
        object_layers[-1].uvmaps[name]= dict(FaceMap=uv_coords)


def read_weight_vmad(ew_bytes, object_layers):
    '''Read the VMAD Weight values.'''
    chunk_len= len(ew_bytes)
    offset= 2
    name, name_len= read_lwostring(ew_bytes[offset:])
    if name != "Edge Weight":
        return  # We just want the Catmull-Clark edge weights

    offset+= name_len
    prev_pol= -1
    prev_pnt= -1
    prev_weight= 0.0
    first_pnt= -1
    poly_pnts= 0
    while offset < chunk_len:
        pnt_id, pnt_id_len= read_vx(ew_bytes[offset:offset+4])
        offset+= pnt_id_len
        pol_id, pol_id_len= read_vx(ew_bytes[offset:offset+4])
        offset+= pol_id_len

        weight,= struct.unpack(">f", ew_bytes[offset:offset+4])
        offset+= 4
        if prev_pol == pol_id:
            # Points on the same poly should define an edge.
            object_layers[-1].edge_weights["{0} {1}".format(prev_pnt, pnt_id)]= weight
            poly_pnts += 1
        else:
            if poly_pnts > 2:
                # Make an edge from the first and last points.
                object_layers[-1].edge_weights["{0} {1}".format(first_pnt, prev_pnt)]= prev_weight
            first_pnt= pnt_id
            prev_pol= pol_id
            poly_pnts= 1

        prev_pnt= pnt_id
        prev_weight= weight

    if poly_pnts > 2:
        object_layers[-1].edge_weights["{0} {1}".format(first_pnt, prev_pnt)]= prev_weight
Ken Nign's avatar
Ken Nign committed


def read_pols(pol_bytes, object_layers):
    '''Read the layer's polygons, each one is just a list of point indexes.'''
    print("\tReading Layer ("+object_layers[-1].name+") Polygons")
    offset= 0
    pols_count = len(pol_bytes)
    old_pols_count= len(object_layers[-1].pols)
Ken Nign's avatar
Ken Nign committed
    while offset < pols_count:
        pnts_count,= struct.unpack(">H", pol_bytes[offset:offset+2])
        offset+= 2
        all_face_pnts= []
        for j in range(pnts_count):
            face_pnt, data_size= read_vx(pol_bytes[offset:offset+4])
            offset+= data_size
            all_face_pnts.append(face_pnt)
Ken Nign's avatar
Ken Nign committed
        object_layers[-1].pols.append(all_face_pnts)
Ken Nign's avatar
Ken Nign committed
    return len(object_layers[-1].pols) - old_pols_count


def read_pols_5(pol_bytes, object_layers):
    '''
    Read the polygons, each one is just a list of point indexes.
    But it also includes the surface index.
    '''
    print("\tReading Layer ("+object_layers[-1].name+") Polygons")
    offset= 0
    chunk_len= len(pol_bytes)
    old_pols_count= len(object_layers[-1].pols)
    poly= 0
Ken Nign's avatar
Ken Nign committed
    while offset < chunk_len:
        pnts_count,= struct.unpack(">H", pol_bytes[offset:offset+2])
        offset+= 2
        all_face_pnts= []
        for j in range(pnts_count):
            face_pnt,= struct.unpack(">H", pol_bytes[offset:offset+2])
            offset+= 2
            all_face_pnts.append(face_pnt)
Ken Nign's avatar
Ken Nign committed
        object_layers[-1].pols.append(all_face_pnts)
        sid,= struct.unpack(">h", pol_bytes[offset:offset+2])
        offset+= 2
        sid= abs(sid) - 1
        if sid not in object_layers[-1].surf_tags:
            object_layers[-1].surf_tags[sid]= []
        object_layers[-1].surf_tags[sid].append(poly)
        poly+= 1
Ken Nign's avatar
Ken Nign committed
    return len(object_layers[-1].pols) - old_pols_count


def read_bones(bone_bytes, object_layers):
    '''Read the layer's skelegons.'''
    print("\tReading Layer ("+object_layers[-1].name+") Bones")
    offset= 0
    bones_count = len(bone_bytes)
Ken Nign's avatar
Ken Nign committed
    while offset < bones_count:
        pnts_count,= struct.unpack(">H", bone_bytes[offset:offset+2])
        offset+= 2
        all_bone_pnts= []
        for j in range(pnts_count):
            bone_pnt, data_size= read_vx(bone_bytes[offset:offset+4])
            offset+= data_size
            all_bone_pnts.append(bone_pnt)
Ken Nign's avatar
Ken Nign committed
        object_layers[-1].bones.append(all_bone_pnts)
Ken Nign's avatar
Ken Nign committed
def read_bone_tags(tag_bytes, object_layers, object_tags, type):
    '''Read the bone name or roll tags.'''
    offset= 0
    chunk_len= len(tag_bytes)
Ken Nign's avatar
Ken Nign committed
    if type == 'BONE':
        bone_dict= object_layers[-1].bone_names
    elif type == 'BNUP':
        bone_dict= object_layers[-1].bone_rolls
    else:
        return
Ken Nign's avatar
Ken Nign committed
    while offset < chunk_len:
        pid, pid_len= read_vx(tag_bytes[offset:offset+4])
        offset+= pid_len
        tid,= struct.unpack(">H", tag_bytes[offset:offset+2])
        offset+= 2
        bone_dict[pid]= object_tags[tid]
Ken Nign's avatar
Ken Nign committed
def read_surf_tags(tag_bytes, object_layers, last_pols_count):
    '''Read the list of PolyIDs and tag indexes.'''
    print("\tReading Layer ("+object_layers[-1].name+") Surface Assignments")
    offset= 0
    chunk_len= len(tag_bytes)
Ken Nign's avatar
Ken Nign committed
    # Read in the PolyID/Surface Index pairs.
    abs_pid= len(object_layers[-1].pols) - last_pols_count
    while offset < chunk_len:
        pid, pid_len= read_vx(tag_bytes[offset:offset+4])
        offset+= pid_len
        sid,= struct.unpack(">H", tag_bytes[offset:offset+2])
        offset+=2
        if sid not in object_layers[-1].surf_tags:
            object_layers[-1].surf_tags[sid]= []
        object_layers[-1].surf_tags[sid].append(pid + abs_pid)
Ken Nign's avatar
Ken Nign committed

def read_surf(surf_bytes, object_surfs):
    '''Read the object's surface data.'''
    if len(object_surfs) == 0:
        print("Reading Object Surfaces")
Ken Nign's avatar
Ken Nign committed
    surf= _obj_surf()
    name, name_len= read_lwostring(surf_bytes)
    if len(name) != 0:
        surf.name = name
Ken Nign's avatar
Ken Nign committed
    # We have to read this, but we won't use it...yet.
    s_name, s_name_len= read_lwostring(surf_bytes[name_len:])
    offset= name_len+s_name_len
    block_size= len(surf_bytes)
    while offset < block_size:
        subchunk_name,= struct.unpack("4s", surf_bytes[offset:offset+4])
        offset+= 4
        subchunk_len,= struct.unpack(">H", surf_bytes[offset:offset+2])
        offset+= 2
Ken Nign's avatar
Ken Nign committed
        # Now test which subchunk it is.
        if subchunk_name == b'COLR':
            surf.colr= struct.unpack(">fff", surf_bytes[offset:offset+12])
            # Don't bother with any envelopes for now.
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'DIFF':
            surf.diff,= struct.unpack(">f", surf_bytes[offset:offset+4])

        elif subchunk_name == b'LUMI':
            surf.lumi,= struct.unpack(">f", surf_bytes[offset:offset+4])
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'SPEC':
            surf.spec,= struct.unpack(">f", surf_bytes[offset:offset+4])
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'REFL':
            surf.refl,= struct.unpack(">f", surf_bytes[offset:offset+4])
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'RBLR':
            surf.rblr,= struct.unpack(">f", surf_bytes[offset:offset+4])

        elif subchunk_name == b'TRAN':
            surf.tran,= struct.unpack(">f", surf_bytes[offset:offset+4])
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'RIND':
            surf.rind,= struct.unpack(">f", surf_bytes[offset:offset+4])
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'TBLR':
            surf.tblr,= struct.unpack(">f", surf_bytes[offset:offset+4])
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'TRNL':
            surf.trnl,= struct.unpack(">f", surf_bytes[offset:offset+4])
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'GLOS':
            surf.glos,= struct.unpack(">f", surf_bytes[offset:offset+4])
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'SHRP':
            surf.shrp,= struct.unpack(">f", surf_bytes[offset:offset+4])
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'SMAN':
            s_angle,= struct.unpack(">f", surf_bytes[offset:offset+4])
            if s_angle > 0.0:
                surf.smooth = True
Ken Nign's avatar
Ken Nign committed
        offset+= subchunk_len
Ken Nign's avatar
Ken Nign committed
    object_surfs[surf.name]= surf
Ken Nign's avatar
Ken Nign committed
def read_surf_5(surf_bytes, object_surfs):
    '''Read the object's surface data.'''
    if len(object_surfs) == 0:
        print("Reading Object Surfaces")
Ken Nign's avatar
Ken Nign committed
    surf= _obj_surf()
    name, name_len= read_lwostring(surf_bytes)
    if len(name) != 0:
        surf.name = name
Ken Nign's avatar
Ken Nign committed
    offset= name_len
    chunk_len= len(surf_bytes)
    while offset < chunk_len:
        subchunk_name,= struct.unpack("4s", surf_bytes[offset:offset+4])
        offset+= 4
        subchunk_len,= struct.unpack(">H", surf_bytes[offset:offset+2])
        offset+= 2
Ken Nign's avatar
Ken Nign committed
        # Now test which subchunk it is.
        if subchunk_name == b'COLR':
            color= struct.unpack(">BBBB", surf_bytes[offset:offset+4])
            surf.colr= [color[0] / 255.0, color[1] / 255.0, color[2] / 255.0]
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'DIFF':
            surf.diff,= struct.unpack(">h", surf_bytes[offset:offset+2])
            surf.diff/= 256.0    # Yes, 256 not 255.

        elif subchunk_name == b'LUMI':
            surf.lumi,= struct.unpack(">h", surf_bytes[offset:offset+2])
            surf.lumi/= 256.0
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'SPEC':
            surf.spec,= struct.unpack(">h", surf_bytes[offset:offset+2])
            surf.spec/= 256.0
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'REFL':
            surf.refl,= struct.unpack(">h", surf_bytes[offset:offset+2])
            surf.refl/= 256.0

        elif subchunk_name == b'TRAN':
            surf.tran,= struct.unpack(">h", surf_bytes[offset:offset+2])
            surf.tran/= 256.0
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'RIND':
            surf.rind,= struct.unpack(">f", surf_bytes[offset:offset+4])
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'GLOS':
            surf.glos,= struct.unpack(">h", surf_bytes[offset:offset+2])
Ken Nign's avatar
Ken Nign committed
        elif subchunk_name == b'SMAN':
            s_angle,= struct.unpack(">f", surf_bytes[offset:offset+4])
            if s_angle > 0.0:
                surf.smooth = True
Ken Nign's avatar
Ken Nign committed
        offset+= subchunk_len
Ken Nign's avatar
Ken Nign committed
    object_surfs[surf.name]= surf


def create_mappack(data, map_name, map_type):
    '''Match the map data to faces.'''
    pack= {}
Ken Nign's avatar
Ken Nign committed
    def color_pointmap(map):
        for fi in range(len(data.pols)):
            if fi not in pack:
                pack[fi]= []
            for pnt in data.pols[fi]:
                if pnt in map:
                    pack[fi].append(map[pnt])
                else:
                    pack[fi].append((1.0, 1.0, 1.0))
Ken Nign's avatar
Ken Nign committed
    def color_facemap(map):
        for fi in range(len(data.pols)):
            if fi not in pack:
                pack[fi]= []
                for p in data.pols[fi]:
                    pack[fi].append((1.0, 1.0, 1.0))
            if fi in map:
                for po in range(len(data.pols[fi])):
                    if data.pols[fi][po] in map[fi]:
                        pack[fi].insert(po, map[fi][data.pols[fi][po]])
                        del pack[fi][po+1]
Ken Nign's avatar
Ken Nign committed
    def uv_pointmap(map):
        for fi in range(len(data.pols)):
            if fi not in pack:
                pack[fi]= []
                for p in data.pols[fi]:
                    pack[fi].append((-0.1,-0.1))
            for po in range(len(data.pols[fi])):
                pnt_id= data.pols[fi][po]
                if pnt_id in map:
                    pack[fi].insert(po, map[pnt_id])
                    del pack[fi][po+1]
Ken Nign's avatar
Ken Nign committed
    def uv_facemap(map):
        for fi in range(len(data.pols)):
            if fi not in pack:
                pack[fi]= []
                for p in data.pols[fi]:
                    pack[fi].append((-0.1,-0.1))
            if fi in map:
                for po in range(len(data.pols[fi])):
                    pnt_id= data.pols[fi][po]
                    if pnt_id in map[fi]:
                        pack[fi].insert(po, map[fi][pnt_id])
                        del pack[fi][po+1]
Ken Nign's avatar
Ken Nign committed
    if map_type == "COLOR":
        # Look at the first map, is it a point or face map
        if "PointMap" in data.colmaps[map_name]:
            color_pointmap(data.colmaps[map_name]["PointMap"])
Ken Nign's avatar
Ken Nign committed
        if "FaceMap" in data.colmaps[map_name]:
            color_facemap(data.colmaps[map_name]["FaceMap"])
    elif map_type == "UV":
        if "PointMap" in data.uvmaps[map_name]:
            uv_pointmap(data.uvmaps[map_name]["PointMap"])
Ken Nign's avatar
Ken Nign committed
        if "FaceMap" in data.uvmaps[map_name]:
            uv_facemap(data.uvmaps[map_name]["FaceMap"])

    return pack
Ken Nign's avatar
Ken Nign committed
def build_armature(layer_data, bones):
    '''Build an armature from the skelegon data in the mesh.'''
    print("Building Armature")

    # New Armatures include a default bone, remove it.
    bones.remove(bones[0])
Ken Nign's avatar
Ken Nign committed
    # Now start adding the bones at the point locations.
    prev_bone= None
    for skb_idx in range(len(layer_data.bones)):
        if skb_idx in layer_data.bone_names:
            nb= bones.new(layer_data.bone_names[skb_idx])
        else:
            nb= bones.new("Bone")
Ken Nign's avatar
Ken Nign committed
        nb.head= layer_data.pnts[layer_data.bones[skb_idx][0]]
        nb.tail= layer_data.pnts[layer_data.bones[skb_idx][1]]
Ken Nign's avatar
Ken Nign committed
        if skb_idx in layer_data.bone_rolls:
            xyz= layer_data.bone_rolls[skb_idx].split(' ')
Ken Nign's avatar
Ken Nign committed
            vec= mathutils.Vector((float(xyz[0]), float(xyz[1]), float(xyz[2])))
Ken Nign's avatar
Ken Nign committed
            quat= vec.to_track_quat('Y', 'Z')
Ken Nign's avatar
Ken Nign committed
            nb.roll= max(quat.to_euler('YZX'))
            if nb.roll == 0.0:
                nb.roll= min(quat.to_euler('YZX')) * -1
            # YZX order seems to produce the correct roll value.
Ken Nign's avatar
Ken Nign committed
        else:
            nb.roll= 0.0
Ken Nign's avatar
Ken Nign committed
        if prev_bone != None:
            if nb.head == prev_bone.tail:
                nb.parent= prev_bone
Ken Nign's avatar
Ken Nign committed
        nb.use_connect= True
        prev_bone= nb
Ken Nign's avatar
Ken Nign committed
def build_objects(object_layers, object_surfs, object_tags, object_name, add_subd_mod, skel_to_arm):
    '''Using the gathered data, create the objects.'''
    ob_dict= {}  # Used for the parenting setup.
    print("Adding %d Materials" % len(object_surfs))
Ken Nign's avatar
Ken Nign committed

    for surf_key in object_surfs:
        surf_data= object_surfs[surf_key]
        surf_data.bl_mat= bpy.data.materials.new(surf_data.name)
        surf_data.bl_mat.diffuse_color= (surf_data.colr[:])
        surf_data.bl_mat.diffuse_intensity= surf_data.diff
        surf_data.bl_mat.emit= surf_data.lumi
        surf_data.bl_mat.specular_intensity= surf_data.spec
        if surf_data.refl != 0.0: