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;
}