#!BPY
"""
Name: 'LightWave + Materials (.lwo)...'
Blender: 239
Group: 'Import'
Tooltip: 'Import LightWave Object File Format (.lwo)'
"""

__author__ = "Alessandro Pirovano, Anthony D'Agostino (Scorpius)"
__url__ = ("blender", "elysiun",
"Anthony's homepage, http://www.redrival.com/scorpius", "Alessandro's homepage, http://uaraus.altervista.org")

importername = "lwo_import 0.2.2b"

# $Id: lightwave_import.py,v 1.9 2005/12/19 17:21:55 ianwill Exp $
#
# +---------------------------------------------------------+
# | Save your work before and after use.                    |
# | Please report any useful comment to:                    |
# | uaraus-dem@yahoo.it                                     |
# | Thanks                                                  |
# +---------------------------------------------------------+
# +---------------------------------------------------------+
# | Copyright (c) 2002 Anthony D'Agostino                   |
# | http://www.redrival.com/scorpius                        |
# | scorpius@netzero.com                                    |
# | April 21, 2002                                          |
# | Import Export Suite v0.5                                |
# +---------------------------------------------------------+
# | Read and write LightWave Object File Format (*.lwo)     |
# +---------------------------------------------------------+
# +---------------------------------------------------------+
# | Alessandro Pirovano tweaked starting on March 2005      |
# | http://uaraus.altervista.org                            |
# +---------------------------------------------------------+
# +----------------------------------------------------------
# | 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
# +----------------------------------------------------------
# +---------------------------------------------------------+
# | Release log:                                            |
# | 0.2.1 : This code works with Blender 2.40 RC1           |
# |         modified material mode assignment to deal with  |
# |         Python API modification                         |
# |         Changed script license to GNU GPL               |
# | 0.2.0:  This code works with Blender 2.40a2 or up       |
# |         Major rewrite to deal with large meshes         |
# |         - 2 pass file parsing                           |
# |         - lower memory footprint                        |
# |           (as long as python gc allows)                 |
# |         2.40a2 - Removed subsurf settings patches=poly  |
# |         2.40a2 - Edge generation instead of 2vert faces |
# | 0.1.16: fixed (try 2) texture offset calculations       |
# |         added hint on axis mapping                      |
# |         added hint on texture blending mode             |
# |         added hint on texture transparency setting      |
# |         search images in original directory first       |
# |         fixed texture order application                 |
# | 0.1.15: added release log                               |
# |         fixed texture offset calculations (non-UV)      |
# |         fixed reverting vertex order in face generation |
# |         associate texture on game-engine settings       |
# |         vector math definitely based on mathutils       |
# |         search images in "Images" and "../Images" dir   |
# |         revised logging facility                        |
# |         fixed subsurf texture and material mappings     |
# | 0.1.14: patched missing mod_vector (not definitive)     |
# | 0.1.13: first public release                            |
# +---------------------------------------------------------+

#blender related import
import Blender

#iosuite related import
try: #new naming
    import meshtools as my_meshtools
except ImportError: #fallback to the old one
    print "using old mod_meshtools"
    import mod_meshtools as my_meshtools

#python specific modules import
import struct, chunk, os, cStringIO, time, operator, copy

# ===========================================================
# === Utility Preamble ======================================
# ===========================================================

textname = "lwo_log"
#uncomment the following line to disable logging facility
#textname = None

# ===========================================================


# ===========================================================
# === Make sure it is a string ... deal with strange chars ==
# ===========================================================
def safestring(st):
    myst = ""
    for ll in xrange(len(st)):
        if st[ll] < " ":
            myst += "#"
        else:
            myst += st[ll]
    return myst

class dotext:

    _NO = 0    #use internal to class only
    LOG = 1    #write only to LOG
    CON = 2    #write to both LOG and CONSOLE

    def __init__(self, tname, where=LOG):
        self.dwhere = where #defaults on console only
        if (tname==None):
            print "*** not using text object to log script"
            self.txtobj = None
            return
        tlist = Blender.Text.get()
        for i in xrange(len(tlist)):
            if (tlist[i].getName()==tname):
                tlist[i].clear()
                #print tname, " text object found and cleared!"
                self.txtobj = tlist[i]
                return
        #print tname, " text object not found and created!"
        self.txtobj = Blender.Text.New(tname)
    # end def __init__

    def write(self, wstring, maxlen=100):
        if (self.txtobj==None): return
        while (1):
            ll = len(wstring)
            if (ll>maxlen):
                self.txtobj.write((wstring[:maxlen]))
                self.txtobj.write("\n")
                wstring = (wstring[maxlen:])
            else:
                self.txtobj.write(wstring)
                break
    # end def write

    def pstring(self, ppstring, where = _NO):
        if where == dotext._NO: where = self.dwhere
        if where == dotext.CON:
            print ppstring
        self.write(ppstring)
        self.write("\n")
    # end def pstring

    def plist(self, pplist, where = _NO):
        self.pprint ("list:[")
        for pp in xrange(len(pplist)):
            self.pprint ("[%d] -> %s" % (pp, pplist[pp]), where)
        self.pprint ("]")
    # end def plist

    def pdict(self, pdict, where = _NO):
        self.pprint ("dict:{", where)
        for pp in pdict.keys():
            self.pprint ("[%s] -> %s" % (pp, pdict[pp]), where)
        self.pprint ("}")
    # end def pdict

    def pprint(self, parg, where = _NO):
        if parg == None:
            self.pstring("_None_", where)
        elif type(parg) == type ([]):
            self.plist(parg, where)
        elif type(parg) == type ({}):
            self.pdict(parg, where)
        else:
            self.pstring(safestring(str(parg)), where)
    # end def pprint

    def logcon(self, parg):
        self.pprint(parg, dotext.CON)
    # end def logcon
# endclass dotext

tobj=dotext(textname)
#uncomment the following line to log all messages on both console and logfile
#tobj=dotext(textname,dotext.CON)

def rlcopy(ll):
    if type(ll) != type ([]):
        return ll
    if ll == []:
        return []
    cpy = [rlcopy(ii) for ii in ll]
    return cpy

# ===========================================================
# === Main read functions ===================================
# ===========================================================

# =============================
# === Read LightWave Format ===
# =============================
def read(filename):
    global tobj

    tobj.logcon ("#####################################################################")
    tobj.logcon ("This is: %s" % importername)
    tobj.logcon ("Importing file:")
    tobj.logcon (filename)
    tobj.pprint ("#####################################################################")

    start = time.clock()
    file = open(filename, "rb")

    editmode = Blender.Window.EditMode()    # are we in edit mode?  If so ...
    if editmode: Blender.Window.EditMode(0) # leave edit mode before getting the mesh    # === LWO header ===

    form_id, form_size, form_type = struct.unpack(">4s1L4s",  file.read(12))
    if (form_type == "LWOB"):
        read_lwob(file, filename)
    elif (form_type == "LWO2"):
        read_lwo2(file, filename)
    else:
        tobj.logcon ("Can't read a file with the form_type: %s" %form_type)
        return

    Blender.Window.DrawProgressBar(1.0, "")    # clear progressbar
    file.close()
    end = time.clock()
    seconds = " in %.2f %s" % (end-start, "seconds")
    if form_type == "LWO2": fmt = " (v6.0 Format)"
    if form_type == "LWOB": fmt = " (v5.5 Format)"
    message = "Successfully imported " + os.path.basename(filename) + fmt + seconds
    #my_meshtools.print_boxed(message)
    tobj.pprint ("#####################################################################")
    tobj.logcon (message)
    tobj.logcon ("#####################################################################")
    if editmode: Blender.Window.EditMode(1)  # optional, just being nice


# enddef read


# =================================
# === Read LightWave 5.5 format ===
# =================================
def read_lwob(file, filename):
    global tobj

    tobj.logcon("LightWave 5.5 format")
    objname = os.path.splitext(os.path.basename(filename))[0]

    while 1:
        try:
            lwochunk = chunk.Chunk(file)
        except EOFError:
            break
        if lwochunk.chunkname == "LAYR":
            objname = read_layr(lwochunk)
        elif lwochunk.chunkname == "PNTS":                         # Verts
            verts = read_verts(lwochunk)
        elif lwochunk.chunkname == "POLS": # Faces v5.5
            faces = read_faces_5(lwochunk)
            my_meshtools.create_mesh(verts, faces, objname)
        else:                                                       # Misc Chunks
            lwochunk.skip()
    return
# enddef read_lwob


