Python Forum
Thread Rating:
  • 1 Vote(s) - 5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
parse animation
#1
Hello, I'm not python developer, my knowledge about this language it's very basic, but anyway I'm trying to develop a "plugin" (technically based in other languages that I've knowledge) to a tool called noesis. Its purpose is to export meshs, nodes, animations and other stuffs of 3D models. I found the specific plugin for the model I need to extract, but it's limited to not export animations. So, as the project looks abandoned, I decided to implement what I need or try at least.

Until now, I only managed to loop through model animations, there is still a long way to go. Basically I'm with trouble about how to read animation's data, such as "Extents", "Name", etc., as explained in this wiki: https://github.com/cmbasnett/io_scene_abc/wiki/ABC

Here's is my code, I hope I've provided all needed informations.
https://paste.ofcode.org/8GbELJ4x7sBjWxCEv7q3jT

from inc_noesis import *
import noesis
import rapi
import os
import ctypes

def registerNoesisTypes():
    '''Register the plugin. Just change the Game name and extension.'''
    
    handle = noesis.register("LithTech", ".abc")
    noesis.setHandlerTypeCheck(handle, noepyCheckType)
    noesis.setHandlerLoadModel(handle, noepyLoadModel)
    return 1

def noepyCheckType(data):
    '''Verify that the format is supported by this plugin. Default yes'''
    
    if len(data) < 8:
        return 0
    try:
        bs = NoeBitStream(data)
        length = bs.readUShort()
        idstring = noeStrFromBytes(bs.readBytes(length))
        if idstring != "Header":
            return 0
        return 1
    except:
        return 0

def noepyLoadModel(data, mdlList):
    '''Build the model, set materials, bones, and animations. You do not
    need all of them as long as they are empty lists (they are by default)'''
    
    inputName = rapi.getLocalFileName(rapi.getInputName())
    filename, ext = os.path.splitext(inputName)    
    ctx = rapi.rpgCreateContext()
    parser = SanaeParser(data, filename)
    parser.parse_file()
    mdl = rapi.rpgConstructModel()
    mdl.setModelMaterials(NoeModelMaterials(parser.texList, parser.matList))
    mdl.setBones(parser.boneList)
    mdl.setAnims(parser.animList)
    mdlList.append(mdl)
    return 1

class LithTechNode(object):
    '''A LithTech node object'''
    
    def __init__(self):
        
        self.nodeName = ""
        self.offset = 0
        
class Mesh(object):
    
    def __init__(self):
        
        self.submeshes = []
        self.normList = []
        self.vertList = []
        self.uvList = []
        self.idxList = []
        self.meshName = ""
        self.numIdx = 0
        self.numVerts = 0

