- Main Page
- Related Pages
- Modules
- Classes
- Examples

# animFileUtils.cpp

    //-
    // ==========================================================================
    // Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All
    // rights reserved.
    //
    // The coded instructions, statements, computer programs, and/or related
    // material (collectively the "Data") in these files contain unpublished
    // information proprietary to Autodesk, Inc. ("Autodesk") and/or its
    // licensors, which is protected by U.S. and Canadian federal copyright
    // law and by international treaties.
    //
    // The Data is provided for use exclusively by You. You have the right
    // to use, modify, and incorporate this Data into other products for
    // purposes authorized by the Autodesk software license agreement,
    // without fee.
    //
    // The copyright notices in the Software and this entire statement,
    // including the above license grant, this restriction and the
    // following disclaimer, must be included in all copies of the
    // Software, in whole or in part, and all derivative works of
    // the Software, unless such copies or derivative works are solely
    // in the form of machine-executable object code generated by a
    // source language processor.
    //
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
    // AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED
    // WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF
    // NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
    // PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR
    // TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS
    // BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL,
    // DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK
    // AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY
    // OR PROBABILITY OF SUCH DAMAGES.
    //
    // ==========================================================================
    //+

    //
    //  File Name: animFileUtils.cc
    //
    //  Description:
    //      Utility classes to read and write .anim files.
    //

    #include <stdlib.h>
    #include <string.h>
    #include <limits.h>
    #include <math.h>

    #include <maya/MIOStream.h>
    #include <maya/MFStream.h>

    #include "animFileUtils.h"
    #include "animImportExportStrings.h"

    #include <maya/MGlobal.h>
    #include <maya/MString.h>

    #include <maya/MFnAnimCurve.h>
    #include <maya/MAnimCurveClipboard.h>
    #include <maya/MAnimCurveClipboardItem.h>
    #include <maya/MAnimCurveClipboardItemArray.h>

    #if defined (OSMac_)
    using namespace std;
    #endif
    //-------------------------------------------------------------------------
    //  Class animUnitNames
    //-------------------------------------------------------------------------

    //  String names for units.
    //
    const char *kMmString =         "mm";
    const char *kCmString =         "cm";
    const char *kMString =          "m";
    const char *kKmString =         "km";
    const char *kInString =         "in";
    const char *kFtString =         "ft";
    const char *kYdString =         "yd";
    const char *kMiString =         "mi";

    const char *kMmLString =        "millimeter";
    const char *kCmLString =        "centimeter";
    const char *kMLString =         "meter";
    const char *kKmLString =        "kilometer";
    const char *kInLString =        "inch";
    const char *kFtLString =        "foot";
    const char *kYdLString =        "yard";
    const char *kMiLString =        "mile";

    const char *kRadString =        "rad";
    const char *kDegString =        "deg";
    const char *kMinString =        "min";
    const char *kSecString =        "sec";

    const char *kRadLString =       "radian";
    const char *kDegLString =       "degree";
    const char *kMinLString =       "minute";
    const char *kSecLString =       "second";

    const char *kHourTString =      "hour";
    const char *kMinTString =       "min";
    const char *kSecTString =       "sec";
    const char *kMillisecTString =  "millisec";

    const char *kGameTString =      "game";
    const char *kFileTString =      "film";
    const char *kPalTString =       "pal";
    const char *kNtscTString =      "ntsc";
    const char *kShowTString =      "show";
    const char *kPalFTString =      "palf";
    const char *kNtscFTString =     "ntscf";

    const char *kUnitlessString = "unitless";
    const char *kUnknownTimeString =    "Unknown Time Unit";
    const char *kUnknownAngularString = "Unknown Angular Unit";
    const char *kUnknownLinearString =  "Unknown Linear Unit";

    animUnitNames::animUnitNames()
    //
    //  Description:
    //      Class constructor.
    //
    {
    }

    animUnitNames::~animUnitNames()
    //
    //  Description:
    //      Class destructor.
    //
    {
    }

    /* static */
    void animUnitNames::setToLongName(const MAngle::Unit& unit, MString& name)
    //
    //  Description:
    //      Sets the string with the long text name of the angle unit.
    //
    {
        switch(unit) {
            case MAngle::kDegrees:
                name.set(kDegLString);
                break;
            case MAngle::kRadians:
                name.set(kRadLString);
                break;
            case MAngle::kAngMinutes:
                name.set(kMinLString);
                break;
            case MAngle::kAngSeconds:
                name.set(kSecLString);
                break;
            default:
                name.set(kUnknownAngularString);
                break;
        }
    }

    /* static */
    void animUnitNames::setToShortName(const MAngle::Unit& unit, MString& name)
    //
    //  Description:
    //      Sets the string with the short text name of the angle unit.
    //
    {
        switch(unit) {
            case MAngle::kDegrees:
                name.set(kDegString);
                break;
            case MAngle::kRadians:
                name.set(kRadString);
                break;
            case MAngle::kAngMinutes:
                name.set(kMinString);
                break;
            case MAngle::kAngSeconds:
                name.set(kSecString);
                break;
            default:
                name.set(kUnknownAngularString);
                break;
        }
    }

    /* static */
    void animUnitNames::setToLongName(const MDistance::Unit& unit, MString& name)
    //
    //  Description:
    //      Sets the string with the long text name of the distance unit.
    //
    {
        switch(unit) {
            case MDistance::kInches:
                name.set(kInLString);
                break;
            case MDistance::kFeet:
                name.set(kFtLString);
                break;
            case MDistance::kYards:
                name.set(kYdLString);
                break;
            case MDistance::kMiles:
                name.set(kMiLString);
                break;
            case MDistance::kMillimeters:
                name.set(kMmLString);
                break;
            case MDistance::kCentimeters:
                name.set(kCmLString);
                break;
            case MDistance::kKilometers:
                name.set(kKmLString);
                break;
            case MDistance::kMeters:
                name.set(kMLString);
                break;
            default:
                name.set(kUnknownLinearString);
                break;
        }
    }

    /* static */
    void animUnitNames::setToShortName(const MDistance::Unit& unit, MString& name)
    //
    //  Description:
    //      Sets the string with the short text name of the distance unit.
    //
    {
        switch(unit) {
            case MDistance::kInches:
                name.set(kInString);
                break;
            case MDistance::kFeet:
                name.set(kFtString);
                break;
            case MDistance::kYards:
                name.set(kYdString);
                break;
            case MDistance::kMiles:
                name.set(kMiString);
                break;
            case MDistance::kMillimeters:
                name.set(kMmString);
                break;
            case MDistance::kCentimeters:
                name.set(kCmString);
                break;
            case MDistance::kKilometers:
                name.set(kKmString);
                break;
            case MDistance::kMeters:
                name.set(kMString);
                break;
            default:
                name.set(kUnknownLinearString);
                break;
        }
    }

    /* static */
    void animUnitNames::setToLongName(const MTime::Unit &unit, MString &name)
    //
    //  Description:
    //      Sets the string with the long text name of the time unit.
    //
    {
        switch(unit) {
            case MTime::kHours:
                name.set(kHourTString);
                break;
            case MTime::kMinutes:
                name.set(kMinTString);
                break;
            case MTime::kSeconds:
                name.set(kSecTString);
                break;
            case MTime::kMilliseconds:
                name.set(kMillisecTString);
                break;
            case MTime::kGames:
                name.set(kGameTString);
                break;
            case MTime::kFilm:
                name.set(kFileTString);
                break;
            case MTime::kPALFrame:
                name.set(kPalTString);
                break;
            case MTime::kNTSCFrame:
                name.set(kNtscTString);
                break;
            case MTime::kShowScan:
                name.set(kShowTString);
                break;
            case MTime::kPALField:
                name.set(kPalFTString);
                break;
            case MTime::kNTSCField:
                name.set(kNtscFTString);
                break;
            default:
                name.set(kUnknownTimeString);
                break;
        }
    }

    /* static */
    void animUnitNames::setToShortName(const MTime::Unit &unit, MString &name)
    //
    //  Description:
    //      Sets the string with the short text name of the time unit.
    //
    {
        setToLongName(unit, name);
    }

    /* static */
    bool animUnitNames::setFromName(const MString &str, MAngle::Unit &unit)
    //
    //  Description:
    //      The angle unit is set based on the passed string. If the string
    //      is not recognized, the angle unit is set to MAngle::kInvalid.
    //
    {
        bool state = true;

        const char *name = str.asChar();

        if ((strcmp(name, kDegString) == 0) ||
            (strcmp(name, kDegLString) == 0)) {
            unit = MAngle::kDegrees;
        } else if ( (strcmp(name, kRadString) == 0) ||
                    (strcmp(name, kRadLString) == 0)) {
            unit = MAngle::kRadians;
        } else if ( (strcmp(name, kMinString) == 0) ||
                    (strcmp(name, kMinLString) == 0)) {
            unit = MAngle::kAngMinutes;
        } else if ( (strcmp(name, kSecString) == 0) ||
                    (strcmp(name, kSecLString) == 0)) {
            unit = MAngle::kAngSeconds;
        } else {
            //  This is not a recognized angular unit.
            //
            unit = MAngle::kInvalid;
            MStatus stat;
            MString msg;
            // Use format to place variable string into message
            MString msgFmt = MStringResource::getString(kInvalidAngleUnits, stat);
            msg.format(msgFmt, str);
            MGlobal::displayError(msg);
            state = false;
        }

        return state;
    }

    /* static */
    bool animUnitNames::setFromName(const MString &str, MDistance::Unit &unit)
    //
    //  Description:
    //      The distance unit is set based on the passed string. If the string
    //      is not recognized, the distance unit is set to MDistance::kInvalid.
    //
    {
        bool state = true;

        const char *name = str.asChar();

        if ((strcmp(name, kInString) == 0) ||
            (strcmp(name, kInLString) == 0)) {
            unit = MDistance::kInches;
        } else if ( (strcmp(name, kFtString) == 0) ||
                    (strcmp(name, kFtLString) == 0)) {
            unit = MDistance::kFeet;
        } else if ( (strcmp(name, kYdString) == 0) ||
                    (strcmp(name, kYdLString) == 0)) {
            unit = MDistance::kYards;
        } else if ( (strcmp(name, kMiString) == 0) ||
                    (strcmp(name, kMiLString) == 0)) {
            unit = MDistance::kMiles;
        } else if ( (strcmp(name, kMmString) == 0) ||
                    (strcmp(name, kMmLString) == 0)) {
            unit = MDistance::kMillimeters;
        } else if ( (strcmp(name, kCmString) == 0) ||
                    (strcmp(name, kCmLString) == 0)) {
            unit = MDistance::kCentimeters;
        } else if ( (strcmp(name, kKmString) == 0) ||
                    (strcmp(name, kKmLString) == 0)) {
            unit = MDistance::kKilometers;
        } else if ( (strcmp(name, kMString) == 0) ||
                    (strcmp(name, kMLString) == 0)) {
            unit = MDistance::kMeters;
        } else {
            //  This is not a recognized distance unit.
            //
            state = false;
            MStatus stat;
            MString msg;
            // Use format to place variable string into message
            MString msgFmt = MStringResource::getString(kInvalidLinearUnits, stat);
            msg.format(msgFmt, str);
            MGlobal::displayError(msg);
            unit = MDistance::kInvalid;
        }

        return state;
    }

    /* static */
    bool animUnitNames::setFromName(const MString &str, MTime::Unit &unit)
    //
    //  Description:
    //      The time unit is set based on the passed string. If the string
    //      is not recognized, the time unit is set to MTime::kInvalid.
    //
    {
        bool state = true;
        const char *name = str.asChar();

        if (strcmp(name, kHourTString) == 0) {
            unit = MTime::kHours;
        } else if (strcmp(name, kMinTString) == 0) {
            unit = MTime::kMinutes;
        } else if (strcmp(name, kSecTString) == 0) {
            unit = MTime::kSeconds;
        } else if (strcmp(name, kMillisecTString) == 0) {
            unit = MTime::kMilliseconds;
        } else if (strcmp(name, kGameTString) == 0) {
            unit = MTime::kGames;
        } else if (strcmp(name, kFileTString) == 0) {
            unit = MTime::kFilm;
        } else if (strcmp(name, kPalTString) == 0) {
            unit = MTime::kPALFrame;
        } else if (strcmp(name, kNtscTString) == 0) {
            unit = MTime::kNTSCFrame;
        } else if (strcmp(name, kShowTString) == 0) {
            unit = MTime::kShowScan;
        } else if (strcmp(name, kPalFTString) == 0) {
            unit = MTime::kPALField;
        } else if (strcmp(name, kNtscFTString) == 0) {
            unit = MTime::kNTSCField;
        } else {
            //  This is not a recognized time unit.
            //
            unit = MTime::kInvalid;
            MStatus stat;
            MString msg;
            // Use format to place variable string into message
            MString msgFmt = MStringResource::getString(kInvalidTimeUnits, stat);
            msg.format(msgFmt, str);
            MGlobal::displayError(msg);
            state = false;
        }

        return state;
    }

    //-------------------------------------------------------------------------
    //  Class animBase
    //-------------------------------------------------------------------------

    // Tangent type words
    //
    const char *kWordTangentGlobal = "global";
    const char *kWordTangentFixed = "fixed";
    const char *kWordTangentLinear = "linear";
    const char *kWordTangentFlat = "flat";
    const char *kWordTangentSmooth = "spline";
    const char *kWordTangentStep = "step";
    const char *kWordTangentSlow = "slow";
    const char *kWordTangentFast = "fast";
    const char *kWordTangentClamped = "clamped";
    const char *kWordTangentPlateau = "plateau";
    const char *kWordTangentStepNext = "stepnext";
    const char *kWordTangentAuto = "auto";

    // Infinity type words
    //
    const char *kWordConstant = "constant";
    const char *kWordLinear = "linear";
    const char *kWordCycle = "cycle";
    const char *kWordCycleRelative = "cycleRelative";
    const char *kWordOscillate = "oscillate";

    //  Param Curve types
    //
    const char *kWordTypeUnknown = "unknown";
    const char *kWordTypeLinear = "linear";
    const char *kWordTypeAngular = "angular";
    const char *kWordTypeTime = "time";
    const char *kWordTypeUnitless = "unitless";

    //  Keywords
    //
    const char *kAnim = "anim";
    const char *kAnimData = "animData";
    const char *kMovData = "movData";
    const char *kMayaVersion = "mayaVersion";
    const char *kAnimVersion = "animVersion";

    const char *kTimeUnit = "timeUnit";
    const char *kLinearUnit = "linearUnit";
    const char *kAngularUnit = "angularUnit";
    const char *kStartTime = "startTime";
    const char *kEndTime = "endTime";
    const char *kStartUnitless = "startUnitless";
    const char *kEndUnitless = "endUnitless";

    // animVersions:
    //
    const char *kAnimVersionString = "1.1";
    // const double kVersion1 = 1.0;                        // initial release
    const double kVersionNonWeightedAndBreakdowns = 1.1;    // added support for non-weighted curves and breakdowns

    const char *kTwoSpace = "  ";

    //  animData keywords
    //
    const char *kInputString = "input";
    const char *kOutputString = "output";
    const char *kWeightedString = "weighted";
    const char *kPreInfinityString = "preInfinity";
    const char *kPostInfinityString = "postInfinity";
    const char *kInputUnitString = "inputUnit";
    const char *kOutputUnitString = "outputUnit";
    const char *kTanAngleUnitString = "tangentAngleUnit";
    const char *kKeysString = "keys";

    //  special characters
    //
    const char kSemiColonChar   = ';';
    const char kSpaceChar       = ' ';
    const char kTabChar         = '\t';
    const char kHashChar        = '#';
    const char kNewLineChar     = '\n';
    const char kSlashChar       = '/';
    const char kBraceLeftChar   = '{';
    const char kBraceRightChar  = '}';
    const char kDoubleQuoteChar = '"';

    animBase::animBase ()
    //
    //  Description:
    //      The constructor.
    //
    {
        resetUnits();
    }

    animBase::~animBase()
    //
    //  Description:
    //      The destructor.
    //
    {
    }

    void animBase::resetUnits()
    //
    //  Description:
    //      Reset the units used by this class to the ui units.
    //
    {
        timeUnit = MTime::uiUnit();
        linearUnit = MDistance::uiUnit();
        angularUnit = MAngle::uiUnit();
    }

    const char *
    animBase::tangentTypeAsWord(MFnAnimCurve::TangentType type)
    //
    //  Description:
    //      Returns a string with a test based desription of the passed
    //      MFnAnimCurve::TangentType.
    //
    {
        switch (type) {
            case MFnAnimCurve::kTangentGlobal:
                return (kWordTangentGlobal);
            case MFnAnimCurve::kTangentFixed:
                return (kWordTangentFixed);
            case MFnAnimCurve::kTangentLinear:
                return (kWordTangentLinear);
            case MFnAnimCurve::kTangentFlat:
                return (kWordTangentFlat);
            case MFnAnimCurve::kTangentSmooth:
                return (kWordTangentSmooth);
            case MFnAnimCurve::kTangentStep:
                return (kWordTangentStep);
            case MFnAnimCurve::kTangentStepNext:
                return (kWordTangentStepNext);
            case MFnAnimCurve::kTangentSlow:
                return (kWordTangentSlow);
            case MFnAnimCurve::kTangentFast:
                return (kWordTangentFast);
            case MFnAnimCurve::kTangentClamped:
                return (kWordTangentClamped);
            case MFnAnimCurve::kTangentPlateau:
                return (kWordTangentPlateau);
            case MFnAnimCurve::kTangentAuto:
                return (kWordTangentAuto);
            default:
                break;
        }
        return (kWordTangentGlobal);
    }

    MFnAnimCurve::TangentType
    animBase::wordAsTangentType (char *type)
    //
    //  Description:
    //      Returns a MFnAnimCurve::TangentType based on the passed string.
    //      If the string is not a recognized tangent type, tnen
    //      MFnAnimCurve::kTangentGlobal is returned.
    //
    {
        if (strcmp(type, kWordTangentGlobal) == 0) {
            return (MFnAnimCurve::kTangentGlobal);
        }
        if (strcmp(type, kWordTangentFixed) == 0) {
            return (MFnAnimCurve::kTangentFixed);
        }
        if (strcmp(type, kWordTangentLinear) == 0) {
            return (MFnAnimCurve::kTangentLinear);
        }
        if (strcmp(type, kWordTangentFlat) == 0) {
            return (MFnAnimCurve::kTangentFlat);
        }
        if (strcmp(type, kWordTangentSmooth) == 0) {
            return (MFnAnimCurve::kTangentSmooth);
        }
        if (strcmp(type, kWordTangentStep) == 0) {
            return (MFnAnimCurve::kTangentStep);
        }
        if (strcmp(type, kWordTangentStepNext) == 0) {
            return (MFnAnimCurve::kTangentStepNext);
        }
        if (strcmp(type, kWordTangentSlow) == 0) {
            return (MFnAnimCurve::kTangentSlow);
        }
        if (strcmp(type, kWordTangentFast) == 0) {
            return (MFnAnimCurve::kTangentFast);
        }
        if (strcmp(type, kWordTangentClamped) == 0) {
            return (MFnAnimCurve::kTangentClamped);
        }
        if (strcmp(type, kWordTangentPlateau) == 0) {
            return (MFnAnimCurve::kTangentPlateau);
        }
        if (strcmp(type, kWordTangentAuto) == 0) {
            return (MFnAnimCurve::kTangentAuto);
        }
        return (MFnAnimCurve::kTangentGlobal);
    }

    const char *
    animBase::infinityTypeAsWord(MFnAnimCurve::InfinityType type)
    //
    //  Description:
    //      Returns a string containing the name of the passed
    //      MFnAnimCurve::InfinityType type.
    //
    {
        switch (type) {
            case MFnAnimCurve::kConstant:
                return (kWordConstant);
            case MFnAnimCurve::kLinear:
                return (kWordLinear);
            case MFnAnimCurve::kCycle:
                return (kWordCycle);
            case MFnAnimCurve::kCycleRelative:
                return (kWordCycleRelative);
            case MFnAnimCurve::kOscillate:
                return (kWordOscillate);
            default:
                break;
        }
        return (kWordConstant);
    }

    MFnAnimCurve::InfinityType
    animBase::wordAsInfinityType(const char *type)
    //
    //  Description:
    //      Returns a MFnAnimCurve::InfinityType from the passed string.
    //      If the string does not match a recognized infinity type,
    //      MFnAnimCurve::kConstant is returned.
    //
    {
        if (strcmp(type, kWordConstant) == 0) {
            return(MFnAnimCurve::kConstant);
        } else if (strcmp(type, kWordLinear) == 0) {
            return (MFnAnimCurve::kLinear);
        } else if (strcmp(type, kWordCycle) == 0) {
            return (MFnAnimCurve::kCycle);
        } else if (strcmp(type, kWordCycleRelative) == 0) {
            return (MFnAnimCurve::kCycleRelative);
        } else if (strcmp(type, kWordOscillate) == 0) {
            return (MFnAnimCurve::kOscillate);
        }

        return (MFnAnimCurve::kConstant);
    }

    const char *
    animBase::outputTypeAsWord (MFnAnimCurve::AnimCurveType type)
    //
    //  Description:
    //      Returns a string identifying the output type of the
    //      passed MFnAnimCurve::AnimCurveType.
    //
    {
        switch (type) {
            case MFnAnimCurve::kAnimCurveTL:
            case MFnAnimCurve::kAnimCurveUL:
                return (kWordTypeLinear);
            case MFnAnimCurve::kAnimCurveTA:
            case MFnAnimCurve::kAnimCurveUA:
                return (kWordTypeAngular);
            case MFnAnimCurve::kAnimCurveTT:
            case MFnAnimCurve::kAnimCurveUT:
                return (kWordTypeTime);
            case MFnAnimCurve::kAnimCurveTU:
            case MFnAnimCurve::kAnimCurveUU:
                return (kWordTypeUnitless);
            case MFnAnimCurve::kAnimCurveUnknown:
                return (kWordTypeUnitless);
        }
        return (kWordTypeUnknown);
    }

    animBase::AnimBaseType
    animBase::wordAsInputType(const char *input)
    //
    //  Description:
    //      Returns an input type based on the passed string.
    //
    {
        if (strcmp(input, kWordTypeTime) == 0) {
            return animBase::kAnimBaseTime;
        } else {
            return animBase::kAnimBaseUnitless;
        }
    }

    animBase::AnimBaseType
    animBase::wordAsOutputType(const char *output)
    //
    //  Description:
    //      Returns a output type based on the passed string.
    //
    {
        if (strcmp(output, kWordTypeLinear) == 0) {
            return animBase::kAnimBaseLinear;
        } else if (strcmp(output, kWordTypeAngular) == 0) {
            return animBase::kAnimBaseAngular;
        } else if (strcmp(output, kWordTypeTime) == 0) {
            return animBase::kAnimBaseTime;
        } else {
            return animBase::kAnimBaseUnitless;
        }
    }

    const char *
    animBase::boolInputTypeAsWord(bool isUnitless)
    //
    //  Description:
    //      Returns a string based on a bool.
    //
    {
        if (isUnitless) {
            return (kWordTypeUnitless);
        } else {
            return (kWordTypeTime);
        }
    }

    MFnAnimCurve::AnimCurveType
    animBase::typeAsAnimCurveType(  animBase::AnimBaseType input,
                                    animBase::AnimBaseType output)
    //
    //  Description:
    //      Returns a MFnAnimCurve::AnimCurveType based on the passed
    //      input and output types. If the input and output types do
    //      not create a valid MFnAnimCurve::AnimCurveType, then a
    //      MFnAnimCurve::kAnimCurveUnknown is returned.
    //
    {
        MFnAnimCurve::AnimCurveType type = MFnAnimCurve::kAnimCurveUnknown;

        switch (output) {
            case kAnimBaseLinear:
                if (kAnimBaseUnitless == input) {
                    type = MFnAnimCurve::kAnimCurveUL;
                } else {
                    type = MFnAnimCurve::kAnimCurveTL;
                }
                break;
            case kAnimBaseAngular:
                if (kAnimBaseUnitless == input) {
                    type = MFnAnimCurve::kAnimCurveUA;
                } else {
                    type = MFnAnimCurve::kAnimCurveTA;
                }
                break;
            case kAnimBaseTime:
                if (kAnimBaseUnitless == input) {
                    type = MFnAnimCurve::kAnimCurveUT;
                } else {
                    type = MFnAnimCurve::kAnimCurveTT;
                }
                break;
            case kAnimBaseUnitless:
                if (kAnimBaseUnitless == input) {
                    type = MFnAnimCurve::kAnimCurveUU;
                } else {
                    type = MFnAnimCurve::kAnimCurveTU;
                }
                break;
            default:
                //  An unknown anim curve type.
                //
                break;
        }

        return type;
    }

    double animBase::asDouble (ifstream &clipFile)
    //
    //  Description:
    //      Reads the next bit of valid data as a double.
    //
    {
        advance(clipFile);

        double value;
        clipFile >> value;

        return (value);
    }

    bool animBase::isNextNumeric(ifstream &clipFile)
    //
    //  Description:
    //      The method skips past whitespace and comments and checks if
    //      the next character is numeric.
    //
    //      true is returned if the character is numeric.
    //
    {
        bool numeric = false;
        advance(clipFile);

        char next = clipFile.peek();
        if (next >= '0' && next <= '9') {
            numeric = true;
        }

        return numeric;
    }

    void animBase::advance (ifstream &clipFile)
    //
    //  Description:
    //      The method skips past all of the whitespace and commented lines
    //      in the ifstream. It will also ignore semi-colons.
    //
    {
        while (clipFile) {
            clipFile >> ws;

            char next = clipFile.peek();

            if (next == kSemiColonChar) {
                clipFile.ignore(1, kSemiColonChar);
                continue;
            }

            if (next == kSlashChar || next == kHashChar) {
                clipFile.ignore(INT_MAX, kNewLineChar);
                continue;
            }

            break;
        }
    }

    char* animBase::asWord (ifstream &clipFile, bool includeWS /* false */)
    //
    //  Description:
    //      Returns the next string of characters in an ifstream. The string
    //      ends when whitespace or a semi-colon is encountered. If the
    //      includeWS argument is true, the string will not end if a white
    //      space character is encountered.
    //
    //      If a double quote is detected '"', then verything up to the next
    //      double quote will be returned.
    //
    //      This method returns a pointer to a static variable, so its contents
    //      should be used immediately.
    //
    {
        static const int kBufLength = 1024;
        static char string[kBufLength];

        advance(clipFile);

        char *c = string;
        clipFile.read (c, 1);

        if (*c == kDoubleQuoteChar) {
            clipFile.read(c, 1);
            while(!clipFile.eof() && (*c != kDoubleQuoteChar)) {
                c++;
                if (c-string >= kBufLength) {
                    break;
                }
                clipFile.read(c, 1);
            }
        } else {

            //  Get the case of the '{' or '}' character
            //
            if (*c == kBraceLeftChar || *c == kBraceRightChar) {
                c++;
            } else {
                while(!clipFile.eof() && (*c != kSemiColonChar)) {
                    if (!includeWS && ((*c == kSpaceChar) || (*c == kTabChar))) {
                        break;
                    }
                    c++;
                    if (c-string >= kBufLength) {
                        break;
                    }
                    clipFile.read(c, 1);
                }
            }
        }
        *c = 0x00;

        return (string);
    }

    char animBase::asChar (ifstream &clipFile)
    //
    //  Description:
    //      Returns the next character of interest in the ifstream. All
    //      whitespace and commented lines are ignored.
    //
    {
        advance(clipFile);
        return clipFile.get();
    }

    bool animBase::isEquivalent(double a, double b)
    //
    //  Description:
    //      Returns true if the doubles are within the tolerance.
    //
    {
        const double tolerance = 1.0e-10;
        return ((a > b) ? (a - b <= tolerance) : (b - a <= tolerance));
    }

    //-----------------------------------------------------------------------------
    //  Class animReader
    //-----------------------------------------------------------------------------

    animReader::animReader ()
    //
    //  Description:
    //      Class constructor.
    //
    :   animVersion (1.0)
    ,   convertAnglesFromV2To3(false)
    ,   convertAnglesFromV3To2(false)
    {
    }

    animReader::~animReader()
    //
    //  Description:
    //      Class destructor.
    //
    {
    }

    MStatus
    animReader::readClipboard(ifstream &readAnim, MAnimCurveClipboard& cb)
    //
    //  Description:
    //      Given a clipboard and an ifstream, read the ifstream and add
    //      all of the anim curves described in ther stream into the
    //      API clipboard.
    //
    {
        //  Set the default values for the start and end of the clipboard.
        //  The MAnimCurveClipboard::set() method will examine all of the
        //  anim curves are determine the proper start and end values, if the
        //  start time is greater than the end value.
        //
        //  By default, the start values are greater than the end values to
        //  ensure correct behavior if the file does not specify the start and
        //  end values.
        //
        //
        double startTime = 1.0;
        double endTime = 0.0;
        double startUnitless = 1.0;
        double endUnitless = 0.0;

        resetUnits();
        convertAnglesFromV2To3 = false;
        convertAnglesFromV3To2 = false;

        //  Read the header. The header officially ends when the first non-header
        //  keyword is found. The header contains clipboard specific information
        //  where the body is anim curve specific.
        //
        char *dataType = NULL;
        bool hasVersionString = false;
        while (!readAnim.eof()) {
            advance(readAnim);
            dataType = asWord(readAnim);

            if (strcmp(dataType, kAnimVersion) == 0) {
                MString version(asWord(readAnim));
                animVersion = version.asDouble();
                MString thisVersion(kAnimVersionString);

                hasVersionString = true;

                //  Add versioning control here.
                //
                if (version != thisVersion) {
                    MStatus stat;
                    MString msg;
                    // Use format to place variable string into message
                    MString msgFmt = MStringResource::getString(kInvalidVersion,
                                                                stat);
                    msg.format(msgFmt, version, thisVersion);
                    MGlobal::displayWarning(msg);
                }
            } else if (strcmp(dataType, kMayaVersion) == 0) {
                MString version(asWord(readAnim, true));

                MString currentVersion = MGlobal::mayaVersion();
                if (currentVersion.substring(0,1) == "2.") {
                    MString vCheck = version.substring(0, 1);
                    if (vCheck != "2.") {
                        convertAnglesFromV3To2 = true;
                    }
                } else {
                    //  If this is a pre-Maya 3.0 file, then the tangent angles
                    //  will need to be converted to work in Maya 3.0+
                    //
                    MString vCheck = version.substring(0, 1);
                    if (vCheck == "2.") {
                        convertAnglesFromV2To3 = true;
                    }
                }
            } else if (strcmp(dataType, kTimeUnit) == 0) {
                MString timeUnitString(asWord(readAnim));
                if (!animUnitNames::setFromName(timeUnitString, timeUnit)) {
                    MString unitName;
                    timeUnit = MTime::uiUnit();
                    animUnitNames::setToShortName(timeUnit, unitName);
                    MStatus stat;
                    MString msg;
                    // Use format to place variable string into message
                    MString msgFmt = MStringResource::getString(kSettingToUnit,
                                                                stat);
                    msg.format(msgFmt, kTimeUnit, unitName);
                    MGlobal::displayWarning(msg);
                }
            } else if (strcmp(dataType, kLinearUnit) == 0) {
                MString linearUnitString(asWord(readAnim));
                if (!animUnitNames::setFromName(linearUnitString, linearUnit)) {
                    MString unitName;
                    linearUnit = MDistance::uiUnit();
                    animUnitNames::setToShortName(linearUnit, unitName);
                    MStatus stat;
                    MString msg;
                    // Use format to place variable string into message
                    MString msgFmt = MStringResource::getString(kSettingToUnit,
                                                                stat);
                    msg.format(msgFmt, kLinearUnit, unitName);
                    MGlobal::displayWarning(msg);
                }
            } else if (strcmp(dataType, kAngularUnit) == 0) {
                MString angularUnitString(asWord(readAnim));
                if (!animUnitNames::setFromName(angularUnitString, angularUnit)) {
                    MString unitName;
                    angularUnit = MAngle::uiUnit();
                    animUnitNames::setToShortName(angularUnit, unitName);
                    MStatus stat;
                    MString msg;
                    // Use format to place variable string into message
                    MString msgFmt = MStringResource::getString(kSettingToUnit,
                                                                stat);
                    msg.format(msgFmt, kAngularUnit, unitName);
                    MGlobal::displayWarning(msg);
                }
            } else if (strcmp(dataType, kStartTime) == 0) {
                startTime = asDouble(readAnim);
            } else if (strcmp(dataType, kEndTime) == 0) {
                endTime = asDouble(readAnim);
            } else if (strcmp(dataType, kStartUnitless) == 0) {
                startUnitless = asDouble(readAnim);
            } else if (strcmp(dataType, kEndUnitless) == 0) {
                endUnitless = asDouble(readAnim);
            } else {
                //  The header should be finished. Begin to parse the body.
                //
                break;
            }
        }

        //  The animVersion string is required.
        //
        if (!hasVersionString) {
            MStatus stat;
            MString msg;
            // Use format to place variable string into message
            MString msgFmt = MStringResource::getString(kMissingKeyword, stat);
            msg.format(msgFmt, kAnimVersion);
            MGlobal::displayError(msg);
            return (MS::kFailure);
        }

        //  Set the linear and angular units to be the same as the file
        //  being read. This will allow fixed tangent data to be read
        //  in properly if the scene has different units than the .anim file.
        //
        MDistance::Unit oldDistanceUnit = MDistance::uiUnit();
        MTime::Unit oldTimeUnit = MTime::uiUnit();

        MDistance::setUIUnit(linearUnit);
        MTime::setUIUnit(timeUnit);

        MAnimCurveClipboardItemArray clipboardArray;
        while (!readAnim.eof()) {

            if (NULL == dataType) {
                dataType = asWord(readAnim);
            }

            if (strcmp(dataType, kAnim) == 0) {
                MString fullAttributeName, leafAttributeName, nodeName;

                //  If this is from an unconnected anim curve, then there
                //  will not be an attribute name.
                //
                if (!isNextNumeric(readAnim)) {
                    fullAttributeName.set(asWord(readAnim));

                    //  If the node names were specified, then the next two
                    //  words should be the leaf attribute and the node name.
                    //
                    if (!isNextNumeric(readAnim)) {
                        leafAttributeName.set(asWord(readAnim));
                        nodeName.set(asWord(readAnim));
                    }
                }

                unsigned rowCount, childCount, attrCount;
                rowCount = (unsigned)asDouble(readAnim);
                childCount = (unsigned)asDouble(readAnim);
                attrCount = (unsigned)asDouble(readAnim);

                //  If the next keyword is not an animData, then this is
                //  a place holder for the API clipboard.
                //
                dataType = asWord(readAnim);
                if (strcmp(dataType, kAnimData) == 0) {
                    MAnimCurveClipboardItem clipboardItem;
                    if (readAnimCurve(readAnim, clipboardItem)) {
                        clipboardItem.setAddressingInfo(rowCount,
                                                        childCount, attrCount);
                        clipboardItem.setNameInfo(  nodeName,
                                                    fullAttributeName,
                                                    leafAttributeName);
                        clipboardArray.append(clipboardItem);
                    } else {
                        //  Could not read the anim curve.
                        //
                        MStatus stringStat;
                        MString msg = MStringResource::getString(kCouldNotReadAnim,
                                                                 stringStat);
                        MGlobal::displayError(msg);
                    }
                } else {
                    //  This must be a place holder object for the clipboard.
                    //
                    MAnimCurveClipboardItem clipboardItem;
                    clipboardItem.setAddressingInfo(rowCount,
                                                    childCount, attrCount);

                    //  Since there is no anim curve specified, what is
                    //  in the fullAttributeName string is really the node
                    //  name.
                    //
                    clipboardItem.setNameInfo(  fullAttributeName,
                                                nodeName,
                                                leafAttributeName);
                    clipboardArray.append(clipboardItem);

                    //  dataType already contains the next keyword.
                    //
                    continue;
                }
            } else {
                if (!readAnim.eof()) {
                    MString warnStr(dataType);
                    MStatus stat;
                    MString msg;
                    // Use format to place variable string into message
                    MString msgFmt = MStringResource::getString(kUnknownKeyword,
                                                                stat);
                    msg.format(msgFmt, warnStr);
                    MGlobal::displayError(msg);

                    //  Skip to the next line, this one is invalid.
                    //
                    readAnim.ignore(INT_MAX, kNewLineChar);
                } else {
                    //  The end of the file was reached.
                    //
                    break;
                }
            }

            //  Skip any whitespace.
            //
            dataType = NULL;
        }

        if (MS::kSuccess != cb.set( clipboardArray,
                            MTime(startTime, timeUnit), MTime(endTime, timeUnit),
                            (float) startUnitless, (float) endUnitless)) {

            MStatus stringStat;
            MString msg = MStringResource::getString(kClipboardFailure,
                                                     stringStat);
            MGlobal::displayError(msg);
        }

        //  Restore the old units.
        //
        MDistance::setUIUnit(oldDistanceUnit);
        MTime::setUIUnit(oldTimeUnit);

        return (MS::kSuccess);
    }

    void animReader::convertAnglesAndWeights3To2(MFnAnimCurve::AnimCurveType type,
                                    bool isWeighted, MAngle &angle, double &weight)
    //
    //  Description:
    //      Converts the tangent angles from Maya 3.0 to Maya2.* formats.
    //
    {
        double oldAngle = angle.as(MAngle::kRadians);
        double newAngle = oldAngle;

        //  Calculate the scale values for the conversion.
        //
        double xScale = 1.0;
        double yScale = 1.0;

        MTime tOne(1.0, MTime::kSeconds);
        if (type == MFnAnimCurve::kAnimCurveTT ||
            type == MFnAnimCurve::kAnimCurveTL ||
            type == MFnAnimCurve::kAnimCurveTA ||
            type == MFnAnimCurve::kAnimCurveTU) {

            xScale = tOne.as(MTime::uiUnit());
        }

        switch (type) {
            case MFnAnimCurve::kAnimCurveTT:
            case MFnAnimCurve::kAnimCurveUT:
                yScale = tOne.as(MTime::uiUnit());
                break;
            case MFnAnimCurve::kAnimCurveTL:
            case MFnAnimCurve::kAnimCurveUL:
                {
                    MDistance dOne(1.0, MDistance::internalUnit());
                    yScale = dOne.as(linearUnit);
                }
                break;
            case MFnAnimCurve::kAnimCurveTA:
            case MFnAnimCurve::kAnimCurveUA:
                {
                    MAngle aOne(1.0, MAngle::internalUnit());
                    yScale = aOne.as(angularUnit);
                }
                break;
            case MFnAnimCurve::kAnimCurveTU:
            case MFnAnimCurve::kAnimCurveUU:
            default:
                break;
        }

        double tanAngle = tan(oldAngle);
        newAngle = atan((xScale*tanAngle)/yScale);

        if (isWeighted) {
            double sinAngle = sin(oldAngle);
            double cosAngle = cos(oldAngle);

            double denominator = (yScale*yScale*sinAngle*sinAngle) +
                                    (xScale*xScale*cosAngle*cosAngle);
            weight = sqrtl(weight/denominator);
        }

        MAngle finalAngle(newAngle, MAngle::kRadians);
        angle = finalAngle;
    }

    void animReader::convertAnglesAndWeights2To3(MFnAnimCurve::AnimCurveType type,
                                    bool isWeighted, MAngle &angle, double &weight)
    //
    //  Description:
    //      Converts the tangent angles from Maya 2.* to Maya3.0+ formats.
    //
    {
        double oldAngle = angle.as(MAngle::kRadians);

        double newAngle = oldAngle;
        double newWeight = weight;

        //  Calculate the scale values for the conversion.
        //
        double xScale = 1.0;
        double yScale = 1.0;

        MTime tOne(1.0, MTime::kSeconds);
        if (type == MFnAnimCurve::kAnimCurveTT ||
            type == MFnAnimCurve::kAnimCurveTL ||
            type == MFnAnimCurve::kAnimCurveTA ||
            type == MFnAnimCurve::kAnimCurveTU) {

            xScale = tOne.as(MTime::uiUnit());
        }

        switch (type) {
            case MFnAnimCurve::kAnimCurveTT:
            case MFnAnimCurve::kAnimCurveUT:
                yScale = tOne.as(MTime::uiUnit());
                break;
            case MFnAnimCurve::kAnimCurveTL:
            case MFnAnimCurve::kAnimCurveUL:
                {
                    MDistance dOne(1.0, MDistance::internalUnit());
                    yScale = dOne.as(linearUnit);
                }
                break;
            case MFnAnimCurve::kAnimCurveTA:
            case MFnAnimCurve::kAnimCurveUA:
                {
                    MAngle aOne(1.0, MAngle::internalUnit());
                    yScale = aOne.as(angularUnit);
                }
                break;
            case MFnAnimCurve::kAnimCurveTU:
            case MFnAnimCurve::kAnimCurveUU:
            default:
                break;
        }

        const double quarter = M_PI/2;
        if (isEquivalent(oldAngle, 0.0) ||
            isEquivalent(oldAngle, quarter) ||
            isEquivalent(oldAngle, -quarter)) {

            newAngle = oldAngle;

            if (isWeighted) {
                newWeight = yScale*oldAngle;
            }
        } else {
            double tanAngle = tan(oldAngle);
            newAngle = atan((yScale*tanAngle)/xScale);

            if (isWeighted) {
                double cosAngle = cos(oldAngle);
                double cosSq = cosAngle*cosAngle;

                double wSq = (weight*weight) *
                        (((xScale*xScale - yScale*yScale)*cosSq) + (yScale*yScale));

                newWeight = sqrtl(wSq);
            }
        }

        weight = newWeight;

        MAngle finalAngle(newAngle, MAngle::kRadians);
        angle = finalAngle;
    }

    bool animReader::readAnimCurve(
    ifstream &clipFile, MAnimCurveClipboardItem &item)
    //
    //  Description:
    //      Read a block of the ifstream that should contain anim curve
    //      data in the format determined by the animData keyword.
    //
    {
        MFnAnimCurve animCurve;
        MObject animCurveObj;

        //  Anim curve defaults.
        //
        animBase::AnimBaseType input = wordAsInputType(kWordTypeTime);
        animBase::AnimBaseType output = wordAsOutputType(kWordTypeLinear);
        MFnAnimCurve::InfinityType preInf = wordAsInfinityType(kWordConstant);
        MFnAnimCurve::InfinityType postInf = wordAsInfinityType(kWordConstant);

        MString inputUnitName;
        animUnitNames::setToShortName(timeUnit, inputUnitName);
        MString outputUnitName;
        MAngle::Unit tanAngleUnit = angularUnit;
        bool isWeighted (false);

        char *dataType;
        while (!clipFile.eof()) {
            advance(clipFile);

            dataType = asWord(clipFile);

            if (strcmp(dataType, kInputString) == 0) {
                input = wordAsInputType(asWord(clipFile));
            } else if (strcmp(dataType, kOutputString) == 0) {
                output = wordAsOutputType(asWord(clipFile));
            } else if (strcmp(dataType, kWeightedString) == 0) {
                isWeighted = (asDouble(clipFile) == 1.0);
            } else if (strcmp(dataType, kPreInfinityString) == 0) {
                preInf = wordAsInfinityType(asWord(clipFile));
            } else if (strcmp(dataType, kPostInfinityString) == 0) {
                postInf = wordAsInfinityType(asWord(clipFile));
            } else if (strcmp(dataType, kInputUnitString) == 0) {
                inputUnitName.set(asWord(clipFile));
            } else if (strcmp(dataType, kOutputUnitString) == 0) {
                outputUnitName.set(asWord(clipFile));
            } else if (strcmp(dataType, kTanAngleUnitString) == 0) {
                MString tUnit(asWord(clipFile));
                if (!animUnitNames::setFromName(tUnit, tanAngleUnit)) {
                    MString unitName;
                    tanAngleUnit = angularUnit;
                    animUnitNames::setToShortName(tanAngleUnit, unitName);

                    MStatus stat;
                    MString msg;
                    // Use format to place variable string into message
                    MString msgFmt = MStringResource::getString(kSettingTanAngleUnit, stat);
                    msg.format(msgFmt, unitName);
                    MGlobal::displayError(msg);
                }
            } else if (strcmp(dataType, kKeysString) == 0) {
                //  Ignore the rest of this line.
                //
                clipFile.ignore(INT_MAX, kNewLineChar);
                break;
            } else if (strcmp(dataType, "{") == 0) {
                //  Skippping the '{' character. Just ignore it.
                //
                continue;
            } else {
                //  An unrecogized keyword was found.
                //
                MString warnStr(dataType);
                MStatus stat;
                MString msg;
                // Use format to place variable string into message
                MString msgFmt = MStringResource::getString(kUnknownKeyword,
                                                            stat);
                msg.format(msgFmt, warnStr);
                MGlobal::displayError(msg);
                continue;
            }
        }

        // Read the animCurve
        //
        MStatus status;
        MFnAnimCurve::AnimCurveType type = typeAsAnimCurveType(input, output);
        animCurveObj = animCurve.create(type, NULL, &status);

        if (status != MS::kSuccess) {
            MString msg = MStringResource::getString(kCouldNotCreateAnim, status);
            MGlobal::displayError(msg);
            return false;
        }

        animCurve.setIsWeighted(isWeighted);
        animCurve.setPreInfinityType(preInf);
        animCurve.setPostInfinityType(postInf);

        //  Set the appropriate units.
        //
        MTime::Unit inputTimeUnit;
        if (input == kAnimBaseTime) {
            if (!animUnitNames::setFromName(inputUnitName, inputTimeUnit)) {
                MString unitName;
                inputTimeUnit = timeUnit;
                animUnitNames::setToShortName(inputTimeUnit, unitName);
                MStatus stat;
                MString msg;
                // Use format to place variable string into message
                MString msgFmt = MStringResource::getString(kSettingToUnit,
                                                            stat);
                msg.format(msgFmt, kInputUnitString, unitName);
                MGlobal::displayWarning(msg);
            }
        }

        MTime::Unit outputTimeUnit;
        if (output == kAnimBaseTime) {
            if (!animUnitNames::setFromName(outputUnitName, outputTimeUnit)) {
                MString unitName;
                outputTimeUnit = timeUnit;
                animUnitNames::setToShortName(outputTimeUnit, unitName);
                MStatus stat;
                MString msg;
                // Use format to place variable string into message
                MString msgFmt = MStringResource::getString(kSettingToUnit,
                                                            stat);
                msg.format(msgFmt, kOutputUnitString, unitName);
                MGlobal::displayWarning(msg);
            }
        }

        int index = 0;
        double conversion = 1.0;
        if (output == kAnimBaseLinear) {
            MDistance::Unit unit;
            if (outputUnitName.length() != 0) {
                if (!animUnitNames::setFromName(outputUnitName, unit)) {
                    MString unitName;
                    unit = linearUnit;
                    animUnitNames::setToShortName(unit, unitName);
                    MStatus stat;
                    MString msg;
                    // Use format to place variable string into message
                    MString msgFmt = MStringResource::getString(kSettingToUnit,
                                                                stat);
                    msg.format(msgFmt, kOutputUnitString, unitName);
                    MGlobal::displayWarning(msg);
                }
            } else {
                unit = linearUnit;
            }
            if (unit != MDistance::kCentimeters) {
                MDistance one(1.0, unit);
                conversion = one.as(MDistance::kCentimeters);
            }
        } else if (output == kAnimBaseAngular) {
            MAngle::Unit unit;
            if (outputUnitName.length() != 0) {
                if (!animUnitNames::setFromName(outputUnitName, unit)) {
                    MString unitName;
                    unit = angularUnit;
                    animUnitNames::setToShortName(unit, unitName);
                    MStatus stat;
                    MString msg;
                    // Use format to place variable string into message
                    MString msgFmt = MStringResource::getString(kSettingToUnit,
                                                                stat);
                    msg.format(msgFmt, kOutputUnitString, unitName);
                    MGlobal::displayWarning(msg);
                }
            } else {
                unit = angularUnit;
            }
            if (unit != MAngle::kRadians) {
                MAngle one(1.0, unit);
                conversion = one.as(MAngle::kRadians);
            }
        }

        // Now read each keyframe
        //
        advance(clipFile);
        char c = clipFile.peek();
        while (clipFile && c != kBraceRightChar) {
            double t = asDouble (clipFile);
            double val = asDouble (clipFile);

            MFnAnimCurve::TangentType tanIn = wordAsTangentType(asWord(clipFile));
            MFnAnimCurve::TangentType tanOut = wordAsTangentType(asWord(clipFile));

            switch (type) {
                case MFnAnimCurve::kAnimCurveTT:
                    index = animCurve.addKey(   MTime(val, inputTimeUnit),
                                                MTime(val, outputTimeUnit),
                                                tanIn, tanOut, NULL, &status);
                    break;
                case MFnAnimCurve::kAnimCurveTL:
                case MFnAnimCurve::kAnimCurveTA:
                case MFnAnimCurve::kAnimCurveTU:
                    index = animCurve.addKey(   MTime(t, inputTimeUnit),
                                                val*conversion, tanIn, tanOut,
                                                NULL, &status);
                    break;
                case MFnAnimCurve::kAnimCurveUL:
                case MFnAnimCurve::kAnimCurveUA:
                case MFnAnimCurve::kAnimCurveUU:
                    index = animCurve.addKey(   t, val*conversion,
                                                tanIn, tanOut,
                                                NULL, &status);
                    break;
                case MFnAnimCurve::kAnimCurveUT:
                    index = animCurve.addKey(   t, MTime(val, outputTimeUnit),
                                                tanIn, tanOut,
                                                NULL, &status);
                    break;
                default:
                    MString msg = MStringResource::getString(kUnknownNode, status);
                    MGlobal::displayError(msg);
                    return false;
            }

            if (status != MS::kSuccess) {
                MStatus stringStat;
                MString msg = MStringResource::getString(kCouldNotKey, stringStat);
                MGlobal::displayError(msg);
            }

            //  Tangent locking needs to be called after the weights and
            //  angles are set for the fixed tangents.
            //
            bool tLocked = bool(asDouble(clipFile) == 1.0);
            bool swLocked = bool(asDouble(clipFile) == 1.0);
            bool isBreakdown (false);
            if (animVersion >= kVersionNonWeightedAndBreakdowns) {
                isBreakdown = (asDouble(clipFile) == 1.0);
            }

            //  Only fixed tangents need additional information.
            //
            if (tanIn == MFnAnimCurve::kTangentFixed) {
                MAngle inAngle(asDouble(clipFile), tanAngleUnit);
                double inWeight = asDouble(clipFile);

                //  If this is from a pre-Maya3.0 file, the tangent angles will
                //  need to be converted.
                //
                if (convertAnglesFromV2To3) {
                    convertAnglesAndWeights2To3(type,isWeighted,inAngle,inWeight);
                } else if (convertAnglesFromV3To2) {
                    convertAnglesAndWeights3To2(type,isWeighted,inAngle,inWeight);
                }

                //  By default, the tangents are locked. When the tangents
                //  are locked, setting the angle and weight of a fixed in
                //  tangent may change the tangent type of the out tangent.
                //
                animCurve.setTangentsLocked(index, false);
                animCurve.setTangent(index, inAngle, inWeight, true);
            }

            //  Only fixed tangents need additional information.
            //
            if (tanOut == MFnAnimCurve::kTangentFixed) {
                MAngle outAngle(asDouble(clipFile), tanAngleUnit);
                double outWeight = asDouble(clipFile);

                //  If this is from a pre-Maya3.0 file, the tangent angles will
                //  need to be converted.
                //
                if (convertAnglesFromV2To3) {
                    convertAnglesAndWeights2To3(type,isWeighted,outAngle,outWeight);
                } else if (convertAnglesFromV3To2) {
                    convertAnglesAndWeights3To2(type,isWeighted,outAngle,outWeight);
                }

                //  By default, the tangents are locked. When the tangents
                //  are locked, setting the angle and weight of a fixed out
                //  tangent may change the tangent type of the in tangent.
                //
                animCurve.setTangentsLocked(index, false);
                animCurve.setTangent(index, outAngle, outWeight, false);
            }

            //  To prevent tangent types from unexpectedly changing, tangent
            //  locking should be the last operation. See the above comments
            //  about fixed tangent types for more information.
            //
            animCurve.setWeightsLocked(index, swLocked);
            animCurve.setTangentsLocked(index, tLocked);
            animCurve.setIsBreakdown (index, isBreakdown);

            //  There should be no additional data on this line. Go to the
            //  next line of data.
            //
            clipFile.ignore(INT_MAX, kNewLineChar);

            //  Skip any comments.
            //
            advance(clipFile);
            c = clipFile.peek();
        }

        //  Ignore the brace that marks the end of the keys block.
        //
        if (c == kBraceRightChar) {
            clipFile.ignore(INT_MAX, kNewLineChar);
        }

        //  Ignore the brace that marks the end of the animData block.
        //
        advance(clipFile);
        if (clipFile.peek() == kBraceRightChar) {
            clipFile.ignore(INT_MAX, kNewLineChar);
        } else {
            //  Something is wrong.
            //
            MStatus stringStat;
            MString msg = MStringResource::getString(kMissingBrace, stringStat);
            MGlobal::displayError(msg);
        }

        //  Do not set the clipboard with an empty clipboard item.
        //
        if (!animCurveObj.isNull()) {
            item.setAnimCurve(animCurveObj);
        }

        //  Delete the copy of the anim curve.
        //
        MGlobal::deleteNode(animCurveObj);

        return true;
    }

    //-------------------------------------------------------------------------
    //  Class animWriter
    //-------------------------------------------------------------------------

    animWriter::animWriter()
    //
    //  Description:
    //      Class constructor.
    //
    {
    }

    animWriter::~animWriter()
    //
    //  Description:
    //      Class destructor.
    //
    {
    }

    MStatus
    animWriter::writeClipboard( ofstream& animFile,
                                const MAnimCurveClipboard &cb,
                                bool nodeNames /* false */,
                                bool verboseUnits /* false */)
    //
    //  Description:
    //      Write the contents of the clipboard to the ofstream.
    //
    {
        MStatus status = MS::kFailure;

        // Check to see if there is anything on the clipboard at all
        //
        if (cb.isEmpty()) {
            return status;
        }

        resetUnits();

        // Write out the clipboard information
        //
        if (!writeHeader(animFile)) {
            return (MS::kFailure);
        }

        // Now write out each animCurve
        //
        const MAnimCurveClipboardItemArray  &clipboardArray =
                                            cb.clipboardItems(&status);
        if (MS::kSuccess != status) {
            return status;
        }

        for (unsigned int i = 0; i < clipboardArray.length(); i++) {
            const MAnimCurveClipboardItem &clipboardItem = clipboardArray[i];

            MStatus statusInLoop = MS::kSuccess;
            const MObject animCurveObj = clipboardItem.animCurve(&statusInLoop);

            //  The clipboard may contain Null anim curves. If a Null anim
            //  curve is returned, it is safe to ignore the error message
            //  and continue to the next anim curve in the list.
            //
            bool placeHolder = false;
            if (MS::kSuccess != statusInLoop || animCurveObj.isNull()) {
                placeHolder = true;
            }

            // Write out animCurve information
            //
            if (!writeAnim(animFile, clipboardItem, placeHolder, nodeNames)) {
                return (MS::kFailure);
            }

            if (placeHolder) {
                continue;
            }

            //  Write out each curve in its specified format.
            //  For now, only the anim curve format.
            //
            if (!writeAnimCurve(animFile, &animCurveObj,
                                clipboardItem.animCurveType(),
                                verboseUnits)) {
                return (MS::kFailure);
            }
        }

        return (MS::kSuccess);
    }

    bool animWriter::writeHeader(ofstream& clip)
    //
    //  Description:
    //      Writes the header for the file. The header contains the clipboard
    //      specific data.
    //
    {
        if (!clip) {
            return false;
        }

        clip << kAnimVersion << kSpaceChar << kAnimVersionString << kSemiColonChar << endl;
        clip << kMayaVersion << kSpaceChar << MGlobal::mayaVersion() << kSemiColonChar << endl;

        static MAnimCurveClipboard &clipboard =
            MAnimCurveClipboard::theAPIClipboard();

        double startTime = clipboard.startTime().as(timeUnit);
        double endTime = clipboard.endTime().as(timeUnit);

        if (startTime != endTime) {
            MString unit;
            animUnitNames::setToShortName(timeUnit, unit);
            clip << kTimeUnit << kSpaceChar << unit << kSemiColonChar << endl;
            animUnitNames::setToShortName(linearUnit, unit);
            clip << kLinearUnit << kSpaceChar << unit << kSemiColonChar << endl;
            animUnitNames::setToShortName(angularUnit, unit);
            clip << kAngularUnit << kSpaceChar << unit << kSemiColonChar << endl;
            clip << kStartTime << kSpaceChar << startTime << kSemiColonChar << endl;
            clip << kEndTime << kSpaceChar << endTime << kSemiColonChar << endl;
        }

        float startUnitless = clipboard.startUnitlessInput();
        float endUnitless = clipboard.endUnitlessInput();

        if (startUnitless != endUnitless) {
            clip << kStartUnitless << kSpaceChar << startUnitless <<
                    kSemiColonChar << endl;
            clip << kEndUnitless << kSpaceChar << endUnitless <<
                    kSemiColonChar << endl;
        }

        return true;
    }

    bool animWriter::writeAnim( ofstream &clip,
                                const MAnimCurveClipboardItem &clipboardItem,
                                bool placeHolder /* false */,
                                bool nodeNames /* false */)
    //
    //  Description:
    //      Write out the anim curve from the clipboard item into the
    //      ofstream. The position of the anim curve in the clipboard
    //      and the attribute to which it is attached is written out in this
    //      method.
    //
    //      This method returns true if the write was successful.
    //
    {
        if (!clip) {
            return false;
        }

        clip << kAnim;

        //  If this is a clipboard place holder then there will be no full
        //  or leaf attribute names.
        //
        if (placeHolder) {
            clip << kSpaceChar << clipboardItem.nodeName().asChar();
        } else {
            clip << kSpaceChar << clipboardItem.fullAttributeName().asChar();

            if (nodeNames) {
                clip << kSpaceChar << clipboardItem.leafAttributeName().asChar();
                clip << kSpaceChar << clipboardItem.nodeName().asChar();
            }
        }

        unsigned int rowCount, childCount, attrCount;
        clipboardItem.getAddressingInfo(rowCount, childCount, attrCount);

        clip << kSpaceChar << rowCount;
        clip << kSpaceChar << childCount;
        clip << kSpaceChar << attrCount;
        clip << kSemiColonChar << endl;

        return true;
    }

    bool animWriter::writeAnimCurve(ofstream &clip,
                                    const MObject *animCurveObj,
                                    MFnAnimCurve::AnimCurveType type,
                                    bool verboseUnits /* false */)
    //
    //  Description:
    //      Write out the anim curve from the clipboard item into the
    //      ofstream. The actual anim curve data is written out.
    //
    //      This method returns true if the write was successful.
    //
    {
        if (NULL == animCurveObj || animCurveObj->isNull() || !clip) {
            return true;
        }

        MStatus status = MS::kSuccess;
        MFnAnimCurve animCurve(*animCurveObj, &status);
        if (MS::kSuccess != status) {
            MString msg = MStringResource::getString(kCouldNotExport, status);
            MGlobal::displayError(msg);
            return false;
        }

        clip << kAnimData << kSpaceChar << kBraceLeftChar << endl;

        clip << kTwoSpace << kInputString << kSpaceChar <<
                boolInputTypeAsWord(animCurve.isUnitlessInput()) <<
                kSemiColonChar << endl;

        clip << kTwoSpace << kOutputString << kSpaceChar <<
                outputTypeAsWord(type) << kSemiColonChar << endl;

        clip << kTwoSpace << kWeightedString << kSpaceChar <<
                (animCurve.isWeighted() ? 1 : 0) << kSemiColonChar << endl;

        //  These units default to the units in the header of the file.
        //
        if (verboseUnits) {
            clip << kTwoSpace << kInputUnitString << kSpaceChar;
            if (animCurve.isTimeInput()) {
                MString unitName;
                animUnitNames::setToShortName(timeUnit, unitName);
                clip << unitName;
            } else {
                //  The anim curve has unitless input.
                //
                clip << kUnitlessString;
            }
            clip << kSemiColonChar << endl;

            clip << kTwoSpace << kOutputUnitString << kSpaceChar;
        }

        double conversion = 1.0;
        MString unitName;
        switch (type) {
            case MFnAnimCurve::kAnimCurveTA:
            case MFnAnimCurve::kAnimCurveUA:
                animUnitNames::setToShortName(angularUnit, unitName);
                if (verboseUnits) clip << unitName;
                {
                    MAngle angle(1.0);
                    conversion = angle.as(angularUnit);
                }
                break;
            case MFnAnimCurve::kAnimCurveTL:
            case MFnAnimCurve::kAnimCurveUL:
                animUnitNames::setToShortName(linearUnit, unitName);
                if (verboseUnits) clip << unitName;
                {
                    MDistance distance(1.0);
                    conversion = distance.as(linearUnit);
                }
                break;
            case MFnAnimCurve::kAnimCurveTT:
            case MFnAnimCurve::kAnimCurveUT:
                animUnitNames::setToShortName(timeUnit, unitName);
                if (verboseUnits) clip << unitName;
                break;
            default:
                if (verboseUnits) clip << kUnitlessString;
                break;
        }
        if (verboseUnits) clip << kSemiColonChar << endl;

        if (verboseUnits) {
            MString angleUnitName;
            animUnitNames::setToShortName(angularUnit, angleUnitName);
            clip << kTwoSpace << kTanAngleUnitString <<
                    kSpaceChar << angleUnitName << kSemiColonChar << endl;
        }

        clip << kTwoSpace << kPreInfinityString << kSpaceChar <<
                infinityTypeAsWord(animCurve.preInfinityType()) <<
                kSemiColonChar << endl;

        clip << kTwoSpace << kPostInfinityString << kSpaceChar <<
                infinityTypeAsWord(animCurve.postInfinityType()) <<
                kSemiColonChar << endl;

        clip << kTwoSpace << kKeysString << kSpaceChar << kBraceLeftChar << endl;

        // And then write out each keyframe
        //
        unsigned numKeys = animCurve.numKeyframes();
        for (unsigned i = 0; i < numKeys; i++) {
            clip << kTwoSpace << kTwoSpace;
            if (animCurve.isUnitlessInput()) {
                clip << animCurve.unitlessInput(i);
            }
            else {
                clip << animCurve.time(i).value();
            }

            // clamp tiny values so that it isn't so small it can't be read in
            //
            double animValue = (conversion*animCurve.value(i));
            if (animBase::isEquivalent(animValue,0.0)) animValue = 0.0;
            clip << kSpaceChar << animValue;

            clip << kSpaceChar << tangentTypeAsWord(animCurve.inTangentType(i));
            clip << kSpaceChar << tangentTypeAsWord(animCurve.outTangentType(i));

            clip << kSpaceChar << (animCurve.tangentsLocked(i) ? 1 : 0);
            clip << kSpaceChar << (animCurve.weightsLocked(i) ? 1 : 0);
            clip << kSpaceChar << (animCurve.isBreakdown(i) ? 1 : 0);

            if (animCurve.inTangentType(i) == MFnAnimCurve::kTangentFixed) {
                MAngle angle;
                double weight;
                animCurve.getTangent(i, angle, weight, true);

                clip << kSpaceChar << angle.as(angularUnit);
                clip << kSpaceChar << weight;
            }
            if (animCurve.outTangentType(i) == MFnAnimCurve::kTangentFixed) {
                MAngle angle;
                double weight;
                animCurve.getTangent(i, angle, weight, false);

                clip << kSpaceChar << angle.as(angularUnit);
                clip << kSpaceChar << weight;
            }

            clip << kSemiColonChar << endl;
        }
        clip << kTwoSpace << kBraceRightChar << endl;

        clip << kBraceRightChar << endl;

        return true;
    }