# =============================
# === Read LightWave Format ===
# =============================
def read_lwo2(file, filename, typ="LWO2"):
    global tobj

    tobj.logcon("LightWave 6 (and above) format")

    dir_part = Blender.sys.dirname(filename)
    fname_part = Blender.sys.basename(filename)
    ask_weird = 1

    #first initialization of data structures
    defaultname = os.path.splitext(fname_part)[0]
    tag_list = []              #tag list: global for the whole file?
    surf_list = []             #surf list: global for the whole file?
    clip_list = []             #clip list: global for the whole file?
    object_index = 0
    object_list = None
    objspec_list = None
    # init value is: object_list = [[None, {}, [], [], {}, {}, 0, {}, {}]]
    #0 - objname                    #original name
    #1 - obj_dict = {TAG}           #objects created
    #2 - verts = []                 #object vertexes
    #3 - faces = []                 #object faces (associations poly -> vertexes)
    #4 - obj_dim_dict = {TAG}       #tuples size and pos in local object coords - used for NON-UV mappings
    #5 - polytag_dict = {TAG}       #tag to polygons mapping
    #6 - patch_flag                 #0 = surf; 1 = patch (subdivision surface) - it was the image list
    #7 - uvcoords_dict = {name}     #uvmap coordinates (mixed mode per vertex/per face)
    #8 - facesuv_dict = {name}      #vmad only coordinates associations poly & vertex -> uv tuples

    #pass 1: look in advance for materials
    tobj.logcon ("#####################################################################")
    tobj.logcon ("Starting Pass 1: hold on tight")
    tobj.logcon ("#####################################################################")
    while 1:
        try:
            lwochunk = chunk.Chunk(file)
        except EOFError:
            break
        tobj.pprint(" ")
        if lwochunk.chunkname == "TAGS":                         # Tags
            tobj.pprint("---- TAGS")
            tag_list.extend(read_tags(lwochunk))
        elif lwochunk.chunkname == "SURF":                         # surfaces
            tobj.pprint("---- SURF")
            surf_list.append(read_surfs(lwochunk, surf_list, tag_list))
        elif lwochunk.chunkname == "CLIP":                         # texture images
            tobj.pprint("---- CLIP")
            clip_list.append(read_clip(lwochunk, dir_part))
            tobj.pprint("read total %s clips up to now" % len(clip_list))
        else:                                                       # Misc Chunks
            if ask_weird:
                ckname = safestring(lwochunk.chunkname)
                if "#" in ckname:
                    choice = Blender.Draw.PupMenu("WARNING: file could be corrupted.%t|Import anyway|Give up")
                    if choice != 1:
                        tobj.logcon("---- %s: Maybe file corrupted. Terminated by user" % lwochunk.chunkname)
                        return
                    ask_weird = 0
            tobj.pprint("---- %s: skipping (maybe later)" % lwochunk.chunkname)
            lwochunk.skip()

    #add default material for orphaned faces, if any
    surf_list.append({'NAME': "_Orphans", 'g_MAT': Blender.Material.New("_Orphans")})

    #pass 2: effectively generate objects
    tobj.logcon ("#####################################################################")
    tobj.logcon ("Pass 2: now for the hard part")
    tobj.logcon ("#####################################################################")
    file.seek(0)
    # === LWO header ===
    form_id, form_size, form_type = struct.unpack(">4s1L4s",  file.read(12))
    if (form_type != "LWO2"):
        tobj.logcon ("??? Inconsistent file type: %s" %form_type)
        return
    while 1:
        try:
            lwochunk = chunk.Chunk(file)
        except EOFError:
            break
        tobj.pprint(" ")
        if lwochunk.chunkname == "LAYR":
            tobj.pprint("---- LAYR")
            objname = read_layr(lwochunk)
            tobj.pprint(objname)
            if objspec_list != None: #create the object
                create_objects(clip_list, objspec_list, surf_list)
                update_material(clip_list, objspec_list, surf_list) #give it all the object
            objspec_list = [objname, {}, [], [], {}, {}, 0, {}, {}]
            object_index += 1
        elif lwochunk.chunkname == "PNTS":                         # Verts
            tobj.pprint("---- PNTS")
            verts = read_verts(lwochunk)
            objspec_list[2] = verts
        elif lwochunk.chunkname == "VMAP":                         # MAPS (UV)
            tobj.pprint("---- VMAP")
            #objspec_list[7] = read_vmap(objspec_list[7], len(objspec_list[2]), lwochunk)
            read_vmap(objspec_list[7], len(objspec_list[2]), lwochunk)
        elif lwochunk.chunkname == "VMAD":                         # MAPS (UV) per-face
            tobj.pprint("---- VMAD")
            #objspec_list[7], objspec_list[8] = read_vmad(objspec_list[7], objspec_list[8], len(objspec_list[3]), len(objspec_list[2]), lwochunk)
            read_vmad(objspec_list[7], objspec_list[8], len(objspec_list[3]), len(objspec_list[2]), lwochunk)
        elif lwochunk.chunkname == "POLS": # Faces v6.0
            tobj.pprint("-------- POLS(6)")
            faces, flag = read_faces_6(lwochunk)
            #flag is 0 for regular polygon, 1 for patches (= subsurf), 2 for anything else to be ignored
            if flag<2:
                if objspec_list[3] != []:
                    #create immediately the object
                    create_objects(clip_list, objspec_list, surf_list)
                    update_material(clip_list, objspec_list, surf_list) #give it all the object
                    #update with new data
                    objspec_list = [objspec_list[0],                  #update name
                                    {},                               #init
                                    objspec_list[2],                  #same vertexes
                                    faces,                            #give it the new faces
                                    {},                               #no need to copy - filled at runtime
                                    {},                               #polygon tagging will follow
                                    flag,                             #patch flag
                                    objspec_list[7],                  #same uvcoords
                                    {}]                               #no vmad mapping
                    object_index += 1
                #end if already has a face list
                objspec_list[3] = faces
                objname = objspec_list[0]
                if objname == None:
                    objname = defaultname
            #end if processing a valid poly type
        elif lwochunk.chunkname == "PTAG":                         # PTags
            tobj.pprint("---- PTAG")
            polytag_dict = read_ptags(lwochunk, tag_list)
            for kk in polytag_dict.keys(): objspec_list[5][kk] = polytag_dict[kk]
        else:                                                       # Misc Chunks
            tobj.pprint("---- %s: skipping (definitely!)" % lwochunk.chunkname)
            lwochunk.skip()
        #uncomment here to log data structure as it is built
        #tobj.pprint(object_list)
    #last object read
    create_objects(clip_list, objspec_list, surf_list)
    update_material(clip_list, objspec_list, surf_list) #give it all the object
    objspec_list = None
    surf_list = None
    clip_list = None


    tobj.pprint ("\n#####################################################################")
    tobj.pprint("Found %d objects:" % object_index)
    tobj.pprint ("#####################################################################")
# enddef read_lwo2






# ===========================================================
# === File reading routines =================================
# ===========================================================
# ==================
# === Read Verts ===
# ==================
def read_verts(lwochunk):
    global tobj

    data = cStringIO.StringIO(lwochunk.read())
    numverts = lwochunk.chunksize/12
    #$verts = []
    verts = [None] * numverts
    for i in xrange(numverts):
        if not i%1000 and my_meshtools.show_progress:
            Blender.Window.DrawProgressBar(float(i)/numverts, "Reading Verts")
        x, y, z = struct.unpack(">fff", data.read(12))
        verts[i] = (x, z, y)
    tobj.pprint("read %d vertexes" % (i+1))
    return verts
# enddef read_verts


# =================
# === Read Name ===
# =================
# modified to deal with odd lenght strings
def read_name(file):
    name = ""
    while 1:
        char = file.read(1)
        if char == "\0": break
        else: name += char
    len_name = len(name) + 1 #count the trailing zero
    if len_name%2==1:
        char = file.read(1) #remove zero padding to even lenght
        len_name += 1
    return name, len_name


# ==================
# === Read Layer ===
# ==================
def read_layr(lwochunk):
    data = cStringIO.StringIO(lwochunk.read())
    idx, flags = struct.unpack(">hh", data.read(4))
    pivot = struct.unpack(">fff", data.read(12))
    layer_name, discard = read_name(data)
    if not layer_name: layer_name = "NoName"
    return layer_name
# enddef read_layr


# ======================
# === Read Faces 5.5 ===
# ======================
def read_faces_5(lwochunk):
    data = cStringIO.StringIO(lwochunk.read())
    faces = []
    i = 0
    while i < lwochunk.chunksize:
        if not i%1000 and my_meshtools.show_progress:
           Blender.Window.DrawProgressBar(float(i)/lwochunk.chunksize, "Reading Faces")
        facev = []
        numfaceverts, = struct.unpack(">H", data.read(2))
        for j in xrange(numfaceverts):
            index, = struct.unpack(">H", data.read(2))
            facev.append(index)
        facev.reverse()
        faces.append(facev)
        surfaceindex, = struct.unpack(">H", data.read(2))
        if surfaceindex < 0:
            tobj.logcon ("***Error. Referencing uncorrect surface index")
            return
        i += (4+numfaceverts*2)
    return faces


# ==================================
# === Read Variable-Length Index ===
# ==================================
def read_vx(data):
    byte1, = struct.unpack(">B", data.read(1))
    if byte1 != 0xFF:    # 2-byte index
        byte2, = struct.unpack(">B", data.read(1))
        index = byte1*256 + byte2
        index_size = 2
    else:                # 4-byte index
        byte2, byte3, byte4 = struct.unpack(">3B", data.read(3))
        index = byte2*65536 + byte3*256 + byte4
        index_size = 4
    return index, index_size


