# ##### 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 #####

bl_info = {
    "name": "Import Unreal Skeleton Mesh (.psk)/Animation Set (psa)",
    "author": "Darknet, flufy3d, camg188",
    "version": (2, 2, 0),
    "blender": (2, 64, 0),
    "location": "File > Import > Skeleton Mesh (.psk)/Animation Set (psa)",
    "description": "Import Skeleleton Mesh/Animation Data",
    "warning": "may produce errors, fix in progress",
    "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"
                "Scripts/Import-Export/Unreal_psk_psa",
    "category": "Import-Export",
}

"""
Version': '2.0' ported by Darknet

Unreal Tournament PSK file to Blender mesh converter V1.0
Author: <NAME> (camg188 at the elYsium forum), ported by Darknet
Imports a *psk file to a new mesh

-No UV Texutre
-No Weight
-No Armature Bones
-No Material ID
-Export Text Log From Current Location File (Bool )
"""

import bpy
import mathutils
import math
# XXX Yuck! 'from foo import *' is really bad!
from mathutils import *
from math import *
from bpy.props import *
from string import *
from struct import *
from math import *
from bpy.props import *

bpy.types.Scene.unrealbonesize = FloatProperty(
    name="Bone Length",
    description="Bone Length from head to tail distance",
    default=1, min=0.001, max=1000
)

#output log in to txt file
DEBUGLOG = False

scale = 1.0
bonesize = 1.0
from bpy_extras.io_utils import unpack_list, unpack_face_list

class md5_bone:
    bone_index = 0
    name = ""
    bindpos = []
    bindmat = []
    origmat = []
    head = []
    tail = []
    scale = []
    parent = ""
    parent_index = 0
    blenderbone = None
    roll = 0

    def __init__(self):
        self.bone_index = 0
        self.name = ""
        self.bindpos = [0.0] * 3
        self.scale = [0.0] * 3
        self.head = [0.0] * 3
        self.tail = [0.0] * 3
        self.bindmat = [None] * 3  # is this how you initialize a 2d-array
        for i in range(3):
            self.bindmat[i] = [0.0] * 3
        self.origmat = [None] * 3  #is this how you initialize a 2d-array
        for i in range(3):
            self.origmat[i] = [0.0] * 3
        self.parent = ""
        self.parent_index = 0
        self.blenderbone = None

    def dump(self):
        print ("bone index: ", self.bone_index)
        print ("name: ", self.name)
        print ("bind position: ", self.bindpos)
        print ("bind translation matrix: ", self.bindmat)
        print ("parent: ", self.parent)
        print ("parent index: ", self.parent_index)
        print ("blenderbone: ", self.blenderbone)

def getheadpos(pbone,bones):
    pos_head = [0.0] * 3

    #pos = mathutils.Vector((x,y,z)) * pbone.origmat
    pos = pbone.bindmat.to_translation()

    """
    tmp_bone = pbone
    while tmp_bone.name != tmp_bone.parent.name:
        pos = pos * tmp_bone.parent.bindmat
        tmp_bone = tmp_bone.parent
    """

    pos_head[0] = pos.x
    pos_head[1] = pos.y
    pos_head[2] = pos.z

    return pos_head

def gettailpos(pbone,bones):
    pos_tail = [0.0] * 3
    ischildfound = False
    childbone = None
    childbonelist = []
    for bone in bones:
        if bone.parent.name == pbone.name:
            ischildfound = True
            childbone = bone
            childbonelist.append(bone)

    if ischildfound:
        tmp_head = [0.0] * 3
        for bone in childbonelist:
            tmp_head[0] += bone.head[0]
            tmp_head[1] += bone.head[1]
            tmp_head[2] += bone.head[2]
        tmp_head[0] /= len(childbonelist)
        tmp_head[1] /= len(childbonelist)
        tmp_head[2] /= len(childbonelist)
        return tmp_head
    else:
        tmp_len = 0.0
        tmp_len += (pbone.head[0] - pbone.parent.head[0]) ** 2
        tmp_len += (pbone.head[1] - pbone.parent.head[1]) ** 2
        tmp_len += (pbone.head[2] - pbone.parent.head[2]) ** 2
        tmp_len = tmp_len ** 0.5 * 0.5
        pos_tail[0] = pbone.head[0] + tmp_len * pbone.bindmat[0][0]
        pos_tail[1] = pbone.head[1] + tmp_len * pbone.bindmat[1][0]
        pos_tail[2] = pbone.head[2] + tmp_len * pbone.bindmat[2][0]

    return pos_tail

