#Noesis Python model import+export test module, imports/exports some data from/to a made-up format

from inc_noesis import *

import noesis
import struct

#rapi methods should only be used during handler callbacks
import rapi

#registerNoesisTypes is called by Noesis to allow the script to register formats.
#Do not implement this function in script files unless you want them to be dedicated format modules!
def registerNoesisTypes():
   handle = noesis.register("Persona 4: Dancing All Night", ".gmd")
   noesis.setHandlerTypeCheck(handle, noepyCheckType)
   noesis.setHandlerLoadModel(handle, noepyLoadModel) #see also noepyLoadModelRPG
   handle = noesis.register("Persona 4: Dancing All Night", ".GFS")
   noesis.setHandlerTypeCheck(handle, noepyCheckType)
   noesis.setHandlerLoadModel(handle, noepyLoadModel)
       #noesis.setHandlerWriteModel(handle, noepyWriteModel)
       #noesis.setHandlerWriteAnim(handle, noepyWriteAnim)
##   noesis.logPopup()
       #print("The log can be useful for catching debug prints from preview loads.\nBut don't leave it on when you release your script, or it will probably annoy people.")
   return 1

NOEPY_HEADER = "GFS0"

#check if it's this type based on the data
def noepyCheckType(data):
   bs = NoeBitStream(data)
   if len(data) < 16:
      return 0
   if bs.readBytes(4).decode("ASCII").rstrip("\0") != NOEPY_HEADER:
      return 0
   return 1       

#load the model
def noepyLoadModel(data, mdlList):
   ctx = rapi.rpgCreateContext()
   bs = NoeBitStream(data, 1)
   sb = NoeBitStream(data)