# ======================
# === Read uvmapping ===
# ======================
def read_vmap(uvcoords_dict, maxvertnum, lwochunk):
    if maxvertnum == 0:
        tobj.pprint ("Found VMAP but no vertexes to map!")
        return uvcoords_dict
    data = cStringIO.StringIO(lwochunk.read())
    map_type = data.read(4)
    if map_type != "TXUV":
        tobj.pprint ("Reading VMAP: No Texture UV map Were Found. Map Type: %s" % map_type)
        return uvcoords_dict
    dimension, = struct.unpack(">H", data.read(2))
    name, i = read_name(data) #i initialized with string lenght + zeros
    tobj.pprint ("TXUV %d %s" % (dimension, name))
    #note if there is already a VMAD it will be lost
    #it is assumed that VMAD will follow the corresponding VMAP
    if uvcoords_dict.has_key(name):
        my_uv_dict = uvcoords_dict[name]          #update existing
    else:
        my_uv_dict = {}    #start a brand new: this could be made more smart
    while (i < lwochunk.chunksize - 6):      #4+2 header bytes already read
        vertnum, vnum_size = read_vx(data)
        u, v = struct.unpack(">ff", data.read(8))
        if vertnum >= maxvertnum:
            tobj.pprint ("Hem: more uvmap than vertexes? ignoring uv data for vertex %d" % vertnum)
        else:
            my_uv_dict[vertnum] = (u, v)
        i += 8 + vnum_size
    #end loop on uv pairs
    uvcoords_dict[name] = my_uv_dict
    #this is a per-vertex mapping AND the uv tuple is vertex-ordered, so faces_uv is the same as faces
    #return uvcoords_dict
    return

# ========================
# === Read uvmapping 2 ===
# ========================
def read_vmad(uvcoords_dict, facesuv_dict, maxfacenum, maxvertnum, lwochunk):
    if maxvertnum == 0 or maxfacenum == 0:
        tobj.pprint ("Found VMAD but no vertexes to map!")
        return uvcoords_dict, facesuv_dict
    data = cStringIO.StringIO(lwochunk.read())
    map_type = data.read(4)
    if map_type != "TXUV":
        tobj.pprint ("Reading VMAD: No Texture UV map Were Found. Map Type: %s" % map_type)
        return uvcoords_dict, facesuv_dict
    dimension, = struct.unpack(">H", data.read(2))
    name, i = read_name(data) #i initialized with string lenght + zeros
    tobj.pprint ("TXUV %d %s" % (dimension, name))
    if uvcoords_dict.has_key(name):
        my_uv_dict = uvcoords_dict[name]          #update existing
    else:
        my_uv_dict = {}    #start a brand new: this could be made more smart
    my_facesuv_list = []
    newindex = maxvertnum + 10 #why +10? Why not?
    #end variable initialization
    while (i < lwochunk.chunksize - 6):  #4+2 header bytes already read
        vertnum, vnum_size = read_vx(data)
        i += vnum_size
        polynum, vnum_size = read_vx(data)
        i += vnum_size
        u, v = struct.unpack(">ff", data.read(8))
        if polynum >= maxfacenum or vertnum >= maxvertnum:
            tobj.pprint ("Hem: more uvmap than vertexes? ignorig uv data for vertex %d" % vertnum)
        else:
            my_uv_dict[newindex] = (u, v)
            my_facesuv_list.append([polynum, vertnum, newindex])
            newindex += 1
        i += 8
    #end loop on uv pairs
    uvcoords_dict[name] = my_uv_dict
    facesuv_dict[name] = my_facesuv_list
    tobj.pprint ("updated %d vertexes data" % (newindex-maxvertnum-10))
    return


# =================
# === Read tags ===
# =================
def read_tags(lwochunk):
    data = cStringIO.StringIO(lwochunk.read())
    tag_list = []
    current_tag = ""
    i = 0
    while i < lwochunk.chunksize:
        char = data.read(1)
        if char == "\0":
            tag_list.append(current_tag)
            if (len(current_tag) % 2 == 0): char = data.read(1)
            current_tag = ""
        else:
            current_tag += char
        i += 1
    tobj.pprint("read %d tags, list follows:" % len(tag_list))
    tobj.pprint( tag_list)
    return tag_list


# ==================
# === Read Ptags ===
# ==================
def read_ptags(lwochunk, tag_list):
    data = cStringIO.StringIO(lwochunk.read())
    polygon_type = data.read(4)
    if polygon_type != "SURF":
        tobj.pprint ("No Surf Were Found. Polygon Type: %s" % polygon_type)
        return {}
    ptag_dict = {}
    i = 0
    while(i < lwochunk.chunksize-4): #4 bytes polygon type already read
        if not i%1000 and my_meshtools.show_progress:
           Blender.Window.DrawProgressBar(float(i)/lwochunk.chunksize, "Reading PTAGS")
        poln, poln_size = read_vx(data)
        i += poln_size
        tag_index, = struct.unpack(">H", data.read(2))
        if tag_index > (len(tag_list)):
            tobj.pprint ("Reading PTAG: Surf belonging to undefined TAG: %d. Skipping" % tag_index)
            return {}
        i += 2
        tag_key = tag_list[tag_index]
        if not(ptag_dict.has_key(tag_key)):
            ptag_dict[tag_list[tag_index]] = [poln]
        else:
            ptag_dict[tag_list[tag_index]].append(poln)
    for i in ptag_dict.keys():
        tobj.pprint ("read %d polygons belonging to TAG %s" % (len(ptag_dict[i]), i))
    return ptag_dict



# ==================
# === Read Clips ===
# ==================
def read_clip(lwochunk, dir_part):
# img, IMG, g_IMG refers to blender image objects
# ima, IMAG, g_IMAG refers to clip dictionary 'ID' entries: refer to blok and surf
    clip_dict = {}
    data = cStringIO.StringIO(lwochunk.read())
    image_index, = struct.unpack(">L", data.read(4))
    clip_dict['ID'] = image_index
    i = 4
    while(i < lwochunk.chunksize):
        subchunkname, = struct.unpack("4s", data.read(4))
        subchunklen, = struct.unpack(">H", data.read(2))
        if subchunkname == "STIL":
            tobj.pprint("-------- STIL")
            clip_name, k = read_name(data)
            #now split text independently from platform
            #depend on the system where image was saved. NOT the one where the script is run
            no_sep = "\\"
            if Blender.sys.sep == no_sep: no_sep ="/"
            if (no_sep in clip_name):
                clip_name = clip_name.replace(no_sep, Blender.sys.sep)
            short_name = Blender.sys.basename(clip_name)
            if (clip_name == "") or (short_name == ""):
                tobj.pprint ("Reading CLIP: Empty clip name not allowed. Skipping")
                discard = data.read(subchunklen-k)
            clip_dict['NAME'] = clip_name
            clip_dict['BASENAME'] = short_name
        elif subchunkname == "XREF":                           #cross reference another image
            tobj.pprint("-------- XREF")
            image_index, = struct.unpack(">L", data.read(4))
            clip_name, k = read_name(data)
            clip_dict['NAME'] = clip_name
            clip_dict['XREF'] = image_index
        elif subchunkname == "NEGA":                           #negate texture effect
            tobj.pprint("-------- NEGA")
            n, = struct.unpack(">H", data.read(2))
            clip_dict['NEGA'] = n
        else:                                                       # Misc Chunks
            tobj.pprint("-------- CLIP:%s: skipping" % subchunkname)
            discard = data.read(subchunklen)
        i = i + 6 + subchunklen
    #end loop on surf chunks
    tobj.pprint("read image:%s" % clip_dict)
    if clip_dict.has_key('XREF'):
        tobj.pprint("Cross-reference: no image pre-allocated.")
        return clip_dict
    #look for images
    img = load_image("",clip_dict['NAME'])
    if img == None:
        tobj.pprint (  "***No image %s found: trying LWO file subdir" % clip_dict['NAME'])
        img = load_image(dir_part,clip_dict['BASENAME'])
    if img == None:
        tobj.pprint (  "***No image %s found in directory %s: trying Images subdir" % (clip_dict['BASENAME'], dir_part))
        img = load_image(dir_part+Blender.sys.sep+"Images",clip_dict['BASENAME'])
    if img == None:
        tobj.pprint (  "***No image %s found: trying alternate Images subdir" % clip_dict['BASENAME'])
        img = load_image(dir_part+Blender.sys.sep+".."+Blender.sys.sep+"Images",clip_dict['BASENAME'])
    if img == None:
        tobj.pprint (  "***No image %s found: giving up" % clip_dict['BASENAME'])
    #lucky we are: we have an image
    tobj.pprint ("Image pre-allocated.")
    clip_dict['g_IMG'] = img
    return clip_dict