def pskimport(infile,importmesh,importbone,bDebugLogPSK,importmultiuvtextures):
    global DEBUGLOG
    DEBUGLOG = bDebugLogPSK
    print ("--------------------------------------------------")
    print ("---------SCRIPT EXECUTING PYTHON IMPORTER---------")
    print ("--------------------------------------------------")
    print (" DEBUG Log:",bDebugLogPSK)
    print ("Importing file: ", infile)

    pskfile = open(infile,'rb')
    if (DEBUGLOG):
        logpath = infile.replace(".psk", ".txt")
        print("logpath:",logpath)
        logf = open(logpath,'w')

    def printlog(strdata):
        if (DEBUGLOG):
            logf.write(strdata)

    objName = infile.split('\\')[-1].split('.')[0]

    me_ob = bpy.data.meshes.new(objName)
    print("objName:",objName)
    printlog(("New Mesh = " + me_ob.name + "\n"))
    #read general header
    indata = unpack('20s3i', pskfile.read(32))
    #not using the general header at this time
    #==================================================================================================
    # vertex point
    #==================================================================================================
    #read the PNTS0000 header
    indata = unpack('20s3i', pskfile.read(32))
    recCount = indata[3]
    printlog(("Nbr of PNTS0000 records: " + str(recCount) + "\n"))
    counter = 0
    verts = []
    verts2 = []
    while counter < recCount:
        counter = counter + 1
        indata = unpack('3f', pskfile.read(12))
        #print(indata[0], indata[1], indata[2])
        verts.extend([(indata[0], indata[1], indata[2])])
        verts2.extend([(indata[0], indata[1], indata[2])])
        #print([(indata[0], indata[1], indata[2])])
        printlog(str(indata[0]) + "|" + str(indata[1]) + "|" + str(indata[2]) + "\n")
        #Tmsh.vertices.append(NMesh.Vert(indata[0], indata[1], indata[2]))

    #==================================================================================================
    # UV
    #==================================================================================================
    #read the VTXW0000 header
    indata = unpack('20s3i', pskfile.read(32))
    recCount = indata[3]
    printlog("Nbr of VTXW0000 records: " + str(recCount)+ "\n")
    counter = 0
    UVCoords = []
    #UVCoords record format = [index to PNTS, U coord, v coord]
    printlog("[index to PNTS, U coord, v coord]\n");
    while counter < recCount:
        counter = counter + 1
        indata = unpack('hhffhh', pskfile.read(16))
        UVCoords.append([indata[0], indata[2], indata[3]])
        printlog(str(indata[0]) + "|" + str(indata[2]) + "|" + str(indata[3]) + "\n")
        #print('mat index %i', indata(4))
        #print([indata[0], indata[2], indata[3]])
        #print([indata[1], indata[2], indata[3]])

    #==================================================================================================
    # Face
    #==================================================================================================
    #read the FACE0000 header
    indata = unpack('20s3i', pskfile.read(32))
    recCount = indata[3]
    printlog("Nbr of FACE0000 records: " + str(recCount) + "\n")
    #PSK FACE0000 fields: WdgIdx1|WdgIdx2|WdgIdx3|MatIdx|AuxMatIdx|SmthGrp
    #associate MatIdx to an image, associate SmthGrp to a material
    SGlist = []
    counter = 0
    faces = []
    faceuv = []
    facesmooth = []
    #the psk values are: nWdgIdx1|WdgIdx2|WdgIdx3|MatIdx|AuxMatIdx|SmthGrp
    printlog("nWdgIdx1|WdgIdx2|WdgIdx3|MatIdx|AuxMatIdx|SmthGrp \n")
    while counter < recCount:
        counter = counter + 1
        indata = unpack('hhhbbi', pskfile.read(12))
        printlog(str(indata[0]) + "|" + str(indata[1]) + "|" + str(indata[2]) + "|" + str(indata[3]) + "|" +
                 str(indata[4]) + "|" + str(indata[5]) + "\n")
        #indata[0] = index of UVCoords
        #UVCoords[indata[0]]=[index to PNTS, U coord, v coord]
        #UVCoords[indata[0]][0] = index to PNTS
        PNTSA = UVCoords[indata[2]][0]
        PNTSB = UVCoords[indata[1]][0]
        PNTSC = UVCoords[indata[0]][0]
        #print(PNTSA, PNTSB, PNTSC) #face id vertex
        #faces.extend([0, 1, 2, 0])
        faces.extend([(PNTSA, PNTSB, PNTSC, 0)])
        uv = []
        u0 = UVCoords[indata[2]][1]
        v0 = UVCoords[indata[2]][2]
        uv.append([u0, 1.0 - v0])
        u1 = UVCoords[indata[1]][1]
        v1 = UVCoords[indata[1]][2]
        uv.append([u1, 1.0 - v1])
        u2 = UVCoords[indata[0]][1]
        v2 = UVCoords[indata[0]][2]
        uv.append([u2, 1.0 - v2])
        faceuv.append([uv, indata[3], indata[4], indata[5]])

        #print("material:", indata[3])
        #print("UV: ", u0, v0)
        #update the uv var of the last item in the Tmsh.faces list
        # which is the face just added above
        ##Tmsh.faces[-1].uv = [(u0, v0), (u1, v1), (u2, v2)]
        #print("smooth:",indata[5])
        #collect a list of the smoothing groups
        facesmooth.append(indata[5])
        #print(indata[5])
        if SGlist.count(indata[5]) == 0:
            SGlist.append(indata[5])
            print("smooth:", indata[5])
        #assign a material index to the face
        #Tmsh.faces[-1].materialIndex = SGlist.index(indata[5])
    printlog("Using Materials to represent PSK Smoothing Groups...\n")
    #==========
    # skip something...
    #==========

    #==================================================================================================
    # Material
    #==================================================================================================
    ##
    #read the MATT0000 header
    indata = unpack('20s3i', pskfile.read(32))
    recCount = indata[3]
    printlog("Nbr of MATT0000 records: " +  str(recCount) + "\n" )
    printlog(" - Not importing any material data now. PSKs are texture wrapped! \n")
    counter = 0
    materialcount = 0
    while counter < recCount:
        counter = counter + 1
        indata = unpack('64s6i', pskfile.read(88))
        materialcount += 1
        print("Material", counter)
        print("Mat name %s", indata[0])

    ##
    #==================================================================================================
    # Bones (Armature)
    #==================================================================================================
    #read the REFSKEL0 header
    indata = unpack('20s3i', pskfile.read(32))
    recCount = indata[3]
    printlog( "Nbr of REFSKEL0 records: " + str(recCount) + "\n")
    #REFSKEL0 fields - Name|Flgs|NumChld|PrntIdx|Qw|Qx|Qy|Qz|LocX|LocY|LocZ|Lngth|XSize|YSize|ZSize

    Bns = []
    bone = []

    md5_bones = []
    bni_dict = {}
    #==================================================================================================
    # Bone Data
    #==================================================================================================
    counter = 0
    print ("---PRASE--BONES---")
    printlog("Name|Flgs|NumChld|PrntIdx|Qx|Qy|Qz|Qw|LocX|LocY|LocZ|Lngth|XSize|YSize|ZSize\n")
    while counter < recCount:
        indata = unpack('64s3i11f', pskfile.read(120))
        #print( "DATA",str(indata))

        bone.append(indata)

        createbone = md5_bone()
        #temp_name = indata[0][:30]
        temp_name = indata[0]
        temp_name = bytes.decode(temp_name)
        temp_name = temp_name.lstrip(" ")
        temp_name = temp_name.rstrip(" ")
        temp_name = temp_name.strip()
        temp_name = temp_name.strip( bytes.decode(b'\x00'))
        printlog(temp_name + "|" + str(indata[1]) + "|" + str(indata[2]) + "|" + str(indata[3]) + "|" +
                 str(indata[4]) + "|" + str(indata[5]) + "|" + str(indata[6]) + "|" + str(indata[7]) + "|" +
                 str(indata[8]) + "|" + str(indata[9]) + "|" + str(indata[10]) + "|" + str(indata[11]) + "|" +
                 str(indata[12]) + "|" + str(indata[13]) + "|" + str(indata[14]) + "\n")
        createbone.name = temp_name
        createbone.bone_index = counter
        createbone.parent_index = indata[3]
        createbone.bindpos[0] = indata[8]
        createbone.bindpos[1] = indata[9]
        createbone.bindpos[2] = indata[10]
        createbone.scale[0] = indata[12]
        createbone.scale[1] = indata[13]
        createbone.scale[2] = indata[14]

        bni_dict[createbone.name] = createbone.bone_index

        #w,x,y,z
        if (counter == 0):#main parent
             createbone.bindmat = mathutils.Quaternion((indata[7], -indata[4], -indata[5], -indata[6])).to_matrix()
             createbone.origmat = mathutils.Quaternion((indata[7], -indata[4], -indata[5], -indata[6])).to_matrix()
        else:
             createbone.bindmat = mathutils.Quaternion((indata[7], -indata[4], -indata[5], -indata[6])).to_matrix()
             createbone.origmat = mathutils.Quaternion((indata[7], -indata[4], -indata[5], -indata[6])).to_matrix()

        createbone.bindmat = mathutils.Matrix.Translation(mathutils.Vector((indata[8], indata[9], indata[10]))) * \
                             createbone.bindmat.to_4x4()

        md5_bones.append(createbone)
        counter = counter + 1
        bnstr = (str(indata[0]))
        Bns.append(bnstr)

    for pbone in md5_bones:
        pbone.parent = md5_bones[pbone.parent_index]

    for pbone in md5_bones:
        if pbone.name != pbone.parent.name:
            pbone.bindmat = pbone.parent.bindmat * pbone.bindmat
            #print(pbone.name)
            #print(pbone.bindmat)
            #print("end")
        else:
            pbone.bindmat = pbone.bindmat

    for pbone in md5_bones:
        pbone.head = getheadpos(pbone, md5_bones)

    for pbone in md5_bones:
        pbone.tail = gettailpos(pbone, md5_bones)

    for pbone in md5_bones:
        pbone.parent =  md5_bones[pbone.parent_index].name

    bonecount = 0
    for armbone in bone:
        temp_name = armbone[0][:30]
        #print ("BONE NAME: ", len(temp_name))
        temp_name=str((temp_name))
        #temp_name = temp_name[1]
        #print ("BONE NAME: ", temp_name)
        bonecount += 1
    print ("-------------------------")
    print ("----Creating--Armature---")
    print ("-------------------------")

    #================================================================================================
    #Check armature if exist if so create or update or remove all and addnew bone
    #================================================================================================
    #bpy.ops.object.mode_set(mode='OBJECT')
    meshname ="ArmObject"
    objectname = "armaturedata"
    # arm = None  # UNUSED
    if importbone:
        obj = bpy.data.objects.get(meshname)
        # arm = obj  # UNUSED

        if not obj:
            armdata = bpy.data.armatures.new(objectname)
            ob_new = bpy.data.objects.new(meshname, armdata)
            #ob_new = bpy.data.objects.new(meshname, 'ARMATURE')
            #ob_new.data = armdata
            bpy.context.collection.objects.link(ob_new)
            #bpy.ops.object.mode_set(mode='OBJECT')
            for i in bpy.context.scene.objects:
                i.select_set(False) #deselect all objects
            ob_new.select_set(True)
            #set current armature to edit the bone
            bpy.context.view_layer.objects.active = ob_new
            #set mode to able to edit the bone
            if bpy.ops.object.mode_set.poll():
                bpy.ops.object.mode_set(mode='EDIT')

            #newbone = ob_new.data.edit_bones.new('test')
            #newbone.tail.y = 1
            print("creating bone(s)")
            bpy.ops.object.mode_set(mode='OBJECT')
            for bone in md5_bones:
                #print(dir(bone))
                bpy.ops.object.mode_set(mode='EDIT')#Go to edit mode for the bones
                newbone = ob_new.data.edit_bones.new(bone.name)
                #parent the bone
                #print("DRI:", dir(newbone))
                parentbone = None
                #note bone location is set in the real space or global not local
                bonesize = bpy.types.Scene.unrealbonesize
                if bone.name != bone.parent:
                    pos_x = bone.bindpos[0]
                    pos_y = bone.bindpos[1]
                    pos_z = bone.bindpos[2]
                    #print("LINKING:" , bone.parent ,"j")
                    parentbone = ob_new.data.edit_bones[bone.parent]
                    newbone.parent = parentbone
                    rotmatrix = bone.bindmat
                    newbone.head.x = bone.head[0]
                    newbone.head.y = bone.head[1]
                    newbone.head.z = bone.head[2]
                    newbone.tail.x = bone.tail[0]
                    newbone.tail.y = bone.tail[1]
                    newbone.tail.z = bone.tail[2]

                    vecp = parentbone.tail - parentbone.head
                    vecc = newbone.tail - newbone.head
                    vecc.normalize()
                    vecp.normalize()
                    if vecp.dot(vecc) > -0.8:
                        newbone.roll = parentbone.roll
                    else:
                        newbone.roll = - parentbone.roll
                else:
                    rotmatrix = bone.bindmat
                    newbone.head.x = bone.head[0]
                    newbone.head.y = bone.head[1]
                    newbone.head.z = bone.head[2]
                    newbone.tail.x = bone.tail[0]
                    newbone.tail.y = bone.tail[1]
                    newbone.tail.z = bone.tail[2]
                    newbone.roll = math.radians(90.0)
                """
                vec = newbone.tail - newbone.head
                if vec.z > 0.0:
                    newbone.roll = math.radians(90.0)
                else:
                    newbone.roll = math.radians(-90.0)
                """
    bpy.context.scene.update()

    #==================================================================================================
    #END BONE DATA BUILD
    #==================================================================================================
    VtxCol = []
    for x in range(len(Bns)):
        #change the overall darkness of each material in a range between 0.1 and 0.9
        tmpVal = ((float(x) + 1.0) / (len(Bns)) * 0.7) + 0.1
        tmpVal = int(tmpVal * 256)
        tmpCol = [tmpVal, tmpVal, tmpVal, 0]
        #Change the color of each material slightly
        if x % 3 == 0:
            if tmpCol[0] < 128:
                tmpCol[0] += 60
            else:
                tmpCol[0] -= 60
        if x % 3 == 1:
            if tmpCol[1] < 128:
                tmpCol[1] += 60
            else:
                tmpCol[1] -= 60
        if x % 3 == 2:
            if tmpCol[2] < 128:
                tmpCol[2] += 60
            else:
                tmpCol[2] -= 60
        #Add the material to the mesh
        VtxCol.append(tmpCol)

    #==================================================================================================
    # Bone Weight
    #==================================================================================================
    #read the RAWW0000 header
    indata = unpack('20s3i', pskfile.read(32))
    recCount = indata[3]
    printlog("Nbr of RAWW0000 records: " + str(recCount) +"\n")
    #RAWW0000 fields: Weight|PntIdx|BoneIdx
    RWghts = []
    counter = 0
    while counter < recCount:
        counter = counter + 1
        indata = unpack('fii', pskfile.read(12))
        RWghts.append([indata[1], indata[2], indata[0]])
        #print("weight:", [indata[1], indata[2], indata[0]])
    #RWghts fields = PntIdx|BoneIdx|Weight
    RWghts.sort()
    printlog("Vertex point and groups count =" + str(len(RWghts)) + "\n")
    printlog("PntIdx|BoneIdx|Weight")
    for vg in RWghts:
        printlog(str(vg[0]) + "|" + str(vg[1]) + "|" + str(vg[2]) + "\n")

    #Tmsh.update_tag()

    #set the Vertex Colors of the faces
    #face.v[n] = RWghts[0]
    #RWghts[1] = index of VtxCol
    """
    for x in range(len(Tmsh.faces)):
        for y in range(len(Tmsh.faces[x].v)):
            #find v in RWghts[n][0]
            findVal = Tmsh.faces[x].v[y].index
            n = 0
            while findVal != RWghts[n][0]:
                n = n + 1
            TmpCol = VtxCol[RWghts[n][1]]
            #check if a vertex has more than one influence
            if n != len(RWghts) - 1:
                if RWghts[n][0] == RWghts[n + 1][0]:
                    #if there is more than one influence, use the one with the greater influence
                    #for simplicity only 2 influences are checked, 2nd and 3rd influences are usually very small
                    if RWghts[n][2] < RWghts[n + 1][2]:
                        TmpCol = VtxCol[RWghts[n + 1][1]]
        Tmsh.faces[x].col.append(NMesh.Col(TmpCol[0], TmpCol[1], TmpCol[2], 0))
    """
    if (DEBUGLOG):
        logf.close()
    #==================================================================================================
    #Building Mesh
    #==================================================================================================
    print("vertex:", len(verts), "faces:", len(faces))
    print("vertex2:", len(verts2))
    me_ob.vertices.add(len(verts2))
    me_ob.tessfaces.add(len(faces))
    me_ob.vertices.foreach_set("co", unpack_list(verts2))
    me_ob.tessfaces.foreach_set("vertices_raw", unpack_list( faces))

    for face in me_ob.tessfaces:
        face.use_smooth = facesmooth[face.index]

    """
    Material setup coding.
    First the mesh has to be create first to get the uv texture setup working.
    -Create material(s) list in the psk pack data from the list.(to do list)
    -Append the material to the from create the mesh object.
    -Create Texture(s)
    -face loop for uv assign and assign material index
    """
    bpy.ops.object.mode_set(mode='OBJECT')
    #===================================================================================================
    #Material Setup
    #===================================================================================================
    print ("-------------------------")
    print ("----Creating--Materials--")
    print ("-------------------------")
    materialname = "pskmat"
    materials = []

    for matcount in range(materialcount):
        #if texturedata is not None:
        matdata = bpy.data.materials.new(materialname + str(matcount))
        #mtex = matdata.texture_slots.new()
        #mtex.texture = texture[matcount].data
        #print(type(texture[matcount].data))
        #print(dir(mtex))
        #print(dir(matdata))
        #for texno in range(len( bpy.data.textures)):
            #print((bpy.data.textures[texno].name))
            #print(dir(bpy.data.textures[texno]))
        #matdata.active_texture = bpy.data.textures[matcount - 1]
        #matdata.texture_coords = 'UV'
        #matdata.active_texture = texturedata
        materials.append(matdata)

    for material in materials:
        #add material to the mesh list of materials
        me_ob.materials.append(material)
    #===================================================================================================
    #UV Setup
    #===================================================================================================
    print ("-------------------------")
    print ("-- Creating UV Texture --")
    print ("-------------------------")
    texture = []
    # texturename = "text1"  # UNUSED
    countm = 0
    for countm in range(materialcount):
        psktexname = "psk" + str(countm)
        me_ob.uv_textures.new(name=psktexname)
        countm += 1
    print("INIT UV TEXTURE...")
    _matcount = 0
    #for mattexcount in materials:
        #print("MATERIAL ID:", _matcount)
    _textcount = 0
    for uv in me_ob.tessface_uv_textures: # uv texture
        print("UV TEXTURE ID:",_textcount)
        print(dir(uv))
        for face in me_ob.tessfaces:# face, uv
            #print(dir(face))
            if faceuv[face.index][1] == _textcount: #if face index and texture index matches assign it
                mfaceuv = faceuv[face.index] #face index
                _uv1 = mfaceuv[0][0] #(0,0)
                uv.data[face.index].uv1 = mathutils.Vector((_uv1[0], _uv1[1])) #set them
                _uv2 = mfaceuv[0][1] #(0,0)
                uv.data[face.index].uv2 = mathutils.Vector((_uv2[0], _uv2[1])) #set them
                _uv3 = mfaceuv[0][2] #(0,0)
                uv.data[face.index].uv3 = mathutils.Vector((_uv3[0], _uv3[1])) #set them
            else: #if not match zero them
                uv.data[face.index].uv1 = mathutils.Vector((0, 0)) #zero them
                uv.data[face.index].uv2 = mathutils.Vector((0, 0)) #zero them
                uv.data[face.index].uv3 = mathutils.Vector((0, 0)) #zero them
        _textcount += 1
        #_matcount += 1
        #print(matcount)
    print("END UV TEXTURE...")

    print("UV TEXTURE LEN:", len(texture))
        #for tex in me_ob.uv_textures:
            #print("mesh tex:", dir(tex))
            #print((tex.name))

    #for face in me_ob.faces:
        #print(dir(face))

    #===================================================================================================
    #
    #===================================================================================================
    obmesh = bpy.data.objects.new(objName,me_ob)
    #===================================================================================================
    #Mesh Vertex Group bone weight
    #===================================================================================================
    print("---- building bone weight mesh ----")
    #print(dir(ob_new.data.bones))
    #create bone vertex group #deal with bone id for index number
    for bone in ob_new.data.bones:
        #print("names:", bone.name, ":", dir(bone))
        #print("names:", bone.name)
        group = obmesh.vertex_groups.new(name=bone.name)

    for vgroup in obmesh.vertex_groups:
        #print(vgroup.name, ":", vgroup.index)
        for vgp in RWghts:
            #bone index
            if vgp[1] == bni_dict[vgroup.name]:
                #print(vgp)
                #[vertex id],weight
                vgroup.add([vgp[0]], vgp[2], 'ADD')

    #check if there is a material to set to
    if len(materials) > 0:
        obmesh.active_material = materials[0] #material setup tmp
    print("---- adding mesh to the scene ----")

    bpy.ops.object.mode_set(mode='OBJECT')
    #bpy.ops.object.select_pattern(extend=True, pattern=obmesh.name, case_sensitive=True)
    #bpy.ops.object.select_pattern(extend=True, pattern=ob_new.name, case_sensitive=True)

    #bpy.ops.object.select_name(name=str(obmesh.name))
    #bpy.ops.object.select_name(name=str(ob_new.name))
    #bpy.context.scene.objects.active = ob_new
    me_ob.update()
    bpy.context.collection.objects.link(obmesh)
    bpy.context.scene.update()
    obmesh.select_set(False)
    ob_new.select_set(False)
    obmesh.select_set(True)
    ob_new.select_set(True)
    bpy.ops.object.parent_set(type="ARMATURE")

    print ("PSK2Blender completed")
