Skip to content
Snippets Groups Projects
io_import_scene_lwo.py 42.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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)",
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        "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/"\
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            "Scripts/Import-Export/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",
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        "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