Help: Combining TL Animations into one Skeleton

CCCenturionCCCenturion Posts: 575
edited May 2012 in Art and Sound
Torchlight skeletons aren't quite in the same format as usual Ogre skeletons -- each individual animation's skeleton is, but normally an Ogre model will have one skeleton file with all of the animations listed in it. (Although I'm not a seasoned Ogre veteran, so this is all just to the best of my understanding, what I've gathered from playing around with it.)

Each skeleton file starts with a list of initial bone positions and rotations, then the bone hierarchy, and then a section of animations. Each animation has a list of keyframe tracks for each bone, and each keyframe contains info for how to transform the position and rotation of the bone. What's unique about Torchlight skeletons is that each animation has its own skeleton file, and each of those skeleton files has its own initial bone positions and rotations.

Basically, a typical Ogre skeleton will have a base (or 'binding') skeleton, and all of the animations tell the skeleton how to move relative to that position. In Torchlight, each animation has its own base skeleton, and the animations tell the skeleton how to move relative to its own base -- the base isn't shared by all of the animations.


I've been trying to compile several of the Alchemist animations into one skeleton file that can be used in generic Ogre applications, but it hasn't been working the way I expect. (Don't worry Runic guys, this is strictly amateur stuff -- for educational purposes only -- I'm not trying to steal your work!)

The Python script I've been using to add animations to the base skeleton is posted below.

To run this script, it needs to be in the same directory as "Alchemist.skeleton.xml" and "Idle.SKELETON.xml"; run the script and it will create a new file, Alchemist0.skeleton.xml, which you can then use the OgreXmlConversion program to turn into an Ogre skeleton file. The script reads a base skeleton (which I call the "actor") and an animation skeleton (which I call the "action"), and follows four steps to add the action to the actor:

1) Calculate the difference between the action's initial bone positions and the actor's initial bone positions. (action position - actor position, for each bone)
2) Calculate the difference between the action's initial bone rotations and the actor's initial bone rotations. (action rotation - actor rotation, for each bone)
3) Add these differences to all of the translations in the action skeleton's animation tracks.
4) Add the animation track to the actor skeleton's animations.


Step 3 is the important one, conceptually. The main problem is that all of the animations give bone transformations relative to the initial bone positions, but the skeletons for individual animations do not have the same initial bone positions as the base skeleton. So I try to calculate the transformations from the base skeleton's initial bone positions to the action skeleton's initial bone positions, and then add those transformations to all of the action's animation keyframes. It's just not working the way I expected.

Am I missing anything here?

I know this is a long post, and it's a fairly complicated problem. Feel free to ask me to clarify anything.


Here's the Python script. The most important functions here are the getOffsets, getRotates, and addAnimation methods, at around lines 100-180 of the script.
import xml.dom.minidom
import math

def norm(x, y, z):
    x = float(x)
    y = float(y)
    z = float(z)
    nrm = math.sqrt(math.pow(x,2) + math.pow(y,2) + math.pow(z,2))
    return "%.8f" % nrm


class Skeleton():
    def __init__(self, model, xmlFile):
        self.model = model
        self.doc   = xml.dom.minidom.parse(xmlFile)
        self.bones      = self.doc.getElementsByTagName("bone")
        self.hierarchy  = self.doc.getElementsByTagName("boneparent")
        self.animations = self.doc.getElementsByTagName("animation")
        self.scalars    = {}
        self.offsets    = {}
        self.rotates    = {}
        self.bonelist   = []
        for bone in self.bones:
            self.bonelist.append(bone.getAttribute("name"))

    def write(self, filename):
        "Print the converted animation to a new file."
        output = open(filename, 'w')
        self.doc.writexml(output)
        output.close()

        infile = open(filename, 'r')
        temp = infile.readlines()
        infile.close()
        temp[0] = '<skeleton>\n'  # delete xml version tag, not used by Ogre
        outfile = open(filename, 'w')
        outfile.writelines(temp)
        outfile.close()


    def getBone(self, name):
        "Find the node that contains info for a particular bone."
        for bone in self.bones:
            if bone.getAttribute("name") == name:
                return bone

    def deleteBones(self, bonelist):
        "Delete all instances of a list of bones from the skeleton."
        # <bone>       : attribute      'name'        not in bonelist
        bones = self.doc.childNodes[0].childNodes[1]
        for node in bones.getElementsByTagName("bone"):
            if node.getAttribute("name") in bonelist:
                bones.removeChild(node)

        # <boneparent> : attribute 'bone' or 'parent' not in bonelist
        hierarchy = self.doc.childNodes[0].childNodes[3]
        for node in hierarchy.getElementsByTagName("boneparent"):
            if (node.getAttribute("bone") in bonelist or
                node.getAttribute("parent") in bonelist):
                hierarchy.removeChild(node)

        # <track>      : attribute      'bone'        not in bonelist
        tracks = (self.doc.childNodes[0].childNodes[5].
                  childNodes[1].childNodes[1])
        for node in tracks.getElementsByTagName("track"):
            if node.getAttribute("bone") in bonelist:
                tracks.removeChild(node)

    def sortBoneIDs(self, skeleton):
        "Set bone id values equal to those found in 'skeleton'."
        bones = self.doc.childNodes[0].childNodes[1]
        for node in bones.getElementsByTagName("bone"):
            name = node.getAttribute("name")
            if name in skeleton.bonelist:
                boneid = skeleton.getBone(name).getAttribute("id")
                node.setAttribute("id", boneid)
        
        
    def setScalars(self, skeleton):
        """
        Calculate the importer/exporter ratio of each bone's length.
        These will be used to re-scale animation skeletons.
        """
        for xbone in skeleton.bones:                # xbone = exporter bone
            name  = xbone.getAttribute("name")
            if name in self.bonelist:
                mbone = self.getBone(name)          # mbone = importer bone
                mx = mbone.childNodes[1].getAttribute("x")
                my = mbone.childNodes[1].getAttribute("y")
                mz = mbone.childNodes[1].getAttribute("z")
                **** = xbone.childNodes[1].getAttribute("x")
                xy = xbone.childNodes[1].getAttribute("y")
                xz = xbone.childNodes[1].getAttribute("z")
                mlength = norm(mx,my,mz)
                xlength = norm(****,xy,xz)
                try:
                    self.scalars[name] = float(mlength)/float(xlength)
                except ZeroDivisionError:
                    self.scalars[name] = 1

    def setOffsets(self, base):
        """
        Calculate the offsets in the initial bone positions.
        These will be used to adjust animation keyframes before addint the
        animation to the base skeleton's file.
        """
        for abone in self.bones:                # abone = animation bone
            name  = abone.getAttribute("name")
            if name in self.bonelist:
                bbone = base.getBone(name)      # bbone = base bone
                bx = float(bbone.childNodes[1].getAttribute("x"))
                by = float(bbone.childNodes[1].getAttribute("y"))
                bz = float(bbone.childNodes[1].getAttribute("z"))
                ax = float(abone.childNodes[1].getAttribute("x"))
                ay = float(abone.childNodes[1].getAttribute("y"))
                az = float(abone.childNodes[1].getAttribute("z"))
                self.offsets[name] = {'x':ax-bx, 'y':ay-by, 'z':az-bz}

    def setRotates(self, base):
        """
        Calculate the offsets in the initial bone positions.
        These will be used to adjust animation keyframes before addint the
        animation to the base skeleton's file.
        """
        for abone in self.bones:                # abone = animation bone
            name  = abone.getAttribute("name")
            if name in self.bonelist:
                bbone = base.getBone(name)      # bbone = base bone
                bangle = float(bbone.childNodes[3].getAttribute("angle"))
                bx = float(bbone.childNodes[3].childNodes[1].getAttribute('x'))
                by = float(bbone.childNodes[3].childNodes[1].getAttribute('y'))
                bz = float(bbone.childNodes[3].childNodes[1].getAttribute('z'))
                aangle = float(abone.childNodes[3].getAttribute("angle"))
                ax = float(abone.childNodes[3].childNodes[1].getAttribute('x'))
                ay = float(abone.childNodes[3].childNodes[1].getAttribute('y'))
                az = float(abone.childNodes[3].childNodes[1].getAttribute('z'))
                self.rotates[name] = {'a':aangle-bangle,
                                      'x':ax-bx, 'y':ay-by, 'z':az-bz}
                
    def addAnimation(self, action):
        "Add the animation node to the base skeleton."
        # Calculate translations from base skeleton to animation base.
        action.setOffsets(self)
        action.setRotates(self)

        # Add offsets and rotates to all keyframes for each bone.
        for track in action.animations[0].getElementsByTagName("track"):
            bone = track.getAttribute("bone")
            keys = track.childNodes[1].getElementsByTagName("keyframe")
            for key in keys:
                keytx = float(key.childNodes[1].getAttribute('x'))
                keyty = float(key.childNodes[1].getAttribute('y'))
                keytz = float(key.childNodes[1].getAttribute('z'))
                newtx = unicode(keytx + action.offsets[bone]['x'])
                newty = unicode(keyty + action.offsets[bone]['y'])
                newtz = unicode(keytz + action.offsets[bone]['z'])
                key.childNodes[1].setAttribute('x', newtx)
                key.childNodes[1].setAttribute('y', newty)
                key.childNodes[1].setAttribute('z', newtz)

                
                keyangle = float(key.childNodes[3].getAttribute('angle'))
                newangle = unicode(keyangle + action.rotates[bone]['a'])
                key.childNodes[3].setAttribute('angle', newangle)
                

                keyrx = float(key.childNodes[3].childNodes[1].getAttribute('x'))
                keyry = float(key.childNodes[3].childNodes[1].getAttribute('y'))
                keyrz = float(key.childNodes[3].childNodes[1].getAttribute('z'))
                newrx = unicode(keyrx + action.rotates[bone]['x'])
                newry = unicode(keyry + action.rotates[bone]['y'])
                newrz = unicode(keyrz + action.rotates[bone]['z'])
                key.childNodes[3].childNodes[1].setAttribute('x', newrx)
                key.childNodes[3].childNodes[1].setAttribute('y', newry)
                key.childNodes[3].childNodes[1].setAttribute('z', newrz)
                #'''
                
        # Add the animation node to the base skeleton.
        self.doc.childNodes[0].childNodes[5].appendChild(action.animations[0])

    def convertAnimation(self, ex_model, animation):
        """
        when this is done, augment it to be able to find and convert
        all of the animations in a directory
        """
        # read the animation file into a doc object
        #convert = xml.dom.minidom.parse(animation)
        convert = Skeleton(ex_model, animation)

        # delete extra bones from exporter
        extras = list(set(convert.bonelist).difference(set(self.bonelist)))
        if extras:
            convert.deleteBones(extras)
            convert.sortBoneIDs(self)

        # adjust all position attributes by self.scalars
          # <position>  attributes 'x', 'y', 'z'
          # <translate> attributes 'x', 'y', 'z' --> is this necessary?

        # change the "name" attribute in the <animation> node
        animationNode = convert.doc.childNodes[0].childNodes[5].childNodes[1]
        oldname = animationNode.getAttribute("name")
        newname = ex_model + '_' + oldname
        animationNode.setAttribute("name", newname)
        
        convert.write(ex_model + "_" + animation)



if __name__ == "__main__":
    # Add Animation to base skeleton:
    actor  = Skeleton("alch", "Alchemist.skeleton.xml")
    action = Skeleton("alch", "Idle.SKELETON.xml")
    actor.addAnimation(action)
    actor.write("Alchemist0.skeleton.xml")