#End of def pskimport#########################

def getInputFilenamepsk(self, filename, importmesh, importbone, bDebugLogPSK, importmultiuvtextures):
    checktype = filename.split('\\')[-1].split('.')[1]
    print ("------------",filename)
    if checktype.lower() != 'psk':
        print ("  Selected file = ", filename)
        raise (IOError, "The selected input file is not a *.psk file")
        #self.report({'INFO'}, ("Selected file:"+ filename))
    else:
        pskimport(filename, importmesh, importbone, bDebugLogPSK, importmultiuvtextures)

def getInputFilenamepsa(self, filename, context):
    checktype = filename.split('\\')[-1].split('.')[1]
    if checktype.lower() != 'psa':
        print ("  Selected file = ", filename)
        raise (IOError, "The selected input file is not a *.psa file")
        #self.report({'INFO'}, ("Selected file:" + filename))
    else:
        psaimport(filename,context)

class IMPORT_OT_psk(bpy.types.Operator):
    '''Load a skeleton mesh psk File'''
    bl_idname = "import_scene.psk"
    bl_label = "Import PSK"
    bl_space_type = "PROPERTIES"
    bl_region_type = "WINDOW"
    bl_options = {'UNDO'}

    # List of operator properties, the attributes will be assigned
    # to the class instance from the operator settings before calling.
    filepath = StringProperty(
            subtype='FILE_PATH',
            )
    filter_glob = StringProperty(
            default="*.psk",
            options={'HIDDEN'},
            )
    importmesh = BoolProperty(
            name="Mesh",
            description="Import mesh only. (not yet build.)",
            default=True,
            )
    importbone = BoolProperty(
            name="Bones",
            description="Import bones only. Current not working yet",
            default=True,
            )
    importmultiuvtextures = BoolProperty(
            name="Single UV Texture(s)",
            description="Single or Multi uv textures",
            default=True,
            )
    bDebugLogPSK = BoolProperty(
            name="Debug Log.txt",
            description="Log the output of raw format. It will save in "
                        "current file dir. Note this just for testing",
            default=False,
            )
    unrealbonesize = FloatProperty(
            name="Bone Length",
            description="Bone Length from head to tail distance",
            default=1,
            min=0.001,
            max=1000,
            )

    def execute(self, context):
        bpy.types.Scene.unrealbonesize = self.unrealbonesize
        getInputFilenamepsk(self, self.filepath, self.importmesh, self.importbone, self.bDebugLogPSK,
                            self.importmultiuvtextures)
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        wm.fileselect_add(self)
        return {'RUNNING_MODAL'}