# ===========================
# === Read Surfaces Block ===
# ===========================
def read_surfblok(subchunkdata):
    lenght = len(subchunkdata)
    my_dict = {}
    my_uvname = ""
    data = cStringIO.StringIO(subchunkdata)
    ##############################################################
    # blok header sub-chunk
    ##############################################################
    subchunkname, = struct.unpack("4s", data.read(4))
    subchunklen, = struct.unpack(">h", data.read(2))
    accumulate_i = subchunklen + 6
    if subchunkname != 'IMAP':
        tobj.pprint("---------- SURF: BLOK: %s: block aborting" % subchunkname)
        return {}, ""
    tobj.pprint ("---------- IMAP")
    ordinal, i = read_name(data)
    my_dict['ORD'] = ordinal
    #my_dict['g_ORD'] = -1
    my_dict['ENAB'] = True
    while(i < subchunklen): # ---------left 6------------------------- loop on header parameters
        sub2chunkname, = struct.unpack("4s", data.read(4))
        sub2chunklen, = struct.unpack(">h", data.read(2))
        i = i + 6 + sub2chunklen
        if sub2chunkname == "CHAN":
            tobj.pprint("------------ CHAN")
            sub2chunkname, = struct.unpack("4s", data.read(4))
            my_dict['CHAN'] = sub2chunkname
            sub2chunklen -= 4
        elif sub2chunkname == "ENAB":                             #only present if is to be disabled
            tobj.pprint("------------ ENAB")
            ena, = struct.unpack(">h", data.read(2))
            my_dict['ENAB'] = ena
            sub2chunklen -= 2
        elif sub2chunkname == "NEGA":                             #only present if is to be enabled
            tobj.pprint("------------ NEGA")
            ena, = struct.unpack(">h", data.read(2))
            if ena == 1:
                my_dict['NEGA'] = ena
            sub2chunklen -= 2
        elif sub2chunkname == "OPAC":                             #only present if is to be disabled
            tobj.pprint("------------ OPAC")
            opa, = struct.unpack(">h", data.read(2))
            s, = struct.unpack(">f", data.read(4))
            envelope, env_size = read_vx(data)
            my_dict['OPAC'] = opa
            my_dict['OPACVAL'] = s
            sub2chunklen -= 6
        elif sub2chunkname == "AXIS":
            tobj.pprint("------------ AXIS")
            ena, = struct.unpack(">h", data.read(2))
            my_dict['DISPLAXIS'] = ena
            sub2chunklen -= 2
        else:                                                       # Misc Chunks
            tobj.pprint("------------ SURF: BLOK: IMAP: %s: skipping" % sub2chunkname)
            discard = data.read(sub2chunklen)
    #end loop on blok header subchunks
    ##############################################################
    # blok attributes sub-chunk
    ##############################################################
    subchunkname, = struct.unpack("4s", data.read(4))
    subchunklen, = struct.unpack(">h", data.read(2))
    accumulate_i += subchunklen + 6
    if subchunkname != 'TMAP':
        tobj.pprint("---------- SURF: BLOK: %s: block aborting" % subchunkname)
        return {}, ""
    tobj.pprint ("---------- TMAP")
    i = 0
    while(i < subchunklen): # -----------left 6----------------------- loop on header parameters
        sub2chunkname, = struct.unpack("4s", data.read(4))
        sub2chunklen, = struct.unpack(">h", data.read(2))
        i = i + 6 + sub2chunklen
        if sub2chunkname == "CNTR":
            tobj.pprint("------------ CNTR")
            x, y, z = struct.unpack(">fff", data.read(12))
            envelope, env_size = read_vx(data)
            my_dict['CNTR'] = [x, y, z]
            sub2chunklen -= (12+env_size)
        elif sub2chunkname == "SIZE":
            tobj.pprint("------------ SIZE")
            x, y, z = struct.unpack(">fff", data.read(12))
            envelope, env_size = read_vx(data)
            my_dict['SIZE'] = [x, y, z]
            sub2chunklen -= (12+env_size)
        elif sub2chunkname == "ROTA":
            tobj.pprint("------------ ROTA")
            x, y, z = struct.unpack(">fff", data.read(12))
            envelope, env_size = read_vx(data)
            my_dict['ROTA'] = [x, y, z]
            sub2chunklen -= (12+env_size)
        elif sub2chunkname == "CSYS":
            tobj.pprint("------------ CSYS")
            ena, = struct.unpack(">h", data.read(2))
            my_dict['CSYS'] = ena
            sub2chunklen -= 2
        else:                                                       # Misc Chunks
            tobj.pprint("------------ SURF: BLOK: TMAP: %s: skipping" % sub2chunkname)
        if  sub2chunklen > 0:
            discard = data.read(sub2chunklen)
    #end loop on blok attributes subchunks
    ##############################################################
    # ok, now other attributes without sub_chunks
    ##############################################################
    while(accumulate_i < lenght): # ---------------------------------- loop on header parameters: lenght has already stripped the 6 bypes header
        subchunkname, = struct.unpack("4s", data.read(4))
        subchunklen, = struct.unpack(">H", data.read(2))
        accumulate_i = accumulate_i + 6 + subchunklen
        if subchunkname == "PROJ":
            tobj.pprint("---------- PROJ")
            p, = struct.unpack(">h", data.read(2))
            my_dict['PROJ'] = p
            subchunklen -= 2
        elif subchunkname == "AXIS":
            tobj.pprint("---------- AXIS")
            a, = struct.unpack(">h", data.read(2))
            my_dict['MAJAXIS'] = a
            subchunklen -= 2
        elif subchunkname == "IMAG":
            tobj.pprint("---------- IMAG")
            i, i_size = read_vx(data)
            my_dict['IMAG'] = i
            subchunklen -= i_size
        elif subchunkname == "WRAP":
            tobj.pprint("---------- WRAP")
            ww, wh = struct.unpack(">hh", data.read(4))
            #reduce width and height to just 1 parameter for both
            my_dict['WRAP'] = max([ww,wh])
            #my_dict['WRAPWIDTH'] = ww
            #my_dict['WRAPHEIGHT'] = wh
            subchunklen -= 4
        elif subchunkname == "WRPW":
            tobj.pprint("---------- WRPW")
            w, = struct.unpack(">f", data.read(4))
            my_dict['WRPW'] = w
            envelope, env_size = read_vx(data)
            subchunklen -= (env_size+4)
        elif subchunkname == "WRPH":
            tobj.pprint("---------- WRPH")
            w, = struct.unpack(">f", data.read(4))
            my_dict['WRPH'] = w
            envelope, env_size = read_vx(data)
            subchunklen -= (env_size+4)
        elif subchunkname == "VMAP":
            tobj.pprint("---------- VMAP")
            vmp, i = read_name(data)
            my_dict['VMAP'] = vmp
            my_uvname = vmp
            subchunklen -= i
        else:                                                    # Misc Chunks
            tobj.pprint("---------- SURF: BLOK: %s: skipping" % subchunkname)
        if  subchunklen > 0:
            discard = data.read(subchunklen)
    #end loop on blok subchunks
    return my_dict, my_uvname


# =====================
# === Read Surfaces ===
# =====================
def read_surfs(lwochunk, surf_list, tag_list):
    my_dict = {}
    data = cStringIO.StringIO(lwochunk.read())
    surf_name, i = read_name(data)
    parent_name, j = read_name(data)
    i += j
    if (surf_name == "") or not(surf_name in tag_list):
        tobj.pprint ("Reading SURF: Actually empty surf name not allowed. Skipping")
        return {}
    if (parent_name != ""):
        parent_index = [x['NAME'] for x in surf_list].count(parent_name)
        if parent_index >0:
            my_dict = surf_list[parent_index-1]
    my_dict['NAME'] = surf_name
    tobj.pprint ("Surface data for TAG %s" % surf_name)
    while(i < lwochunk.chunksize):
        subchunkname, = struct.unpack("4s", data.read(4))
        subchunklen, = struct.unpack(">H", data.read(2))
        i = i + 6 + subchunklen #6 bytes subchunk header
        if subchunkname == "COLR":                             #color: mapped on color
            tobj.pprint("-------- COLR")
            r, g, b = struct.unpack(">fff", data.read(12))
            envelope, env_size = read_vx(data)
            my_dict['COLR'] = [r, g, b]
            subchunklen -= (12+env_size)
        elif subchunkname == "DIFF":                           #diffusion: mapped on reflection (diffuse shader)
            tobj.pprint("-------- DIFF")
            s, = struct.unpack(">f", data.read(4))
            envelope, env_size = read_vx(data)
            my_dict['DIFF'] = s
            subchunklen -= (4+env_size)
        elif subchunkname == "SPEC":                           #specularity: mapped to specularity (spec shader)
            tobj.pprint("-------- SPEC")
            s, = struct.unpack(">f", data.read(4))
            envelope, env_size = read_vx(data)
            my_dict['SPEC'] = s
            subchunklen -= (4+env_size)
        elif subchunkname == "REFL":                           #reflection: mapped on raymirror
            tobj.pprint("-------- REFL")
            s, = struct.unpack(">f", data.read(4))
            envelope, env_size = read_vx(data)
            my_dict['REFL'] = s
            subchunklen -= (4+env_size)
        elif subchunkname == "TRNL":                           #translucency: mapped on same param
            tobj.pprint("-------- TRNL")
            s, = struct.unpack(">f", data.read(4))
            envelope, env_size = read_vx(data)
            my_dict['TRNL'] = s
            subchunklen -= (4+env_size)
        elif subchunkname == "GLOS":                           #glossiness: mapped on specularity hardness (spec shader)
            tobj.pprint("-------- GLOS")
            s, = struct.unpack(">f", data.read(4))
            envelope, env_size = read_vx(data)
            my_dict['GLOS'] = s
            subchunklen -= (4+env_size)
        elif subchunkname == "TRAN":                           #transparency: inverted and mapped on alpha channel
            tobj.pprint("-------- TRAN")
            s, = struct.unpack(">f", data.read(4))
            envelope, env_size = read_vx(data)
            my_dict['TRAN'] = s
            subchunklen -= (4+env_size)
        elif subchunkname == "LUMI":                           #luminosity: mapped on emit channel
            tobj.pprint("-------- LUMI")
            s, = struct.unpack(">f", data.read(4))
            envelope, env_size = read_vx(data)
            my_dict['LUMI'] = s
            subchunklen -= (4+env_size)
        elif subchunkname == "GVAL":                           #glow: mapped on add channel
            tobj.pprint("-------- GVAL")
            s, = struct.unpack(">f", data.read(4))
            envelope, env_size = read_vx(data)
            my_dict['GVAL'] = s
            subchunklen -= (4+env_size)
        elif subchunkname == "SMAN":                           #smoothing angle
            tobj.pprint("-------- SMAN")
            s, = struct.unpack(">f", data.read(4))
            my_dict['SMAN'] = s
            subchunklen -= 4
        elif subchunkname == "SIDE":                           #double sided?
            tobj.pprint("-------- SIDE")                             #if 1 side do not define key
            s, = struct.unpack(">H", data.read(2))
            if s == 3:
                my_dict['SIDE'] = s
            subchunklen -= 2
        elif subchunkname == "RIND":                           #Refraction: mapped on IOR
            tobj.pprint("-------- RIND")
            s, = struct.unpack(">f", data.read(4))
            envelope, env_size = read_vx(data)
            my_dict['RIND'] = s
            subchunklen -= (4+env_size)
        elif subchunkname == "BLOK":                           #blocks
            tobj.pprint("-------- BLOK")
            rr, uvname = read_surfblok(data.read(subchunklen))
            #paranoia setting: preventing adding an empty dict
            if rr != {}:
                if not(my_dict.has_key('BLOK')):
                    my_dict['BLOK'] = [rr]
                else:
                    my_dict['BLOK'].append(rr)
            if uvname != "":
                my_dict['UVNAME'] = uvname                            #theoretically there could be a number of them: only one used per surf
            if not(my_dict.has_key('g_IMAG')) and (rr.has_key('CHAN')) and (rr.has_key('OPAC')) and (rr.has_key('IMAG')):
                if (rr['CHAN'] == 'COLR') and (rr['OPAC'] == 0):
                    my_dict['g_IMAG'] = rr['IMAG']                 #do not set anything, just save image object for later assignment
            subchunklen = 0 #force ending
        else:                                                       # Misc Chunks
            tobj.pprint("-------- SURF:%s: skipping" % subchunkname)
        if  subchunklen > 0:
            discard = data.read(subchunklen)
    #end loop on surf chunks
    if my_dict.has_key('BLOK'):
       my_dict['BLOK'].reverse() #texture applied in reverse order with respect to reading from lwo
    #uncomment this if material pre-allocated by read_surf
    my_dict['g_MAT'] = Blender.Material.New(my_dict['NAME'])
    tobj.pprint("-> Material pre-allocated.")
    return my_dict