##   bs.setByteEndianForBits(NOE_BIGENDIAN)
   bs.setEndian(NOE_BIGENDIAN)
   rapi.rpgSetOption(noesis.RPGOPT_BIGENDIAN, 1)

   DDSSpitOut = 0
   SkipMaterials = 0
   Skin = 1
   Optimize = 0
   EXPMorph = 1
   MATDEBUG = 0
   MESHDEBUG = 0
   EmergencyNameDump = 0
   BoneOverride = 0
   if BoneOverride:
      Skin = 0
      SkipNodes = 1
   else:
      SkipNodes = 0
   BL = None
   
   MAGIC = bs.readBytes(4)
   TOPID = bs.readUInt()
   TOPTYPE = bs.readUInt()
   BLANK = bs.readUInt()
   CHUNKID = bs.readUInt()
   CHUNKTYPE = bs.readUInt()
   CHUNKSIZE = ((bs.readUInt()) - 8)
   BLANK = bs.readUInt()
   ContentCOUNT = bs.readUInt()

   DDSNames = []
   DDSContent = []
   print(bs.tell())
   texList = []
   texIndex = []
   for D in range(0, ContentCOUNT):
      NameCharCount = bs.readUShort()
      if MATDEBUG:
         print("NameCharCount", NameCharCount, bs.tell())
      DDSName = bs.readBytes(NameCharCount).decode("ASCII").rstrip("\0")
      texIndex.append(DDSName)
      TextureFormat = bs.readUShort()
      TextureSize = bs.readUInt()
      if MATDEBUG:
         print("TextureSize", TextureSize)
      Return = bs.tell()
      bs.seek(12, NOESEEK_REL)
      z = bs.tell()
      sb.seek(z, NOESEEK_ABS)
      DDSHeight = sb.readUInt()
      DDSWidth = sb.readUInt()
      bs.seek(72, NOESEEK_REL)
      DDSFormat = bs.readBytes(4).decode("ASCII").rstrip("\0")
      print(DDSFormat, bs.tell())
      bs.seek(Return, NOESEEK_ABS)
      bs.seek(128, NOESEEK_REL)
      Content = (TextureSize - 128)
      DDSContent2 = bs.readBytes(Content)
      bs.seek(Return, NOESEEK_ABS)
      DDSContent = bs.readBytes(TextureSize)
      DDSFooter = bs.readUInt()
      if DDSFormat == ("DXT5"):
         DDSFormat2 = noesis.NOESISTEX_DXT5
      if DDSFormat == ("DXT1"):
         DDSFormat2 = noesis.NOESISTEX_DXT1
      if DDSFormat == ("DXT3"):
         DDSFormat2 = noesis.NOESISTEX_DXT3
      texList.append(NoeTexture(DDSName, DDSWidth, DDSHeight, DDSContent2, DDSFormat2))
      if DDSSpitOut == 1:
         dds = NoeBitStream()
         dds.writeBytes(DDSContent)
         ExportDDSName = (noesis.getSelectedDirectory() + DDSName)
         if MATDEBUG:
            print("Writing", ExportDDSName)
         o = open(ExportDDSName, 'wb')
         o.write(dds.getBuffer())
         o.close()
      if MATDEBUG:
         print("DDSEND", bs.tell())
   print(DDSName)

   MaterialStart = (bs.tell())
   print(MaterialStart)
   ChunkID = bs.readUInt()
   ChunkType = bs.readUInt()
   ChunkSize = (bs.readUInt())
   Blank = bs.readUInt()
   ContentCount = bs.readUInt()
   if SkipMaterials == 1:
      bs.seek(ChunkSize - 20, NOESEEK_REL)
   else:
      matList = []
      matNames = []
      for M in range(0, ContentCount):
         if MATDEBUG:
            print("Reading MATS", bs.tell())
         MaterialNameChar = bs.readUShort()
         MaterialName = bs.readBytes(MaterialNameChar).decode("ASCII").rstrip("\0")
         shit = bs.readUInt()
         MaterialName = MaterialName.replace(':', '_')
         Material = NoeMaterial(MaterialName, "")
         MatType = bs.readUShort()
         MatBS = bs.readUShort()
         if MATDEBUG:
            print("MATTYPE", MatType)
         if MatType == int(0):
            Diffuse = NoeVec4([0.0, 0.0, 0.0, 0.0])
            Material.setDiffuseColor(Diffuse)
            bs.seek(102, NOESEEK_REL)
         if MatType == int(17):
            AmbX = bs.readFloat()
            AmbY = bs.readFloat()
            AmbZ = bs.readFloat()
            Amb = NoeVec4([AmbX, AmbY, AmbZ, 1.0])
            blank = bs.readUInt()
            RimC = NoeVec3.fromBytes(bs.readBytes(12))
            Material.setRimLighting(RimC)
            Spec = NoeVec4([0.0, 0.0, 0.0, 0.0,])
            Material.setSpecularColor(Spec)
            bs.seek(74, NOESEEK_REL)
            CurrentCount = bs.readUShort()
            CurrentTex = bs.readBytes(CurrentCount).decode("ASCII").rstrip("\0")
            Material.setTexture(CurrentTex)
            Material.setAmbientColor(Amb)
            bs.seek(124, NOESEEK_REL)
         if MatType == int(16):
            bs.seek(102, NOESEEK_REL)
            CurrentCount = bs.readUShort()
            CurrentTex = bs.readBytes(CurrentCount).decode("ASCII").rstrip("\0")
            Material.setTexture(CurrentTex)
            bs.seek(76, NOESEEK_REL)
         if MatType == int(145):
            AmbX = bs.readFloat()
            AmbY = bs.readFloat()
            AmbZ = bs.readFloat()
            Amb = NoeVec4([AmbX, AmbY, AmbZ, 1.0])
            blank = bs.readUInt()
            RimC = NoeVec3.fromBytes(bs.readBytes(12))
            Material.setRimLighting(RimC)
            Spec = NoeVec4([0.0, 0.0, 0.0, 0.0,])
            Material.setSpecularColor(Spec)
            bs.seek(50, NOESEEK_REL)
            Four = bs.readUInt()
            Byte = bs.readUByte()
            print(Byte)
            Count = bs.readUByte()
            Two = bs.readUInt()
            bs.seek(14, NOESEEK_REL)
            for TC in range(0, 2):
               CurrentCount = bs.readUShort()
               CurrentTex = bs.readBytes(CurrentCount).decode("ASCII").rstrip("\0")
               if TC == 0:
                  Material.setTexture(CurrentTex)
               if TC == 1:
                  Material.setSpecularTexture(CurrentTex)
               bs.seek(76, NOESEEK_REL)
               if MATDEBUG:
                  print("Tex ", TC, " Done", bs.tell())
            bs.seek(48, NOESEEK_REL)
            if MATDEBUG:
               print("LoopDone", bs.tell())
            Material.setAmbientColor(Amb)
         matList.append(Material)
         matNames.append(MaterialName)
         Material = None

