Apertura del repositorio

This commit is contained in:
2025-05-24 18:09:39 -03:00
parent 76e6359dad
commit d883ddd0d0
35253 changed files with 2891973 additions and 2 deletions
+730
View File
@@ -0,0 +1,730 @@
#!/usr/bin/env python3
"""\
@file anim_tool.py
@author Brad Payne, Nat Goodspeed
@date 2015-09-15
@brief This module contains tools for manipulating the .anim files supported
for Second Life animation upload. Note that this format is unrelated
to any non-Second Life formats of the same name.
This code is a Python translation of the logic in
LLKeyframeMotion::serialize() and deserialize():
https://bitbucket.org/lindenlab/viewer-release/src/827a910542a9af0a39b0ca03663c02e5c83869ea/indra/llcharacter/llkeyframemotion.cpp?at=default&fileviewer=file-view-default#llkeyframemotion.cpp-1864
https://bitbucket.org/lindenlab/viewer-release/src/827a910542a9af0a39b0ca03663c02e5c83869ea/indra/llcharacter/llkeyframemotion.cpp?at=default&fileviewer=file-view-default#llkeyframemotion.cpp-1220
save that there is no support for old-style .anim files, permitting
simpler code.
$LicenseInfo:firstyear=2015&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2015, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
import math
import os
import random
from io import StringIO
import struct
import sys
from xml.etree import ElementTree
class Error(Exception):
pass
class BadFormat(Error):
"""
Something went wrong trying to read the specified .anim file.
"""
pass
class ExtraneousData(BadFormat):
"""
Specifically, the .anim file in question contains more data than needed.
This could happen if the file isn't a .anim at all, and it 'just happens'
to read properly otherwise -- e.g. a block of all zero bytes could look
like empty name strings, empty arrays etc. That could be a legitimate
error -- or it could be due to a sloppy tool. Break this exception out
separately so caller can distinguish if desired.
"""
pass
U16MAX = 65535
# One Over U16MAX, for scaling
OOU16MAX = 1.0/float(U16MAX)
LL_MAX_PELVIS_OFFSET = 5.0
class FilePacker(object):
def __init__(self):
self.buffer = StringIO()
def write(self,filename):
with open(filename,"wb") as f:
f.write(self.buffer.getvalue())
def pack(self,fmt,*args):
buf = struct.pack(fmt, *args)
self.buffer.write(buf)
def pack_string(self,str,size=0):
# If size == 0, caller doesn't care, just wants a terminating nul byte
size = size or (len(str) + 1)
# Nonzero size means a fixed-length field. If the passed string (plus
# its terminating nul) exceeds that fixed length, we'll have to
# truncate. But make sure we still leave room for the final nul byte!
str = str[:size-1]
# Now pad what's left of str out to 'size' with nul bytes.
buf = str + ("\000" * (size-len(str)))
self.buffer.write(buf)
class FileUnpacker(object):
def __init__(self, filename):
with open(filename,"rb") as f:
self.buffer = f.read()
self.offset = 0
def unpack(self,fmt):
result = struct.unpack_from(fmt, self.buffer, self.offset)
self.offset += struct.calcsize(fmt)
return result
def unpack_string(self, size=0):
# Nonzero size means we must consider exactly the next 'size'
# characters in self.buffer.
if size:
self.offset += size
# but stop at the first nul byte
return self.buffer[self.offset-size:self.offset].split("\000", 1)[0]
# Zero size means consider everything until the next nul character.
result = self.buffer[self.offset:].split("\000", 1)[0]
# don't forget to skip the nul byte too
self.offset += len(result) + 1
return result
# translated from the C++ version in lldefs.h
def llclamp(a, minval, maxval):
if a<minval:
return minval
if a>maxval:
return maxval
return a
# translated from the C++ version in llquantize.h
def F32_to_U16(val, lower, upper):
val = llclamp(val, lower, upper);
# make sure that the value is positive and normalized to <0, 1>
val -= lower;
val /= (upper - lower);
# return the U16
return int(math.floor(val*U16MAX))
# translated from the C++ version in llquantize.h
def U16_to_F32(ival, lower, upper):
if ival < 0 or ival > U16MAX:
raise ValueError("U16 out of range: %s" % ival)
val = ival*OOU16MAX
delta = (upper - lower)
val *= delta
val += lower
max_error = delta*OOU16MAX;
# make sure that zeroes come through as zero
if abs(val) < max_error:
val = 0.0
return val;
class RotKey(object):
def __init__(self, time, duration, rot):
"""
This constructor instantiates a RotKey object from scratch, as it
were, converting from float time to time_short.
"""
self.time = time
self.time_short = F32_to_U16(time, 0.0, duration) \
if time is not None else None
self.rotation = rot
@staticmethod
def unpack(duration, fup):
"""
This staticmethod constructs a RotKey by loadingfrom a FileUnpacker.
"""
# cheat the other constructor
this = RotKey(None, None, None)
# load time_short directly from the file
(this.time_short, ) = fup.unpack("<H")
# then convert to float time
this.time = U16_to_F32(this.time_short, 0.0, duration)
# convert each coordinate of the rotation from short to float
(x,y,z) = fup.unpack("<HHH")
this.rotation = [U16_to_F32(i, -1.0, 1.0) for i in (x,y,z)]
return this
def dump(self, f):
print(" rot_key: t %.3f" % self.time,"st",self.time_short,"rot",",".join("%.3f" % f for f in self.rotation), file=f)
def pack(self, fp):
fp.pack("<H",self.time_short)
(x,y,z) = [F32_to_U16(v, -1.0, 1.0) for v in self.rotation]
fp.pack("<HHH",x,y,z)
class PosKey(object):
def __init__(self, time, duration, pos):
"""
This constructor instantiates a PosKey object from scratch, as it
were, converting from float time to time_short.
"""
self.time = time
self.time_short = F32_to_U16(time, 0.0, duration) \
if time is not None else None
self.position = pos
@staticmethod
def unpack(duration, fup):
"""
This staticmethod constructs a PosKey by loadingfrom a FileUnpacker.
"""
# cheat the other constructor
this = PosKey(None, None, None)
# load time_short directly from the file
(this.time_short, ) = fup.unpack("<H")
# then convert to float time
this.time = U16_to_F32(this.time_short, 0.0, duration)
# convert each coordinate of the rotation from short to float
(x,y,z) = fup.unpack("<HHH")
this.position = [U16_to_F32(i, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET)
for i in (x,y,z)]
return this
def dump(self, f):
print(" pos_key: t %.3f" % self.time,"pos ",",".join("%.3f" % f for f in self.position), file=f)
def pack(self, fp):
fp.pack("<H",self.time_short)
(x,y,z) = [F32_to_U16(v, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET) for v in self.position]
fp.pack("<HHH",x,y,z)
class Constraint(object):
@staticmethod
def unpack(duration, fup):
this = Constraint()
(this.chain_length, this.constraint_type) = fup.unpack("<BB")
this.source_volume = fup.unpack_string(16)
this.source_offset = fup.unpack("<fff")
this.target_volume = fup.unpack_string(16)
this.target_offset = fup.unpack("<fff")
this.target_dir = fup.unpack("<fff")
(this.ease_in_start, this.ease_in_stop, this.ease_out_start, this.ease_out_stop) = \
fup.unpack("<ffff")
return this
def pack(self, fp):
fp.pack("<BB", self.chain_length, self.constraint_type)
fp.pack_string(self.source_volume, 16)
fp.pack("<fff", *self.source_offset)
fp.pack_string(self.target_volume, 16)
fp.pack("<fff", *self.target_offset)
fp.pack("<fff", *self.target_dir)
fp.pack("<ffff", self.ease_in_start, self.ease_in_stop,
self.ease_out_start, self.ease_out_stop)
def dump(self, f):
print(" constraint:", file=f)
print(" chain_length",self.chain_length, file=f)
print(" constraint_type",self.constraint_type, file=f)
print(" source_volume",self.source_volume, file=f)
print(" source_offset",self.source_offset, file=f)
print(" target_volume",self.target_volume, file=f)
print(" target_offset",self.target_offset, file=f)
print(" target_dir",self.target_dir, file=f)
print(" ease_in_start",self.ease_in_start, file=f)
print(" ease_in_stop",self.ease_in_stop, file=f)
print(" ease_out_start",self.ease_out_start, file=f)
print(" ease_out_stop",self.ease_out_stop, file=f)
class Constraints(object):
@staticmethod
def unpack(duration, fup):
this = Constraints()
(num_constraints, ) = fup.unpack("<i")
this.constraints = [Constraint.unpack(duration, fup)
for i in range(num_constraints)]
return this
def pack(self, fp):
fp.pack("<i",len(self.constraints))
for c in self.constraints:
c.pack(fp)
def dump(self, f):
print("constraints:",len(self.constraints), file=f)
for c in self.constraints:
c.dump(f)
class PositionCurve(object):
def __init__(self):
self.keys = []
def is_static(self):
if self.keys:
k0 = self.keys[0]
for k in self.keys:
if k.position != k0.position:
return False
return True
@staticmethod
def unpack(duration, fup):
this = PositionCurve()
(num_pos_keys, ) = fup.unpack("<i")
this.keys = [PosKey.unpack(duration, fup)
for k in range(num_pos_keys)]
return this
def pack(self, fp):
fp.pack("<i",len(self.keys))
for k in self.keys:
k.pack(fp)
def dump(self, f):
print(" position_curve:", file=f)
print(" num_pos_keys", len(self.keys), file=f)
for k in self.keys:
k.dump(f)
class RotationCurve(object):
def __init__(self):
self.keys = []
def is_static(self):
if self.keys:
k0 = self.keys[0]
for k in self.keys:
if k.rotation != k0.rotation:
return False
return True
@staticmethod
def unpack(duration, fup):
this = RotationCurve()
(num_rot_keys, ) = fup.unpack("<i")
this.keys = [RotKey.unpack(duration, fup)
for k in range(num_rot_keys)]
return this
def pack(self, fp):
fp.pack("<i",len(self.keys))
for k in self.keys:
k.pack(fp)
def dump(self, f):
print(" rotation_curve:", file=f)
print(" num_rot_keys", len(self.keys), file=f)
for k in self.keys:
k.dump(f)
class JointInfo(object):
def __init__(self, name, priority):
self.joint_name = name
self.joint_priority = priority
self.rotation_curve = RotationCurve()
self.position_curve = PositionCurve()
@staticmethod
def unpack(duration, fup):
this = JointInfo(None, None)
this.joint_name = fup.unpack_string()
(this.joint_priority, ) = fup.unpack("<i")
this.rotation_curve = RotationCurve.unpack(duration, fup)
this.position_curve = PositionCurve.unpack(duration, fup)
return this
def pack(self, fp):
fp.pack_string(self.joint_name)
fp.pack("<i", self.joint_priority)
self.rotation_curve.pack(fp)
self.position_curve.pack(fp)
def dump(self, f):
print("joint:", file=f)
print(" joint_name:",self.joint_name, file=f)
print(" joint_priority:",self.joint_priority, file=f)
self.rotation_curve.dump(f)
self.position_curve.dump(f)
class Anim(object):
def __init__(self, filename=None, verbose=False):
# set this FIRST as it's consulted by read() and unpack()
self.verbose = verbose
if filename:
self.read(filename)
def read(self, filename):
fup = FileUnpacker(filename)
try:
self.unpack(fup)
except struct.error as err:
raise BadFormat("error reading %s: %s" % (filename, err))
# By the end of streaming data in from our FileUnpacker, we should
# have consumed the entire thing. If there's excess data, it's
# entirely possible that this is a garbage file that happens to
# resemble a valid degenerate .anim file, e.g. with zero counts of
# things.
if fup.offset != len(fup.buffer):
raise ExtraneousData("extraneous data in %s; is it really a Linden .anim file?" %
filename)
# various validity checks could be added - see LLKeyframeMotion::deserialize()
def unpack(self,fup):
(self.version, self.sub_version, self.base_priority, self.duration) = fup.unpack("@HHhf")
if self.version == 0 and self.sub_version == 1:
self.old_version = True
raise BadFormat("old version not supported")
elif self.version == 1 and self.sub_version == 0:
self.old_version = False
else:
raise BadFormat("Bad combination of version, sub_version: %d %d" % (self.version, self.sub_version))
# Also consult BVH conversion code for stricter checks
# C++ deserialize() checks self.base_priority against
# LLJoint::ADDITIVE_PRIORITY and LLJoint::USE_MOTION_PRIORITY,
# possibly sets self.max_priority
# checks self.duration against MAX_ANIM_DURATION !!
# checks self.emote_name != str(self.ID)
# checks self.hand_pose against LLHandMotion::NUM_HAND_POSES !!
# checks 0 < num_joints <= LL_CHARACTER_MAX_JOINTS (no need --
# validate names)
# checks each joint_name neither "mScreen" nor "mRoot" ("attempted to
# animate special joint") !!
# checks each joint_name can be found in mCharacter
# checks each joint_priority >= LLJoint::USE_MOTION_PRIORITY
# tracks max observed joint_priority, excluding USE_MOTION_PRIORITY
# checks each 0 <= RotKey.time <= self.duration !!
# checks each RotKey.rotation.isFinite() !!
# checks each PosKey.position.isFinite() !!
# checks 0 <= num_constraints <= MAX_CONSTRAINTS !!
# checks each Constraint.chain_length <= num_joints
# checks each Constraint.constraint_type < NUM_CONSTRAINT_TYPES !!
# checks each Constraint.source_offset.isFinite() !!
# checks each Constraint.target_offset.isFinite() !!
# checks each Constraint.target_dir.isFinite() !!
# from https://bitbucket.org/lindenlab/viewer-release/src/827a910542a9af0a39b0ca03663c02e5c83869ea/indra/llcharacter/llkeyframemotion.cpp?at=default&fileviewer=file-view-default#llkeyframemotion.cpp-1812 :
# find joint to which each Constraint's collision volume is attached;
# for each link in Constraint.chain_length, walk to joint's parent,
# find that parent in list of joints, set its index in index list
self.emote_name = fup.unpack_string()
(self.loop_in_point, self.loop_out_point, self.loop,
self.ease_in_duration, self.ease_out_duration, self.hand_pose, num_joints) = \
fup.unpack("@ffiffII")
self.joints = [JointInfo.unpack(self.duration, fup)
for j in range(num_joints)]
if self.verbose:
for joint_info in self.joints:
print("unpacked joint",joint_info.joint_name)
self.constraints = Constraints.unpack(self.duration, fup)
self.buffer = fup.buffer
def pack(self, fp):
fp.pack("@HHhf", self.version, self.sub_version, self.base_priority, self.duration)
fp.pack_string(self.emote_name, 0)
fp.pack("@ffiffII", self.loop_in_point, self.loop_out_point, self.loop,
self.ease_in_duration, self.ease_out_duration, self.hand_pose, len(self.joints))
for j in self.joints:
j.pack(fp)
self.constraints.pack(fp)
def dump(self, filename="-"):
if filename=="-":
f = sys.stdout
else:
f = open(filename,"w")
print("versions: ", self.version, self.sub_version, file=f)
print("base_priority: ", self.base_priority, file=f)
print("duration: ", self.duration, file=f)
print("emote_name: ", self.emote_name, file=f)
print("loop_in_point: ", self.loop_in_point, file=f)
print("loop_out_point: ", self.loop_out_point, file=f)
print("loop: ", self.loop, file=f)
print("ease_in_duration: ", self.ease_in_duration, file=f)
print("ease_out_duration: ", self.ease_out_duration, file=f)
print("hand_pose", self.hand_pose, file=f)
print("num_joints", len(self.joints), file=f)
for j in self.joints:
j.dump(f)
self.constraints.dump(f)
def write(self, filename):
fp = FilePacker()
self.pack(fp)
fp.write(filename)
def write_src_data(self, filename):
print("write file",filename)
with open(filename,"wb") as f:
f.write(self.buffer)
def find_joint(self, name):
joints = [j for j in self.joints if j.joint_name == name]
if joints:
return joints[0]
else:
return None
def add_joint(self, name, priority):
if not self.find_joint(name):
self.joints.append(JointInfo(name, priority))
def delete_joint(self, name):
j = self.find_joint(name)
if j:
if self.verbose:
print("removing joint", name)
self.joints.remove(j)
else:
if self.verbose:
print("joint not found to remove", name)
def summary(self):
nj = len(self.joints)
nz = len([j for j in self.joints if j.joint_priority > 0])
nstatic = len([j for j in self.joints
if j.rotation_curve.is_static()
and j.position_curve.is_static()])
print("summary: %d joints, non-zero priority %d, static %d" % (nj, nz, nstatic))
def add_pos(self, joint_names, positions):
js = [joint for joint in self.joints if joint.joint_name in joint_names]
for j in js:
if self.verbose:
print("adding positions",j.joint_name,positions)
j.joint_priority = 4
j.position_curve.keys = [PosKey(self.duration * i / (len(positions) - 1),
self.duration,
pos)
for i,pos in enumerate(positions)]
def add_rot(self, joint_names, rotations):
js = [joint for joint in self.joints if joint.joint_name in joint_names]
for j in js:
print("adding rotations",j.joint_name)
j.joint_priority = 4
j.rotation_curve.keys = [RotKey(self.duration * i / (len(rotations) - 1),
self.duration,
rot)
for i,rot in enumerate(rotations)]
def twistify(anim, joint_names, rot1, rot2):
js = [joint for joint in anim.joints if joint.joint_name in joint_names]
for j in js:
print("twisting",j.joint_name)
print(len(j.rotation_curve.keys))
j.joint_priority = 4
# Set the joint(s) to rot1 at time 0, rot2 at the full duration.
j.rotation_curve.keys = [
RotKey(0.0, anim.duration, rot1),
RotKey(anim.duration, anim.duration, rot2)]
def float_triple(arg):
vals = arg.split()
if len(vals)==3:
return [float(x) for x in vals]
else:
raise ValueError("arg %s does not resolve to a float triple" % arg)
def get_joint_by_name(tree,name):
if tree is None:
return None
matches = [elt for elt in tree.getroot().iter()
if elt.get("name")==name
and elt.tag in ["bone", "collision_volume", "attachment_point"]]
if len(matches)==1:
return matches[0]
elif len(matches)>1:
print("multiple matches for name",name)
return None
else:
return None
def get_elt_pos(elt):
if elt.get("pos"):
return float_triple(elt.get("pos"))
elif elt.get("position"):
return float_triple(elt.get("position"))
else:
return (0.0, 0.0, 0.0)
def resolve_joints(names, skel_tree, lad_tree, no_hud=False):
print("resolve joints, no_hud is",no_hud)
if skel_tree and lad_tree:
all_elts = [elt for elt in skel_tree.getroot().iter()]
all_elts.extend([elt for elt in lad_tree.getroot().iter()])
matches = set()
for elt in all_elts:
if elt.get("name") is None:
continue
#print elt.get("name"),"hud",elt.get("hud")
if no_hud and elt.get("hud"):
#print "skipping hud joint", elt.get("name")
continue
if elt.get("name") in names or elt.tag in names:
matches.add(elt.get("name"))
return list(matches)
else:
return names
def main(*argv):
import argparse
# default search location for config files is defined relative to
# the script location; assuming they live in the same viewer repo
# Use sys.argv[0] because (a) this script lives where it lives regardless
# of what our caller passes and (b) we don't expect our caller to pass the
# script name anyway.
pathname = os.path.dirname(sys.argv[0])
# we're in scripts/content_tools; hop back to base of repository clone
path_to_skel = os.path.join(os.path.abspath(pathname),os.pardir,os.pardir,
"indra","newview","character")
parser = argparse.ArgumentParser(description="process SL animations")
parser.add_argument("--verbose", help="verbose flag", action="store_true")
parser.add_argument("--dump", help="dump to stdout", action="store_true")
parser.add_argument("--use_aliases", help="use alias names for bones", action="store_true")
parser.add_argument("--rot", help="specify sequence of rotations", type=float_triple, nargs="+")
parser.add_argument("--rand_pos", help="request NUM random positions (default %(default)s)",
metavar="NUM", type=int, default=2)
parser.add_argument("--reset_pos", help="request original positions", action="store_true")
parser.add_argument("--pos", help="specify sequence of positions", type=float_triple, nargs="+")
parser.add_argument("--duration", help="specify duration", type=float)
parser.add_argument("--loop_in", help="specify loop in time", type=float)
parser.add_argument("--loop_out", help="specify loop out time", type=float)
parser.add_argument("--num_pos", help="number of positions to create", type=int, default=2)
parser.add_argument("--delete_joints", help="specify joints to be deleted", nargs="+",
metavar="JOINT")
parser.add_argument("--joints", help="specify joints to be added or modified", nargs="+",
metavar="JOINT")
parser.add_argument("--summary", help="print summary of the output animation", action="store_true")
parser.add_argument("--skel", help="name of the avatar_skeleton file (default %(default)s)",
default=os.path.join(path_to_skel,"avatar_skeleton.xml"),
metavar="FILEPATH")
parser.add_argument("--lad", help="name of the avatar_lad file (default %(default)s)",
default=os.path.join(path_to_skel,"avatar_lad.xml"),
metavar="FILEPATH")
parser.add_argument("--set_version", nargs=2, type=int,
help="set version and sub-version to specified values",
metavar=("VERSION", "SUB-VERSION"))
parser.add_argument("--no_hud", help="omit hud joints from list of attachments", action="store_true")
parser.add_argument("--base_priority", help="set base priority", type=int)
parser.add_argument("--joint_priority", help="set joint priority for all joints", type=int)
parser.add_argument("--force_joints", help="don't check validity of joint names", action="store_true")
parser.add_argument("infilename", help="name of a .anim file to input")
parser.add_argument("outfilename", nargs="?", help="name of a .anim file to output")
args = parser.parse_args(argv)
print("anim_tool.py: " + " ".join(argv))
print("dump is", args.dump)
print("infilename",args.infilename,"outfilename",args.outfilename)
print("rot",args.rot)
print("pos",args.pos)
print("joints",args.joints)
anim = Anim(args.infilename, args.verbose)
skel_tree = None
lad_tree = None
joints = []
if args.skel:
skel_tree = ElementTree.parse(args.skel)
if skel_tree is None:
raise Error("failed to parse " + args.skel)
if args.lad:
lad_tree = ElementTree.parse(args.lad)
if lad_tree is None:
raise Error("failed to parse " + args.lad)
if args.joints:
if args.force_joints:
joints = args.joints
else:
joints = resolve_joints(args.joints, skel_tree, lad_tree, args.no_hud)
if args.use_aliases:
joints = map(lambda name: "avatar_" + name, joints)
if args.verbose:
print("joints resolved to",joints)
for name in joints:
anim.add_joint(name,0)
if args.delete_joints:
for name in args.delete_joints:
anim.delete_joint(name)
if joints and args.rot:
anim.add_rot(joints, args.rot)
if joints and args.pos:
anim.add_pos(joints, args.pos)
if joints and args.rand_pos:
# pick a random sequence of positions for each joint specified
for joint in joints:
# generate a list of rand_pos triples
pos_array = [tuple(random.uniform(-1,1) for i in range(3))
for j in range(args.rand_pos)]
# close the loop by cycling back to the first entry
pos_array.append(pos_array[0])
anim.add_pos([joint], pos_array)
if joints and args.reset_pos:
for joint in joints:
elt = get_joint_by_name(skel_tree,joint) or get_joint_by_name(lad_tree,joint)
if elt is not None:
anim.add_pos([joint], 2*[get_elt_pos(elt)])
else:
print("no elt or no pos data for",joint)
if args.set_version:
anim.version, anim.sub_version = args.set_version
if args.base_priority is not None:
print("set base priority",args.base_priority)
anim.base_priority = args.base_priority
# --joint_priority sets priority for ALL joints, not just the explicitly-
# specified ones
if args.joint_priority is not None:
print("set joint priority",args.joint_priority)
for joint in anim.joints:
joint.joint_priority = args.joint_priority
if args.duration is not None:
print("set duration",args.duration)
anim.duration = args.duration
if args.loop_in is not None:
print("set loop_in",args.loop_in)
anim.loop_in_point = args.loop_in
if args.loop_out is not None:
print("set loop_out",args.loop_out)
anim.loop_out_point = args.loop_out
if args.dump:
anim.dump("-")
if args.summary:
anim.summary()
if args.outfilename:
anim.write(args.outfilename)
if __name__ == "__main__":
try:
sys.exit(main(*sys.argv[1:]))
except Error as err:
sys.exit("%s: %s" % (err.__class__.__name__, err))
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""\
This module contains tools for comparing files output by LLVOAvatar::dumpArchetypeXML
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
import argparse
from lxml import etree
from itertools import chain
def node_key(e):
if e.tag == "param":
return e.tag + " " + e.get("id")
if e.tag == "texture":
return e.tag + " " + e.get("te")
if e.get("name"):
return e.tag + " " + e.get("name")
return None
def compare_matched_nodes(key,items,summary):
tags = list(set([e.tag for e in items]))
if len(tags) != 1:
print("different tag types for key",key)
summary.setdefault("tag_mismatch",0)
summary["tag_mismatch"] += 1
return
all_attrib = list(set(chain.from_iterable([list(e.attrib.keys()) for e in items])))
#print key,"all_attrib",all_attrib
for attr in all_attrib:
vals = [e.get(attr) for e in items]
#print "key",key,"attr",attr,"vals",vals
if len(set(vals)) != 1:
print(key,"- attr",attr,"multiple values",vals)
summary.setdefault("attr",{})
summary["attr"].setdefault(attr,0)
summary["attr"][attr] += 1
def compare_trees(file_trees):
print("compare_trees")
summary = {}
all_keys = list(set([node_key(e) for tree in file_trees for e in tree.getroot().iter() if node_key(e)]))
#print "keys",all_keys
tree_nodes = []
for i,tree in enumerate(file_trees):
nodes = dict((node_key(e),e) for e in tree.getroot().iter() if node_key(e))
tree_nodes.append(nodes)
for key in sorted(all_keys):
items = []
for nodes in tree_nodes:
if not key in nodes:
print("file",i,"missing item for key",key)
summary.setdefault("missing",0)
summary["missing"] += 1
else:
items.append(nodes[key])
compare_matched_nodes(key,items,summary)
print("Summary:")
print(summary)
def dump_appearance_params(tree):
vals = []
for e in tree.getroot().iter():
if e.tag == "param":
g = int(e.get("group"))
if g in [0,3]:
vals.append("{" + e.get("id") + "," +e.get("u8") + "}")
#print e.get("id"), e.get("name"), e.get("group"), e.get("u8")
if len(vals)==253:
print(", ".join(vals))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="compare avatar XML archetype files")
parser.add_argument("--verbose", help="verbose flag", action="store_true")
parser.add_argument("--compare", help="compare flag", action="store_true")
parser.add_argument("--appearance_params", help="compare flag", action="store_true")
parser.add_argument("files", nargs="+", help="name of one or more archtype files")
args = parser.parse_args()
print("files",args.files)
file_trees = [etree.parse(filename) for filename in args.files]
print(args)
if args.compare:
compare_trees(file_trees)
if args.appearance_params:
dump_appearance_params(file_trees[0])
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""\
This module contains tools for manipulating collada files
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
import argparse
import random
# Need to pip install numpy and pycollada
import numpy as np
from collada import *
from lxml import etree
def mesh_summary(mesh):
print("scenes",mesh.scenes)
for scene in mesh.scenes:
print("scene",scene)
for node in scene.nodes:
print("node",node)
def mesh_lock_offsets(tree, joints):
print("mesh_lock_offsets",tree,joints)
for joint_node in tree.iter():
if "node" not in joint_node.tag:
continue
if joint_node.get("type") != "JOINT":
continue
if joint_node.get("name") in joints or "bone" in joints:
for matrix_node in list(joint_node):
if "matrix" in matrix_node.tag:
floats = [float(x) for x in matrix_node.text.split()]
if len(floats) == 16:
floats[3] += 0.0001
floats[7] += 0.0001
floats[11] += 0.0001
matrix_node.text = " ".join([str(f) for f in floats])
print(joint_node.get("name"),matrix_node.tag,"text",matrix_node.text,len(floats),floats)
def mesh_random_offsets(tree, joints):
print("mesh_random_offsets",tree,joints)
for joint_node in tree.iter():
if "node" not in joint_node.tag:
continue
if joint_node.get("type") != "JOINT":
continue
if not joint_node.get("name"):
continue
if joint_node.get("name") in joints or "bone" in joints:
for matrix_node in list(joint_node):
if "matrix" in matrix_node.tag:
floats = [float(x) for x in matrix_node.text.split()]
print("randomizing",floats)
if len(floats) == 16:
floats[3] += random.uniform(-1.0,1.0)
floats[7] += random.uniform(-1.0,1.0)
floats[11] += random.uniform(-1.0,1.0)
matrix_node.text = " ".join([str(f) for f in floats])
print(joint_node.get("name"),matrix_node.tag,"text",matrix_node.text,len(floats),floats)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="process SL animations")
parser.add_argument("--verbose", action="store_true",help="verbose flag")
parser.add_argument("infilename", help="name of a collada (dae) file to input")
parser.add_argument("outfilename", nargs="?", help="name of a collada (dae) file to output", default = None)
parser.add_argument("--lock_offsets", nargs="+", help="tweak position of listed joints to lock their offsets")
parser.add_argument("--random_offsets", nargs="+", help="random offset position for listed joints")
parser.add_argument("--summary", action="store_true", help="print summary info about input file")
args = parser.parse_args()
mesh = None
tree = None
if args.infilename:
print("reading",args.infilename)
mesh = Collada(args.infilename)
tree = etree.parse(args.infilename)
if args.summary:
print("summarizing",args.infilename)
mesh_summary(mesh)
if args.lock_offsets:
print("locking offsets for",args.lock_offsets)
mesh_lock_offsets(tree, args.lock_offsets)
if args.random_offsets:
print("adding random offsets for",args.random_offsets)
mesh_random_offsets(tree, args.random_offsets)
if args.outfilename:
print("writing",args.outfilename)
f = open(args.outfilename,"w")
print(etree.tostring(tree, pretty_print=True), file=f) #need update to get: , short_empty_elements=True)
+520
View File
@@ -0,0 +1,520 @@
#!/usr/bin/env python3
"""\
This module contains tools for manipulating and validating the avatar skeleton file.
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
import argparse
from lxml import etree
def get_joint_names(tree):
joints = [element.get('name') for element in tree.getroot().iter() if element.tag in ['bone','collision_volume']]
print("joints:",joints)
return joints
def get_aliases(tree):
aliases = {}
alroot = tree.getroot()
for element in alroot.iter():
for key in list(element.keys()):
if key == 'aliases':
name = element.get('name')
val = element.get('aliases')
aliases[name] = val
return aliases
def fix_name(element):
pass
def enforce_precision_rules(element):
pass
def float_tuple(str, n=3):
try:
result = tuple(float(e) for e in str.split())
if len(result)==n:
return result
else:
print("tuple length wrong:", str,"gave",result,"wanted len",n,"got len",len(result))
raise Exception()
except:
print("convert failed for:",str)
raise
def check_symmetry(name, field, vec1, vec2):
if vec1[0] != vec2[0]:
print(name,field,"x match fail")
if vec1[1] != -vec2[1]:
print(name,field,"y mirror image fail")
if vec1[2] != vec2[2]:
print(name,field,"z match fail")
def enforce_alias_rules(tree, element, fix=False):
if element.tag != "bone":
return
alias_lis = []
aliases = element.get("aliases")
if aliases:
alias_lis = aliases.split(" ")
name = element.get("name")
if name:
std_alias = "avatar_" + name
if not std_alias in alias_lis:
print "missing expected alias",name,std_alias
for alias in alias_lis:
if alias.startswith("avatar_") and alias != std_alias:
print "invalid avatar_ alias",name,alias
def enforce_symmetry(tree, element, field, fix=False):
name = element.get("name")
if not name:
return
if "Right" in name:
left_name = name.replace("Right","Left")
left_element = get_element_by_name(tree, left_name)
pos = element.get(field)
left_pos = left_element.get(field)
pos_tuple = float_tuple(pos)
left_pos_tuple = float_tuple(left_pos)
check_symmetry(name,field,pos_tuple,left_pos_tuple)
def get_element_by_name(tree,name):
if tree is None:
return None
matches = [elt for elt in tree.getroot().iter() if elt.get("name")==name]
if len(matches)==1:
return matches[0]
elif len(matches)>1:
print("multiple matches for name",name)
return None
else:
return None
def list_skel_tree(tree):
for element in tree.getroot().iter():
if element.tag == "bone":
print(element.get("name"),"-",element.get("support"))
def validate_child_order(tree, ogtree, fix=False):
unfixable = 0
#print "validate_child_order am failing for NO RAISIN!"
#unfixable += 1
tofix = set()
for element in tree.getroot().iter():
if element.tag != "bone":
continue
og_element = get_element_by_name(ogtree,element.get("name"))
if og_element is not None:
for echild,ochild in zip(list(element),list(og_element)):
if echild.get("name") != ochild.get("name"):
print("Child ordering error, parent",element.get("name"),echild.get("name"),"vs",ochild.get("name"))
if fix:
tofix.add(element.get("name"))
children = {}
for name in tofix:
print("FIX",name)
element = get_element_by_name(tree,name)
og_element = get_element_by_name(ogtree,name)
children = []
# add children matching the original joints first, in the same order
for og_elt in list(og_element):
elt = get_element_by_name(tree,og_elt.get("name"))
if elt is not None:
children.append(elt)
print("b:",elt.get("name"))
else:
print("b missing:",og_elt.get("name"))
# then add children that are not present in the original joints
for elt in list(element):
og_elt = get_element_by_name(ogtree,elt.get("name"))
if og_elt is None:
children.append(elt)
print("e:",elt.get("name"))
# if we've done this right, we have a rearranged list of the same length
if len(children)!=len(element):
print("children",[e.get("name") for e in children])
print("element",[e.get("name") for e in element])
print("children changes for",name,", cannot reconcile")
else:
element[:] = children
return unfixable
# Checklist for the final file, started from SL-276:
# - new "end" attribute on all bones
# - new "connected" attribute on all bones
# - new "support" tag on all bones and CVs
# - aliases where appropriate for backward compatibility. rFoot and lFoot associated with mAnkle bones (not mFoot bones)
# - correct counts of bones and collision volumes in header
# - check all comments
# - old fields of old bones and CVs should be identical to their previous values.
# - old bones and CVs should retain their previous ordering under their parent, with new joints going later in any given child list
# - corresponding right and left joints should be mirror symmetric.
# - childless elements should be in short form (<bone /> instead of <bone></bone>)
# - digits of precision should be consistent (again, except for old joints)
# - new bones should have pos, pivot the same
def validate_skel_tree(tree, ogtree, reftree, fix=False):
print("validate_skel_tree")
(num_bones,num_cvs) = (0,0)
unfixable = 0
defaults = {"connected": "false",
"group": "Face"
}
for element in tree.getroot().iter():
og_element = get_element_by_name(ogtree,element.get("name"))
ref_element = get_element_by_name(reftree,element.get("name"))
# Preserve values from og_file:
for f in ["pos","rot","scale","pivot"]:
if og_element is not None and og_element.get(f) and (str(element.get(f)) != str(og_element.get(f))):
print(element.get("name"),"field",f,"has changed:",og_element.get(f),"!=",element.get(f))
if fix:
element.set(f, og_element.get(f))
# Pick up any other fields that we can from ogtree and reftree
fields = []
if element.tag in ["bone","collision_volume"]:
fields = ["support","group"]
if element.tag == 'bone':
fields.extend(["end","connected"])
for f in fields:
if not element.get(f):
print(element.get("name"),"missing required field",f)
if fix:
if og_element is not None and og_element.get(f):
print("fix from ogtree")
element.set(f,og_element.get(f))
elif ref_element is not None and ref_element.get(f):
print("fix from reftree")
element.set(f,ref_element.get(f))
else:
if f in defaults:
print("fix by using default value",f,"=",defaults[f])
element.set(f,defaults[f])
elif f == "support":
if og_element is not None:
element.set(f,"base")
else:
element.set(f,"extended")
else:
print("unfixable:",element.get("name"),"no value for field",f)
unfixable += 1
fix_name(element)
enforce_alias_rules(tree, element, fix)
enforce_precision_rules(element)
for field in ["pos","pivot"]:
enforce_symmetry(tree, element, field, fix)
if element.get("support")=="extended":
if element.get("pos") != element.get("pivot"):
print("extended joint",element.get("name"),"has mismatched pos, pivot")
if element.tag == "linden_skeleton":
num_bones = int(element.get("num_bones"))
num_cvs = int(element.get("num_collision_volumes"))
all_bones = [e for e in tree.getroot().iter() if e.tag=="bone"]
all_cvs = [e for e in tree.getroot().iter() if e.tag=="collision_volume"]
if num_bones != len(all_bones):
print("wrong bone count, expected",len(all_bones),"got",num_bones)
if fix:
element.set("num_bones", str(len(all_bones)))
if num_cvs != len(all_cvs):
print("wrong cv count, expected",len(all_cvs),"got",num_cvs)
if fix:
element.set("num_collision_volumes", str(len(all_cvs)))
print("skipping child order code")
#unfixable += validate_child_order(tree, ogtree, fix)
if fix and (unfixable > 0):
print("BAD FILE:", unfixable,"errs could not be fixed")
def slider_info(ladtree,skeltree):
for param in ladtree.iter("param"):
for skel_param in param.iter("param_skeleton"):
bones = [b for b in skel_param.iter("bone")]
if bones:
print("param",param.get("name"),"id",param.get("id"))
value_min = float(param.get("value_min"))
value_max = float(param.get("value_max"))
neutral = 100.0 * (0.0-value_min)/(value_max-value_min)
print(" neutral",neutral)
for b in bones:
scale = float_tuple(b.get("scale","0 0 0"))
offset = float_tuple(b.get("offset","0 0 0"))
print(" bone", b.get("name"), "scale", scale, "offset", offset)
scale_min = [value_min * s for s in scale]
scale_max = [value_max * s for s in scale]
offset_min = [value_min * t for t in offset]
offset_max = [value_max * t for t in offset]
if (scale_min != scale_max):
print(" Scale MinX", scale_min[0])
print(" Scale MinY", scale_min[1])
print(" Scale MinZ", scale_min[2])
print(" Scale MaxX", scale_max[0])
print(" Scale MaxY", scale_max[1])
print(" Scale MaxZ", scale_max[2])
if (offset_min != offset_max):
print(" Offset MinX", offset_min[0])
print(" Offset MinY", offset_min[1])
print(" Offset MinZ", offset_min[2])
print(" Offset MaxX", offset_max[0])
print(" Offset MaxY", offset_max[1])
print(" Offset MaxZ", offset_max[2])
# Check contents of avatar_lad file relative to a specified skeleton
def validate_lad_tree(ladtree,skeltree,orig_ladtree):
print("validate_lad_tree")
bone_names = [elt.get("name") for elt in skeltree.iter("bone")]
bone_names.append("mScreen")
bone_names.append("mRoot")
cv_names = [elt.get("name") for elt in skeltree.iter("collision_volume")]
#print "bones\n ","\n ".join(sorted(bone_names))
#print "cvs\n ","\n ".join(sorted(cv_names))
for att in ladtree.iter("attachment_point"):
att_name = att.get("name")
#print "attachment",att_name
joint_name = att.get("joint")
if not joint_name in bone_names:
print("att",att_name,"linked to invalid joint",joint_name)
for skel_param in ladtree.iter("param_skeleton"):
skel_param_id = skel_param.get("id")
skel_param_name = skel_param.get("name")
#if not skel_param_name and not skel_param_id:
# print "strange skel_param"
# print etree.tostring(skel_param)
# for k,v in skel_param.attrib.iteritems():
# print k,"->",v
for bone in skel_param.iter("bone"):
bone_name = bone.get("name")
if not bone_name in bone_names:
print("skel param references invalid bone",bone_name)
print(etree.tostring(bone))
bone_scale = float_tuple(bone.get("scale","0 0 0"))
bone_offset = float_tuple(bone.get("offset","0 0 0"))
param = bone.getparent().getparent()
if bone_scale==(0, 0, 0) and bone_offset==(0, 0, 0):
print("no-op bone",bone_name,"in param",param.get("id","-1"))
# check symmetry of sliders
if "Right" in bone.get("name"):
left_name = bone_name.replace("Right","Left")
left_bone = None
for b in skel_param.iter("bone"):
if b.get("name")==left_name:
left_bone = b
if left_bone is None:
print("left_bone not found",left_name,"in",param.get("id","-1"))
else:
left_scale = float_tuple(left_bone.get("scale","0 0 0"))
left_offset = float_tuple(left_bone.get("offset","0 0 0"))
if left_scale != bone_scale:
print("scale mismatch between",bone_name,"and",left_name,"in param",param.get("id","-1"))
param_id = int(param.get("id","-1"))
if param_id in [661]: # shear
expected_offset = tuple([bone_offset[0],bone_offset[1],-bone_offset[2]])
elif param_id in [30656, 31663, 32663]: # shift
expected_offset = bone_offset
else:
expected_offset = tuple([bone_offset[0],-bone_offset[1],bone_offset[2]])
if left_offset != expected_offset:
print("offset mismatch between",bone_name,"and",left_name,"in param",param.get("id","-1"))
drivers = {}
for driven_param in ladtree.iter("driven"):
driver = driven_param.getparent().getparent()
driven_id = driven_param.get("id")
driver_id = driver.get("id")
actual_param = next(param for param in ladtree.iter("param") if param.get("id")==driven_id)
if not driven_id in drivers:
drivers[driven_id] = set()
drivers[driven_id].add(driver_id)
if (actual_param.get("value_min") != driver.get("value_min") or \
actual_param.get("value_max") != driver.get("value_max")):
if args.verbose:
print("MISMATCH min max:",driver.get("id"),"drives",driven_param.get("id"),"min",driver.get("value_min"),actual_param.get("value_min"),"max",driver.get("value_max"),actual_param.get("value_max"))
for driven_id in drivers:
dset = drivers[driven_id]
if len(dset) != 1:
print("driven_id",driven_id,"has multiple drivers",dset)
else:
if args.verbose:
print("driven_id",driven_id,"has one driver",dset)
if orig_ladtree:
# make sure expected message format is unchanged
orig_message_params_by_id = dict((int(param.get("id")),param) for param in orig_ladtree.iter("param") if param.get("group") in ["0","3"])
orig_message_ids = sorted(orig_message_params_by_id.keys())
#print "orig_message_ids",orig_message_ids
message_params_by_id = dict((int(param.get("id")),param) for param in ladtree.iter("param") if param.get("group") in ["0","3"])
message_ids = sorted(message_params_by_id.keys())
#print "message_ids",message_ids
if (set(message_ids) != set(orig_message_ids)):
print("mismatch in message ids!")
print("added",set(message_ids) - set(orig_message_ids))
print("removed",set(orig_message_ids) - set(message_ids))
else:
print("message ids OK")
def remove_joint_by_name(tree, name):
print("remove joint:",name)
elt = get_element_by_name(tree,name)
while elt is not None:
children = list(elt)
parent = elt.getparent()
print("graft",[e.get("name") for e in children],"into",parent.get("name"))
print("remove",elt.get("name"))
#parent_children = list(parent)
loc = parent.index(elt)
parent[loc:loc+1] = children
elt[:] = []
print("parent now:",[e.get("name") for e in list(parent)])
elt = get_element_by_name(tree,name)
def compare_skel_trees(atree,btree):
diffs = {}
realdiffs = {}
a_missing = set()
b_missing = set()
a_names = set(e.get("name") for e in atree.getroot().iter() if e.get("name"))
b_names = set(e.get("name") for e in btree.getroot().iter() if e.get("name"))
print("a_names\n ",str("\n ").join(sorted(list(a_names))))
print()
print("b_names\n ","\n ".join(sorted(list(b_names))))
all_names = set.union(a_names,b_names)
for name in all_names:
if not name:
continue
a_element = get_element_by_name(atree,name)
b_element = get_element_by_name(btree,name)
if a_element is None or b_element is None:
print("something not found for",name,a_element,b_element)
if a_element is not None and b_element is not None:
all_attrib = set.union(set(a_element.attrib.keys()),set(b_element.attrib.keys()))
print(name,all_attrib)
for att in all_attrib:
if a_element.get(att) != b_element.get(att):
if not att in diffs:
diffs[att] = set()
diffs[att].add(name)
print("tuples",name,att,float_tuple(a_element.get(att)),float_tuple(b_element.get(att)))
if float_tuple(a_element.get(att)) != float_tuple(b_element.get(att)):
print("diff in",name,att)
if not att in realdiffs:
realdiffs[att] = set()
realdiffs[att].add(name)
for att in diffs:
print("Differences in",att)
for name in sorted(diffs[att]):
print(" ",name)
for att in realdiffs:
print("Real differences in",att)
for name in sorted(diffs[att]):
print(" ",name)
a_missing = b_names.difference(a_names)
b_missing = a_names.difference(b_names)
if len(a_missing) or len(b_missing):
print("Missing from comparison")
for name in a_missing:
print(" ",name)
print("Missing from infile")
for name in b_missing:
print(" ",name)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="process SL avatar_skeleton/avatar_lad files")
parser.add_argument("--verbose", action="store_true",help="verbose flag")
parser.add_argument("--ogfile", help="specify file containing base bones", default="avatar_skeleton_orig.xml")
parser.add_argument("--ref_file", help="specify another file containing replacements for missing fields")
parser.add_argument("--lad_file", help="specify avatar_lad file to check", default="avatar_lad.xml")
parser.add_argument("--orig_lad_file", help="specify avatar_lad file to compare to", default="avatar_lad_orig.xml")
parser.add_argument("--aliases", help="specify file containing bone aliases")
parser.add_argument("--validate", action="store_true", help="check specified input file for validity")
parser.add_argument("--fix", action="store_true", help="try to correct errors")
parser.add_argument("--remove", nargs="+", help="remove specified joints")
parser.add_argument("--list", action="store_true", help="list joint names")
parser.add_argument("--compare", help="alternate skeleton file to compare")
parser.add_argument("--slider_info", help="information about the lad file sliders and affected bones", action="store_true")
parser.add_argument("infilename", nargs="?", help="name of a skel .xml file to input", default="avatar_skeleton.xml")
parser.add_argument("outfilename", nargs="?", help="name of a skel .xml file to output")
args = parser.parse_args()
tree = etree.parse(args.infilename)
aliases = {}
if args.aliases:
altree = etree.parse(args.aliases)
aliases = get_aliases(altree)
# Parse input files
ogtree = None
reftree = None
ladtree = None
orig_ladtree = None
if args.ogfile:
ogtree = etree.parse(args.ogfile)
if args.ref_file:
reftree = etree.parse(args.ref_file)
if args.lad_file:
ladtree = etree.parse(args.lad_file)
if args.orig_lad_file:
orig_ladtree = etree.parse(args.orig_lad_file)
if args.remove:
for name in args.remove:
remove_joint_by_name(tree,name)
# Do processing
if args.validate and ogtree:
validate_skel_tree(tree, ogtree, reftree)
if args.validate and ladtree:
validate_lad_tree(ladtree, tree, orig_ladtree)
if args.fix and ogtree:
validate_skel_tree(tree, ogtree, reftree, True)
if args.list and tree:
list_skel_tree(tree)
if args.compare and tree:
compare_tree = etree.parse(args.compare)
compare_skel_trees(compare_tree,tree)
if ladtree and tree and args.slider_info:
slider_info(ladtree,tree)
if args.outfilename:
f = open(args.outfilename,"w")
print(etree.tostring(tree, pretty_print=True), file=f) #need update to get: , short_empty_elements=True)