Paladin Class: discussion thread, download page
Shared Animations Library: discussion thread, download page
«1

Comments

  • DushoDusho Posts: 988
    Hi CCC, so you're still into this...
    I remember this thing from Ogre forums, from people that wanted to use TL assets. They had to merge the skeletons in order to have it animated properly. Haven't tried that, but instead of calculating differences like you do isn't it just sufficient to replace frame 0 (base?) of animation with that inside idle animation? Sorry, if I'm talking non-sense.. still haven't looked into this in detail.
  • PhanjamPhanjam Posts: 3,287 ✭✭✭
    gosh I wish I could help out CCC, but you're just too far ahead of me on this one, sorry
    Lvw5p3z.png
    Torchlight 1 Class Pack (TL1CP) Mod for TL2: Steam | RGF
  • Dusho wrote:
    Hi CCC, so you're still into this...
    I remember this thing from Ogre forums, from people that wanted to use TL assets. They had to merge the skeletons in order to have it animated properly. Haven't tried that, but instead of calculating differences like you do isn't it just sufficient to replace frame 0 (base?) of animation with that inside idle animation? Sorry, if I'm talking non-sense.. still haven't looked into this in detail.
    Of course I'm still into this. I literally do not know how to give up when I get obsessed with a project that interests me!


    Well, I picked this up from the Ogre forums:
    Bone positions in the first section are in parent space, meaning: They are relative to the parent bone's position. If a bone does not have a parent, then the position is in object space. Same goes for the rotation.

    In animation keyframes the translation and all other transforms are in parent space too, but relative to the binding position, which is the position in the first bones section of the skeleton xml file.

    It is perfectly normal that most translate-values are zero, animations are mostly rotations. Look at your own joints when you turn your forearm it does not move away from the elbow joint, it only rotates.

    So it looks like all of the measurements in the first frame of the animation are applied to the base skeleton, and there can only be one base skeleton, so if you put multiple animations in one file they'll all need to be relative to the same base.

    Here's another thing I noticed. I cloned the Alchemist mesh and named it "Idle.mesh" and linked it to Idle.skeleton. Then I loaded the Alchemist model in TorchED (in his idle animation) and loaded the idle mesh in the Ogre Mesh Viewer, and got this:
    25tjkn5.jpg
    (Note: the models are both vertical; the rotation is only due to the camera angles.)

    The one on the left is basically moving like he should, but he's standing straight up. So apparently Torchlight is doing something more with the data than just reading the skeleton files the way Ogre normally does.
    Paladin Class: discussion thread, download page
    Shared Animations Library: discussion thread, download page
  • Looks like this has been solved on the Ogre forums.
    check Ogre::Skeleton::_mergeSkeletonAnimations(const Skeleton* source,
    const BoneHandleMap& boneHandleMap,
    const StringVector& animations = StringVector());


    Unfortunately I think I still have quite a few Ogre tutorials ahead of me before I figure out what this means.
    Paladin Class: discussion thread, download page
    Shared Animations Library: discussion thread, download page
  • DushoDusho Posts: 988
    Ok so I finally checked some TL skeleton files to understand animation stuff there.
    Every animated model carries it's skeleton inside .skeleton file linked from mesh file.
    But additionally every animation (idle, walk, run, ...) carries another skeleton inside it's animation file. For some simple animations and models skeletons are identical (cave_flier), but some models (more complex and more precise animations) the skeleton is different (troll).
    Not sure how is it in Blender, if you can have multiple skeletons attached to mesh and just switch between those for every animation.
    I guess, there shouldn't be problem working with one animation at a time when you will override bones positions and rotations (of your in mesh assigned skeleton) with positions and rotations inside specific animation.
    Multiple skeletons looks like to be TL way how to have animations in separate files and yet to have it Ogre compliant.
    If you'll apply difference between skeletons on animations (relative translations and rotations) you should have skeleton's in animations identical to base one.
    But I guess you knew all that already...

    EDIT: ehm. Ok, so I just checked your script and .xml animation files again.
    What you did with rotation doesn't look right. You can't just make difference on all elements there.
    Variables there - angle, axis (x,y,z) - specify rotation axis and angle you need to rotate around that axis -> so theoretically if axis (x,y,z) are identical you just need to subtract angle .
    The way it should be handled universally is to create Quaternions from actor and action rotations and add (ActionQuat - ActorQuat) on frames rotations. Basically how you did the differences, but first create quaternions from rotation info.
    There is method to do so:
    Mathutils.Quaternion([x, y, z], angle) (import math)
    hope that helps..
  • Dusho wrote:
    blah blah blah ... Quaternions ... blah blah blah

    Ah yes, thanks for pointing that out. I've been meaning to read about Quaternions for a while now, and I guess it's time to learn how to use them. Thanks again, Dusho!
    Paladin Class: discussion thread, download page
    Shared Animations Library: discussion thread, download page
  • PhanjamPhanjam Posts: 3,287 ✭✭✭
    Dusho wrote:
    blah blah blah ... Quaternions ... blah blah blah

    Ah yes, thanks for pointing that out. I've been meaning to read about Quaternions for a while now, and I guess it's time to learn how to use them. Thanks again, Dusho!

    isn't that what gives jedi their force powers?
    Lvw5p3z.png
    Torchlight 1 Class Pack (TL1CP) Mod for TL2: Steam | RGF
  • Psssh... Midichlorians are for Jedi wussies. Quaternions are for the Sith.
    Paladin Class: discussion thread, download page
    Shared Animations Library: discussion thread, download page
  • Okay, back to business.

    Here's my latest script:
    import xml.dom.minidom
    import math
    from Quaternion import Quat, normalize
    
    def norm(x, y, z):
        x = float(x)
        y = float(y)
        z = float(z)
        nrm = math.sqrt(math.pow(x,2) + math.pow(y,2) + math.pow(z,2))
        return "%.8f" % nrm
    
    
    class Skeleton():
        def __init__(self, model, xmlFile):
            self.model = model
            self.doc   = xml.dom.minidom.parse(xmlFile)
            self.bones      = self.doc.getElementsByTagName("bone")
            self.hierarchy  = self.doc.getElementsByTagName("boneparent")
            self.animations = self.doc.getElementsByTagName("animation")
            self.scalars    = {}
            self.offsets    = {}
            #self.rotates    = {}
            self.quats      = {}
            self.bonelist   = []
            for bone in self.bones:
                self.bonelist.append(bone.getAttribute("name"))
    
        def write(self, filename):
            "Print the converted animation to a new file."
            output = open(filename, 'w')
            self.doc.writexml(output)
            output.close()
    
            infile = open(filename, 'r')
            temp = infile.readlines()
            infile.close()
            temp[0] = '<skeleton>\n'  # delete xml version tag, not used by Ogre
            outfile = open(filename, 'w')
            outfile.writelines(temp)
            outfile.close()
    
    
        def getBone(self, name):
            "Find the node that contains info for a particular bone."
            for bone in self.bones:
                if bone.getAttribute("name") == name:
                    return bone
    
        def deleteBones(self, extras):
            "Delete all instances of a list of bones from the skeleton."
            # <bone>       : attribute      'name'        not in bonelist
            bones = self.doc.childNodes[0].childNodes[1]
            for node in bones.getElementsByTagName("bone"):
                if node.getAttribute("name") in extras:
                    bones.removeChild(node)
    
            # <boneparent> : attribute 'bone' or 'parent' not in bonelist
            hierarchy = self.doc.childNodes[0].childNodes[3]
            for node in hierarchy.getElementsByTagName("boneparent"):
                if (node.getAttribute("bone") in extras or
                    node.getAttribute("parent") in extras):
                    hierarchy.removeChild(node)
    
            # <track>      : attribute      'bone'        not in bonelist
            tracks = (self.doc.childNodes[0].childNodes[5].
                      childNodes[1].childNodes[1])
            for node in tracks.getElementsByTagName("track"):
                if node.getAttribute("bone") in extras:
                    tracks.removeChild(node)
    
            for bone in self.bonelist[:]:
                if bone in extras:
                    self.bonelist.remove(bone)
    
        def sortBoneIDs(self, skeleton):
            "Set bone id values equal to those found in 'skeleton'."
            bones = self.doc.childNodes[0].childNodes[1]
            for node in bones.getElementsByTagName("bone"):
                name = node.getAttribute("name")
                if name in skeleton.bonelist:
                    boneid = skeleton.getBone(name).getAttribute("id")
                    node.setAttribute("id", boneid)
            
            
        def setScalars(self, skeleton):
            """
            Calculate the importer/exporter ratio of each bone's length.
            These will be used to re-scale animation skeletons.
            """
            for xbone in skeleton.bones:                # xbone = exporter bone
                name  = xbone.getAttribute("name")
                if name in self.bonelist:
                    mbone = self.getBone(name)          # mbone = importer bone
                    mx = mbone.childNodes[1].getAttribute("x")
                    my = mbone.childNodes[1].getAttribute("y")
                    mz = mbone.childNodes[1].getAttribute("z")
                    **** = xbone.childNodes[1].getAttribute("x")
                    xy = xbone.childNodes[1].getAttribute("y")
                    xz = xbone.childNodes[1].getAttribute("z")
                    mlength = norm(mx,my,mz)
                    xlength = norm(****,xy,xz)
                    try:
                        self.scalars[name] = float(mlength)/float(xlength)
                    except ZeroDivisionError:
                        self.scalars[name] = 1
    
        def setOffsets(self, base):
            """
            Calculate the offsets in the initial bone positions.
            These will be used to adjust animation keyframes before addint the
            animation to the base skeleton's file.
            """
            for abone in self.bones:                # abone = animation bone
                name  = abone.getAttribute("name")
                if name in self.bonelist:
                    bbone = base.getBone(name)      # bbone = base bone
                    bx = float(bbone.childNodes[1].getAttribute("x"))
                    by = float(bbone.childNodes[1].getAttribute("y"))
                    bz = float(bbone.childNodes[1].getAttribute("z"))
                    ax = float(abone.childNodes[1].getAttribute("x"))
                    ay = float(abone.childNodes[1].getAttribute("y"))
                    az = float(abone.childNodes[1].getAttribute("z"))
                    self.offsets[name] = {'x':ax-bx, 'y':ay-by, 'z':az-bz}
    
        def setQuaternions(self, base):
            """
            Calculate the offsets in the initial bone positions.
            These will be used to adjust animation keyframes before addint the
            animation to the base skeleton's file.
            """
            for abone in self.bones:                # abone = animation bone
                name  = abone.getAttribute("name")
                if name in self.bonelist:
                    bbone = base.getBone(name)      # bbone = base bone
                    bw = float(bbone.childNodes[3].getAttribute("angle"))
                    bx = float(bbone.childNodes[3].childNodes[1].getAttribute('x'))
                    by = float(bbone.childNodes[3].childNodes[1].getAttribute('y'))
                    bz = float(bbone.childNodes[3].childNodes[1].getAttribute('z'))
                    aw = float(abone.childNodes[3].getAttribute("angle"))
                    ax = float(abone.childNodes[3].childNodes[1].getAttribute('x'))
                    ay = float(abone.childNodes[3].childNodes[1].getAttribute('y'))
                    az = float(abone.childNodes[3].childNodes[1].getAttribute('z'))
    
                    aquat = Quat(normalize([ax, ay, az, aw]))
                    bquat = Quat(normalize([bx, by, bz, bw])) 
    
                    self.quats[name] = aquat.q - bquat.q
                    #{'a':aangle-bangle, 'x':az-bz, 'y':ay-by, 'z':ax-bx}
                    
        def addAnimation(self, action):
            "Add the animation node to the base skeleton."
            # Calculate translations from base skeleton to animation base.
            action.setOffsets(self)
            action.setQuaternions(self)
    
            # Add offsets and rotates to all keyframes for each bone.
            for track in action.animations[0].getElementsByTagName("track"):
                bone = track.getAttribute("bone")
                keys = track.childNodes[1].getElementsByTagName("keyframe")
                for key in keys:
                    keyTx = float(key.childNodes[1].getAttribute('x'))
                    keyTy = float(key.childNodes[1].getAttribute('y'))
                    keyTz = float(key.childNodes[1].getAttribute('z'))
                    newTx = unicode(keyTx + action.offsets[bone]['x'])
                    newTy = unicode(keyTy + action.offsets[bone]['y'])
                    newTz = unicode(keyTz + action.offsets[bone]['z'])
                    key.childNodes[1].setAttribute('x', newTx)
                    key.childNodes[1].setAttribute('y', newTy)
                    key.childNodes[1].setAttribute('z', newTz)
                    
                    keyRx = float(key.childNodes[3].childNodes[1].getAttribute('x'))
                    keyRy = float(key.childNodes[3].childNodes[1].getAttribute('y'))
                    keyRz = float(key.childNodes[3].childNodes[1].getAttribute('z'))
                    keyRw = float(key.childNodes[3].getAttribute('angle'))
                    quat0 = Quat(normalize([keyRx, keyRy, keyRz, keyRw]))
                    quat1 = quat0.q + action.quats[bone]
    
                    newRx = unicode(quat1[0])
                    newRy = unicode(quat1[1])
                    newRz = unicode(quat1[2])
                    newRw = unicode(quat1[3])
    
                    key.childNodes[3].childNodes[1].setAttribute('x', newRx)
                    key.childNodes[3].childNodes[1].setAttribute('y', newRy)
                    key.childNodes[3].childNodes[1].setAttribute('z', newRz)
                    key.childNodes[3].setAttribute('angle', newRw)
                    #'''
                    
            # Add the animation node to the base skeleton.
            self.doc.childNodes[0].childNodes[5].appendChild(action.animations[0])
    
        def convertAnimation(self, exporter, action):
            """
            when this is done, augment it to be able to find and convert
            all of the animations in a directory
            """
            # compare the scale of the importer and exporter
            self.setScalars(exporter)
    
            # delete extra bones from exporter
            extras = list(set(exporter.bonelist).difference(set(self.bonelist)))
            if extras:
                action.deleteBones(extras)
                action.sortBoneIDs(self)
    
            # adjust all position attributes by self.scalars
              # <position>  attributes 'x', 'y', 'z'
              # <translate> attributes 'x', 'y', 'z' --> is this necessary?
            for bone in action.bonelist:
                position = action.getBone(bone).childNodes[1]
                for i in ('x', 'y', 'z'):
                    transform = float(position.getAttribute(i)) * self.scalars[bone]
                    position.setAttribute(i, unicode(transform))
    
            # change the "name" attribute in the <animation> node
            animationNode = action.doc.childNodes[0].childNodes[5].childNodes[1]
            oldname = animationNode.getAttribute("name")
            newname = exporter.model + '_' + oldname
            animationNode.setAttribute("name", newname)
        
    
    if __name__ == "__main__":
        # Add Animation to base skeleton:
        # actor  = Skeleton("alch", "Alchemist.skeleton.xml")
        # action = Skeleton("alch", "Idle.SKELETON.xml")
        # actor.addAnimation(action)
        # actor.write("Alchemist0.skeleton.xml")
    
        # Convert Animation from Exporter Skeleton:
        importer = Skeleton("alch", "Alchemist.skeleton.xml")
        exporter = Skeleton("destro", "player.SKELETON.xml")
        action   = Skeleton("destro", "Special_Stomp.SKELETON.xml")
        importer.convertAnimation(exporter, action)
        action.write("destro_Special_Stomp.SKELETON.xml")
    

    It does two things, one of them successfully, the other... not so much.

    The addAnimation method is supposed to extract an animation from one skeleton file, modify it, and then add it into another one. This is the method I'll ultimately use to put all of the animations in one file. It's not working yet -- I'm using quaternions to do the transformations now, but I did something wrong.
    This is my attempt to add the Alchemist's idle animation to his base skeleton file:
    8wk40m.jpg
    He doesn't look too comfortable.

    The convertAnimation method (demonstrated on the last 5 lines of the script) will convert an animation skeleton for one class into the proportions required for another class... in the script, it takes the Destroyer's Special_Stomp.skeleton file and converts the proportions of the bones to fit the alchemist's body. So now the Alchemist can use the Stomp animation. (If you just copy Special_Stomp.skeleton over to the alchemist folder and edit alchemist.animation, he can do the stomp animation, but his arms will be stretched out and his legs will be shrunk down to the Destroyer's proportions.)

    When I get things working the right way, I'll make this a bit more user friendly and maybe even slap on a GUI, but for now if you want to convert animations on your own, you'll need:
    Python 2.6
    the Quaternion module (I'd prefer to do this with the Blender module, but I can't find it)
    Ogre Command Line Tools (bottom of the page)

    0. Install whatever you need, then put skeletonConverter.py in a directory with you importer class's base skeleton, and the exporter class's base skeleton and animation skeleton.
    1. Run all three skeleton files through the OgreXmlConverter (just drag their icons over the OXC icon)
    2. Edit skeletonConverter.py (lines 230-232) to read from your three skeleton files
    3. Edit skeletonConverter.py (line 234) to write to the correct file name
    4. Run skeletonConverter.py
    5. Run the output file through the OgreXmlConverter
    6. Create a mod folder that contains these directories: media/models/<character>
    7. Place your new skeleton file in the <character folder>
    8. Place a copy of <character>.animation in the mod folder.
    9. Open the animation file for your exporter class, copy the section that links to the animation you exported. (It begins with the tag [Animation] and ends with [/Animation].) Paste it in the importer's animation file. Change the file name <string> to match your new skeleton's file name.

    That should do it. Like I said, I'll take care of as much of this as I can to make it more user friendly. So far I've only tested the Destroyer's Stomp animation and the Vanquisher's Dash, both on the Alchemist model, and they both look pretty good.
    Paladin Class: discussion thread, download page
    Shared Animations Library: discussion thread, download page
  • DushoDusho Posts: 988
    sorry.. I have a total aversion against Python (I'm a spoiled Visual Studio user) so what does that normalize() in Quat constructor do? Cause I wouldn't normalize there anything... maybe just x,y,z, but definitely not angle (that Mathutils doesn't work?)
    I'm pretty sure I posted script where one guy tried to deal with animations in TL already, do you have that script? I'm loosing track what I did sent to what people..
    Here is the script, maybe it will help http://www.dusho.net/_archiv/tl/ogre_Import_Rene.zip
    What are you using to write those python scripts? I find blender text editor pretty useless...
  • CCCenturionCCCenturion Posts: 575
    edited December 2010
    The normalize() command is in the Quaternion module I found, which requires quaternions to be normalized to do math operations on them. That seemed kind of fishy to me, which is a big reason why I'm still trying to find the Blender module so I can use the Quaternion class in Blender.Mathutils. Until I find that module, I won't be able to run the script you just posted, either. I know that Blender can access that module, but I can't import it to other Python scripts that I run outside of Blender.

    As for writing Python scripts, I find that IDLE does a good enough job for my needs.
    Paladin Class: discussion thread, download page
    Shared Animations Library: discussion thread, download page
  • DushoDusho Posts: 988
    I'm noob when it comes to Blender modules.. (did I mention that I hate Python?)
    Will this help? This works for me, (blend file): http://www.dusho.net/_archiv/tl/import.torchlight.zip
    Have just fully installed Python and blender, no other magic.. and script runs.
  • You're running the script from inside Blender, I take it? I suppose that's what I should be doing. :oops:

    By the way, what is that ogre_Import_ReneLacson.py script supposed to do? I tried running it in Blender, and it ran, but didn't really do anything helpful. It just imported a pair of boots and a ton of vectors pointing in different directions. I can figure it out on my own if I have to, but do you have a link to a forum where this was discussed, or any other clues about what the script is meant to do?
    Paladin Class: discussion thread, download page
    Shared Animations Library: discussion thread, download page
  • DushoDusho Posts: 988
    mesh import in that script doesn't work with sharedvertices, so some models won't work.
    try pointing to directory with cave_flier model and animations
    but ye.. you should go through code to see what it does precisely
  • CCCenturionCCCenturion Posts: 575
    edited December 2010
    Hey Dusho, what are you getting when you run that Ogre Import script? Mine is looking like this:
    292li09.jpg
    with a bunch of arrows on each bone, which I moved to another Blender layer in order to take a clearer picture.

    When you run the script does it import a mesh and skeleton with all of the animations loaded as well? That seems to be what the script describes.
    It does work for smaller and simpler models, like the cave flier and the alchemist's Imp. I wonder where it's breaking down for more complex models.


    Edit: However, it also looks like it's only adding one animation to the base skeleton. Still, that's a start.


    Edit #2:

    Uh oh. I just dug up this old thread on the Ogre forums. Sinbad is Ogre's founder, so I assume he knows a thing or two about how it works:
    Right, back to the question. I believe what you're saying is that you're linking animations across 2 skeletons which don't have the same bind pose, correct?

    You can't really do this. The reason is simple - the bind position is used at either end of the skeletal animation calculation - to reset the bones and then to calculate a final transforms to take each vertex in object space into bone space and then back out to the final animated position. If you blend together animations from one skeleton with animations from another, they have to have the same bind position otherwise that won't work. There really isn't anything you can do about that. You can vary the elements that aren't animated (a common one is scale for example, so you can use common animations from one humanoid to another much larger one), but the bind settings of the parts that change in the animation (position, orientation) must be consistent.

    Well this basically kills my dream of putting all of a character's animations in one skeleton, unless I also create my own animations that take the character from one bind pose to another, and then tack it on to the beginning of each animation. While that would sort of work, if I just had the character jump to the new bind pose at the first frame of the animation, the animation still ends on the original bind pose, so the character would flash to that pose for one frame every time the animation loops.

    But I think my main problem is that I've been asking the wrong questions. Instead of trying to compile everything in one file, I just need to learn more about how Ogre uses linked skeletons.
    Paladin Class: discussion thread, download page
    Shared Animations Library: discussion thread, download page
  • DushoDusho Posts: 988
    as I told in previous post.. when I compared import static mesh script (from D-man) with this script I noticed that this script doesn't handle vertex information inside 'sharedvertices' tags (in .xml form).
    But yes.. if there is a way to combine all scripts together.. we can have perfect TL importer.
    Originally I thought to use D-mans importer (cause it works with all TL meshes and imports skeleton and vertex weights correctly) and take pieces with loading the animation from Rene's script - but you know.. **** Python.
    Though I see you're gaining python and blender knowledge really fast.. maybe you can try now..
    I will help anyhow I can..
  • Dusho wrote:
    as I told in previous post.. when I compared import static mesh script (from D-man) with this script I noticed that this script doesn't handle vertex information inside 'sharedvertices' tags (in .xml form).
    But yes.. if there is a way to combine all scripts together.. we can have perfect TL importer.
    Originally I thought to use D-mans importer (cause it works with all TL meshes and imports skeleton and vertex weights correctly) and take pieces with loading the animation from Rene's script - but you know.. **** Python.
    Though I see you're gaining python and blender knowledge really fast.. maybe you can try now..
    I will help anyhow I can..

    Well, I think I have a fairly good grasp on Rene's importer, although it's only importing one animation at a time for me so far. I haven't tried to edit it yet, so I might be able to find something that can be fixed. I'll take a look at D-man's importer too, and see where he does things better.
    Paladin Class: discussion thread, download page
    Shared Animations Library: discussion thread, download page
  • PhanjamPhanjam Posts: 3,287 ✭✭✭
    GO CCC! GO DUSHO!
    sorry - cheering from the sidelines is all i can do on this one :(
    (aside from making stupid jokes every now and then... :D )
    Lvw5p3z.png
    Torchlight 1 Class Pack (TL1CP) Mod for TL2: Steam | RGF
  • Well this basically kills my dream of putting all of a character's animations in one skeleton, unless I also create my own animations that take the character from one bind pose to another, and then tack it on to the beginning of each animation. While that would sort of work, if I just had the character jump to the new bind pose at the first frame of the animation, the animation still ends on the original bind pose, so the character would flash to that pose for one frame every time the animation loops.

    But I think my main problem is that I've been asking the wrong questions. Instead of trying to compile everything in one file, I just need to learn more about how Ogre uses linked skeletons.

    On second thought, I take back what I said. I think I'm on the right track; I just need to figure out the math to calculate the quaternion that would move each bone from its rotation in the bind pose in the base file to its bind pose in each animation file. Then I can simply multiply that by the quaternions in every keyframe of the animation and get the transformation I need. I don't even think I need to use Blender to do this. Of course, we still need to do something about the Export script exploding the skeletons if we ever want to create new animations in Blender.
    Paladin Class: discussion thread, download page
    Shared Animations Library: discussion thread, download page
  • DushoDusho Posts: 988
    now you're confusing me a bit with editing your old posts
    I would definitely love to have all animations merged together and working in Blender and the way of merging and using (action-actionAnimation) difference applied to bind position seams right approach to me. I don't think there should be a problem with cycling animations.
    You'll have :
    (frames 0-20) - idle animation
    (frames 21-50) - walk
    (frames 51-70) - run
    So for 'walk' you will cycle just from 21 to 50, then again 21 to 50 and so on.. no return to bind position. On stop walking you will blend from your current 'walk' frame (say frame 34) to frame 0, where your idle animation starts. And everything should be still fluent.
  • Yeah, sorry about that. I got a little sloppy and started using the thread as my own personal notebook.

    I think I did the wrong calculations to figure out the transformation that takes you from the actor skeleton's basis pose to the action skeleton's basis pose. I forgot to send myself the latest version of my Python script, so I'll have to wait until I get to my work computer to try this out. And then I really, really need to stop doing this stuff while I'm at work!
    Paladin Class: discussion thread, download page
    Shared Animations Library: discussion thread, download page
  • DushoDusho Posts: 988
    It's xmas time.. so I'm sure no one expects you to work
    I'm sure that even work instructions says that during xmas time you should just watch youtube, read forums and send xmas spam mails..
  • DushoDusho Posts: 988
    hey CCC
    still working on this? I gave python a try (still think it's a dumb script language) and modified Rene's script a bit. It handles submeshes now, so you can try to import character mesh.
    http://www.dusho.net/_archiv/tl/importTL.04.zip
    There is a flag fix_TLAnimation (set to true) that tries to modify animations according to different between action skeleton and bind skeleton. I checked Ogre source code for the math, but I still wasn't able to make it work properly. There is just something wrong with that math, I guess. I ended up making transformations that are different from Ogre code, but it comes closest to correct result. Still, rotations are bit off, and there are some glitches in animation.
    Math (according to Ogre source code) should be like this:
    deltaTranslate = bindPosition - actionPosition
    deltaRotate = actionRotation.inverse() * bindRotation   // all Quaternions
    
    and then application should be:
    RealActionPosition = deltaTranslate + bindPosition
    RealActionRotation = deltaRotate * bindRotation
    
    You can check source here: http://transporter-game.googlecode.com/svn/trunk/src/ogre/OgreSkeleton.cpp, method '_mergeSkeletonAnimations'.
    Had also problems transforming rotations to blender's coordinate system... weird stuff.
    If anythings comes to mind, feel free to jump in.
  • Thanks for looking into that. I've been pretty busy this week but once things calm down a bit (and after I finish tweaking my animation converter to work for the Vanq) I do plan to get back into this project. Hopefully that will be sometime early next week.
    Paladin Class: discussion thread, download page
    Shared Animations Library: discussion thread, download page
  • DushoDusho Posts: 988
    So I managed to get a hold of D-Man and he already had partial script for torchlight animations.
    Based on his scripts I combined everything I had, and seems like this script can import TL non-animated models, animated mobs and also character models with animations. And it looks good.
    Script: http://www.dusho.net/_archiv/tl/importTL.zip
    Script still requires to have OgreTools installed and path to converter must be copied into the script
    # here place path to your OgreXmlConverter
    ogreXMLConverter = "D:\stuff\Torchlight_modding\orge_tools\OgreXmlConverter.exe -q"
    
    Script can be placed into '...\.blender\scripts\' folder and will be detected by Blender as Import script (File->Import->OGRE for Torchlight).
    I'm sure there are still some problems, so let me know if somethings pops up.
    With this I think we are ready to import any art asset in TL1 or TL2. Enjoy.
  • PhanjamPhanjam Posts: 3,287 ✭✭✭
    I can't personally take advantage of this and add any knowledge in this area, but this is fantastic Dusho, thanks and am really excited to see where all this can go!
    Lvw5p3z.png
    Torchlight 1 Class Pack (TL1CP) Mod for TL2: Steam | RGF
  • Can't wait to try this out. Thanks Dusho!
    Paladin Class: discussion thread, download page
    Shared Animations Library: discussion thread, download page
  • I fixed a bug in the script. The function "GetBlender249Name" will resize a file name if it is too long for Blender. However, it handles the file names with a sequence of if/else statements, and there's a missing "else" that leaves one of the ifs uncaught. Here's the old code:
    def GetBlender249Name(name):
        if(len(name) &gt; 20):
            if(name.find("/") &gt;= 0):
                if(name.find("Material") &gt;= 0):
                    # replace 'Material' string with only 'Mt'
                    newname = name.replace("Material","Mt")
                # check if it's still above 20
                if(len(newname) &gt; 20):
                    suffix = newname&#91;newname.find("/"):&#93;
                    prefix = newname&#91;0:(21-len(suffix))&#93;
                    newname = prefix + suffix
            else:
                newname = name&#91;0:21&#93;
        else:
            newname = name
    		
        if(newname!=name):
            print("WARNING: Name truncated (" + name + " -&gt; " + newname + ")")
                
        return newname
    

    Here's the new code (notice that there are now three "else" statements near the bottom):
    def GetBlender249Name(name):
        if(len(name) &gt; 20):
            if(name.find("/") &gt;= 0):
                if(name.find("Material") &gt;= 0):
                    # replace 'Material' string with only 'Mt'
                    newname = name.replace("Material","Mt")
                    # check if it's still above 20
                    if(len(newname) &gt; 20):
                        suffix = newname&#91;newname.find("/"):&#93;
                        prefix = newname&#91;0:(21-len(suffix))&#93;
                        newname = prefix + suffix
                else:
                    newname = name&#91;0:21&#93;
            else:
                newname = name&#91;0:21&#93;
        else:
            newname = name
            
        if(newname!=name):
            print("WARNING: Name truncated (" + name + " -&gt; " + newname + ")")
                
        return newname
    


    And here's the revised script:
    #!BPY
    
    """
    Name: 'OGRE for Torchlight &#40;*.MESH&#41;'
    Blender: 249b
    Group: 'Import'
    Tooltip: 'Import Torchlight OGRE files'
        
    Author: Daniel Handke &#40;D-Man&#41;
    Changes: Dusho, Rene Lacson
    """
    
    __author__ = "Daniel Handke & Dusho"
    __version__ = "0.91 9-January-2010"
    
    __bpydoc__ = """\
    This script imports Torchlight Ogre models into Blender.
    
    Supported:&lt;br&gt;
        * multiple submeshes &#40;triangle list&#41;
        * uvs
        * materials &#40;textures only&#41;
        * vertex colours        
        * skeletons
        * animations &#40;also with separate rest positions - skeletons&#41;
        * only texture info from materials
    
    Missing:&lt;br&gt;    
        * submeshes provided as triangle strips and triangle fans
        * materials &#40;diffuse, ambient, specular, alpha mode, etc.&#41;
    
    Known issues:&lt;br&gt;
        * location difference between different skeleton in animations may be wrong
        * blender only supports a single uv set, always the first is taken
          and only the first texture unit in a material, even if it is not for
          the first uv set.
         
    History:&lt;br&gt;
        * v0.91 - added name truncating &#40;Blender limits names to 21 chars -&gt; shows warning when done so&#41;
        * v0.9  - added location difference &#40;using stored base skeleton BoneDic instead of reading Blender bone geometry&#41;
        * v0.8  - added support for sharedgeometry &#40;Torchlight mobs models&#41; and texture name resolving
        * v0.7  - initial version &#40;combination of Daniel's and Rene's code 
    """
    
    from Blender import *
    from xml.dom import minidom
    import bpy
    import math
    import os
    
    
    # SETTINGS
    DEBUG = False
    # command line parameter to execute OgreXMLConverter
    # here place path to your OgreXmlConverter
    ogreXMLConverter = "C:\OgreCommandLineTools\OgreXmlConverter.exe -q"
    
    has_skeleton = False
    BonesData = {}
    BoneIDDic = {}
    
    def OpenFile&#40;filename&#41;:
        xml_file = open&#40;filename&#41;
        try:
            xml_doc = minidom.parse&#40;xml_file&#41;
            output = xml_doc
        except:
            print "File not valid!"
            output = 'None'
        xml_file.close&#40;&#41;
        return output
    
    # makes sure name doesn't exceeds blender naming limits
    # also keeps after name &#40;as Torchlight uses names to identify types -boots, chest, ...- with names&#41;
    def GetBlender249Name&#40;name&#41;:
        if&#40;len&#40;name&#41; &gt; 20&#41;:
            if&#40;name.find&#40;"/"&#41; &gt;= 0&#41;:
                if&#40;name.find&#40;"Material"&#41; &gt;= 0&#41;:
                    # replace 'Material' string with only 'Mt'
                    newname = name.replace&#40;"Material","Mt"&#41;
                    # check if it's still above 20
                    if&#40;len&#40;newname&#41; &gt; 20&#41;:
                        suffix = newname&#91;newname.find&#40;"/"&#41;:&#93;
                        prefix = newname&#91;0:&#40;21-len&#40;suffix&#41;&#41;&#93;
                        newname = prefix + suffix
                else:
                    newname = name&#91;0:21&#93;
            else:
                newname = name&#91;0:21&#93;
        else:
            newname = name
            
        if&#40;newname!=name&#41;:
            print&#40;"WARNING: Name truncated &#40;" + name + " -&gt; " + newname + "&#41;"&#41;
                
        return newname
        
    
    ############################################################################################
    ############################################################################################
    ####                                                                                    ####
    ####            mm        mm     mmmmmmmmm       mmmmmm       mm      mm                ####
    ####            mmm      mmm     mmmmmmmmm     mmmmmmmmmm     mm      mm                ####
    ####            mmmmm  mmmmm     mm            mm      mm     mm      mm                ####
    ####            mm mmmmmm mm     mm            mm             mm      mm                ####
    ####            mm  mmmm  mm     mmmmmmm       mmmmmmmmm      mmmmmmmmmm                ####
    ####            mm   mm   mm     mmmmmmm        mmmmmmmmm     mmmmmmmmmm                ####
    ####            mm   mm   mm     mm                    mm     mm      mm                ####
    ####            mm        mm     mm            mm      mm     mm      mm                ####
    ####            mm        mm     mmmmmmmmm     mmmmmmmmmm     mm      mm                ####
    ####            mm        mm     mmmmmmmmm      mmmmmmmm      mm      mm                ####
    ####                                                                                    ####
    ############################################################################################
    ############################################################################################
       
    def collectFaceData&#40;facedata&#41;:
        faces = &#91;&#93;
        for face in facedata.childNodes:
            if face.localName == 'face':
                v1 = int&#40;face.getAttributeNode&#40;'v1'&#41;.value&#41;
                v2 = int&#40;face.getAttributeNode&#40;'v2'&#41;.value&#41;
                v3 = int&#40;face.getAttributeNode&#40;'v3'&#41;.value&#41;
                faces.append&#40;&#91;v1,v2,v3&#93;&#41;
        
        return faces
    #---
    
    #### VertexData Read
    #---
    def collectVertexData&#40;data&#41;:
        vertexdata = {}
        vertices = &#91;&#93;
        normals = &#91;&#93;
        vertexcolors = &#91;&#93;
        
        for vb in data.childNodes:
            if vb.localName == 'vertexbuffer':
                if vb.hasAttribute&#40;'positions'&#41;:
                    for vertex in vb.getElementsByTagName&#40;'vertex'&#41;:
                        for vp in vertex.childNodes:
                            if vp.localName == 'position':
                                x = float&#40;vp.getAttributeNode&#40;'x'&#41;.value&#41;
                                y = -float&#40;vp.getAttributeNode&#40;'z'&#41;.value&#41;
                                z = float&#40;vp.getAttributeNode&#40;'y'&#41;.value&#41;
                                vertices.append&#40;&#91;x,y,z&#93;&#41;
                    vertexdata&#91;'positions'&#93; = vertices          
                
                if vb.hasAttribute&#40;'normals'&#41;:
                    for vertex in vb.getElementsByTagName&#40;'vertex'&#41;:
                        for vn in vertex.childNodes:
                            if vn.localName == 'normal':
                                x = float&#40;vn.getAttributeNode&#40;'x'&#41;.value&#41;
                                y = -float&#40;vn.getAttributeNode&#40;'z'&#41;.value&#41;
                                z = float&#40;vn.getAttributeNode&#40;'y'&#41;.value&#41;
                                normals.append&#40;&#91;x,y,z&#93;&#41;
                    vertexdata&#91;'normals'&#93; = normals             
                
                if vb.hasAttribute&#40;'colours_diffuse'&#41;:
                    for vertex in vb.getElementsByTagName&#40;'vertex'&#41;:
                        for vcd in vertex.childNodes:
                            if vcd.localName == 'colour_diffuse':
                                rgba = vcd.getAttributeNode&#40;'value'&#41;.value
                                r = float&#40;rgba.split&#40;&#41;&#91;0&#93;&#41;
                                g = float&#40;rgba.split&#40;&#41;&#91;1&#93;&#41;
                                b = float&#40;rgba.split&#40;&#41;&#91;2&#93;&#41;
                                a = float&#40;rgba.split&#40;&#41;&#91;3&#93;&#41;
                                vertexcolors.append&#40;&#91;r,g,b,a&#93;&#41;
                    vertexdata&#91;'vertexcolors'&#93; = vertexcolors
                
                if vb.hasAttribute&#40;'texture_coord_dimensions_0'&#41;:
                    texcosets = int&#40;vb.getAttributeNode&#40;'texture_coords'&#41;.value&#41;
                    vertexdata&#91;'texcoordsets'&#93; = texcosets
                    uvcoordset = &#91;&#93;
                    for vertex in vb.getElementsByTagName&#40;'vertex'&#41;:
                        uvcoords = &#91;&#93;
                        for vt in vertex.childNodes:
                            if vt.localName == 'texcoord':
                                u = float&#40;vt.getAttributeNode&#40;'u'&#41;.value&#41;
                                v = -float&#40;vt.getAttributeNode&#40;'v'&#41;.value&#41;+1.0
                                uvcoords.append&#40;&#91;u,v&#93;&#41;
                                    
                        if len&#40;uvcoords&#41; &gt; 0:
                            uvcoordset.append&#40;uvcoords&#41;
                    vertexdata&#91;'uvsets'&#93; = uvcoordset               
                            
        return vertexdata       
    #---
    
    #### VertexGroupsData
    #---
    def collectBoneAssignments&#40;data&#41;:
        global BoneIDDic
        
        VertexGroups = {}
        for vg in data.childNodes:
            if vg.localName == 'vertexboneassignment':
                VG = str&#40;vg.getAttributeNode&#40;'boneindex'&#41;.value&#41;
                if VG in BoneIDDic.keys&#40;&#41;:
                    VGNew = BoneIDDic&#91;VG&#93;
                else:
                    VGNew = VG
                if VGNew not in VertexGroups.keys&#40;&#41;:
                    VertexGroups&#91;VGNew&#93; = &#91;&#93;
                    
        for vg in data.childNodes:
            if vg.localName == 'vertexboneassignment':
                
                VG = str&#40;vg.getAttributeNode&#40;'boneindex'&#41;.value&#41;
                if VG in BoneIDDic.keys&#40;&#41;:
                    VGNew = BoneIDDic&#91;VG&#93;
                else:
                    VGNew = VG
                verti = int&#40;vg.getAttributeNode&#40;'vertexindex'&#41;.value&#41;
                weight = float&#40;vg.getAttributeNode&#40;'weight'&#41;.value&#41;
                
                VertexGroups&#91;VGNew&#93;.append&#40;&#91;verti,weight&#93;&#41;
                
        return VertexGroups
    #---
    
    #### Create Meshes
    def CreateMeshes&#40;xmldoc,meshname,Textures,dirname&#41;:
        global has_skeleton
        
        faceslist = &#91;&#93;
        OGREObjects = &#91;&#93;
        allObjs = &#91;&#93;
        isSharedGeometry = False
        sharedGeom = &#91;&#93;
            
        for submeshes in xmldoc.getElementsByTagName&#40;'submeshes'&#41;:
            for submesh in submeshes.childNodes:
                if submesh.localName == 'submesh':
                    material = str&#40;submesh.getAttributeNode&#40;'material'&#41;.value&#41;
                    # to avoid Blender naming limit problems
                    material = GetBlender249Name&#40;material&#41;
                    sm = &#91;&#93;
                    sm.append&#40;material&#41;
                    for subnodes in submesh.childNodes:
                        if subnodes.localName == 'faces':
                            facescount = int&#40;subnodes.getAttributeNode&#40;'count'&#41;.value&#41;
                            sm.append&#40;collectFaceData&#40;subnodes&#41;&#41;
                        
                            if len&#40;collectFaceData&#40;subnodes&#41;&#41; != facescount:
                                print "FacesCount doesn't match!"
                                break 
                        
                        if &#40;subnodes.localName == 'geometry'&#41;:
                            vertexcount = int&#40;subnodes.getAttributeNode&#40;'vertexcount'&#41;.value&#41;
                            sm.append&#40;collectVertexData&#40;subnodes&#41;&#41;
                            
                        if subnodes.localName == 'boneassignments':
                            sm.append&#40;collectBoneAssignments&#40;subnodes&#41;&#41; 
                            
                    OGREObjects.append&#40;sm&#41;
        
        if&#40;len&#40;xmldoc.getElementsByTagName&#40;'sharedgeometry'&#41;&#41; &gt; 0&#41;:
            for subnodes in xmldoc.getElementsByTagName&#40;'sharedgeometry'&#41;:
                sharedGeom = collectVertexData&#40;subnodes&#41;
                isSharedGeometry = True
                print&#40;"sharedGeometry"&#41;
                
        scn = Scene.GetCurrent&#40;&#41;    
        for i in range&#40;len&#40;OGREObjects&#41;&#41;:
            obj = Object.New&#40;'Mesh',OGREObjects&#91;i&#93;&#91;0&#93;&#41;
            vertices = &#91;&#93;
            if&#40;isSharedGeometry&#41;:
                vertices = sharedGeom
            else:
                vertices = OGREObjects&#91;i&#93;&#91;2&#93;
            faces = OGREObjects&#91;i&#93;&#91;1&#93;
            allObjs.append&#40;obj&#41;
        
            me = Mesh.New&#40;OGREObjects&#91;i&#93;&#91;0&#93;&#41;
            me.verts.extend&#40;vertices&#91;'positions'&#93;&#41;
            me.faces.extend&#40;faces&#41;  
        
            c = 0
            for v in me.verts:
                if vertices.has_key&#40;'normals'&#41;:
                    normals = vertices&#91;'normals'&#93;
                    v.no = Mathutils.Vector&#40;normals&#91;c&#93;&#91;0&#93;,normals&#91;c&#93;&#91;1&#93;,normals&#91;c&#93;&#91;2&#93;&#41;
                    c+=1
            
            for f in me.faces:
                f.smooth = 1        
        
            if vertices.has_key&#40;'texcoordsets'&#41;:
                for j in range&#40;vertices&#91;'texcoordsets'&#93;&#41;:
                    me.addUVLayer&#40;'UVLayer'+str&#40;j&#41;&#41;
                    me.activeUVLayer = 'UVLayer'+str&#40;j&#41;
                
                    for f in me.faces:  
                        if vertices.has_key&#40;'uvsets'&#41;:
                            uvco1sets = vertices&#91;'uvsets'&#93;&#91;f.v&#91;0&#93;.index&#93;
                            uvco2sets = vertices&#91;'uvsets'&#93;&#91;f.v&#91;1&#93;.index&#93;
                            uvco3sets = vertices&#91;'uvsets'&#93;&#91;f.v&#91;2&#93;.index&#93;
                            uvco1 = Mathutils.Vector&#40;uvco1sets&#91;j&#93;&#91;0&#93;,uvco1sets&#91;j&#93;&#91;1&#93;&#41;
                            uvco2 = Mathutils.Vector&#40;uvco2sets&#91;j&#93;&#91;0&#93;,uvco2sets&#91;j&#93;&#91;1&#93;&#41;
                            uvco3 = Mathutils.Vector&#40;uvco3sets&#91;j&#93;&#91;0&#93;,uvco3sets&#91;j&#93;&#91;1&#93;&#41;
                            f.uv = &#40;uvco1,uvco2,uvco3&#41;
                    
            if vertices.has_key&#40;'vertexcolors'&#41;:
                me.vertexColors = True
            
                vcolors = vertices&#91;'vertexcolors'&#93;
            
                for f in me.faces:
                    for k,v in enumerate&#40;f.v&#41;:
                        col = f.col&#91;k&#93;
                        vcol = vcolors&#91;k&#93;
                        col.r = int&#40;vcol&#91;0&#93;*255&#41;
                        col.g = int&#40;vcol&#91;1&#93;*255&#41;
                        col.b = int&#40;vcol&#91;2&#93;*255&#41;
                        col.a = int&#40;vcol&#91;3&#93;*255&#41;
            
            Mat = Material.New&#40;OGREObjects&#91;i&#93;&#91;0&#93;&#41;       
            if Textures != 'None':
                Tex = Texture.New&#40;OGREObjects&#91;i&#93;&#91;0&#93;&#41;
                Tex.setType&#40;'Image'&#41;
                try:
                    img = Image.Load&#40;Textures&#91;OGREObjects&#91;i&#93;&#91;0&#93;&#93;&#41;
                    Tex.image = img
                except:
                    print "Image File",Textures&#91;OGREObjects&#91;i&#93;&#91;0&#93;&#93;, "skipped!"
                Mat.setRef&#40;1.0&#41;
                Mat.setTexture&#40;0,Tex,Texture.TexCo.UV,Texture.MapTo.COL&#41;
            Mat.setSpec&#40;0.0&#41;
            Mat.setHardness&#40;1&#41;
            Mat.setRGBCol&#40;122,122,122&#41;
            me.materials += &#91;Mat&#93;
            
            obj.link&#40;me&#41;        
            
            if has_skeleton:
                VGS = &#91;&#93;
                #for shared geometry need to fetch boneassignments from root
                if&#40;isSharedGeometry&#41;:
                    for subnodes in xmldoc.getElementsByTagName&#40;'boneassignments'&#41;: 
                        VGS = collectBoneAssignments&#40;subnodes&#41;
                else:
                    VGS = OGREObjects&#91;i&#93;&#91;3&#93;
                
                for VG in VGS.keys&#40;&#41;:
                
                    me.addVertGroup&#40;VG&#41;
                    for vert in VGS&#91;VG&#93;:
                        weight = vert&#91;1&#93;
                        vertex = &#91;vert&#91;0&#93;&#93;
                        me.assignVertsToGroup&#40;VG,vertex,weight,Mesh.AssignModes&#91;'REPLACE'&#93;&#41;
                    me.update&#40;&#41;         
            
            
            scn.objects.link&#40;obj&#41;
            
        Redraw&#40;&#41;
        
        return allObjs  
    
    def OGREBoneIDsDic&#40;xmldoc&#41;:
        
        global BoneIDDic
    
        for bones in xmldoc.getElementsByTagName&#40;'bones'&#41;:
        
            for bone in bones.childNodes:
                if bone.localName == 'bone':
                    BoneName = str&#40;bone.getAttributeNode&#40;'name'&#41;.value&#41;
                    BoneID = int&#40;bone.getAttributeNode&#40;'id'&#41;.value&#41;
                    BoneIDDic&#91;str&#40;BoneID&#41;&#93; = BoneName
                    
    def GetTextures&#40;matfile, folder&#41;:
        try:
            filein = open&#40;matfile&#41;
        except:
            print "File", matfile, "not found!"
            return 'None' 
        data = filein.readlines&#40;&#41;
        filein.close&#40;&#41;
        MaterialDic = {}
        
        for line in data:
            if "material" in line:
                MaterialName = line.split&#40;&#41;&#91;1&#93;
                # to avoid Blender naming limit problems
                MaterialName = GetBlender249Name&#40;MaterialName&#41;
                MaterialDic&#91;MaterialName&#93; = &#91;&#93;
                count = 0
            if "{" in line:
                count += 1
            if  count &gt; 0:
                MaterialDic&#91;MaterialName&#93;.append&#40;line&#41;
            if "}" in line:
                count -= 1
        Textures = {}
        for Material in MaterialDic.keys&#40;&#41;:
            print "Materialname:", Material
            for line in MaterialDic&#91;Material&#93;:
                if "texture_unit" in line:
                    Textures&#91;Material&#93; = ""
                    count = 0
                if "{" in line:
                    count+=1
                if &#40;count &gt; 0&#41; and &#40;"texture" in line&#41;:
                    file = os.path.join&#40;folder, &#40;line.split&#40;&#41;&#91;1&#93;&#41;&#41;          
                    
                    if&#40;not os.path.isfile&#40;file&#41;&#41;:
                        # just force to use .dds if there isn't file specified in material file
                        file = os.path.join&#40;folder, os.path.splitext&#40;&#40;line.split&#40;&#41;&#91;1&#93;&#41;&#41;&#91;0&#93; + ".dds"&#41;
                    Textures&#91;Material&#93; += file
                        
                if "}" in line:
                    count-=1
        
        return Textures
    
    ################################################################################
    ################################################################################
    ####                                                                        ####
    ####       aa    aaaaa   aa    aa    aa    aaaaa  a    a  aaaaa   aaaaaa    ####
    ####      a  a   a    a  a a  a a   a  a     a    a    a  a    a  a         ####
    ####     a    a  a    a  a  aa  a  a    a    a    a    a  a    a  a         ####
    ####     aaaaaa  aaaaa   a  aa  a  aaaaaa    a    a    a  aaaaa   aaaaa     ####
    ####     a    a  a    a  a      a  a    a    a    a    a  a    a  a         ####
    ####     a    a  a    a  a      a  a    a    a    a    a  a    a  a         ####
    ####     a    a  a    a  a      a  a    a    a     aaaa   a    a  aaaaaa    ####
    ####                                                                        ####
    ################################################################################
    ################################################################################ 
        
            
    def CreateEmptys&#40;BonesDic&#41;:
        scn = Scene.GetCurrent&#40;&#41;
        for bone in BonesDic.keys&#40;&#41;:
            obj = Object.New&#40;'Empty',bone&#41;
            scn.objects.link&#40;obj&#41;
            
        for bone in BonesDic.keys&#40;&#41;:
            if BonesDic&#91;bone&#93;.has_key&#40;'parent'&#41;:
                Parent = Object.Get&#40;BonesDic&#91;bone&#93;&#91;'parent'&#93;&#41;
                object = Object.Get&#40;bone&#41;
                Parent.makeParent&#40;&#91;object&#93;&#41;
            
        for bone in BonesDic.keys&#40;&#41;:
            obj = Object.Get&#40;bone&#41;
            rot = BonesDic&#91;bone&#93;&#91;'rotation'&#93;
            loc = BonesDic&#91;bone&#93;&#91;'position'&#93;
            euler = Mathutils.RotationMatrix&#40;math.degrees&#40;rot&#91;3&#93;&#41;,3,'r',Mathutils.Vector&#40;rot&#91;0&#93;,-rot&#91;2&#93;,rot&#91;1&#93;&#41;&#41;.toEuler&#40;&#41;
            obj.setLocation&#40;loc&#91;0&#93;,-loc&#91;2&#93;,loc&#91;1&#93;&#41;
            obj.setEuler&#40;math.radians&#40;euler&#91;0&#93;&#41;,math.radians&#40;euler&#91;1&#93;&#41;,math.radians&#40;euler&#91;2&#93;&#41;&#41;
        Redraw&#40;&#41;
        
        for bone in BonesDic.keys&#40;&#41;:
            obj = Object.Get&#40;bone&#41;
            rotmatAS = obj.getMatrix&#40;&#41;.rotationPart&#40;&#41;
            BonesDic&#91;bone&#93;&#91;'rotmatAS'&#93; = rotmatAS
            
        
        for bone in BonesDic.keys&#40;&#41;:
            obj = Object.Get&#40;bone&#41;
            scn.objects.unlink&#40;obj&#41;
            del obj
        
        
    def VectorSum&#40;vec1,vec2&#41;:
        vecout = &#91;0,0,0&#93;
        vecout&#91;0&#93; = vec1&#91;0&#93;+vec2&#91;0&#93;
        vecout&#91;1&#93; = vec1&#91;1&#93;+vec2&#91;1&#93;
        vecout&#91;2&#93; = vec1&#91;2&#93;+vec2&#91;2&#93;
        
        return vecout
    
    #### Building the OGRE Bones Dictionary
    def OGREBonesDic&#40;xmldoc&#41;:
        OGRE_Bones = {}
        for bones in xmldoc.getElementsByTagName&#40;'bones'&#41;:
        
            for bone in bones.childNodes:
                OGRE_Bone = {}
                if bone.localName == 'bone':
                    BoneName = str&#40;bone.getAttributeNode&#40;'name'&#41;.value&#41;
                    BoneID = int&#40;bone.getAttributeNode&#40;'id'&#41;.value&#41;
                    OGRE_Bone&#91;'name'&#93; = BoneName
                    OGRE_Bone&#91;'id'&#93; = BoneID
                                
                    for b in bone.childNodes:
                        if b.localName == 'position':
                            x = float&#40;b.getAttributeNode&#40;'x'&#41;.value&#41;
                            y = float&#40;b.getAttributeNode&#40;'y'&#41;.value&#41;
                            z = float&#40;b.getAttributeNode&#40;'z'&#41;.value&#41;
                            OGRE_Bone&#91;'position'&#93; = &#91;x,y,z&#93;
                        if b.localName == 'rotation':
                            angle = float&#40;b.getAttributeNode&#40;'angle'&#41;.value&#41;
                            axis = b.childNodes&#91;1&#93;
                            axisx = float&#40;axis.getAttributeNode&#40;'x'&#41;.value&#41;
                            axisy = float&#40;axis.getAttributeNode&#40;'y'&#41;.value&#41;
                            axisz = float&#40;axis.getAttributeNode&#40;'z'&#41;.value&#41;
                            OGRE_Bone&#91;'rotation'&#93; = &#91;axisx,axisy,axisz,angle&#93;
                    
                    OGRE_Bones&#91;BoneName&#93; = OGRE_Bone
                        
        for bonehierarchy in xmldoc.getElementsByTagName&#40;'bonehierarchy'&#41;:
            for boneparent in bonehierarchy.childNodes:
                if boneparent.localName == 'boneparent':
                    Bone = str&#40;boneparent.getAttributeNode&#40;'bone'&#41;.value&#41;
                    Parent = str&#40;boneparent.getAttributeNode&#40;'parent'&#41;.value&#41;
                    OGRE_Bones&#91;Bone&#93;&#91;'parent'&#93; = Parent
            
        return OGRE_Bones
    
    #### Add Bones Armature Head Positions to OGRE Bones Dictionary
    def SetBonesASPositions&#40;BonesData&#41;:
        
        for key in BonesData.keys&#40;&#41;:
            
            start = 0       
            thisbone = key
            posh = BonesData&#91;key&#93;&#91;'position'&#93;
            
            while start == 0:
                if BonesData&#91;thisbone&#93;.has_key&#40;'parent'&#41;:
                    parentbone = BonesData&#91;thisbone&#93;&#91;'parent'&#93;
                    prot = BonesData&#91;parentbone&#93;&#91;'rotation'&#93;
                    ppos = BonesData&#91;parentbone&#93;&#91;'position'&#93;            
                    
                    protmat = Mathutils.RotationMatrix&#40;math.degrees&#40;prot&#91;3&#93;&#41;,3,'r',Mathutils.Vector&#40;prot&#91;0&#93;,prot&#91;1&#93;,prot&#91;2&#93;&#41;&#41;.invert&#40;&#41;
                    
                    newposh = protmat * Mathutils.Vector&#40;posh&#91;0&#93;,posh&#91;1&#93;,posh&#91;2&#93;&#41;
                    
                    positionh = VectorSum&#40;ppos,newposh&#41;
                
                    posh = positionh
                    
                    thisbone = parentbone
                else:
                    start = 1
            
            BonesData&#91;key&#93;&#91;'posHAS'&#93; = posh
            
    #### Add list of direct bones to each bone in the OGRE Bones Dictionary
    def ChildList&#40;BonesData&#41;:
        for bone in BonesData.keys&#40;&#41;:
            childlist = &#91;&#93;
            for key in BonesData.keys&#40;&#41;:
                if BonesData&#91;key&#93;.has_key&#40;'parent'&#41;:
                    parent = BonesData&#91;key&#93;&#91;'parent'&#93;
                    if parent == bone:
                        childlist.append&#40;key&#41;
            BonesData&#91;bone&#93;&#91;'children'&#93; = childlist
            
    #### Add some Helper Bones
    def HelperBones&#40;BonesData&#41;:
        ChildList&#40;BonesData&#41;
        count = 0
        for bone in BonesData.keys&#40;&#41;:
            if &#40;len&#40;BonesData&#91;bone&#93;&#91;'children'&#93;&#41; == 0&#41; or &#40;len&#40;BonesData&#91;bone&#93;&#91;'children'&#93;&#41; &gt; 1&#41;:
                HelperBone = {}
                HelperBone&#91;'position'&#93; = &#91;0.2,0.0,0.0&#93;
                HelperBone&#91;'parent'&#93; = bone
                HelperBone&#91;'rotation'&#93; = &#91;1.0,0.0,0.0,0.0&#93;
                HelperBone&#91;'flag'&#93; = 'helper'
                BonesData&#91;'Helper'+str&#40;count&#41;&#93; = HelperBone
                count+=1
                
    #### Add some Helper Bones for Zero sizedBones
    def ZeroBones&#40;BonesData&#41;:
        for bone in BonesData.keys&#40;&#41;:
            pos = BonesData&#91;bone&#93;&#91;'position'&#93;
            if &#40;math.sqrt&#40;pos&#91;0&#93;**2+pos&#91;1&#93;**2+pos&#91;2&#93;**2&#41;&#41; == 0:
                ZeroBone = {}
                ZeroBone&#91;'position'&#93; = &#91;0.2,0.0,0.0&#93;
                ZeroBone&#91;'rotation'&#93; = &#91;1.0,0.0,0.0,0.0&#93;
                ZeroBone&#91;'parent'&#93; = BonesData&#91;bone&#93;&#91;'parent'&#93;
                ZeroBone&#91;'flag'&#93; = 'zerobone'
                BonesData&#91;'Zero'+bone&#93; = ZeroBone
                BonesData&#91;BonesData&#91;bone&#93;&#91;'parent'&#93;&#93;&#91;'children'&#93;.append&#40;'Zero'+bone&#41;
    
    def CalcBoneLength&#40;vec&#41;:
        return math.sqrt&#40;vec&#91;0&#93;**2+vec&#91;1&#93;**2+vec&#91;2&#93;**2&#41;
    
        
    #### Create the Blender Armature                        
    def CreateBindSkeleton&#40;xmldoc,skname&#41;:
        
        global BonesData
        
        BonesDic = OGREBonesDic&#40;xmldoc&#41;
        HelperBones&#40;BonesDic&#41;
        ZeroBones&#40;BonesDic&#41;
        CreateEmptys&#40;BonesDic&#41;
        SetBonesASPositions&#40;BonesDic&#41;
    
        BonesData = BonesDic
        
        scn = Scene.GetCurrent&#40;&#41;
    
        obj = Object.New&#40;'Armature',skname&#41;
        arm = Armature.New&#40;skname&#41;
        obj.link&#40;arm&#41;
        scn.link&#40;obj&#41;
        arm_mat = obj.matrixWorld.rotationPart&#40;&#41;
        for bone in BonesDic.keys&#40;&#41;:
            arm.makeEditable&#40;&#41;
            eb = Armature.Editbone&#40;&#41;
            headPos = BonesDic&#91;bone&#93;&#91;'posHAS'&#93;
            if BonesDic&#91;bone&#93;.has_key&#40;'children'&#41;:
                childlist = BonesDic&#91;bone&#93;&#91;'children'&#93;
                if len&#40;childlist&#41; == 1:
                    childname = childlist&#91;0&#93;
                    vectailadd = CalcBoneLength&#40;BonesDic&#91;childname&#93;&#91;'position'&#93;&#41;
                    
                else:
                    vectailadd = 0.2
            else:
                vectailadd = 0.2            
            
            vechead = Mathutils.Vector&#40;headPos&#91;0&#93;,-headPos&#91;2&#93;,headPos&#91;1&#93;&#41;
            vectail = Mathutils.Vector&#40;headPos&#91;0&#93;,-headPos&#91;2&#93;,headPos&#91;1&#93;+vectailadd&#41;
            eb.head = vechead
            eb.tail = vectail
            rotmat = BonesDic&#91;bone&#93;&#91;'rotmatAS'&#93;
            newrotmat = Mathutils.Matrix&#40;rotmat&#91;1&#93;,rotmat&#91;0&#93;,rotmat&#91;2&#93;&#41;
            
            eb.matrix = newrotmat       
            
            arm.bones&#91;bone&#93; = eb
            arm.update&#40;&#41;    
        
        for bone in BonesDic.keys&#40;&#41;:
            arm.makeEditable&#40;&#41;
            if BonesDic&#91;bone&#93;.has_key&#40;'parent'&#41;:
                parent = BonesDic&#91;bone&#93;&#91;'parent'&#93;
                arm.bones&#91;bone&#93;.parent = arm.bones&#91;parent&#93;
            arm.update&#40;&#41;
        
        for bone in arm.bones.keys&#40;&#41;:
            if BonesDic&#91;bone&#93;.has_key&#40;'flag'&#41;:
                arm.makeEditable&#40;&#41;
                del arm.bones&#91;bone&#93;
                arm.update&#40;&#41;
        
        
        Redraw&#40;&#41;
    
    def CreateRestPoseAction&#40;xmldoc,skname&#41;:
        
        BonesDic = OGREBonesDic&#40;xmldoc&#41;
        armature = Object.Get&#40;skname&#41;
        pose = armature.getPose&#40;&#41;
        newAction = Armature.NLA.NewAction&#40;'RestPose'&#41;
        newAction.setActive&#40;armature&#41;
        
        for bone in BonesDic.keys&#40;&#41;:
            pbone = pose.bones&#91;bone&#93;
            pbone.quat = Mathutils.Quaternion&#40;Mathutils.Vector&#40;1,0,0&#41;,0&#41;
            pbone.insertKey&#40;armature,1,Object.Pose.ROT&#41;
            pbone.insertKey&#40;armature,10,Object.Pose.ROT&#41;
            pbone.loc = Mathutils.Vector&#40;0,0,0&#41;
            pbone.insertKey&#40;armature,1,Object.Pose.LOC&#41;
            pbone.insertKey&#40;armature,10,Object.Pose.LOC&#41;
        
    #
    # ANIMATION
    #
        
    def ParseAnimationSkeleton&#40;xmldoc&#41;:
        OGRE_Bones = {}
            
        for bones in xmldoc.getElementsByTagName&#40;'bones'&#41;:
        
            for bone in bones.childNodes:
                OGRE_Bone = {}
                if bone.localName == 'bone':
                    BoneName = str&#40;bone.getAttributeNode&#40;'name'&#41;.value&#41;
                    BoneID = int&#40;bone.getAttributeNode&#40;'id'&#41;.value&#41;
                    OGRE_Bone&#91;'name'&#93; = BoneName
                    OGRE_Bone&#91;'id'&#93; = BoneID
                                
                    for b in bone.childNodes:
                        if b.localName == 'position':
                            x = float&#40;b.getAttributeNode&#40;'x'&#41;.value&#41;
                            y = float&#40;b.getAttributeNode&#40;'y'&#41;.value&#41;
                            z = float&#40;b.getAttributeNode&#40;'z'&#41;.value&#41;
                            OGRE_Bone&#91;'position'&#93; = &#91;x,y,z&#93;
                        if b.localName == 'rotation':
                            angle = float&#40;b.getAttributeNode&#40;'angle'&#41;.value&#41;
                            axis = b.childNodes&#91;1&#93;
                            axisx = float&#40;axis.getAttributeNode&#40;'x'&#41;.value&#41;
                            axisy = float&#40;axis.getAttributeNode&#40;'y'&#41;.value&#41;
                            axisz = float&#40;axis.getAttributeNode&#40;'z'&#41;.value&#41;
                            OGRE_Bone&#91;'rotation'&#93; = &#91;axisx,axisy,axisz,angle&#93;
                    
                    OGRE_Bones&#91;BoneName&#93; = OGRE_Bone
                        
        for bonehierarchy in xmldoc.getElementsByTagName&#40;'bonehierarchy'&#41;:
            for boneparent in bonehierarchy.childNodes:
                if boneparent.localName == 'boneparent':
                    Bone = str&#40;boneparent.getAttributeNode&#40;'bone'&#41;.value&#41;
                    Parent = str&#40;boneparent.getAttributeNode&#40;'parent'&#41;.value&#41;
                    OGRE_Bones&#91;Bone&#93;&#91;'parent'&#93; = Parent
            
        return OGRE_Bones
    
    def ParseActionAnimations&#40;xmldoc&#41;:
        
        Animations = {}
            
        for animations in xmldoc.getElementsByTagName&#40;'animations'&#41;:
            for animation in animations.getElementsByTagName&#40;'animation'&#41;:
                aniname = str&#40;animation.getAttributeNode&#40;'name'&#41;.value&#41;
                print aniname
                Track = {}
                for tracks in animation.getElementsByTagName&#40;'tracks'&#41;:
                    for track in tracks.getElementsByTagName&#40;'track'&#41;:
                        trackname = str&#40;track.getAttributeNode&#40;'bone'&#41;.value&#41;
                        Track&#91;trackname&#93; = &#91;&#93;
                        for keyframes in track.getElementsByTagName&#40;'keyframes'&#41;:
                            for keyframe in keyframes.getElementsByTagName&#40;'keyframe'&#41;:
                                time = float&#40;keyframe.getAttributeNode&#40;'time'&#41;.value&#41;
                                for translate in keyframe.getElementsByTagName&#40;'translate'&#41;:
                                    x = float&#40;translate.getAttributeNode&#40;'x'&#41;.value&#41;
                                    y = float&#40;translate.getAttributeNode&#40;'y'&#41;.value&#41;
                                    z = float&#40;translate.getAttributeNode&#40;'z'&#41;.value&#41;
                                    translate = &#91;x,y,z&#93;
                                for rotate in keyframe.getElementsByTagName&#40;'rotate'&#41;:
                                    angle = float&#40;rotate.getAttributeNode&#40;'angle'&#41;.value&#41;
                                    for axis in rotate.getElementsByTagName&#40;'axis'&#41;:
                                        rx = float&#40;axis.getAttributeNode&#40;'x'&#41;.value&#41;
                                        ry = float&#40;axis.getAttributeNode&#40;'y'&#41;.value&#41;
                                        rz = float&#40;axis.getAttributeNode&#40;'z'&#41;.value&#41;
                                    rotation = &#91;rx,ry,rz,angle&#93;
                                Track&#91;trackname&#93;.append&#40;&#91;time,translate,rotation&#93;&#41;
                    Animations&#91;aniname&#93; = Track
    
        return Animations
    
    #### Writing the Actions
    
    def CreateActions&#40;ActionsDic,armaturename,BonesDic&#41;:
        
        global BonesData
        
        armature = Object.Get&#40;armaturename&#41;
        pose = armature.getPose&#40;&#41;
        restpose = armature.getData&#40;&#41;
            
        for Action in ActionsDic.keys&#40;&#41;:
            newAction = Armature.NLA.NewAction&#40;Action&#41;
            newAction.setActive&#40;armature&#41;
            isActionData = False
            
            for track in ActionsDic&#91;Action&#93;.keys&#40;&#41;:
                isActionData = True
                rpbone = restpose.bones&#91;track&#93;          
                rpbonequat = rpbone.matrix&#91;'BONESPACE'&#93;.rotationPart&#40;&#41;.toQuat&#40;&#41;         
                rpbonetrans = Mathutils.Vector&#40;BonesData&#91;track&#93;&#91;'position'&#93;&#91;2&#93;, BonesData&#91;track&#93;&#91;'position'&#93;&#91;0&#93;, BonesData&#91;track&#93;&#91;'position'&#93;&#91;1&#93;&#41;
                sprot = BonesDic&#91;track&#93;&#91;'rotation'&#93;
                sploc = BonesDic&#91;track&#93;&#91;'position'&#93;
                spbonequat = Mathutils.Quaternion&#40;Mathutils.Vector&#40;sprot&#91;2&#93;,sprot&#91;0&#93;,sprot&#91;1&#93;&#41;,math.degrees&#40;sprot&#91;3&#93;&#41;&#41;
                spbonetrans = Mathutils.Vector&#40;sploc&#91;2&#93;, sploc&#91;0&#93;, sploc&#91;1&#93;&#41;
                quatdiff = Mathutils.DifferenceQuats&#40;rpbonequat,spbonequat&#41;.toMatrix&#40;&#41;
                transdiff = spbonetrans - rpbonetrans           
                pbone = pose.bones&#91;track&#93;
                lastTime = ActionsDic&#91;Action&#93;&#91;track&#93;&#91;len&#40;ActionsDic&#91;Action&#93;&#91;track&#93;&#41;-1&#93;&#91;0&#93;
                
                for kfrs in ActionsDic&#91;Action&#93;&#91;track&#93;:
                    
                    frame = int&#40;1+&#40;kfrs&#91;0&#93;*25&#41;&#41;             
                    # in ActionDic = &#91;animation&#93;&#91;bone&#93;{&#91;time&#93;, &#91;locX, locY, locZ&#93;, &#91;rotX, rotY, rotZ, rotAngle&#93;}
                    quataction = Mathutils.Quaternion&#40;Mathutils.Vector&#40;kfrs&#91;2&#93;&#91;2&#93;,kfrs&#91;2&#93;&#91;0&#93;,kfrs&#91;2&#93;&#91;1&#93;&#41;,math.degrees&#40;kfrs&#91;2&#93;&#91;3&#93;&#41;&#41;.toMatrix&#40;&#41;
                    
                    quat = &#40;quataction*quatdiff&#41;.toQuat&#40;&#41;           
                    pbone.quat = quat
                    pbone.insertKey&#40;armature,frame,Object.Pose.ROT&#41;         
                    pbone.loc = Mathutils.Vector&#40;kfrs&#91;1&#93;&#91;2&#93;,kfrs&#91;1&#93;&#91;0&#93;,kfrs&#91;1&#93;&#91;1&#93;&#41; + transdiff
                    pbone.insertKey&#40;armature,frame,Object.Pose.LOC&#41;
            # only if there are actions     
            if&#40;isActionData&#41;:       
                ChannelIPOS = newAction.getAllChannelIpos&#40;&#41;
                for bone in BonesDic.keys&#40;&#41;:
                    if not bone in ChannelIPOS.keys&#40;&#41; and not bone == 'root':
                        rpbonequat = restpose.bones&#91;bone&#93;.matrix&#91;'BONESPACE'&#93;.rotationPart&#40;&#41;.toQuat&#40;&#41;
                        sprot = BonesDic&#91;bone&#93;&#91;'rotation'&#93;
                        spbonequat = Mathutils.Quaternion&#40;Mathutils.Vector&#40;sprot&#91;2&#93;,sprot&#91;0&#93;,sprot&#91;1&#93;&#41;,math.degrees&#40;sprot&#91;3&#93;&#41;&#41;
                        quatdiff = Mathutils.DifferenceQuats&#40;rpbonequat,spbonequat&#41;
                        pbone = pose.bones&#91;bone&#93;
                        pbone.quat = quatdiff
                        pbone.insertKey&#40;armature,1,Object.Pose.ROT&#41;
                        pbone.insertKey&#40;armature,int&#40;1+lastTime*25&#41;,Object.Pose.ROT&#41;
    
    def CreateSkeleton&#40;xml_doc, folder, name&#41;:
    
        global has_skeleton
    
        scene = bpy.data.scenes.active
        
        if&#40;len&#40;xml_doc.getElementsByTagName&#40;"skeletonlink"&#41;&#41; &gt; 0&#41;:
            # get the skeleton link of the mesh
            skeleton_link = xml_doc.getElementsByTagName&#40;"skeletonlink"&#41;&#91;0&#93;
            filename = os.path.join&#40;folder, skeleton_link.getAttribute&#40;"name"&#41;&#41;
            skel_xml_doc = OpenFile&#40;filename + ".xml"&#41;  
            if skel_xml_doc == "None":
                print "%s file not found!" % filename
                return
        else:
            return
    
        has_skeleton = True
        
        CreateBindSkeleton&#40;skel_xml_doc, name&#41;  
        CreateRestPoseAction&#40;skel_xml_doc, name&#41;
        OGREBoneIDsDic&#40;skel_xml_doc&#41;
        
        
    def CreateMesh&#40;xml_doc, folder, name, materialFile&#41;:
    
        textures = 'None'
        if&#40;materialFile != "None"&#41;:
            textures = GetTextures&#40;materialFile, folder&#41;
    
        allObjs = CreateMeshes&#40;xml_doc, name, textures, folder&#41;
        
        scene = bpy.data.scenes.active      
        mesh = bpy.data.meshes.new&#40;name&#41;            
        obj = scene.objects.new&#40;mesh, str&#40;name + "Mesh"&#41;&#41;
        obj.join&#40;allObjs&#41;           
        # remove submeshes
        for ob in allObjs:
            scene.objects.unlink&#40;ob&#41;
                
        if has_skeleton:
            # parent mesh to armature with deform properties
            Object.Get&#40;name&#41;.makeParentDeform&#40;&#91;obj&#93;, 0, 0&#41;
        
        
    def AddAnimation&#40;xml_doc, name&#41;:
        # get skeleton in rest pose and action animation
        BonesDic = ParseAnimationSkeleton&#40;xml_doc&#41;
        Actions = ParseActionAnimations&#40;xml_doc&#41;
    
        CreateActions&#40;Actions, name, BonesDic&#41;  
        
    def ImportOgre&#40;path&#41;:
    
        global has_skeleton
    
        Window.WaitCursor&#40;1&#41;
        folder = os.path.split&#40;path&#41;&#91;0&#93;
        name = "mesh"
        files = &#91;&#93;
        materialFile = "None"
    
        if not DEBUG:   
            # convert MESH and SKELETON file to MESH.XML and SKELETON.XML respectively
            for filename in os.listdir&#40;folder&#41;:
                # we're going to do string comparisons. assume lower case to simplify code
                filename = os.path.join&#40;folder, filename.lower&#40;&#41;&#41;
                # process .mesh and .skeleton files while skipping .xml files
                if &#40;&#40;".skeleton" in filename&#41; or &#40;".mesh" in filename&#41;&#41; and &#40;".xml" not in filename&#41;:
                    os.system&#40;'%s "%s"' % &#40;ogreXMLConverter, filename&#41;&#41;
    
        # get all the filenames in the chosen directory, put in list and sort it
        for filename in os.listdir&#40;folder&#41;:
            # we're going to do string comparisons. assume lower case to simplify code
            filename = filename.lower&#40;&#41;
            # process .mesh and .skeleton files while skipping .xml files
            if ".skeleton.xml" in filename:
                files.append&#40;os.path.join&#40;folder, filename&#41;&#41;
            elif ".mesh.xml" in filename:
                # get the name of the MESH file without extension. Use this base name to name our imported object
                name = filename.split&#40;'.'&#41;&#91;0&#93;
                # to avoid Blender naming limit problems
                name = GetBlender249Name&#40;name&#41;
                # put MESH file on top of the file list
                files.insert&#40;0, os.path.join&#40;folder, filename&#41;&#41;
            elif ".material" in filename:
                # material file
                materialFile = os.path.join&#40;folder, filename&#41;
    
        # now that we have a list of files, process them
        filename = files&#91;0&#93;
        # import the mesh
        if &#40;".mesh" in filename&#41;:
            mesh_data = OpenFile&#40;filename&#41;
            if mesh_data != "None":
                CreateSkeleton&#40;mesh_data, folder, name&#41;
                CreateMesh&#40;mesh_data, folder, name, materialFile&#41;
                if not DEBUG:
                    # cleanup by deleting the XML file we created
                    os.unlink&#40;"%s" % filename&#41;
        
        if has_skeleton:
            # process all the files again. this time process skeleton files which must be processed after mesh file
            # the armature must already be created &#40;when mesh was processed&#41; prior to this.
            for filename in files:
                # import the skeleton file
                if &#40;".skeleton" in filename&#41; and &#40;str&#40;name + ".skeleton"&#41; not in filename&#41;:
                    skeleton_data = OpenFile&#40;filename&#41;
                    if skeleton_data != "None":
                        AddAnimation&#40;skeleton_data, name&#41;
                        if not DEBUG:
                            # cleanup by deleting the XML file we created
                            os.unlink&#40;"%s" % filename&#41;
    
        Window.WaitCursor&#40;0&#41;
        Window.RedrawAll&#40;&#41;
    
    
    
    print "\n\n\n"
    Window.FileSelector&#40;ImportOgre, "Import"&#41;
    
    Paladin Class: discussion thread, download page
    Shared Animations Library: discussion thread, download page
  • Hmmm, still having some problems.

    So here's what I did:
    - installed the new import script
    - ran Blender, tried importing Alchemist.mesh.xml (maybe I should have just imported alchemist.mesh?)
    - got an error message, fixed the GetBlender249Name code
    - successfully imported the Alchemist mesh + skeleton (although the imported mesh pieces were not located at the origin)
    - put the skeleton in pose mode, and got this:

    faswb9.jpg

    Not sure why it worked out this way. And oddly enough, when I put the skeleton into Pose Mode, it went into the bind position for the Vanquisher's Hamstring skill (and I don't have that file in my TorchED directory).

    I'll keep playing around with it, and experimenting with the old script to see where things may have gone wrong.
    Paladin Class: discussion thread, download page
    Shared Animations Library: discussion thread, download page
  • DushoDusho Posts: 988
    Hm.. that GetBlender249Name gave you error? The code I wrote there is like I meant it to have.. unless it give an error.. of course.
    You noticed that script snatches everything it finds in directory (all .skeleton files - no matter if they are referenced by .mesh or not, as there is no internal link between mesh and animation files).
    Alchemist is actually one model I haven't tried. Script worked well with destroyer, vanquisher, vanquisher_gunslinger_set, cave_flier, troll.
    Will check it when I get from work.
    And ye.. if you have path for your OgreConverter.exe put in script, you don't have to convert manually from .mesh to .mesh.xml.
    You tried to import original alchemist model, or you somehow tempered with skeleton files?
«1
Sign In or Register to comment.