# ===========================================================
# === Generation Routines ===================================
# ===========================================================
# ==================================================
# === Compute vector distance between two points ===
# ==================================================
def dist_vector (head, tail): #vector from head to tail
    return Blender.Mathutils.Vector([head[0] - tail[0], head[1] - tail[1], head[2] - tail[2]])


# ================
# === Find Ear ===
# ================
def find_ear(normal, list_dict, verts, face):
    nv = len(list_dict['MF'])
    #looping through vertexes trying to find an ear
    #most likely in case of panic
    mlc = 0
    mla = 1
    mlb = 2

    for c in xrange(nv):
        a = (c+1) % nv; b = (a+1) % nv

        if list_dict['P'][a] > 0.0: #we have to start from a convex vertex
        #if (list_dict['P'][a] > 0.0) and (list_dict['P'][b] <= 0.0): #we have to start from a convex vertex
            mlc = c
            mla = a
            mlb = b
            #tobj.pprint ("## mmindex: %s, %s, %s  'P': %s, %s, %s" % (c, a, b, list_dict['P'][c],list_dict['P'][a],list_dict['P'][b]))
            #tobj.pprint ("   ok, this one passed")
            concave = 0
            concave_inside = 0
            for xx in xrange(nv): #looking for concave vertex
                if (list_dict['P'][xx] <= 0.0) and (xx != b) and (xx != c): #cannot be a: it's convex
                    #ok, found concave vertex
                    concave = 1
                    #a, b, c, xx are all meta-meta vertex indexes
                    mva = list_dict['MF'][a] #meta-vertex-index
                    mvb = list_dict['MF'][b]
                    mvc = list_dict['MF'][c]
                    mvxx = list_dict['MF'][xx]
                    va = face[mva] #vertex
                    vb = face[mvb]
                    vc = face[mvc]
                    vxx = face[mvxx]

                    #Distances
                    d_ac_v = list_dict['D'][c]
                    d_ba_v = list_dict['D'][a]
                    d_cb_v = dist_vector(verts[vc], verts[vb])

                    #distance from triangle points
                    d_xxa_v = dist_vector(verts[vxx], verts[va])
                    d_xxb_v = dist_vector(verts[vxx], verts[vb])
                    d_xxc_v = dist_vector(verts[vxx], verts[vc])

                    #normals
                    n_xxa_v = Blender.Mathutils.CrossVecs(d_ba_v, d_xxa_v)
                    n_xxb_v = Blender.Mathutils.CrossVecs(d_cb_v, d_xxb_v)
                    n_xxc_v = Blender.Mathutils.CrossVecs(d_ac_v, d_xxc_v)

                    #how are oriented the normals?
                    p_xxa_v = Blender.Mathutils.DotVecs(normal, n_xxa_v)
                    p_xxb_v = Blender.Mathutils.DotVecs(normal, n_xxb_v)
                    p_xxc_v = Blender.Mathutils.DotVecs(normal, n_xxc_v)

                    #if normals are oriented all to same directions - so it is insida
                    if ((p_xxa_v > 0.0) and (p_xxb_v > 0.0) and (p_xxc_v > 0.0)) or ((p_xxa_v <= 0.0) and (p_xxb_v <= 0.0) and (p_xxc_v <= 0.0)):
                        #print "vertex %d: concave inside" % xx
                        concave_inside = 1
                        break
                #endif found a concave vertex
            #end loop looking for concave vertexes
            if (concave == 0) or (concave_inside == 0):
                #no concave vertexes in polygon (should not be): return immediately
                #looped all concave vertexes and no one inside found
                return [c, a, b]
        #no convex vertex, try another one
    #end loop to find a suitable base vertex for ear
    #looped all candidate ears and find no-one suitable
    tobj.pprint ("Reducing face: no valid ear found to reduce!")
    return [mlc, mla, mlb] #uses most likely




# ====================
# === Reduce Faces ===
# ====================
# http://www-cgrl.cs.mcgill.ca/~godfried/teaching/cg-projects/97/Ian/cutting_ears.html per l'import
def reduce_face(verts, face):
    nv = len (face)
    if nv == 3: return [[0,1,2]] #trivial decomposition list
    list_dict = {}
    #meta-vertex indexes
    list_dict['MF'] = range(nv) # these are meta-vertex-indexes
    list_dict['D'] = [None] * nv
    list_dict['X'] = [None] * nv
    list_dict['P'] = [None] * nv
    #list of distances
    for mvi in list_dict['MF']:
        #vector between two vertexes
        mvi_hiend = (mvi+1) % nv      #last-to-first
        vi_hiend = face[mvi_hiend] #vertex
        vi = face[mvi]
        list_dict['D'][mvi] = dist_vector(verts[vi_hiend], verts[vi])
    #list of cross products - normals evaluated into vertexes
    for vi in xrange(nv):
        list_dict['X'][vi] = Blender.Mathutils.CrossVecs(list_dict['D'][vi], list_dict['D'][vi-1])
    my_face_normal = Blender.Mathutils.Vector([list_dict['X'][0][0], list_dict['X'][0][1], list_dict['X'][0][2]])
    #list of dot products
    list_dict['P'][0] = 1.0
    for vi in xrange(1, nv):
        list_dict['P'][vi] = Blender.Mathutils.DotVecs(my_face_normal, list_dict['X'][vi])
    #is there at least one concave vertex?
    #one_concave = reduce(lambda x, y: (x) or (y<=0.0), list_dict['P'], 0)
    one_concave = reduce(lambda x, y: (x) + (y<0.0), list_dict['P'], 0)
    decomposition_list = []

    while 1:
        if nv == 3: break
        if one_concave:
            #look for triangle
            ct = find_ear(my_face_normal, list_dict, verts, face)
            mv0 = list_dict['MF'][ct[0]] #meta-vertex-index
            mv1 = list_dict['MF'][ct[1]]
            mv2 = list_dict['MF'][ct[2]]
            #add the triangle to output list
            decomposition_list.append([mv0, mv1, mv2])
            #update data structures removing remove middle vertex from list
            #distances
            v0 = face[mv0] #vertex
            v1 = face[mv1]
            v2 = face[mv2]
            list_dict['D'][ct[0]] = dist_vector(verts[v2], verts[v0])
            #cross products
            list_dict['X'][ct[0]] = Blender.Mathutils.CrossVecs(list_dict['D'][ct[0]], list_dict['D'][ct[0]-1])
            list_dict['X'][ct[2]] = Blender.Mathutils.CrossVecs(list_dict['D'][ct[2]], list_dict['D'][ct[0]])
            #list of dot products
            list_dict['P'][ct[0]] = Blender.Mathutils.DotVecs(my_face_normal, list_dict['X'][ct[0]])
            list_dict['P'][ct[2]] = Blender.Mathutils.DotVecs(my_face_normal, list_dict['X'][ct[2]])
            #physical removal
            list_dict['MF'].pop(ct[1])
            list_dict['D'].pop(ct[1])
            list_dict['X'].pop(ct[1])
            list_dict['P'].pop(ct[1])
            one_concave = reduce(lambda x, y: (x) or (y<0.0), list_dict['P'], 0)
            nv -=1
        else: #here if no more concave vertexes
            if nv == 4: break  #quads only if no concave vertexes
            decomposition_list.append([list_dict['MF'][0], list_dict['MF'][1], list_dict['MF'][2]])
            #physical removal
            list_dict['MF'].pop(1)
            nv -=1
    #end while there are more my_face to triangulate
    decomposition_list.append(list_dict['MF'])
    return decomposition_list