class SanaeParser(object):
    
    def __init__(self, data, filename):    
        '''Initialize some data. Refer to Sanae.py to see what is already
        initialized'''
        
        self.inFile = NoeBitStream(data)
        self.animList = []
        self.texList = []
        self.matList = []
        self.boneList = []
        self.nodeList = []
        self.numMesh = 0
        self.numSubmesh = 0
        self.numBones = 0
        self.basename = filename
        self.meshList = []
        
    def read_name(self):
        
        string = self.inFile.readBytes(self.inFile.readUShort())
        return noeStrFromBytes(string)
    
    def build_vertices(self, mesh, meshName):
        
        vertBuff = bytes()
        normBuff = bytes()
        uvBuff = bytes()
        idxBuff = bytes()
        rapi.rpgSetName(meshName)
        for i in range(mesh.numIdx):
            index = mesh.idxList[i]
            vertBuff += struct.pack('3f', *mesh.vertList[index])
            normBuff += struct.pack('3f', *mesh.normList[index])
            uvBuff += struct.pack('2f', *mesh.uvList[i])
            idxBuff += struct.pack('H', i)
            
        rapi.rpgBindPositionBuffer(vertBuff, noesis.RPGEODATA_FLOAT, 12)
        rapi.rpgBindNormalBuffer(normBuff, noesis.RPGEODATA_FLOAT, 12)
        rapi.rpgBindUV1Buffer(uvBuff, noesis.RPGEODATA_FLOAT, 8)
        

        matName = meshName
        material = NoeMaterial(matName, self.basename)
        material.setDefaultBlend(0)
        self.matList.append(material)
        
        rapi.rpgSetMaterial(meshName)
        rapi.rpgCommitTriangles(idxBuff, noesis.RPGEODATA_USHORT, mesh.numIdx, noesis.RPGEO_TRIANGLE, 1)
    
    def build_meshes(self):
        
        i = 0
        for mesh in self.meshList:
            for submesh in mesh.submeshes:
                meshName = "%s[%i]" %(mesh.meshName, i)
                self.build_vertices(submesh, meshName)
                i += 1
        
    def parse_vertices(self, numVerts, mesh):
         
        #vertBuff = bytes()
        #normBuff = bytes()
        vertList = []
        normList = []
        verts = 0
        for i in range(numVerts):
            count = self.inFile.readUShort()
            self.inFile.readUShort()
            for j in range(count):
                self.inFile.readUInt()
                self.inFile.read('4f')
            #vertBuff += self.inFile.readBytes(12)
            #normBuff += self.inFile.readBytes(12)
            vertList.append(self.inFile.read('3f'))
            normList.append(self.inFile.read('3f'))
        #rapi.rpgBindPositionBuffer(vertBuff, noesis.RPGEODATA_FLOAT, 12)
        #rapi.rpgBindNormalBuffer(normBuff, noesis.RPGEODATA_FLOAT, 12)
        
        mesh.numVerts = numVerts
        mesh.vertList = vertList
        mesh.normList = normList
    
    def parse_faces(self, numFaces, mesh):
        
        idxList = []
        uvList = []
        for i in range(numFaces):
            uvList.append(self.inFile.read('2f'))
            idxList.append(self.inFile.readUShort())
            uvList.append(self.inFile.read('2f'))
            idxList.append(self.inFile.readUShort())
            uvList.append(self.inFile.read('2f'))
            idxList.append(self.inFile.readUShort())
        
        mesh.numIdx = numFaces * 3
        mesh.idxList = idxList
        mesh.uvList = uvList
    
    def parse_bones(self):
        
        for i in range(self.numBones):
            boneName = self.read_name()
            self.inFile.readShort()
            self.inFile.readByte()
            matrix = self.inFile.read('16f')
            self.inFile.readUInt()
            
        count = self.inFile.readUInt() 
        for i in range(count):
            self.read_name()
            numBones = self.inFile.readUInt()
            self.inFile.seek(numBones*4, 1)
            
    def parse_meshes(self):
        
        self.inFile.read('2L')
        for i in range(self.numMesh):
            mesh = Mesh()
            self.inFile.readUShort()
            self.inFile.read('3f')
            self.inFile.readUShort()
            meshName = self.read_name()
            mesh.meshName = meshName
            for j in range(self.numSubmesh):
                submesh = Mesh()
                numFaces = self.inFile.readUInt()
                idxBuff = self.parse_faces(numFaces, submesh)
                numVerts = self.inFile.readUInt()
                self.parse_vertices(numVerts, submesh)
                mesh.submeshes.append(submesh)                
            self.meshList.append(mesh)
                
    def parse_header(self):
        
        #number of integers
        count = self.inFile.readUInt()
        self.inFile.read('2L')
        self.numBones = self.inFile.readUInt()
        self.numMesh = self.inFile.readUInt()
        self.inFile.read('4L')
        self.numSubmesh = self.inFile.readUInt()

    def parse_animations(self):

        count = self.inFile.readUInt()

        #ctypes.windll.user32.MessageBoxW(0, "Anim Count = %i" % count, "Your title", 1)

        for i in range(count):
            

    def parse_nodes(self):
        
        for node in self.nodeList:
            name = node.name
            offset = node.offset
            
            self.inFile.seek(offset)
            if name == "Header":
                self.parse_header()
            elif name == "Pieces":
                self.parse_meshes()
            elif name == "Nodes":
                self.parse_bones()
            elif name == "ChildModels":
                pass
            elif name == "Animation":
                self.parse_animations()
                #pass
            elif name == "Sockets":
                pass
            elif name == "AnimBindings":
                pass
        
    def parse_file(self):
        
        offset = 0
        while offset != -1:
            self.inFile.seek(offset)
            node = LithTechNode()
            node.name = self.read_name()            
            self.nodeList.append(node)
            offset = self.inFile.readInt()
            
            #record the start of node data
            node.offset = self.inFile.tell()
            
        self.parse_nodes()
        self.build_meshes()
Reply
#2
Since the library is undocumented your best bet is to look at the source code.

Like HERE
name,extent are instance variable so they must be accessible via dot(.) operator on Animation class object.