class psa_bone:
    name=""
    Transform=None
    parent=None
    def __init__(self):
        self.name=""
        self.Transform=None
        self.parent=None

def psaimport(filename,context):
    print ("--------------------------------------------------")
    print ("---------SCRIPT EXECUTING PYTHON IMPORTER---------")
    print ("--------------------------------------------------")
    print ("Importing file: ", filename)
    psafile = open(filename,'rb')
    debug = True
    if (debug):
        logpath = filename.replace(".psa", ".txt")
        print("logpath:", logpath)
        logf = open(logpath, 'w')
    def printlog(strdata):
        if (debug):
            logf.write(strdata)
    def printlogplus(name, data):
        if (debug):
            logf.write(str(name) + '\n')
            if isinstance(data, bytes):
                logf.write(str(bytes.decode(data).strip(bytes.decode(b'\x00'))))
            else:
                logf.write(str(data))
            logf.write('\n')

    printlog('-----------Log File------------\n')
    #General Header
    indata = unpack('20s3i', psafile.read(32))
    printlogplus('ChunkID', indata[0])
    printlogplus('TypeFlag', indata[1])
    printlogplus('DataSize', indata[2])
    printlogplus('DataCount', indata[3])
    #Bones Header
    indata = unpack('20s3i', psafile.read(32))
    printlogplus('ChunkID', indata[0])
    printlogplus('TypeFlag', indata[1])
    printlogplus('DataSize', indata[2])
    printlogplus('DataCount', indata[3])
    #Bones Data
    BoneIndex2NamePairMap = {}
    BoneNotFoundList = []
    printlog("Name|Flgs|NumChld|PrntIdx|Qx|Qy|Qz|Qw|LocX|LocY|LocZ|Length|XSize|YSize|ZSize\n")
    recCount = indata[3]
    counter = 0
    nobonematch = True
    while counter < recCount:
        indata = unpack('64s3i11f', psafile.read(120))
        #printlogplus('bone', indata[0])
        bonename = str(bytes.decode(indata[0]).strip(bytes.decode(b'\x00')))
        if bonename in bpy.data.armatures['armaturedata'].bones.keys():
            BoneIndex2NamePairMap[counter] = bonename
            print('find bone', bonename)
            nobonematch = False
        else:
            print('can not find the bone:', bonename)
            BoneNotFoundList.append(counter)
        counter += 1

    if nobonematch:
        print('no bone was match so skip import!')
        return

    #Animations Header
    indata = unpack('20s3i', psafile.read(32))
    printlogplus('ChunkID', indata[0])
    printlogplus('TypeFlag', indata[1])
    printlogplus('DataSize', indata[2])
    printlogplus('DataCount', indata[3])
    #Animations Data
    recCount = indata[3]
    counter = 0
    Raw_Key_Nums = 0
    Action_List = []
    while counter < recCount:
        indata = unpack('64s64s4i3f3i', psafile.read(64 + 64 + 4 * 4 + 3 * 4 + 3 * 4))
        printlogplus('Name', indata[0])
        printlogplus('Group', indata[1])
        printlogplus('totalbones', indata[2])
        printlogplus('NumRawFrames', indata[-1])
        Name = str(bytes.decode(indata[0]).strip(bytes.decode(b'\x00')))
        Group = str(bytes.decode(indata[1]).strip(bytes.decode(b'\x00')))
        totalbones = indata[2]
        NumRawFrames = indata[-1]

        Raw_Key_Nums += indata[2] * indata[-1]
        Action_List.append((Name,Group,totalbones,NumRawFrames))

        counter += 1

    #Raw keys Header
    Raw_Key_List = []
    indata = unpack('20s3i', psafile.read(32))
    printlogplus('ChunkID', indata[0])
    printlogplus('TypeFlag', indata[1])
    printlogplus('DataSize', indata[2])
    printlogplus('DataCount', indata[3])
    if(Raw_Key_Nums != indata[3]):
        print('error! Raw_Key_Nums Inconsistent')
        return
    #Raw keys Data
    recCount = Raw_Key_Nums
    counter = 0
    while counter < recCount:
        indata = unpack('3f4f1f', psafile.read(3 * 4 + 4 * 4 + 4))
        pos = mathutils.Vector((indata[0], indata[1], indata[2]))
        quat = mathutils.Quaternion((indata[6], indata[3], indata[4], indata[5]))
        time = indata[7]
        Raw_Key_List.append((pos, quat, time))
        counter += 1
    #Scale keys Header,Scale keys Data,Curve keys Header,Curve keys Data
    curFilePos = psafile.tell()
    psafile.seek(0, 2)
    endFilePos = psafile.tell()
    if curFilePos == endFilePos:
        print('no Scale keys,Curve keys')

    #build the animation line
    if bpy.ops.object.mode_set.poll():
        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

    NeededBoneMatrix = {}
    ARMATURE_OBJ = 'ArmObject'
    ARMATURE_DATA = 'armaturedata'
    if bpy.context.scene.udk_importarmatureselect:
        if len(bpy.context.scene.udkas_list) > 0:
            print("CHECKING ARMATURE...")
            #for bone in bpy.data.objects[ARMATURE_OBJ].pose.bones:
            #for objd in bpy.data.objects:
                #print("NAME:", objd.name, " TYPE:", objd.type)
                #if objd.type == 'ARMARURE':
                    #print(dir(objd))
            armature_list = bpy.context.scene.udkas_list #armature list array
            armature_idx = bpy.context.scene.udkimportarmature_list_idx #armature index selected
            ARMATURE_OBJ = bpy.data.objects[armature_list[armature_idx]].name #object armature
            ARMATURE_DATA = bpy.data.objects[armature_list[armature_idx]].data.name #object data

    for bone in bpy.data.armatures[ARMATURE_DATA].bones:
        name = bone.name
        ori_matrix = bone.matrix
        matrix = bone.matrix_local.to_3x3()
        bone_rest_matrix = Matrix(matrix)
        #bone_rest_matrix = bone.matrix_local.to_3x3()
        #bone_rest_matrix = bone.matrix_local.to_quaternion().conjugated().to_matrix()
        bone_rest_matrix_inv = Matrix(bone_rest_matrix)
        bone_rest_matrix_inv.invert()
        bone_rest_matrix_inv.resize_4x4()
        bone_rest_matrix.resize_4x4()
        NeededBoneMatrix[name] = (bone_rest_matrix,bone_rest_matrix_inv,ori_matrix)

    #build tmp pose bone tree
    psa_bones = {}
    for bone in bpy.data.objects[ARMATURE_OBJ].pose.bones:
        _psa_bone = psa_bone()
        _psa_bone.name = bone.name
        _psa_bone.Transform = bone.matrix
        if bone.parent is not None:
            _psa_bone.parent = psa_bones[bone.parent.name]
        else:
            _psa_bone.parent = None
        psa_bones[bone.name] = _psa_bone

    raw_key_index = 0

    for raw_action in Action_List:
        Name = raw_action[0]
        Group = raw_action[1]
        Totalbones = raw_action[2]
        NumRawFrames = raw_action[3]
        context.scene.update()
        object = bpy.data.objects['ArmObject']
        object.animation_data_create()
        action = bpy.data.actions.new(name=Name)
        object.animation_data.action = action
        for i in range(NumRawFrames):
            context.scene.frame_set(i + 1)
            pose_bones = object.pose.bones
            for j in range(Totalbones):
                if j not in BoneNotFoundList:
                    bName = BoneIndex2NamePairMap[j]
                    pbone = psa_bones[bName]
                    pos = Raw_Key_List[raw_key_index][0]
                    quat = Raw_Key_List[raw_key_index][1]

                    mat = Matrix()
                    if pbone.parent is not None:
                        quat = quat.conjugated()
                        mat = Matrix.Translation(pos) * quat.to_matrix().to_4x4()
                        mat = pose_bones[bName].parent.matrix * mat
                        #mat = pbone.parent.Transform * mat
                    else:
                        mat = pbone.Transform * Matrix.Translation(pos) * quat.to_matrix().to_4x4()

                    pose_bones[bName].matrix = mat
                    pbone.Transform = mat

                raw_key_index += 1

            #bpy.data.meshes[1]
            for bone in pose_bones:
                bone.matrix = psa_bones[bone.name].Transform
                bone.keyframe_insert("rotation_quaternion")
                bone.keyframe_insert("location")

            def whirlSingleBone(pose_bone,quat):
                bpy.context.scene.update()
                #record child's matrix and origin rotate
                hymat = Quaternion((0.707, -0.707, 0, 0)).inverted().to_matrix().to_4x4()
                children_infos = {}
                childrens = pose_bone.children
                for child in childrens:
                    armmat = bpy.data.armatures['armaturedata'].bones[child.name].matrix.copy().to_4x4()
                    cmat = child.matrix.copy() * armmat.inverted() * hymat.inverted()
                    pos = cmat.to_translation()
                    rotmat = cmat.to_3x3()
                    children_infos[child] = (armmat, pos, rotmat)

                #whirl this bone by quat
                pose_bone.matrix *= quat.to_matrix().to_4x4()
                pose_bone.keyframe_insert("location")
                pose_bone.keyframe_insert("rotation_quaternion")
                bpy.context.scene.update()
                #set back children bon to original position
                #reverse whirl child bone by quat.inverse()

                for child in childrens:
                    armmat = children_infos[child][0]
                    pos = children_infos[child][1]
                    rotmat = children_infos[child][2]

                    child.matrix = Matrix.Translation(pos) * rotmat.to_4x4() * hymat * armmat
                    child.keyframe_insert("location")
                    child.keyframe_insert("rotation_quaternion")

            for bone in pose_bones:
                if bone.parent is not None:
                    whirlSingleBone(bone,Quaternion((0.707, 0, 0, -0.707)))
                else:
                    bone.rotation_quaternion *= Quaternion((0.707, -0.707, 0, 0)) * Quaternion((0.707, 0, 0, -0.707))
                    bone.keyframe_insert("rotation_quaternion")

        break

    context.scene.frame_set(0)
    if(debug):
        logf.close()