# =========================
# === Recalculate Faces ===
# =========================

def get_uvface(complete_list, facenum):
    # extract from the complete list only vertexes of the desired polygon
    my_facelist = []
    for elem in complete_list:
        if elem[0] == facenum:
            my_facelist.append(elem)
    return my_facelist

def get_newindex(polygon_list, vertnum):
    # extract from the polygon list the new index associated to a vertex
    if polygon_list == []:
        return -1
    for elem in polygon_list:
        if elem[1] == vertnum:
            return elem[2]
    #tobj.pprint("WARNING: expected vertex %s for polygon %s. Polygon_list dump follows" % (vertnum, polygon_list[0][0]))
    #tobj.pprint(polygon_list)
    return -1

def get_surf(surf_list, cur_tag):
    for elem in surf_list:
        if elem['NAME'] == cur_tag:
            return elem
    return {}



# ==========================================
# === Revert list (keeping first vertex) ===
# ==========================================
def revert (llist):
    #different flavors: the reverse one is the one that works better
    #rhead = [llist[0]]
    #rtail = llist[1:]
    #rhead.extend(rtail)
    #return rhead
    #--------------
    rhead=rlcopy(llist)
    rhead.reverse()
    return rhead
    #--------------
    #return llist


# ====================================
# === Modified Create Blender Mesh ===
# ====================================
def my_create_mesh(clip_list, surf, objspec_list, current_facelist, objname, not_used_faces):
    #take the needed faces and update the not-used face list
    complete_vertlist = objspec_list[2]
    complete_facelist = objspec_list[3]
    uvcoords_dict = objspec_list[7]
    facesuv_dict = objspec_list[8]
    vertex_map = {} #implementation as dict
    cur_ptag_faces = []
    cur_ptag_faces_indexes = []
    maxface = len(complete_facelist)
    for ff in current_facelist:
        if ff >= maxface:
            tobj.logcon("Non existent face addressed: Giving up with this object")
            return None, not_used_faces              #return the created object
        cur_face = complete_facelist[ff]
        cur_ptag_faces_indexes.append(ff)
        if not_used_faces != []: not_used_faces[ff] = -1
        for vv in cur_face: vertex_map[vv] = 1
    #end loop on faces
    store_edge = 0

    msh = Blender.NMesh.GetRaw()
    # Name the Object
    if not my_meshtools.overwrite_mesh_name:
        objname = my_meshtools.versioned_name(objname)
    Blender.NMesh.PutRaw(msh, objname)    # Name the Mesh
    obj = Blender.Object.GetSelected()[0]
    obj.name=objname
    # Associate material and mesh properties => from create materials
    msh = obj.getData()
    mat_index = len(msh.getMaterials(1))
    mat = None
    if surf.has_key('g_MAT'):
        mat = surf['g_MAT']
        msh.addMaterial(mat)
    msh.mode |= Blender.NMesh.Modes.AUTOSMOOTH #smooth it anyway
    if surf.has_key('SMAN'):
        #not allowed mixed mode mesh (all the mesh is smoothed and all with the same angle)
        #only one smoothing angle will be active! => take the max one
        s = int(surf['SMAN']/3.1415926535897932384626433832795*180.0)     #lwo in radians - blender in degrees
        if msh.getMaxSmoothAngle() < s: msh.setMaxSmoothAngle(s)

    img = None
    if surf.has_key('g_IMAG'):
        ima = lookup_imag(clip_list, surf['g_IMAG'])
        if ima != None:
            img = ima['g_IMG']

    #uv_flag = ((surf.has_key('UVNAME')) and (uvcoords_dict.has_key(surf['UVNAME'])) and (img != None))
    uv_flag = ((surf.has_key('UVNAME')) and (uvcoords_dict.has_key(surf['UVNAME'])))

    if uv_flag:        #assign uv-data; settings at mesh level
        msh.hasFaceUV(1)
    msh.update(1)

    tobj.pprint ("\n#===================================================================#")
    tobj.pprint("Processing Object: %s" % objname)
    tobj.pprint ("#===================================================================#")

    jj = 0
    vertlen = len(vertex_map.keys())
    maxvert = len(complete_vertlist)
    for i in vertex_map.keys():
        if not jj%1000 and my_meshtools.show_progress: Blender.Window.DrawProgressBar(float(i)/vertlen, "Generating Verts")
        if i >= maxvert:
            tobj.logcon("Non existent vertex addressed: Giving up with this object")
            return obj, not_used_faces              #return the created object
        x, y, z = complete_vertlist[i]
        msh.verts.append(Blender.NMesh.Vert(x, y, z))
        vertex_map[i] = jj
        jj += 1
    #end sweep over vertexes

    #append faces
    jj = 0
    for i in cur_ptag_faces_indexes:
        if not jj%1000 and my_meshtools.show_progress: Blender.Window.DrawProgressBar(float(jj)/len(cur_ptag_faces_indexes), "Generating Faces")
        cur_face = complete_facelist[i]
        numfaceverts = len(cur_face)
        vmad_list = []    #empty VMAD in any case
        if uv_flag:    #settings at original face level
            if facesuv_dict.has_key(surf['UVNAME']): #yes = has VMAD; no = has VMAP only
                vmad_list = get_uvface(facesuv_dict[surf['UVNAME']],i)  #this for VMAD

        if numfaceverts == 2:
            #This is not a face is an edge
            store_edge = 1
            if msh.edges == None:  #first run
                msh.addEdgeData()
            #rev_face = revert(cur_face)
            i1 = vertex_map[cur_face[1]]
            i2 = vertex_map[cur_face[0]]
            ee = msh.addEdge(msh.verts[i1],msh.verts[i2])
            ee.flag |= Blender.NMesh.EdgeFlags.EDGEDRAW
            ee.flag |= Blender.NMesh.EdgeFlags.EDGERENDER

        elif numfaceverts == 3:
            #This face is a triangle skip face reduction
            face = Blender.NMesh.Face()
            msh.faces.append(face)
            # Associate face properties => from create materials
            if mat != None: face.materialIndex = mat_index
            face.smooth = 1 #smooth it anyway

            #rev_face = revert(cur_face)
            rev_face = [cur_face[2], cur_face[1], cur_face[0]]

            for vi in rev_face:
                index = vertex_map[vi]
                face.v.append(msh.verts[index])

                if uv_flag:
                    ni = get_newindex(vmad_list, vi)
                    if ni > -1:
                        uv_index = ni
                    else: #VMAP - uses the same criteria as face
                        uv_index = vi
                    if uvcoords_dict[surf['UVNAME']].has_key(uv_index):
                        uv_tuple = uvcoords_dict[surf['UVNAME']][uv_index]
                    else:
                        uv_tuple = (0,0)
                    face.uv.append(uv_tuple)

            if uv_flag and img != None:
                face.mode |= Blender.NMesh.FaceModes['TEX']
                face.image = img
                face.mode |= Blender.NMesh.FaceModes.TWOSIDE                  #set it anyway
                face.transp = Blender.NMesh.FaceTranspModes['SOLID']
                face.flag = Blender.NMesh.FaceTranspModes['SOLID']
                #if surf.has_key('SIDE'):
                #    msh.faces[f].mode |= Blender.NMesh.FaceModes.TWOSIDE             #set it anyway
                if surf.has_key('TRAN') and mat.getAlpha()<1.0:
                    face.transp = Blender.NMesh.FaceTranspModes['ALPHA']

        elif numfaceverts > 3:
            #Reduce all the faces with more than 3 vertexes (& test if the quad is concave .....)

            meta_faces = reduce_face(complete_vertlist, cur_face)        # Indices of triangles.
            for mf in meta_faces:
                face = Blender.NMesh.Face()
                msh.faces.append(face)

                if len(mf) == 3: #triangle
                    #rev_face = revert([cur_face[mf[0]], cur_face[mf[1]], cur_face[mf[2]]])
                    rev_face = [cur_face[mf[2]], cur_face[mf[1]], cur_face[mf[0]]]
                else:        #quads
                    #rev_face = revert([cur_face[mf[0]], cur_face[mf[1]], cur_face[mf[2]], cur_face[mf[3]]])
                    rev_face = [cur_face[mf[3]], cur_face[mf[2]], cur_face[mf[1]], cur_face[mf[0]]]

                # Associate face properties => from create materials
                if mat != None: face.materialIndex = mat_index
                face.smooth = 1 #smooth it anyway

                for vi in rev_face:
                    index = vertex_map[vi]
                    face.v.append(msh.verts[index])

                    if uv_flag:
                        ni = get_newindex(vmad_list, vi)
                        if ni > -1:
                            uv_index = ni
                        else: #VMAP - uses the same criteria as face
                            uv_index = vi
                        if uvcoords_dict[surf['UVNAME']].has_key(uv_index):
                            uv_tuple = uvcoords_dict[surf['UVNAME']][uv_index]
                        else:
                            uv_tuple = (0,0)
                        face.uv.append(uv_tuple)

                if uv_flag and img != None:
                    face.mode |= Blender.NMesh.FaceModes['TEX']
                    face.image = img
                    face.mode |= Blender.NMesh.FaceModes.TWOSIDE                  #set it anyway
                    face.transp = Blender.NMesh.FaceTranspModes['SOLID']
                    face.flag = Blender.NMesh.FaceTranspModes['SOLID']
                    #if surf.has_key('SIDE'):
                    #    msh.faces[f].mode |= Blender.NMesh.FaceModes.TWOSIDE             #set it anyway
                    if surf.has_key('TRAN') and mat.getAlpha()<1.0:
                        face.transp = Blender.NMesh.FaceTranspModes['ALPHA']

        jj += 1

    if not(uv_flag):        #clear eventual UV data
        msh.hasFaceUV(0)
    msh.update(1,store_edge)
    Blender.Redraw()
    return obj, not_used_faces              #return the created object