##   MESHES
   ChunkID = bs.readUInt()
   ChunkType = bs.readUInt()
   ChunkSize = bs.readUInt()
   Blank = bs.readUInt()
   MeshType = bs.readUInt()
##MeshTypes: 7 = Skinned Mesh; 3 = Static Mesh; 11 = Morphed Mesh; 30 = World Mesh
   B = None
   NameList = []
   BoneMats = []
   BoneName = []
   if MeshType == 7:
      BoneCount = bs.readUInt()
      BoneList = []
      BoneParents = []
      for B in range(0, BoneCount):
         boneMat44 = NoeMat44.fromBytes(bs.readBytes(64), NOE_BIGENDIAN)
         boneMat43 = boneMat44.toMat43()
         BoneMats.append(boneMat43.inverse())
##         BoneList.append(NoeBone(B, "bone_"+str(B), boneMat43, None, 0))
      for BP in range(0, BoneCount):
         BoneParents.append(bs.readUShort())
      BBOXSHIT = bs.readBytes(40)
   if MeshType == 11:
      BBOXSHIT = bs.readBytes(40)
      BoneList = []
      BoneParents = []
   if MeshType == 3:
      Shit = bs.readBytes(40)
   Other = []
   Extra = []
   BS2 = []
   BSC = 0
   BSP = []
   NM = 0
   BPP = [-1]
   BB = -1
   BSBoneList = []
   RigMatrix = []
   Mesh = 0
   Bone = 0
   MeshCount = 0
   RootNodeChar = bs.readUShort()
   RootNodeName = bs.readBytes(RootNodeChar).decode("ASCII").rstrip("\0")
   RootHash = bs.readUInt()
   POSX = bs.readFloat()
   POSY = bs.readFloat()
   POSZ = bs.readFloat()
   ROTX = bs.readFloat()
   ROTY = bs.readFloat()
   ROTZ = bs.readFloat()
   SCAX = bs.readFloat()
   SCAY = bs.readFloat()
   SCAZ = bs.readFloat()
   Scale = bs.readFloat()
   Matrix0 = NoeVec3((ROTX, ROTY, ROTZ))
   Matrix1 = Matrix0.toAnglesDirect()
   Matrix = Matrix1.toMat43()
   Matrix.__setitem__(3,(POSX,POSY,POSZ))
   BS2.append(NoeBone(BSC, RootNodeName, Matrix, None, -1))
   Blank = bs.readUInt()
   Byte = bs.readUByte()
   Scale = bs.readFloat()
   Test2 = bs.readUInt()
   if Test2 > 0:
      for T in range(0, (Test2)):
         BSP.append(BSC)
   SuperTest = int(0)
   while (SuperTest != int(272)):
      NM = (NM + 1)
      NodeChar = bs.readUShort()
      NodeName = bs.readBytes(NodeChar).decode("ASCII").rstrip("\0")
      if MESHDEBUG:
         print(NodeName)
      NodeHash = bs.readUInt()
      POSX = bs.readFloat()
      POSY = bs.readFloat()
      POSZ = bs.readFloat()
      ROTX = bs.readFloat()
      ROTY = bs.readFloat()
      ROTZ = bs.readFloat()
      SCAX = bs.readFloat()
      SCAY = bs.readFloat()
      SCAZ = bs.readFloat()
      Scale = bs.readFloat()
      Matrix0 = NoeVec3((ROTX,ROTY,-ROTZ))
      Matrix1 = Matrix0.toAnglesDirect()
      Matrix = Matrix1.toMat43()
      Matrix.__setitem__(3,(POSX,POSY,POSZ))
      if MeshType == 11 or BoneOverride:
         if BoneOverride:
            print("BONEOVERRIDE", NodeName)
         Remember = BSP[-1]
         BSP.pop(-1)
         if NodeName == "root":
            Remember = -1
         BS2.append(NoeBone(NM, NodeName, Matrix, None, Remember))
      
      Test = bs.readUInt()
      ModelType = 0
      if MESHDEBUG:
         print("Set Bone", BB, bs.tell())
      if Test != int(0):
         if MESHDEBUG:
            print("A Mesh", NM)
         Mesh = 1
         Bone = 0
         SubMeshCount = Test
         if MeshType == 11:
            if SubMeshCount > 1:
               SubMeshCount = (SubMeshCount - 1)
         MeshName = NodeName