So something like this should work:
a=Animation()
print(a.name) #a.extents
Reply
#3
Thank for your answer. Based in your informations, I was able to read animation's data, but I'm with a doubt about KeyframeTransform[NodeCount][KeyframeCount]. Apparently it is a 2D array. How can I read data from it?

from inc_noesis import *
import noesis
import rapi
import os
import ctypes

def registerNoesisTypes():
    '''Register the plugin. Just change the Game name and extension.'''
    
    handle = noesis.register("LithTech", ".abc")
    noesis.setHandlerTypeCheck(handle, noepyCheckType)
    noesis.setHandlerLoadModel(handle, noepyLoadModel)
    return 1

def noepyCheckType(data):
    '''Verify that the format is supported by this plugin. Default yes'''
    
    if len(data) < 8:
        return 0
    try:
        bs = NoeBitStream(data)
        length = bs.readUShort()
        idstring = noeStrFromBytes(bs.readBytes(length))
        if idstring != "Header":
            return 0
        return 1
    except:
        return 0

def noepyLoadModel(data, mdlList):
    '''Build the model, set materials, bones, and animations. You do not
    need all of them as long as they are empty lists (they are by default)'''
    
    inputName = rapi.getLocalFileName(rapi.getInputName())
    filename, ext = os.path.splitext(inputName)    
    ctx = rapi.rpgCreateContext()
    parser = SanaeParser(data, filename)
    parser.parse_file()
    mdl = rapi.rpgConstructModel()
    mdl.setModelMaterials(NoeModelMaterials(parser.texList, parser.matList))
    mdl.setBones(parser.boneList)
    mdl.setAnims(parser.animList)
    mdlList.append(mdl)
    return 1

class LithTechNode(object):
    '''A LithTech node object'''
    
    def __init__(self):
        
        self.nodeName = ""
        self.offset = 0
        
class Mesh(object):
    
    def __init__(self):
        
        self.submeshes = []
        self.normList = []
        self.vertList = []
        self.uvList = []
        self.idxList = []
        self.meshName = ""
        self.numIdx = 0
        self.numVerts = 0

class Animation(object):
    
    def __init__(self):
        
        self.extents = 0
        self.animName = ""