class IMPORT_OT_psa(bpy.types.Operator):
    '''Load a skeleton anim psa File'''
    bl_idname = "import_scene.psa"
    bl_label = "Import PSA"
    bl_space_type = "PROPERTIES"
    bl_region_type = "WINDOW"

    filepath: StringProperty(
            subtype='FILE_PATH',
            )
    filter_glob: StringProperty(
            default="*.psa",
            options={'HIDDEN'},
            )

    def execute(self, context):
        getInputFilenamepsa(self,self.filepath,context)
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        wm.fileselect_add(self)
        return {'RUNNING_MODAL'}

class IMPORT_OT_psa(bpy.types.Operator):
    '''Load a skeleton anim psa File'''
    bl_idname = "import_scene.psa"
    bl_label = "Import PSA"
    bl_space_type = "PROPERTIES"
    bl_region_type = "WINDOW"

    filepath = StringProperty(
            subtype='FILE_PATH',
            )
    filter_glob = StringProperty(
            default="*.psa",
            options={'HIDDEN'},
            )

    def execute(self, context):
        getInputFilenamepsa(self,self.filepath,context)
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        wm.fileselect_add(self)
        return {'RUNNING_MODAL'}

bpy.types.Scene.udk_importpsk = StringProperty(
        name = "Import .psk",
        description = "Skeleton mesh file path for psk",
        default = "")