##         print("SubMesh", SubMeshCount, bs.tell())
         for SC in range(0, SubMeshCount):
            print(bs.tell(), SC, SubMeshCount)
            Four = bs.readUInt()

            if Four == 9:
               if MESHDEBUG:
                  print("Mesh ", BB, " Has Morphs")
               MorphThing1 = bs.readUInt()
               if MorphThing1 == 15:
                  if MESHDEBUG:
                     print("Morph Thing is 15")
                  shit = bs.readBytes(60)
               elif MorphThing1 == 6:
                  shit = bs.readBytes(24)
               MorphSetNameChar = bs.readUShort()
               MorphSetName = bs.readBytes(MorphSetNameChar).decode("ASCII").rstrip("\0")
               MorphSetID = bs.readUInt()
               FourAgain = bs.readUInt()

            Thing = bs.readUShort()
            ModelType = bs.readUShort()
            if ModelType == 94:
               if MESHDEBUG:
                  print("Morph Enabled", SC)
               Morph = 1
            else:
               Morph = 0
            Stride = bs.readUInt()
            if MESHDEBUG:
               print("Stride", SC, Stride, bs.tell())
            if Stride == int(18):
               VertStride = 44
               UVStride = None
               NormOff = 12
               IAmStatic = 0
            if Stride == int(82):
               if Morph == 1:
                  VertStride = 28
                  UVStride = None
                  IAmStatic = 1
               elif ModelType == 30:
                  VertStride = 28
                  UVStride = None
                  IAmStatic = 1
               else:
                  VertStride = 48
                  UVStride = None
                  IAmStatic = 0
            elif Stride == int(274):
               if ModelType == 30:
                  VertStride = 32
                  UVStride = 24
                  NormOff = 12
                  IAmStatic = 1
               else:
                  VertStride = 52
                  UVStride = 24
                  NormOff = 12
                  IAmStatic = 0
            elif Stride == int(338):
               if Morph == 1:
                  VertStride = 36
                  UVStride = 28
                  IAmStatic = 1
               else:
                  VertStride = 56
                  UVStride = 28
                  IAmStatic = 0
               UVType = noesis.RPGEODATA_FLOAT
               NormOff = 12
               if ModelType == 30 or ModelType == 1310:
                  VertStride = 36
                  UVStride = 28
                  IAmStatic = 1
            FaceBuffSize = (bs.readUInt() * 6)
            FaceCount = (FaceBuffSize / 2)
            FaceCount = int(FaceCount)
            One = bs.readUShort()
            if MESHDEBUG:
               print("VertAT", bs.tell())
            VertCount = bs.readUInt()
            Blank = bs.readUInt()

            VPOS = []
            VPOS2 = []
            NORM = []
            UV = []
            Weights = []
            Indexs = []
            print(Stride, VertStride, VertCount)
            for VS in range(0, VertCount):
               VPOSX = bs.readFloat()
               VPOSY = bs.readFloat()
               VPOSZ = bs.readFloat()
               VPOS.append(VPOSX)
               VPOS.append(VPOSY)
               VPOS.append(VPOSZ)
               VPOS2.append((VPOSX, VPOSY, VPOSZ))
               NormX = bs.readFloat()
               NormY = bs.readFloat()
               NormZ = bs.readFloat()
               NORM.append(NormX)
               NORM.append(NormY)
               NORM.append(NormZ)
               if (Stride == int(338)) or (Stride == int(82)):
                  Negative1 = bs.readUInt()
               if UVStride != None:
                  U = bs.readFloat()
                  V = bs.readFloat()
                  UV.append(U)
                  UV.append(V)
               if UVStride == None:
                  U = float(0.0)
                  V = float(0.0)
                  UV.append(U)
                  UV.append(V)
               if MeshType == 7:
                  if IAmStatic == 0:
                     Weight4 = bs.readFloat()
                     Weight3 = bs.readFloat()
                     Weight2 = bs.readFloat()
                     Weight1 = bs.readFloat()
                     Index1 = bs.readByte()
                     Index2 = bs.readByte()
                     Index3 = bs.readByte()
                     Index4 = bs.readByte()
                     Weights.append(Weight4)
                     Weights.append(Weight3)
                     Weights.append(Weight2)
                     Weights.append(Weight1)
                     Indexs.append(Index4)
                     Indexs.append(Index3)
                     Indexs.append(Index2)
                     Indexs.append(Index1)
            POSBUFF = struct.pack('>' + 'f'*len(VPOS), *VPOS)
            UVBUFF = struct.pack('>' + 'f'*len(UV), *UV)
            NORMBUFF = struct.pack('>' + 'f'*len(NORM), *NORM)
            if MeshType == 7:
               if ModelType != 30:
                  WeightBuff = struct.pack('>' + 'f'*len(Weights), *Weights)
                  IndexBuff = struct.pack('>' + 'b'*len(Indexs), *Indexs)
            if Morph == 1:
               if MESHDEBUG:
                  print("Doing Morph Things", SC)
               MorphThing1 = bs.readUInt()
               MorphCount = bs.readUInt()
               MPOS = []
               MPOS2 = []
               MFrame = []
               for MBS in range(0, MorphCount):
                  MorphThing2 = bs.readUInt()
                  MorphVertCount = bs.readUInt()
   ##               print(bs.tell())
                  if MorphVertCount != VertCount:
                     print("PANIC", MorphVertCount, VertCount, MBS)
                  for MVS in range(0, MorphVertCount):
                     X = VPOS2[MVS]
                     XX = X[0]
                     XXX = X[1]
                     XXXX = X[2]
                     MPOSX = (bs.readFloat() + XX)
                     MPOSY = (bs.readFloat() + XXX)
                     MPOSZ = (bs.readFloat() + XXXX)
                     MPOS.append(MPOSX)
                     MPOS.append(MPOSY)
                     MPOS.append(MPOSZ)
                  MORPHBUFF = struct.pack('>' + 'f'*len(MPOS), *MPOS)
                  MFrame.append(MORPHBUFF)
                  MPOS = []
                  MBS = 0
                  MVS = 0
                  MVVS = 0
            if MESHDEBUG:
               print("VertDone", bs.tell())
            FaceBuff = bs.readBytes(FaceBuffSize)
            if MESHDEBUG:
               print("FaceBuff Done", bs.tell())
            MaterialChar = bs.readUShort()
            CurrentMaterial = bs.readBytes(MaterialChar).decode("ASCII").rstrip("\0")
            CurrentMaterial = CurrentMaterial.replace(':', '_')
            if SubMeshCount > 1:
               MeshName = (MeshName + "_sub_" + str(SC))
            if EXPMorph == 1:
               if Morph == 1:
                  rapi.rpgBindPositionBuffer(POSBUFF, noesis.RPGEODATA_FLOAT, 12)
                  rapi.rpgSetName(MeshName)
                  rapi.rpgBindNormalBuffer(NORMBUFF, noesis.RPGEODATA_FLOAT, 12)
                  rapi.rpgBindUV1Buffer(UVBUFF, noesis.RPGEODATA_FLOAT, 8)
                  rapi.rpgSetMaterial(CurrentMaterial)
                  rapi.rpgCommitTriangles(FaceBuff, noesis.RPGEODATA_USHORT, FaceCount, noesis.RPGEO_TRIANGLE, 1)
                  rapi.rpgClearBufferBinds()
                  for Frame1 in range(0, MorphCount):
                     rapi.rpgBindPositionBuffer(MFrame[Frame1], noesis.RPGEODATA_FLOAT, 12)
                     rapi.rpgSetName(MeshName + "_Morph" + str(Frame1))
                     rapi.rpgBindNormalBuffer(NORMBUFF, noesis.RPGEODATA_FLOAT, 12)
                     rapi.rpgBindUV1Buffer(UVBUFF, noesis.RPGEODATA_FLOAT, 8)
                     rapi.rpgSetMaterial(CurrentMaterial)
                     rapi.rpgCommitTriangles(FaceBuff, noesis.RPGEODATA_USHORT, FaceCount, noesis.RPGEO_TRIANGLE, 1)
                     rapi.rpgClearBufferBinds()
               else:
                  rapi.rpgBindPositionBuffer(POSBUFF, noesis.RPGEODATA_FLOAT, 12)
                  rapi.rpgBindNormalBuffer(NORMBUFF, noesis.RPGEODATA_FLOAT, 12)
                  rapi.rpgBindUV1Buffer(UVBUFF, noesis.RPGEODATA_FLOAT, 8)
                  rapi.rpgSetName(MeshName)
                  rapi.rpgSetMaterial(CurrentMaterial)
                  if MeshType == 7:
                        if ModelType == 31:
                           if Skin == 1:
                              rapi.rpgBindBoneWeightBuffer(WeightBuff, noesis.RPGEODATA_FLOAT, 16, 4)
                              rapi.rpgBindBoneIndexBuffer(IndexBuff, noesis.RPGEODATA_BYTE, 4, 4)
                  rapi.rpgCommitTriangles(FaceBuff, noesis.RPGEODATA_USHORT, FaceCount, noesis.RPGEO_TRIANGLE, 1)
                  if Optimize:
                     rapi.rpgOptimize()
                  rapi.rpgClearBufferBinds()
                  MFrame = []
                  MeshCount = (MeshCount + 1)
            MaterialID = bs.readUInt()
            BBOXSHIT = bs.readBytes(40)
            MeshName = NodeName
            NameList.append(MeshName)
            print(SC, " Done ", bs.tell())
      Byte = bs.readUByte()
      if Byte == int(1):
         if Mesh != 1:
            Bone = 1
            Mesh = 0
            BB = (BB + 1)
         Prop2 = bs.readUInt()
         if MESHDEBUG:
            print("Extra Params Start", bs.tell(), "0", "Count", Prop2)
         for BSProp in range(0, Prop2):
            Thing = bs.readUInt()
            propchar = bs.readUShort()
            propname = bs.readBytes(propchar).decode("ASCII").rstrip("\0")
            prophash = bs.readUInt()
            Type2 = bs.readUInt()