class SanaeParser(object):
    
    def __init__(self, data, filename):    
        '''Initialize some data. Refer to Sanae.py to see what is already
        initialized'''
        
        self.inFile = NoeBitStream(data)
        self.animList = []
        self.texList = []
        self.matList = []
        self.boneList = []
        self.nodeList = []
        self.numMesh = 0
        self.numSubmesh = 0
        self.numBones = 0
        self.basename = filename
        self.meshList = []
        
    def read_name(self):
        
        string = self.inFile.readBytes(self.inFile.readUShort())
        return noeStrFromBytes(string)
    
    def build_vertices(self, mesh, meshName):
        
        vertBuff = bytes()
        normBuff = bytes()
        uvBuff = bytes()
        idxBuff = bytes()
        rapi.rpgSetName(meshName)
        for i in range(mesh.numIdx):
            index = mesh.idxList[i]
            vertBuff += struct.pack('3f', *mesh.vertList[index])
            normBuff += struct.pack('3f', *mesh.normList[index])
            uvBuff += struct.pack('2f', *mesh.uvList[i])
            idxBuff += struct.pack('H', i)
            
        rapi.rpgBindPositionBuffer(vertBuff, noesis.RPGEODATA_FLOAT, 12)
        rapi.rpgBindNormalBuffer(normBuff, noesis.RPGEODATA_FLOAT, 12)
        rapi.rpgBindUV1Buffer(uvBuff, noesis.RPGEODATA_FLOAT, 8)
        

        matName = meshName
        material = NoeMaterial(matName, self.basename)
        material.setDefaultBlend(0)
        self.matList.append(material)
        
        rapi.rpgSetMaterial(meshName)
        rapi.rpgCommitTriangles(idxBuff, noesis.RPGEODATA_USHORT, mesh.numIdx, noesis.RPGEO_TRIANGLE, 1)
    
    def build_meshes(self):
        
        i = 0
        for mesh in self.meshList:
            for submesh in mesh.submeshes:
                meshName = "%s[%i]" %(mesh.meshName, i)
                self.build_vertices(submesh, meshName)
                i += 1
        
    def parse_vertices(self, numVerts, mesh):
         
        #vertBuff = bytes()
        #normBuff = bytes()
        vertList = []
        normList = []
        verts = 0
        for i in range(numVerts):
            count = self.inFile.readUShort()
            self.inFile.readUShort()
            for j in range(count):
                self.inFile.readUInt()
                self.inFile.read('4f')
            #vertBuff += self.inFile.readBytes(12)
            #normBuff += self.inFile.readBytes(12)
            vertList.append(self.inFile.read('3f'))
            normList.append(self.inFile.read('3f'))
        #rapi.rpgBindPositionBuffer(vertBuff, noesis.RPGEODATA_FLOAT, 12)
        #rapi.rpgBindNormalBuffer(normBuff, noesis.RPGEODATA_FLOAT, 12)
        
        mesh.numVerts = numVerts
        mesh.vertList = vertList
        mesh.normList = normList
    
    def parse_faces(self, numFaces, mesh):
        
        idxList = []
        uvList = []
        for i in range(numFaces):
            uvList.append(self.inFile.read('2f'))
            idxList.append(self.inFile.readUShort())
            uvList.append(self.inFile.read('2f'))
            idxList.append(self.inFile.readUShort())
            uvList.append(self.inFile.read('2f'))
            idxList.append(self.inFile.readUShort())
        
        mesh.numIdx = numFaces * 3
        mesh.idxList = idxList
        mesh.uvList = uvList
    
    def parse_bones(self):
        
        for i in range(self.numBones):
            boneName = self.read_name()
            self.inFile.readShort()
            self.inFile.readByte()
            matrix = self.inFile.read('16f')
            self.inFile.readUInt()
            
        count = self.inFile.readUInt() 
        for i in range(count):
            self.read_name()
            numBones = self.inFile.readUInt()
            self.inFile.seek(numBones*4, 1)
            
    def parse_meshes(self):
        
        self.inFile.read('2L')
        for i in range(self.numMesh):
            mesh = Mesh()
            self.inFile.readUShort()
            self.inFile.read('3f')
            self.inFile.readUShort()
            meshName = self.read_name()
            mesh.meshName = meshName
            for j in range(self.numSubmesh):
                submesh = Mesh()
                numFaces = self.inFile.readUInt()
                idxBuff = self.parse_faces(numFaces, submesh)
                numVerts = self.inFile.readUInt()
                self.parse_vertices(numVerts, submesh)
                mesh.submeshes.append(submesh)                
            self.meshList.append(mesh)
                
    def parse_header(self):
        
        #number of integers
        count = self.inFile.readUInt()
        self.inFile.read('2L')
        self.numBones = self.inFile.readUInt()
        self.numMesh = self.inFile.readUInt()
        self.inFile.read('4L')
        self.numSubmesh = self.inFile.readUInt()

    def parse_animations(self):

        #ctypes.windll.user32.MessageBoxW(0, "Anim Count = %i" % count, "Your title", 1)
        #ctypes.windll.user32.MessageBoxW(0, "Bone Count = %i" % self.numBones, "Your title", 1)
        animCount = self.inFile.readUInt()

        #for i in range(animCount):
        anim = Animation()
        anim.extents = NoeVec3.fromBytes(self.inFile.readBytes(12))
        anim.animName = self.read_name()
        self.inFile.readInt()
        self.inFile.readUInt()
        frameCount = self.inFile.readUInt()

        for j in range(frameCount):
            self.inFile.readUInt()
            self.read_name()
            
        #ctypes.windll.user32.MessageBoxW(0, "Anim Name = %s, Num Frames = %d" % (anim.animName, frameCount), "Your title", 1)


    def parse_nodes(self):
        
        for node in self.nodeList:
            name = node.name
            offset = node.offset
            
            self.inFile.seek(offset)
            if name == "Header":
                self.parse_header()
            elif name == "Pieces":
                self.parse_meshes()
            elif name == "Nodes":
                self.parse_bones()
            elif name == "ChildModels":
                pass
            elif name == "Animation":
                self.parse_animations()
                #pass
            elif name == "Sockets":
                pass
            elif name == "AnimBindings":
                pass
        
    def parse_file(self):
        
        offset = 0
        while offset != -1:
            self.inFile.seek(offset)
            node = LithTechNode()
            node.name = self.read_name()            
            self.nodeList.append(node)
            offset = self.inFile.readInt()
            
            #record the start of node data
            node.offset = self.inFile.tell()
            
        self.parse_nodes()
        self.build_meshes()
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020