# ============================================
# === Set Subsurf attributes on given mesh ===
# ============================================
def set_subsurf(obj):
    msh = obj.getData()
    msh.setSubDivLevels([2, 2])
    #does not work any more in 2.40 alpha 2
    #msh.mode |= Blender.NMesh.Modes.SUBSURF
    if msh.edges != None:
        msh.update(1,1)
    else:
        msh.update(1)
    obj.makeDisplayList()
    return


# =================================
# === object size and dimension ===
# =================================
def obj_size_pos(obj):
    bbox = obj.getBoundBox()
    bbox_min = map(lambda *row: min(row), *bbox) #transpose & get min
    bbox_max = map(lambda *row: max(row), *bbox) #transpose & get max
    obj_size = (bbox_max[0]-bbox_min[0], bbox_max[1]-bbox_min[1], bbox_max[2]-bbox_min[2])
    obj_pos = ( (bbox_max[0]+bbox_min[0]) / 2, (bbox_max[1]+bbox_min[1]) / 2, (bbox_max[2]+bbox_min[2]) / 2)
    return (obj_size, obj_pos)


# =========================
# === Create the object ===
# =========================
def create_objects(clip_list, objspec_list, surf_list):
    nf = len(objspec_list[3])
    not_used_faces = range(nf)
    ptag_dict = objspec_list[5]
    obj_dict = {}  #links tag names to object, used for material assignments
    obj_dim_dict = {}
    obj_list = []  #have it handy for parent association
    middlechar = "+"
    endchar = ""
    if (objspec_list[6] == 1):
        middlechar = endchar = "#"
    for cur_tag in ptag_dict.keys():
        if ptag_dict[cur_tag] != []:
            cur_surf = get_surf(surf_list, cur_tag)
            cur_obj, not_used_faces=  my_create_mesh(clip_list, cur_surf, objspec_list, ptag_dict[cur_tag], objspec_list[0][:9]+middlechar+cur_tag[:9], not_used_faces)
            #does not work any more in 2.40 alpha 2
            #if objspec_list[6] == 1:
            #    set_subsurf(cur_obj)
            if cur_obj != None:
                obj_dict[cur_tag] = cur_obj
                obj_dim_dict[cur_tag] = obj_size_pos(cur_obj)
                obj_list.append(cur_obj)
    #end loop on current group
    #and what if some faces not used in any named PTAG? get rid of unused faces
    orphans = []
    for tt in not_used_faces:
        if tt > -1: orphans.append(tt)
    #end sweep on unused face list
    not_used_faces = None
    if orphans != []:
        cur_surf = get_surf(surf_list, "_Orphans")
        cur_obj, not_used_faces = my_create_mesh(clip_list, cur_surf, objspec_list, orphans, objspec_list[0][:9]+middlechar+"Orphans", [])
        if cur_obj != None:
            if objspec_list[6] == 1:
                set_subsurf(cur_obj)
            obj_dict["_Orphans"] = cur_obj
            obj_dim_dict["_Orphans"] = obj_size_pos(cur_obj)
            obj_list.append(cur_obj)
    objspec_list[1] = obj_dict
    objspec_list[4] = obj_dim_dict
    scene = Blender.Scene.getCurrent ()                   # get the current scene
    ob = Blender.Object.New ('Empty', objspec_list[0]+endchar)    # make empty object
    scene.link (ob)                                       # link the object into the scene
    ob.makeParent(obj_list, 1, 0)                         # set the root for created objects (no inverse, update scene hyerarchy (slow))
    Blender.Redraw()
    return


# =====================
# === Load an image ===
# =====================
#extensively search for image name
def load_image(dir_part, name):
    img = None
    nname = Blender.sys.splitext(name)
    lname = [c.lower() for c in nname]
    ext_list = []
    if lname[1] != nname[1]:
        ext_list.append(lname[1])
    ext_list.extend(['.tga', '.png', '.jpg', '.gif', '.bmp'])  #order from best to worst (personal judgement) bmp last cause of nasty bug
    #first round: original "case"
    current = Blender.sys.join(dir_part, name)
    name_list = [current]
    name_list.extend([Blender.sys.makename(current, ext) for ext in ext_list])
    #second round: lower "case"
    if lname[0] != nname[0]:
        current = Blender.sys.join(dir_part, lname[0])
        name_list.extend([Blender.sys.makename(current, ext) for ext in ext_list])
    for nn in name_list:
        if Blender.sys.exists(nn) == 1:
            break
    try:
        img = Blender.Image.Load(nn)
        return img
    except IOError:
        return None


# ===========================================
# === Lookup for image index in clip_list ===
# ===========================================
def lookup_imag(clip_list,ima_id):
    for ii in clip_list:
        if ii['ID'] == ima_id:
            if ii.has_key('XREF'):
                #cross reference - recursively look for images
                return lookup_imag(clip_list, ii['XREF'])
            else:
                return ii
    return None