##            if prophash == int(1882726475) or int(1253562415):
##               Type2 = (Type2 - 1)
            if Thing == int(4):
               Type2 = (Type2 - 1)
            PropData = bs.seek(Type2, NOESEEK_REL)
            if MESHDEBUG:
               print(BSProp, bs.tell())
      if MESHDEBUG:
         print("Extra Params End", bs.tell(), "1")
      Float = bs.readFloat()
      Test2 = bs.readUInt()
      if Test2 != 0:
         for T2 in range(0, Test2):
            if ModelType == 30:
               BSP.append(NM)
            else:
               BSP.append(NM)
               if Bone:
                  BPP.append(BB)
      if Bone:
         BoneName.append(NodeName)
         RigMatrix.append(Matrix)
      SuperTest = bs.readUShort()
      if SuperTest != int(272):
         bs.seek(-2, NOESEEK_REL)
      if MESHDEBUG:
         print("Complete", bs.tell())
      Mesh = 0
   mdl = rapi.rpgConstructModel()
         
   if MeshType == 7:
      if not BoneOverride:
         if MESHDEBUG:
            print("Setting Bones")
         MaxBoneList = max(BoneParents)
         print("BoneParentListMax ", MaxBoneList)
         print(len(BPP), BPP)
         print(len(BoneName), BoneName)
         print(BoneCount)
         Diff = (MaxBoneList - BoneCount)
         for BD in range(0, BoneCount):
            CBP = BoneParents[BD]
            print(BD, CBP)
            CBP = (CBP - 1)
            BoneList.append(NoeBone(BD, "bone_"+str(BD), BoneMats[BD], None, 0))
         mdl.setBones(BoneList)
   if MeshType == 11 or MeshType == 3 or BoneOverride:
      if MESHDEBUG:
         print("Setting Bones")
      BS2 = rapi.multiplyBones(BS2)
      mdl.setBones(BS2)
   mdl.setModelMaterials(NoeModelMaterials(texList, matList))
   if EmergencyNameDump:
      print(NameList)
   mdlList.append(mdl)         
      
   rapi.rpgClearBufferBinds()   
   return 1