bpy.types.Scene.udk_importpsa = StringProperty(
        name = "Import .psa",
        description = "Animation Data to Action Set(s) file path for psa",
        default = "")
bpy.types.Scene.udk_importarmatureselect = BoolProperty(
        name = "Armature Selected",
        description = "Select Armature to Import psa animation data",
        default = False)

class Panel_UDKImport(bpy.types.Panel):
    bl_label = "UDK Import"
    bl_idname = "OBJECT_PT_udk_import"
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    bl_category = "File I/O"
    bl_context = "objectmode"

    filepath = StringProperty(
            subtype='FILE_PATH',
            )

    #@classmethod
    #def poll(cls, context):
    #   return context.active_object

    def draw(self, context):
        layout = self.layout
        layout.operator(OBJECT_OT_PSKPath.bl_idname)

        layout.prop(context.scene, "udk_importarmatureselect")
        if bpy.context.scene.udk_importarmatureselect:
            layout.operator(OBJECT_OT_UDKImportArmature.bl_idname)
            layout.template_list("UI_UL_list", "udkimportarmature_list", context.scene, "udkimportarmature_list",
                                 context.scene, "udkimportarmature_list_idx", rows=5)
        layout.operator(OBJECT_OT_PSAPath.bl_idname)

class OBJECT_OT_PSKPath(bpy.types.Operator):
    """Select .psk file path to import for skeleton mesh"""
    bl_idname = "object.pskpath"
    bl_label = "Import PSK Path"

    filepath = StringProperty(
            subtype='FILE_PATH',
            )
    filter_glob = StringProperty(
            default="*.psk",
            options={'HIDDEN'},
            )
    importmesh = BoolProperty(
            name="Mesh",
            description="Import mesh only. (not yet build.)",
            default=True,
            )
    importbone = BoolProperty(
            name="Bones",
            description="Import bones only. Current not working yet",
            default=True,
            )
    importmultiuvtextures = BoolProperty(
            name="Single UV Texture(s)",
            description="Single or Multi uv textures",
            default=True,
            )
    bDebugLogPSK = BoolProperty(
            name="Debug Log.txt",
            description="Log the output of raw format. It will save in " \
                        "current file dir. Note this just for testing",
            default=False,
            )
    unrealbonesize = FloatProperty(
            name="Bone Length",
            description="Bone Length from head to tail distance",
            default=1,
            min=0.001,
            max=1000,
            )

    def execute(self, context):
        #context.scene.importpskpath = self.properties.filepath
        bpy.types.Scene.unrealbonesize = self.unrealbonesize
        getInputFilenamepsk(self, self.filepath, self.importmesh, self.importbone, self.bDebugLogPSK,
                            self.importmultiuvtextures)
        return {'FINISHED'}

    def invoke(self, context, event):
        #bpy.context.window_manager.fileselect_add(self)
        wm = context.window_manager
        wm.fileselect_add(self)
        return {'RUNNING_MODAL'}

