diff options
| author | Brad Payne (Vir Linden) <vir@lindenlab.com> | 2016-03-24 13:32:05 -0400 | 
|---|---|---|
| committer | Brad Payne (Vir Linden) <vir@lindenlab.com> | 2016-03-24 13:32:05 -0400 | 
| commit | bf9626c0a985510add0ef459af4f515111073630 (patch) | |
| tree | a9aee15c0d2a94fa3831178cf3707cb9f0220e56 | |
| parent | e19c4098d4af38271549a8fbe697138b5acbbba1 (diff) | |
SL-353 - bento: utilities for skeleton and anim file manipulation.
| -rw-r--r-- | scripts/content_tools/anim_tool.py | 571 | ||||
| -rw-r--r-- | scripts/content_tools/skel_tool.py | 345 | 
2 files changed, 916 insertions, 0 deletions
| diff --git a/scripts/content_tools/anim_tool.py b/scripts/content_tools/anim_tool.py new file mode 100644 index 0000000000..deac2607ba --- /dev/null +++ b/scripts/content_tools/anim_tool.py @@ -0,0 +1,571 @@ +#!runpy.sh + +"""\ + +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. + +$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 sys +import struct +import StringIO +import math +import argparse +import random +from lxml import etree + +U16MAX = 65535 +OOU16MAX = 1.0/(float)(U16MAX) + +LL_MAX_PELVIS_OFFSET = 5.0 + +class FilePacker(object): +    def __init__(self): +        self.data = StringIO.StringIO() +        self.offset = 0 + +    def write(self,filename): +        f = open(filename,"wb") +        f.write(self.data.getvalue()) +        f.close() + +    def pack(self,fmt,*args): +        buf = struct.pack(fmt, *args) +        self.offset += struct.calcsize(fmt) +        self.data.write(buf) + +    def pack_string(self,str,size=0): +        buf = str + "\000" +        if size and (len(buf) < size): +            buf += "\000" * (size-len(buf)) +        self.data.write(buf) +         +class FileUnpacker(object): +    def __init__(self, filename): +        f = open(filename,"rb") +        self.data = f.read() +        self.offset = 0 + +    def unpack(self,fmt): +        result = struct.unpack_from(fmt, self.data, self.offset) +        self.offset += struct.calcsize(fmt) +        return result +     +    def unpack_string(self, size=0): +        result = "" +        i = 0 +        while (self.data[self.offset+i] != "\000"): +            result += self.data[self.offset+i] +            i += 1 +        i += 1 +        if size: +            # fixed-size field for the string +            i = size +        self.offset += i +        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 Exception("U16 out of range: "+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 BadFormat(Exception): +    pass + +class RotKey(object): +    def __init__(self): +        pass + +    def unpack(self, anim, fup): +        (self.time_short, ) = fup.unpack("<H") +        self.time = U16_to_F32(self.time_short, 0.0, anim.duration) +        (x,y,z) = fup.unpack("<HHH") +        self.rotation = [U16_to_F32(i, -1.0, 1.0) for i in (x,y,z)] + +    def dump(self, f): +        print >>f, "    rot_key: t",self.time,"st",self.time_short,"rot",",".join([str(f) for f in self.rotation]) + +    def pack(self, anim, fp): +        if not hasattr(self,"time_short"): +            self.time_short = F32_to_U16(self.time, 0.0, anim.duration) +        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): +        pass + +    def unpack(self, anim, fup): +        (self.time_short, ) = fup.unpack("<H") +        self.time = U16_to_F32(self.time_short, 0.0, anim.duration) +        (x,y,z) = fup.unpack("<HHH") +        self.position = [U16_to_F32(i, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET) for i in (x,y,z)] + +    def dump(self, f): +        print >>f, "    pos_key: t",self.time,"pos ",",".join([str(f) for f in self.position]) +         +    def pack(self, anim, fp): +        if not hasattr(self,"time_short"): +            self.time_short = F32_to_U16(self.time, 0.0, anim.duration) +        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): +    def __init__(self): +        pass + +    def unpack(self, anim, fup): +        (self.chain_length, self.constraint_type) = fup.unpack("<BB") +        self.source_volume = fup.unpack_string(16) +        self.source_offset = fup.unpack("<fff") +        self.target_volume = fup.unpack_string(16) +        self.target_offset = fup.unpack("<fff") +        self.target_dir = fup.unpack("<fff") +        fmt = "<ffff" +        (self.ease_in_start, self.ease_in_stop, self.ease_out_start, self.ease_out_stop) = fup.unpack("<ffff") + +    def pack(self, anim, 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 >>f, "  constraint:" +        print >>f, "    chain_length",self.chain_length +        print >>f, "    constraint_type",self.constraint_type +        print >>f, "    source_volume",self.source_volume +        print >>f, "    source_offset",self.source_offset +        print >>f, "    target_volume",self.target_volume +        print >>f, "    target_offset",self.target_offset +        print >>f, "    target_dir",self.target_dir +        print >>f, "    ease_in_start",self.ease_in_start +        print >>f, "    ease_in_stop",self.ease_in_stop +        print >>f, "    ease_out_start",self.ease_out_start +        print >>f, "    ease_out_stop",self.ease_out_stop +         +class Constraints(object): +    def __init__(self): +        pass + +    def unpack(self, anim, fup): +        (self.num_constraints, ) = fup.unpack("<i") +        self.constraints = [] +        for i in xrange(self.num_constraints): +            constraint = Constraint() +            constraint.unpack(anim, fup) +            self.constraints.append(constraint) + +    def pack(self, anim, fp): +        fp.pack("<i",self.num_constraints) +        for c in self.constraints: +            c.pack(anim,fp) + +    def dump(self, f): +        print >>f, "constraints:",self.num_constraints +        for c in self.constraints: +            c.dump(f) + +class PositionCurve(object): +    def __init__(self): +        self.num_pos_keys = 0 +        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 + +    def unpack(self, anim, fup): +        (self.num_pos_keys, ) = fup.unpack("<i") +        self.keys = [] +        for k in xrange(0,self.num_pos_keys): +            pos_key = PosKey() +            pos_key.unpack(anim, fup) +            self.keys.append(pos_key) + +    def pack(self, anim, fp): +        fp.pack("<i",self.num_pos_keys) +        for k in self.keys: +            k.pack(anim, fp) + +    def dump(self, f): +        print >>f, "  position_curve:" +        print >>f, "    num_pos_keys", self.num_pos_keys +        for k in xrange(0,self.num_pos_keys): +            self.keys[k].dump(f) + +class RotationCurve(object): +    def __init__(self): +        self.num_rot_keys = 0 +        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 + +    def unpack(self, anim, fup): +        (self.num_rot_keys, ) = fup.unpack("<i") +        self.keys = [] +        for k in xrange(0,self.num_rot_keys): +            rot_key = RotKey() +            rot_key.unpack(anim, fup) +            self.keys.append(rot_key) + +    def pack(self, anim, fp): +        fp.pack("<i",self.num_rot_keys) +        for k in self.keys: +            k.pack(anim, fp) + +    def dump(self, f): +        print >>f, "  rotation_curve:" +        print >>f, "    num_rot_keys", self.num_rot_keys +        for k in xrange(0,self.num_rot_keys): +            self.keys[k].dump(f) +             +class JointInfo(object): +    def __init__(self): +        pass + +    def unpack(self, anim, fup): +        self.joint_name = fup.unpack_string() +        (self.joint_priority, ) = fup.unpack("<i") +        self.rotation_curve = RotationCurve() +        self.rotation_curve.unpack(anim, fup) +        self.position_curve = PositionCurve() +        self.position_curve.unpack(anim, fup) + +    def pack(self, anim, fp): +        fp.pack_string(self.joint_name) +        fp.pack("<i", self.joint_priority) +        self.rotation_curve.pack(anim, fp) +        self.position_curve.pack(anim, fp) + +    def dump(self, f): +        print >>f, "joint:" +        print >>f, "  joint_name:",self.joint_name +        print >>f, "  joint_priority:",self.joint_priority +        self.rotation_curve.dump(f) +        self.position_curve.dump(f) + +class Anim(object): +    def __init__(self, filename=None): +        if filename: +            self.read(filename) + +    def read(self, filename): +        fup = FileUnpacker(filename) +        self.unpack(fup) + +    # 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)) + +        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, self.num_joints) = fup.unpack("@ffiffII") +         +        self.joints = [] +        for j in xrange(0,self.num_joints): +            joint_info = JointInfo() +            joint_info.unpack(self, fup) +            self.joints.append(joint_info) +            print "unpacked joint",joint_info.joint_name +        self.constraints = Constraints() +        self.constraints.unpack(self, fup) +        self.data = fup.data +         +    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, self.num_joints) +        for j in self.joints: +            j.pack(anim, fp) +        self.constraints.pack(anim, fp) + +    def dump(self, filename="-"): +        if filename=="-": +            f = sys.stdout +        else: +            f = open(filename,"w") +        print >>f, "versions: ", self.version, self.sub_version +        print >>f, "base_priority: ", self.base_priority +        print >>f, "duration: ", self.duration +        print >>f, "emote_name: ", self.emote_name +        print >>f, "loop_in_point: ", self.loop_in_point +        print >>f, "loop_out_point: ", self.loop_out_point +        print >>f, "loop: ", self.loop +        print >>f, "ease_in_duration: ", self.ease_in_duration +        print >>f, "ease_out_duration: ", self.ease_out_duration +        print >>f, "hand_pose", self.hand_pose +        print >>f, "num_joints", self.num_joints +        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 +        f = open(filename,"wb") +        f.write(self.data) +        f.close() +         +    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): +            j = JointInfo() +            j.joint_name = name +            j.joint_priority = priority +            j.rotation_curve = RotationCurve() +            j.position_curve = PositionCurve() +            self.joints.append(j) +            self.num_joints = len(self.joints) + +    def delete_joint(self, name): +        j = self.find_joint(name) +        if j: +            anim.joints.remove(j) +            anim.num_joints = len(self.joints) + +    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 args.verbose: +                print "adding positions",j.joint_name,positions +            j.joint_priority = 4 +            j.position_curve.num_pos_keys = len(positions) +            j.position_curve.keys = [] +            for i,pos in enumerate(positions): +                key = PosKey() +                key.time = self.duration * i / (len(positions) - 1) +                key.time_short = F32_to_U16(key.time, 0.0, self.duration) +                key.position = pos +                j.position_curve.keys.append(key) + +    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.num_rot_keys = len(rotations) +            j.rotation_curve.keys = [] +            for i,pos in enumerate(rotations): +                key = RotKey() +                key.time = self.duration * i / (len(rotations) - 1) +                key.time_short = F32_to_U16(key.time, 0.0, self.duration) +                key.rotation = pos +                j.rotation_curve.keys.append(key) + +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 j.rotation_curve.num_rot_keys +        j.joint_priority = 4 +        j.rotation_curve.num_rot_keys = 2 +        j.rotation_curve.keys = [] +        key1 = RotKey() +        key1.time_short = 0 +        key1.time = U16_to_F32(key1.time_short, 0.0, anim.duration) +        key1.rotation = rot1 +        key2 = RotKey() +        key2.time_short = U16MAX +        key2.time = U16_to_F32(key2.time_short, 0.0, anim.duration) +        key2.rotation = rot2 +        j.rotation_curve.keys.append(key1) +        j.rotation_curve.keys.append(key2) + +def float_triple(arg): +    vals = arg.split() +    if len(vals)==3: +        return [float(x) for x in vals] +    else: +        raise Exception("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): +    if skel_tree and lad_tree: +        matches = [element.get("name") for element in skel_tree.getroot().iter() if (element.get("name") in names) or (element.tag in names)] +        matches.extend([element.get("name") for element in lad_tree.getroot().iter() if (element.get("name") in names) or (element.tag in names)]) +        return matches +    else: +        return names + +if __name__ == "__main__": + +    parser = argparse.ArgumentParser(description="process SL animations") +    parser.add_argument("--verbose", help="verbose flag", action="store_true") +    parser.add_argument("--dump", help="dump to specified file") +    parser.add_argument("--rot", help="specify sequence of rotations", type=float_triple, nargs="+") +    parser.add_argument("--rand_pos", help="request random positions", action="store_true") +    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("--delete_joints", help="specify joints to be deleted", nargs="+") +    parser.add_argument("--joints", help="specify joints to be added or modified", nargs="+") +    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") +    parser.add_argument("--lad", help="name of the avatar_lad file") +    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() + +    print "anim_tool.py: " + " ".join(sys.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 + +    try: +        anim = Anim(args.infilename) +        skel_tree = None +        lad_tree = None +        joints = [] +        if args.skel: +            skel_tree = etree.parse(args.skel) +        if args.lad: +            lad_tree = etree.parse(args.lad) +        if args.joints: +            joints = resolve_joints(args.joints, skel_tree, lad_tree) +            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: +            for joint in joints: +                pos_array = list(tuple(random.uniform(-1,1) for i in xrange(3)) for j in xrange(2)) +                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) +                if elt is None: +                    elt = get_joint_by_name(lad_tree,joint) +                if elt is not None: +                    pos_array = [] +                    pos_array.append(get_elt_pos(elt)) +                    pos_array.append(pos_array[0]) +                    anim.add_pos([joint], pos_array) +                else: +                    print "no elt or no pos data for",joint +        if args.dump: +            anim.dump(args.dump) +        if args.summary: +            anim.summary() +        if args.outfilename: +            anim.write(args.outfilename) +    except: +        raise + diff --git a/scripts/content_tools/skel_tool.py b/scripts/content_tools/skel_tool.py new file mode 100644 index 0000000000..d46d6ca3f3 --- /dev/null +++ b/scripts/content_tools/skel_tool.py @@ -0,0 +1,345 @@ +#!runpy.sh + +"""\ + +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 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): +    try: +        return [float(e) for e in str.split(" ")] +    except: +        return (0,0,0) + +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_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_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) +def validate_tree(tree, ogtree, reftree, fix=False): +    print "validate_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_precision_rules(element) +        for field in ["pos","pivot"]: +            enforce_symmetry(tree, element, field, fix) + +        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 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_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 animations") +    parser.add_argument("--ogfile", help="specify file containing base bones") +    parser.add_argument("--ref_file", help="specify another file containing replacements for missing fields") +    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("infilename", help="name of a skel .xml file to input") +    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) + +    ogtree = None +    reftree = None +    if args.ogfile: +        ogtree = etree.parse(args.ogfile) + +    if args.ref_file: +        reftree = etree.parse(args.ref_file) + +    if args.remove: +        for name in args.remove: +            remove_joint_by_name(tree,name) + +    if args.validate and ogtree: +        validate_tree(tree, ogtree, reftree) + +    if args.fix and ogtree: +        validate_tree(tree, ogtree, reftree, True) + +    if args.list and tree: +        list_tree(tree) + +    if args.compare and tree: +        compare_tree = etree.parse(args.compare) +        compare_trees(compare_tree,tree) + +    if args.outfilename: +        f = open(args.outfilename,"w") +        print >>f, etree.tostring(tree, pretty_print=True) #need update to get: , short_empty_elements=True) + | 