# ===================================================
# === Create and assign image mapping to material ===
# ===================================================
def create_blok(surf, mat, clip_list, obj_size, obj_pos):

    def output_size_ofs(size, pos, blok):
        #just automate repetitive task
        c_map = [0,1,2]
        c_map_txt = ["    X--", "    -Y-", "    --Z"]
        if blok['MAJAXIS'] == 0:
            c_map = [1,2,0]
        if blok['MAJAXIS'] == 2:
            c_map = [0,2,1]
        tobj.pprint ("!!!axis mapping:")
        for mp in c_map: tobj.pprint (c_map_txt[mp])

        s = ["1.0 (Forced)"] * 3
        o = ["0.0 (Forced)"] * 3
        if blok['SIZE'][0] > 0.0:          #paranoia controls
            s[0] = "%.5f" % (size[0]/blok['SIZE'][0])
            o[0] = "%.5f" % ((blok['CNTR'][0]-pos[0])/blok['SIZE'][0])
        if blok['SIZE'][1] > 0.0:
            s[2] = "%.5f" % (size[2]/blok['SIZE'][1])
            o[2] = "%.5f" % ((blok['CNTR'][1]-pos[2])/blok['SIZE'][1])
        if blok['SIZE'][2] > 0.0:
            s[1] = "%.5f" % (size[1]/blok['SIZE'][2])
            o[1] = "%.5f" % ((blok['CNTR'][2]-pos[1])/blok['SIZE'][2])
        tobj.pprint ("!!!texture size and offsets:")
        tobj.pprint ("    sizeX = %s; sizeY = %s; sizeZ = %s" % (s[c_map[0]], s[c_map[1]], s[c_map[2]]))
        tobj.pprint ("    ofsX = %s; ofsY = %s; ofsZ = %s" % (o[c_map[0]], o[c_map[1]], o[c_map[2]]))
        return

    ti = 0
    for blok in surf['BLOK']:
        tobj.pprint ("#...................................................................#")
        tobj.pprint ("# Processing texture block no.%s for surf %s" % (ti,surf['NAME']))
        tobj.pprint ("#...................................................................#")
        tobj.pdict (blok)
        if ti > 9: break                                    #only 8 channels 0..7 allowed for texture mapping
        if not blok['ENAB']:
            tobj.pprint (  "***Image is not ENABled! Quitting this block")
            break
        if not(blok.has_key('IMAG')):
            tobj.pprint (  "***No IMAGe for this block? Quitting")
            break                 #extract out the image index within the clip_list
        tobj.pprint ("looking for image number %d" % blok['IMAG'])
        ima = lookup_imag(clip_list, blok['IMAG'])
        if ima == None:
            tobj.pprint (  "***Block index image not within CLIP list? Quitting Block")
            break                              #safety check (paranoia setting)
        img = ima['g_IMG']
        if img == None:
            tobj.pprint ("***Failed to pre-allocate image %s found: giving up" % ima['BASENAME'])
            break
        tname = str(ima['ID'])
        if blok.has_key('CHAN'):
            tname = tname + "+" + blok['CHAN']
        newtex = Blender.Texture.New(tname)
        newtex.setType('Image')                 # make it an image texture
        newtex.image = img
        #how does it extends beyond borders
        if blok.has_key('WRAP'):
            if (blok['WRAP'] == 3) or (blok['WRAP'] == 2):
                newtex.setExtend('Extend')
            elif (blok['WRAP'] == 1):
                newtex.setExtend('Repeat')
            elif (blok['WRAP'] == 0):
                newtex.setExtend('Clip')
        tobj.pprint ("generated texture %s" % tname)

        blendmode_list = ['Mix',
                         'Subtractive',
                         'Difference',
                         'Multiply',
                         'Divide',
                         'Mix (CalcAlpha already set; try setting Stencil!',
                         'Texture Displacement',
                         'Additive']
        set_blendmode = 7 #default additive
        if blok.has_key('OPAC'):
            set_blendmode = blok['OPAC']
        if set_blendmode == 5: #transparency
            newtex.imageFlags |= Blender.Texture.ImageFlags.CALCALPHA
        tobj.pprint ("!!!Set Texture -> MapTo -> Blending Mode = %s" % blendmode_list[set_blendmode])

        set_dvar = 1.0
        if blok.has_key('OPACVAL'):
            set_dvar = blok['OPACVAL']

        #MapTo is determined by CHAN parameter
        mapflag = Blender.Texture.MapTo.COL  #default to color
        if blok.has_key('CHAN'):
            if blok['CHAN'] == 'COLR':
                tobj.pprint ("!!!Set Texture -> MapTo -> Col = %.3f" % set_dvar)
            if blok['CHAN'] == 'BUMP':
                mapflag = Blender.Texture.MapTo.NOR
                tobj.pprint ("!!!Set Texture -> MapTo -> Nor = %.3f" % set_dvar)
            if blok['CHAN'] == 'LUMI':
                mapflag = Blender.Texture.MapTo.EMIT
                tobj.pprint ("!!!Set Texture -> MapTo -> DVar = %.3f" % set_dvar)
            if blok['CHAN'] == 'DIFF':
                mapflag = Blender.Texture.MapTo.REF
                tobj.pprint ("!!!Set Texture -> MapTo -> DVar = %.3f" % set_dvar)
            if blok['CHAN'] == 'SPEC':
                mapflag = Blender.Texture.MapTo.SPEC
                tobj.pprint ("!!!Set Texture -> MapTo -> DVar = %.3f" % set_dvar)
            if blok['CHAN'] == 'TRAN':
                mapflag = Blender.Texture.MapTo.ALPHA
                tobj.pprint ("!!!Set Texture -> MapTo -> DVar = %.3f" % set_dvar)
        if blok.has_key('NEGA'):
            tobj.pprint ("!!!Watch-out: effect of this texture channel must be INVERTED!")

        #the TexCo flag is determined by PROJ parameter
        if blok.has_key('PROJ'):
            if blok['PROJ'] == 0: #0 - Planar
               tobj.pprint ("!!!Flat projection")
               coordflag = Blender.Texture.TexCo.ORCO
               output_size_ofs(obj_size, obj_pos, blok)
            elif blok['PROJ'] == 1: #1 - Cylindrical
               tobj.pprint ("!!!Cylindrical projection")
               coordflag = Blender.Texture.TexCo.ORCO
               output_size_ofs(obj_size, obj_pos, blok)
            elif blok['PROJ'] == 2: #2 - Spherical
               tobj.pprint ("!!!Spherical projection")
               coordflag = Blender.Texture.TexCo.ORCO
               output_size_ofs(obj_size, obj_pos, blok)
            elif blok['PROJ'] == 3: #3 - Cubic
               tobj.pprint ("!!!Cubic projection")
               coordflag = Blender.Texture.TexCo.ORCO
               output_size_ofs(obj_size, obj_pos, blok)
            elif blok['PROJ'] == 4: #4 - Front Projection
               tobj.pprint ("!!!Front projection")
               coordflag = Blender.Texture.TexCo.ORCO
               output_size_ofs(obj_size, obj_pos, blok)
            elif blok['PROJ'] == 5: #5 - UV
               tobj.pprint ("UVMapped")
               coordflag = Blender.Texture.TexCo.UV
        mat.setTexture(ti, newtex, coordflag, mapflag)
        ti += 1
    #end loop over bloks
    return




# ========================================
# === Create and assign a new material ===
# ========================================
#def update_material(surf_list, ptag_dict, obj, clip_list, uv_dict, dir_part):
def update_material(clip_list, objspec, surf_list):
    if (surf_list == []) or (objspec[5] == {}) or (objspec[1] == {}):
        tobj.pprint( "something getting wrong in update_material: dump follows  ...")
        tobj.pprint( surf_list)
        tobj.pprint( objspec[5])
        tobj.pprint( objspec[1])
        return
    obj_dict = objspec[1]
    all_faces = objspec[3]
    obj_dim_dict = objspec[4]
    ptag_dict = objspec[5]
    uvcoords_dict = objspec[7]
    facesuv_dict = objspec[8]
    for surf in surf_list:
        if (surf['NAME'] in ptag_dict.keys()):
            tobj.pprint ("#-------------------------------------------------------------------#")
            tobj.pprint ("Processing surface (material): %s" % surf['NAME'])
            tobj.pprint ("#-------------------------------------------------------------------#")
            #material set up
            facelist = ptag_dict[surf['NAME']]
            #bounding box and position
            cur_obj = obj_dict[surf['NAME']]
            obj_size = obj_dim_dict[surf['NAME']][0]
            obj_pos = obj_dim_dict[surf['NAME']][1]
            tobj.pprint(surf)
            #uncomment this if material pre-allocated by read_surf
            mat = surf['g_MAT']
            if mat == None:
                tobj.pprint ("Sorry, no pre-allocated material to update. Giving up for %s." % surf['NAME'])
                break
            #mat = Blender.Material.New(surf['NAME'])
            #surf['g_MAT'] = mat
            if surf.has_key('COLR'):
                mat.rgbCol = surf['COLR']
            if surf.has_key('LUMI'):
                mat.setEmit(surf['LUMI'])
            if surf.has_key('GVAL'):
                mat.setAdd(surf['GVAL'])
            if surf.has_key('SPEC'):
                mat.setSpec(surf['SPEC'])                        #it should be * 2 but seems to be a bit higher lwo [0.0, 1.0] - blender [0.0, 2.0]
            if surf.has_key('DIFF'):
                mat.setRef(surf['DIFF'])                         #lwo [0.0, 1.0] - blender [0.0, 1.0]
            if surf.has_key('REFL'):
                mat.setRayMirr(surf['REFL'])                     #lwo [0.0, 1.0] - blender [0.0, 1.0]
                #mat.setMode('RAYMIRROR') NO! this will reset all the other modes
                #mat.mode |= Blender.Material.Modes.RAYMIRROR No more usable?
                mm = mat.getMode()
                mm |= Blender.Material.Modes.RAYMIRROR
                mm &= 327679 #4FFFF this is implementation dependent
                mat.setMode(mm)
            #WARNING translucency not implemented yet check 2.36 API
            #if surf.has_key('TRNL'):
            #
            if surf.has_key('GLOS'):                             #lwo [0.0, 1.0] - blender [0, 255]
                glo = int(371.67 * surf['GLOS'] - 42.334)        #linear mapping - seems to work better than exp mapping
                if glo <32:  glo = 32                            #clamped to 32-255
                if glo >255: glo = 255
                mat.setHardness(glo)
            if surf.has_key('TRAN'):
                mat.setAlpha(1.0-surf['TRAN'])                                        #lwo [0.0, 1.0] - blender [1.0, 0.0]
                #mat.mode |= Blender.Material.Modes.RAYTRANSP
                mm = mat.getMode()
                mm |= Blender.Material.Modes.RAYTRANSP
                mm &= 327679 #4FFFF this is implementation dependent
                mat.setMode(mm)
            if surf.has_key('RIND'):
                s = surf['RIND']
                if s < 1.0: s = 1.0
                if s > 3.0: s = 3.0
                mat.setIOR(s)                                                         #clipped to blender [1.0, 3.0]
                #mat.mode |= Blender.Material.Modes.RAYTRANSP
                mm = mat.getMode()
                mm |= Blender.Material.Modes.RAYTRANSP
                mm &= 327679 #4FFFF this is implementation dependent
                mat.setMode(mm)
            if surf.has_key('BLOK') and surf['BLOK'] != []:
                #update the material according to texture.
                create_blok(surf, mat, clip_list, obj_size, obj_pos)
            #finished setting up the material
        #end if exist SURF
    #end loop on materials (SURFs)
    return


# ======================
# === Read Faces 6.0 ===
# ======================
def read_faces_6(lwochunk):
    data = cStringIO.StringIO(lwochunk.read())
    faces = []
    polygon_type = data.read(4)
    subsurf = 0
    if polygon_type != "FACE" and polygon_type != "PTCH":
        tobj.pprint("No FACE/PATCH Were Found. Polygon Type: %s" % polygon_type)
        return "", 2
    if polygon_type == 'PTCH': subsurf = 1
    i = 0
    while(i < lwochunk.chunksize-4):
        if not i%1000 and my_meshtools.show_progress:
            Blender.Window.DrawProgressBar(float(i)/lwochunk.chunksize, "Reading Faces")
        facev = []
        numfaceverts, = struct.unpack(">H", data.read(2))
        i += 2

        for j in xrange(numfaceverts):
            index, index_size = read_vx(data)
            i += index_size
            facev.append(index)
        faces.append(facev)
    tobj.pprint("read %s faces; type of block %d (0=FACE; 1=PATCH)" % (len(faces), subsurf))
    return faces, subsurf



# ===========================================================
# === Start the show and main callback ======================
# ===========================================================

def fs_callback(filename):
    read(filename)

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