class UDKImportArmaturePG(bpy.types.PropertyGroup):
    #boolean = BoolProperty(default=False)
    string = StringProperty()
    bexport = BoolProperty(default=False, name="Export", options={"HIDDEN"},
                           description = "This will be ignore when exported")
    bselect = BoolProperty(default=False, name="Select", options={"HIDDEN"},
                           description = "This will be ignore when exported")
    otype = StringProperty(name="Type",description = "This will be ignore when exported")

bpy.utils.register_class(UDKImportArmaturePG)
bpy.types.Scene.udkimportarmature_list = CollectionProperty(type=UDKImportArmaturePG)
bpy.types.Scene.udkimportarmature_list_idx = IntProperty()

class OBJECT_OT_PSAPath(bpy.types.Operator):
    """Select .psa file path to import for animation data"""
    bl_idname = "object.psapath"
    bl_label = "Import PSA Path"

    filepath = StringProperty(name="PSA File Path", description="Filepath used for importing the PSA file",
                              maxlen=1024, default="")
    filter_glob = StringProperty(
            default="*.psa",
            options={'HIDDEN'},
            )

    def execute(self, context):
        #context.scene.importpsapath = self.properties.filepath
        getInputFilenamepsa(self,self.filepath,context)
        return {'FINISHED'}

    def invoke(self, context, event):
        bpy.context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

