In this tutorial I will be using XNALara Posing Studio (XPS) 11.4 by XNAaraL from http://xnalaraitalia.deviantart.com/ and replicate a pose from a Tomb Raider Level Editor (TRLE) wad animation.
The first step is to change the model’s bind pose in XNALara to match the “No animating” pose as shown in Wadmerger’s Animation Editor.
Usually this will mean changing the position of the arms so they are beside the model’s body and not in a T-pose.
For best results you must use an XNALara model that is similar to the TRLE model such as the “too_lara_1996” model included with XPS which I use here.
Position the arms beside the body.
Now go to the Modify menu and click Save Generic_Item and select “modify armature according to the current pose”.
Click OK and the confirm you want to modify the armature and then save the model into a new folder inside the “data” folder.
Reset the scene and load the model you just saved.
In Wadmerger’s Animation Editor select an animation and export it from the Animation menu as a .TRW file.
Open the animation in my TRW Editor program which displays the rotation values for all the meshes for each keyframe of the animation.
Now what you do is match a mesh to a bone in XPS, select that bone and enter –1 x RotX for the X axis value, –1 x RotY for the Y axis value and RotZ for the Z axis value.
The angles in TRW Editor are from 0 to 1024 representing 0 to 360 degrees but if you right click on an angle the value in degrees will be shown.
I matched the bones as follows.
- Mesh 0 – root hips
- Mesh 1 – leg left thigh
- Mesh 2 – leg left knee
- Mesh 3 – leg left ankle
- Mesh 4 – leg right thigh
- Mesh 5 – leg right knee
- Mesh 6 – leg right ankle
- Mesh 7 – spine lower
- Mesh 8 – arm right shoulder 2
- Mesh 9 – arm right elbow
- Mesh10 – arm right wrist
- Mesh11 – arm left shoulder 2
- Mesh12 – arm left elbow
- Mesh13 – arm left wrist
- Mesh14 – head neck upper
You can load the pose for other models to see how it looks but if the proportions of the model do not match the TRLE model the pose may not be perfect.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#python 2.5 | |
# copy this python file to your XNALara pose folder and run from there | |
# since pose files are written to the same folder the python file runs from. | |
# This script copies all keyframes of a TRW file to a pose file each. | |
#WARNING: will overwrite files of the same name | |
import os.path, sys | |
from tkFileDialog import askopenfilename as tkFileGet | |
import Tkinter as tk | |
import struct | |
bonenames =["root hips", | |
"leg left thigh", | |
"leg left knee", | |
"leg left ankle", | |
"leg right thigh", | |
"leg right knee" , | |
"leg right ankle", | |
"spine lower", | |
"arm right shoulder 2", | |
"arm right elbow" , | |
"arm right wrist", | |
"arm left shoulder 2", | |
"arm left elbow", | |
"arm left wrist", | |
"head neck upper"] | |
class AppData: | |
pass | |
class BBox: | |
def __init__(self): | |
self.x1,self.x2 = 0,0 | |
self.y1,self.y2 = 0,0 | |
self.z1,self.z2 = 0,0 | |
self.size = 12 | |
def clear(self): | |
self.x1,self.x2 = 0,0 | |
self.y1,self.y2 = 0,0 | |
self.z1,self.z2 = 0,0 | |
self.size = 12 | |
return | |
def __str__(self): | |
return "%i %i %i %i %i %i"%(self.x1,self.x2,self.y1,self.y2,self.z1,self.z2) | |
class Offset: | |
def __init__(self): | |
self.x = 0 | |
self.y = 0 | |
self.z = 0 | |
self.size = 6 | |
def __str__(self): | |
return "(%i %i %i)"%(self.x,self.y,self.z) | |
class Angle: | |
def __init__(self): | |
self.rotx = 0 | |
self.roty = 0 | |
self.rotz = 0 | |
self.size = self.calcSize() | |
def __str__(self): | |
return "(%4i %4i %4i)"%(self.rotx, self.roty,self.rotz) | |
def calcSize(self): | |
l = [self.rotx,self.roty,self.rotz] | |
if l.count(0) < 2: | |
size = 4 # 3 axis rotation | |
else: | |
size = 2 # single axis rotation | |
return size | |
def toDegrees(self): | |
return self.rotx*360.0/1024, self.roty*360.0/1024, self.rotz*360.0/1024 | |
class Keyframe: | |
def __init__(self): | |
self.id = None | |
self.bb = None | |
self.offset = None | |
self.nummeshes = None | |
self.angles = [] | |
self.size = 0 | |
class AnimCommand: | |
def __init__(self): | |
self.cmd = None | |
self.op1 = None | |
self.op2 = None | |
self.op3 = None | |
class TRW: | |
def __init__(self): | |
self.name = "" | |
self.version = None | |
self.keyoffset = None | |
self.frameduration = None | |
self.keyframesize = None | |
self.stateid = None | |
self.unknown1 = None | |
self.speed = None | |
self.acc = None | |
self.unknown2 = None | |
self.framestart = None | |
self.frameend = None | |
self.nextanim = None | |
self.frameIn = None | |
self.numStateChanges = None | |
self.changesIndex = None | |
self.numCommands = None | |
self.commandsOffset = None | |
self.ffff=-1 | |
self.numAnimCommands = None | |
self.animcommands = [] | |
self.keypackagesize = None | |
self.numFrames = None | |
self.numKeys = None | |
self.keys = [] | |
self.maxkeysize = 0 | |
self.nummeshes = 0 | |
def parseTRW(fn): | |
ok=False | |
trw = TRW() | |
print fn | |
f =open(fn,"rb") | |
trw.version = f.read(8) #190 0 5 0 ???? | |
trw.keyoffset, = struct.unpack('I',f.read(4)) | |
print 'keyoffset',trw.keyoffset | |
trw.frameduration, = struct.unpack('B',f.read(1)) | |
print 'frameduration', trw.frameduration | |
trw.keyframesize,=struct.unpack('B',f.read(1)) | |
print 'keyframesize %i words'%trw.keyframesize | |
trw.stateid, = struct.unpack('H',f.read(2)) | |
print 'stateid ',trw.stateid | |
trw.unknown1 = f.read(2) | |
trw.speed, = struct.unpack('h',f.read(2)) | |
print 'speed ',trw.speed | |
trw.acc,=struct.unpack('i',f.read(4)) | |
print 'acc ',trw.acc*1.0/65536 | |
trw.unknown2 = f.read(8) | |
trw.framestart,=struct.unpack('H',f.read(2)) | |
print 'framestart ',trw.framestart | |
trw.frameend, = struct.unpack('H',f.read(2)) | |
print 'frameend ',trw.frameend | |
trw.nextanim,=struct.unpack('h',f.read(2)) #offset | |
print 'nextanim (offset)',trw.nextanim | |
trw.frameIn,=struct.unpack('H',f.read(2)) | |
print 'frameIn ',trw.frameIn | |
trw.numStateChanges,=struct.unpack('H',f.read(2)) | |
print 'numStateChanges ',trw.numStateChanges | |
trw.changesIndex,=struct.unpack('H',f.read(2)) | |
print 'changesIndex ',trw.changesIndex | |
trw.numCommands, = struct.unpack('H',f.read(2)) | |
print 'numCommands ',trw.numCommands | |
trw.commandsOffset, = struct.unpack('H',f.read(2)) | |
print 'commandsOffset ', trw.commandsOffset | |
trw.ffff = f.read(4) #0xFFFF marker??? | |
trw.numAnimCommands, = struct.unpack('H',f.read(2)) | |
print 'numAnimCommands ',trw.numAnimCommands | |
for i in range(trw.numAnimCommands): | |
ac = AnimCommand() | |
ac.cmd, = struct.unpack('H',f.read(2)) | |
ac.op1,ac.op2,ac.op3=struct.unpack('hhh',f.read(6)) | |
trw.animcommands.append(ac) | |
print '\tanimcommand %i %i %i %i'%(ac.cmd,ac.op1,ac.op2,ac.op3) | |
trw.keypackagesize, = struct.unpack('I',f.read(4)) | |
print 'keypackagesize is %i words'%trw.keypackagesize | |
trw.numFrames, = struct.unpack('I',f.read(4)) | |
print 'numFrames ',trw.numFrames | |
trw.numKeys, = struct.unpack('I',f.read(4)) | |
print 'numKeys ',trw.numKeys | |
''' | |
test = keypackagesize/keyframesize | |
if numKeys != test: | |
tkMessageBox.showinfo('Error.','Error with numkeys.') | |
numKeys = test | |
''' | |
keysizelist=[] | |
keys = [] | |
for i in range(trw.numKeys): | |
key = Keyframe() | |
#print '-'*20 | |
key.id = i | |
#print 'key %i'%i | |
bb = BBox() | |
bb.x1,bb.x2,bb.y1,bb.y2,bb.z1,bb.z2 = struct.unpack('6h',f.read(12)) | |
key.bb = bb | |
key.size += bb.size | |
off = Offset() | |
off.x,off.y,off.z = struct.unpack('3h',f.read(6)) | |
key.offset = off | |
key.size += off.size # should equal 18 | |
#print 'bb: %i %i %i %i %i %i offset: %i %i %i'%(bb.x1,bb.x2,bb.y1,bb.y2,bb.z1,bb.z2,off.x,off.y,off.z) | |
key.nummeshes, = struct.unpack('H',f.read(2)) | |
trw.nummeshes = key.nummeshes | |
for i in range(key.nummeshes): | |
ang = Angle() | |
ang.rotx,ang.roty,ang.rotz = struct.unpack('HHH',f.read(6)) #360 degrees = 1024 | |
rx,ry,rz = convertAngle(ang.rotx,ang.roty,ang.rotz) | |
#print 'Mesh#%i %i %i %i (%f %f %f)'%(i,ang.rotx,ang.roty,ang.rotz,rx,ry,rz) | |
key.size += ang.calcSize() | |
key.angles.append(ang) | |
#size += getKeySize(rotx,roty,rotz) | |
#print 'size in wad: %i bytes'%key.size | |
keys.append(key) | |
keysizelist.append(key.size) | |
f.close() | |
trw.keys = keys | |
trw.maxkeysize = max(keysizelist) | |
if trw.maxkeysize%2 != 0: | |
print 'Error calculating keysize. Not multiple of 2' | |
return | |
else: | |
trw.maxkeysize /=2 | |
print 'maxkeysize %i words'%trw.maxkeysize | |
AppData.trw = trw | |
ok=True | |
return ok | |
def getKeySize(rx,ry,rz): | |
list = [rx,ry,rz] | |
if list.count(0) < 2: | |
size = 4 # 3 axis rotation | |
else: | |
size = 2 # single axis rotation | |
return size | |
def convertAngle(rx,ry,rz): | |
return rx*360.0/1024,ry*360.0/1024,rz*360.0/1024 | |
def openFile(): | |
fn = tkFileGet(title='Select a file', | |
filetypes=[('Wadmerger .trw','*.TRW'),('All Files','*.*')]) | |
if fn: | |
if fn[-4:].lower() in ['.trw']: | |
print "Testing %s"%fn | |
if not parseTRW(fn): | |
print 'Error reading file' | |
return False | |
else: | |
return True | |
else: | |
print fn+' selected\n\nNeed .TRW file.' | |
return False | |
else: | |
print 'No file selected.' | |
return False | |
if __name__=='__main__': | |
if openFile(): | |
for kf in range(AppData.trw.numKeys): | |
keyframe = AppData.trw.keys[kf] | |
print "Writing pose file" | |
f = open("keyframe%i.pose"%kf,"w") | |
for k in range(keyframe.nummeshes): | |
ang = keyframe.angles[k] | |
rx,ry,rz = convertAngle(ang.rotx,ang.roty,ang.rotz) | |
f.write("%s: %.6f %.6f %.6f 0 0 0\n"%(bonenames[k],-rx,-ry,rz)) | |
f.close() | |
print "Done" |
Karlito modified the script to include root translation and to use filenames suitable for XPS animation. [link]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#python 2.5 | |
# copy this python file to your XNALara pose folder and run from there | |
# since pose files are written to the same folder the python file runs from. | |
# This script copies all keyframes of a TRW file to a pose file each. | |
#WARNING: will overwrite files of the same name | |
import os, shutil | |
import os.path, sys | |
from tkFileDialog import askopenfilename as tkFileGet | |
import Tkinter as tk | |
import struct | |
bonenames =["root hips", | |
"leg left thigh", | |
"leg left knee", | |
"leg left ankle", | |
"leg right thigh", | |
"leg right knee" , | |
"leg right ankle", | |
"spine lower", | |
"arm right shoulder 2", | |
"arm right elbow" , | |
"arm right wrist", | |
"arm left shoulder 2", | |
"arm left elbow", | |
"arm left wrist", | |
"head neck upper"] | |
filename = "default" | |
bb0 = [] # y offset value for root hips | |
bb1 = [] # y offset value for root hips | |
bb2 = [] # z offset value for root hips | |
class AppData: | |
pass | |
class BBox: | |
def __init__(self): | |
self.x1,self.x2 = 0,0 | |
self.y1,self.y2 = 0,0 | |
self.z1,self.z2 = 0,0 | |
self.size = 12 | |
def clear(self): | |
self.x1,self.x2 = 0,0 | |
self.y1,self.y2 = 0,0 | |
self.z1,self.z2 = 0,0 | |
self.size = 12 | |
return | |
def __str__(self): | |
return "%i %i %i %i %i %i"%(self.x1,self.x2,self.y1,self.y2,self.z1,self.z2) | |
class Offset: | |
def __init__(self): | |
self.x = 0 | |
self.y = 0 | |
self.z = 0 | |
self.size = 6 | |
def __str__(self): | |
return "(%i %i %i)"%(self.x,self.y,self.z) | |
class Angle: | |
def __init__(self): | |
self.rotx = 0 | |
self.roty = 0 | |
self.rotz = 0 | |
self.size = self.calcSize() | |
def __str__(self): | |
return "(%4i %4i %4i)"%(self.rotx, self.roty,self.rotz) | |
def calcSize(self): | |
l = [self.rotx,self.roty,self.rotz] | |
if l.count(0) < 2: | |
size = 4 # 3 axis rotation | |
else: | |
size = 2 # single axis rotation | |
return size | |
def toDegrees(self): | |
return self.rotx*360.0/1024, self.roty*360.0/1024, self.rotz*360.0/1024 | |
class Keyframe: | |
def __init__(self): | |
self.id = None | |
self.bb = None | |
self.offset = None | |
self.nummeshes = None | |
self.angles = [] | |
self.size = 0 | |
class AnimCommand: | |
def __init__(self): | |
self.cmd = None | |
self.op1 = None | |
self.op2 = None | |
self.op3 = None | |
class TRW: | |
def __init__(self): | |
self.name = "" | |
self.version = None | |
self.keyoffset = None | |
self.frameduration = None | |
self.keyframesize = None | |
self.stateid = None | |
self.unknown1 = None | |
self.speed = None | |
self.acc = None | |
self.unknown2 = None | |
self.framestart = None | |
self.frameend = None | |
self.nextanim = None | |
self.frameIn = None | |
self.numStateChanges = None | |
self.changesIndex = None | |
self.numCommands = None | |
self.commandsOffset = None | |
self.ffff=-1 | |
self.numAnimCommands = None | |
self.animcommands = [] | |
self.keypackagesize = None | |
self.numFrames = None | |
self.numKeys = None | |
self.keys = [] | |
self.maxkeysize = 0 | |
self.nummeshes = 0 | |
def parseTRW(fn): | |
ok=False | |
trw = TRW() | |
print fn | |
f =open(fn,"rb") | |
trw.version = f.read(8) #190 0 5 0 ???? | |
trw.keyoffset, = struct.unpack('I',f.read(4)) | |
print 'keyoffset',trw.keyoffset | |
trw.frameduration, = struct.unpack('B',f.read(1)) | |
print 'frameduration', trw.frameduration | |
trw.keyframesize,=struct.unpack('B',f.read(1)) | |
print 'keyframesize %i words'%trw.keyframesize | |
trw.stateid, = struct.unpack('H',f.read(2)) | |
print 'stateid ',trw.stateid | |
trw.unknown1 = f.read(2) | |
trw.speed, = struct.unpack('h',f.read(2)) | |
print 'speed ',trw.speed | |
trw.acc,=struct.unpack('i',f.read(4)) | |
print 'acc ',trw.acc*1.0/65536 | |
trw.unknown2 = f.read(8) | |
trw.framestart,=struct.unpack('H',f.read(2)) | |
print 'framestart ',trw.framestart | |
trw.frameend, = struct.unpack('H',f.read(2)) | |
print 'frameend ',trw.frameend | |
trw.nextanim,=struct.unpack('h',f.read(2)) #offset | |
print 'nextanim (offset)',trw.nextanim | |
trw.frameIn,=struct.unpack('H',f.read(2)) | |
print 'frameIn ',trw.frameIn | |
trw.numStateChanges,=struct.unpack('H',f.read(2)) | |
print 'numStateChanges ',trw.numStateChanges | |
trw.changesIndex,=struct.unpack('H',f.read(2)) | |
print 'changesIndex ',trw.changesIndex | |
trw.numCommands, = struct.unpack('H',f.read(2)) | |
print 'numCommands ',trw.numCommands | |
trw.commandsOffset, = struct.unpack('H',f.read(2)) | |
print 'commandsOffset ', trw.commandsOffset | |
trw.ffff = f.read(4) #0xFFFF marker??? | |
trw.numAnimCommands, = struct.unpack('H',f.read(2)) | |
print 'numAnimCommands ',trw.numAnimCommands | |
for i in range(trw.numAnimCommands): | |
ac = AnimCommand() | |
ac.cmd, = struct.unpack('H',f.read(2)) | |
ac.op1,ac.op2,ac.op3=struct.unpack('hhh',f.read(6)) | |
trw.animcommands.append(ac) | |
print '\tanimcommand %i %i %i %i'%(ac.cmd,ac.op1,ac.op2,ac.op3) | |
trw.keypackagesize, = struct.unpack('I',f.read(4)) | |
print 'keypackagesize is %i words'%trw.keypackagesize | |
trw.numFrames, = struct.unpack('I',f.read(4)) | |
print 'numFrames ',trw.numFrames | |
trw.numKeys, = struct.unpack('I',f.read(4)) | |
print 'numKeys ',trw.numKeys | |
''' | |
test = keypackagesize/keyframesize | |
if numKeys != test: | |
tkMessageBox.showinfo('Error.','Error with numkeys.') | |
numKeys = test | |
''' | |
keysizelist=[] | |
keys = [] | |
for i in range(trw.numKeys): | |
key = Keyframe() | |
#print '-'*20 | |
key.id = i | |
#print 'key %i'%i | |
bb = BBox() | |
bb.x1,bb.x2,bb.y1,bb.y2,bb.z1,bb.z2 = struct.unpack('6h',f.read(12)) | |
key.bb = bb | |
key.size += bb.size | |
off = Offset() | |
off.x,off.y,off.z = struct.unpack('3h',f.read(6)) | |
key.offset = off | |
key.size += off.size # should equal 18 | |
#print 'bb: %i %i %i %i %i %i offset: %i %i %i'%(bb.x1,bb.x2,bb.y1,bb.y2,bb.z1,bb.z2,off.x,off.y,off.z) | |
bb0.append(off.x) | |
bb1.append(off.y) | |
bb2.append(off.z) | |
key.nummeshes, = struct.unpack('H',f.read(2)) | |
trw.nummeshes = key.nummeshes | |
for i in range(key.nummeshes): | |
ang = Angle() | |
ang.rotx,ang.roty,ang.rotz = struct.unpack('HHH',f.read(6)) #360 degrees = 1024 | |
rx,ry,rz = convertAngle(ang.rotx,ang.roty,ang.rotz) | |
#print 'Mesh#%i %i %i %i (%f %f %f)'%(i,ang.rotx,ang.roty,ang.rotz,rx,ry,rz) | |
key.size += ang.calcSize() | |
key.angles.append(ang) | |
#size += getKeySize(rotx,roty,rotz) | |
#print 'size in wad: %i bytes'%key.size | |
keys.append(key) | |
keysizelist.append(key.size) | |
f.close() | |
trw.keys = keys | |
trw.maxkeysize = max(keysizelist) | |
if trw.maxkeysize%2 != 0: | |
print 'Error calculating keysize. Not multiple of 2' | |
return | |
else: | |
trw.maxkeysize /=2 | |
print 'maxkeysize %i words'%trw.maxkeysize | |
AppData.trw = trw | |
ok=True | |
return ok | |
def getKeySize(rx,ry,rz): | |
list = [rx,ry,rz] | |
if list.count(0) < 2: | |
size = 4 # 3 axis rotation | |
else: | |
size = 2 # single axis rotation | |
return size | |
def convertAngle(rx,ry,rz): | |
return rx*360.0/1024,ry*360.0/1024,rz*360.0/1024 | |
def openFile(): | |
fn = tkFileGet(title='Select a file', | |
filetypes=[('Wadmerger .trw','*.TRW'),('All Files','*.*')]) | |
filename = fn | |
if fn: | |
if fn[-4:].lower() in ['.trw']: | |
print "Testing %s"%fn | |
if not parseTRW(fn): | |
print 'Error reading file' | |
return False | |
else: | |
return fn | |
else: | |
print fn+' selected\n\nNeed .TRW file.' | |
return False | |
else: | |
print 'No file selected.' | |
return False | |
if __name__=='__main__': | |
filename = openFile() | |
if filename: | |
directory = os.path.splitext(filename)[0] # for testing use: directory = "default" | |
if not os.path.exists(directory): | |
os.makedirs(directory) | |
for the_file in os.listdir(directory): | |
file_path = os.path.join(directory, the_file) | |
try: | |
if os.path.isfile(file_path): | |
os.unlink(file_path) | |
#elif os.path.isdir(file_path): shutil.rmtree(file_path) | |
except Exception as e: | |
print(e) | |
for kf in range(AppData.trw.numKeys): | |
keyframe = AppData.trw.keys[kf] | |
print "Writing pose file" | |
#print 'y offset: %i'%(bb1[0]) | |
if kf > 999: | |
f = open("%s\keyframe%i.pose"%(directory,kf),"w") | |
else: | |
if kf > 99: | |
f = open("%s\keyframe0%i.pose"%(directory,kf),"w") | |
else: | |
if kf > 9: | |
f = open("%s\keyframe00%i.pose"%(directory,kf),"w") | |
else: | |
f = open("%s\keyframe000%i.pose"%(directory,kf),"w") | |
for k in range(keyframe.nummeshes): | |
ang = keyframe.angles[k] | |
rx,ry,rz = convertAngle(ang.rotx,ang.roty,ang.rotz) | |
if k == 0: | |
f.write("%s: %.6f %.6f %.6f %.6f %.6f %.6f\n"%(bonenames[k],-rx,-ry,rz,-bb0[kf]/1000.0,-bb1[kf]/1000.0,bb2[kf]/1000.0)) | |
else: | |
f.write("%s: %.6f %.6f %.6f 0 0 0\n"%(bonenames[k],-rx,-ry,rz)) | |
f.close() | |
print "Done" |
TRLE -> ms3d: Negate RotY and RotZ and convert from yxzr order to xyzs.
No comments:
Post a Comment