class OBJECT_OT_UDKImportArmature(bpy.types.Operator):
    """This will update the filter of the mesh and armature"""
    bl_idname = "object.udkimportarmature"
    bl_label = "Update Armature"

    def execute(self, context):
        my_objlist = bpy.context.scene.udkimportarmature_list
        objectl = []
        for objarm in bpy.context.scene.objects:#list and filter only mesh and armature
            if objarm.type == 'ARMATURE':
                objectl.append(objarm)
        for _objd in objectl:#check if list has in udk list
            bfound_obj = False
            for _obj in my_objlist:
                if _obj.name == _objd.name and _obj.otype == _objd.type:
                    _obj.bselect = _objd.select
                    bfound_obj = True
                    break
            if bfound_obj == False:
                #print("ADD ARMATURE...")
                my_item = my_objlist.add()
                my_item.name = _objd.name
                my_item.bselect = _objd.select
                my_item.otype = _objd.type
        removeobject = []
        for _udkobj in my_objlist:
            bfound_objv = False
            for _objd in bpy.context.scene.objects: #check if there no existing object from sense to remove it
                if _udkobj.name == _objd.name and _udkobj.otype == _objd.type:
                    bfound_objv = True
                    break
            if bfound_objv == False:
                removeobject.append(_udkobj)
        #print("remove check...")
        for _item in removeobject: #loop remove object from udk list object
            count = 0
            for _obj in my_objlist:
                if _obj.name == _item.name and _obj.otype == _item.otype:
                    my_objlist.remove(count)
                    break
                count += 1
        return{'FINISHED'}

class OBJECT_OT_UDKImportA(bpy.types.Operator):
    """This will update the filter of the mesh and armature"""
    bl_idname = "object.udkimporta"
    bl_label = "Update Armature"

    def execute(self, context):
        for objd in bpy.data.objects:
            print("NAME:",objd.name," TYPE:",objd.type)
            if objd.type == "ARMATURE":
                print(dir(objd))
                print((objd.data.name))
        return{'FINISHED'}

def menu_func(self, context):
    self.layout.operator(IMPORT_OT_psk.bl_idname, text="Skeleton Mesh (.psk)")
    self.layout.operator(IMPORT_OT_psa.bl_idname, text="Skeleton Anim (.psa)")

def register():
    bpy.utils.register_module(__name__)
    bpy.types.TOPBAR_MT_file_import.append(menu_func)

def unregister():
    bpy.utils.unregister_module(__name__)
    bpy.types.TOPBAR_MT_file_import.remove(menu_func)

if __name__ == "__main__":
    register()

#note this only read the data and will not be place in the scene
#getInputFilename('C:\\blenderfiles\\BotA.psk')
#getInputFilename('C:\\blenderfiles\\AA.PSK')
