animImportExport.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:  animImport.cc
//
//  Description:
//      Imports a .anim file into anim curves attached to the selected
//      Maya objects.
//

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

#include <maya/MFStream.h>
#include <maya/MGlobal.h>
#include <maya/MString.h>
#include <maya/MFnPlugin.h>
#include <maya/MPxFileTranslator.h>

#include <maya/MSelectionList.h>

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

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

#if defined (OSMac_)
#   include <sys/param.h>
extern "C" int strcasecmp (const char *, const char *);
#endif
//-----------------------------------------------------------------------------
//  anim Importer
//-----------------------------------------------------------------------------

const char *const animImportOptionScript = "animImportOptions";
const char *const animImportDefaultOptions = 
    "targetTime=4;copies=1;option=replace;pictures=0;connect=0;";

// Register all strings used by the plugin C++ code
static MStatus registerMStringResources(void)
{
    MStringResource::registerString(kNothingSelected);
    MStringResource::registerString(kPasteFailed);
    MStringResource::registerString(kAnimCurveNotFound);        
    MStringResource::registerString(kInvalidAngleUnits);
    MStringResource::registerString(kInvalidLinearUnits);
    MStringResource::registerString(kInvalidTimeUnits);
    MStringResource::registerString(kInvalidVersion);
    MStringResource::registerString(kSettingToUnit);
    MStringResource::registerString(kMissingKeyword);
    MStringResource::registerString(kCouldNotReadAnim);
    MStringResource::registerString(kCouldNotCreateAnim);   
    MStringResource::registerString(kUnknownKeyword);
    MStringResource::registerString(kClipboardFailure);
    MStringResource::registerString(kSettingTanAngleUnit);
    MStringResource::registerString(kUnknownNode);
    MStringResource::registerString(kCouldNotKey);
    MStringResource::registerString(kMissingBrace);
    MStringResource::registerString(kCouldNotExport);
    return MS::kSuccess;
}



animImport::animImport()
: MPxFileTranslator()
{
}

animImport::~animImport()
{
}

void *animImport::creator()
{
    return new animImport();
}

MStatus animImport::reader( const MFileObject& file,
                                const MString& options,
                                FileAccessMode mode)
{
    MStatus status = MS::kFailure;

    MString fileName = file.fullName();
#if defined (OSMac_)    
    char fname[MAXPATHLEN];
    strcpy (fname, fileName.asChar());
    ifstream animFile(fname);
#else
    ifstream animFile(fileName.asChar());
#endif
    //  Parse the options. The options syntax is in the form of
    //  "flag=val;flag1=val;flag2=val"
    //
    MString pasteFlags;
    if (options.length() > 0) {
        //  Set up the flags for the paste command.
        //
        const MString flagTargetTime("targetTime");
        const MString flagTime("time");
        const MString flagCopies("copies");
        const MString flagOption("option");
        const MString flagConnect("connect");

        MString copyValue;
        MString flagValue;
        MString connectValue;
        MString timeValue;

        //  Start parsing.
        //
        MStringArray optionList;
        MStringArray theOption;
        options.split(';', optionList);

        unsigned nOptions = optionList.length();
        for (unsigned i = 0; i < nOptions; i++) {

            theOption.clear();
            optionList[i].split('=', theOption);
            if (theOption.length() < 1) {
                continue;
            }

            if (theOption[0] == flagCopies && theOption.length() > 1) {
                copyValue = theOption[1];;
            } else if (theOption[0] == flagOption && theOption.length() > 1) {
                flagValue = theOption[1];
            } else if (theOption[0] == flagConnect && theOption.length() > 1) {
                if (theOption[1].asInt() != 0) {
                    connectValue += theOption[1];
                }
            } else if (theOption[0] == flagTime && theOption.length() > 1) {
                timeValue += theOption[1];
            } 
        }
    
        if (copyValue.length() > 0) {
            pasteFlags += " -copies ";
            pasteFlags += copyValue;
            pasteFlags += " ";
        } 
        if (flagValue.length() > 0) {
            pasteFlags += " -option \"";
            pasteFlags += flagValue;
            pasteFlags += "\" ";
        } 
        if (connectValue.length() > 0) {
            pasteFlags += " -connect ";
            pasteFlags += connectValue;
            pasteFlags += " ";
        } 
        if (timeValue.length() > 0) {
            bool useQuotes = !timeValue.isDouble();
            pasteFlags += " -time ";
            if (useQuotes) pasteFlags += "\"";
            pasteFlags += timeValue;
            if (useQuotes) pasteFlags += "\"";
            pasteFlags += " ";
        } 
    }

    if (mode == kImportAccessMode) {
        status = importAnim(animFile, pasteFlags);
    }

    animFile.close();
    return status;
}

bool animImport::haveReadMethod() const
{
    return true;
}

bool animImport::haveWriteMethod() const
{
    return false;
}

bool animImport::canBeOpened() const
{
    return false;
}

MString animImport::defaultExtension() const
{
    return MString("anim");
}

MPxFileTranslator::MFileKind animImport::identifyFile(
                                const MFileObject& fileName,
                                const char* buffer,
                                short size) const
{
    const char *name = fileName.name().asChar();
    int   nameLength = (int)strlen(name);

    if ((nameLength > 5) && !strcasecmp(name+nameLength-5, ".anim")) {
        return kIsMyFileType;
    }

    //  Check the buffer to see if this contains the correct keywords
    //  to be a anim file.
    //
    if (strncmp(buffer, "animVersion", 11) == 0) {
        return kIsMyFileType;
    }

    return  kNotMyFileType;
}

MStatus 
animImport::importAnim(ifstream &animFile, const MString &pasteFlags)
{
    MStatus status = MS::kFailure;
    MAnimCurveClipboard::theAPIClipboard().clear();

    //  If the selection list is empty, there is nothing to import.
    //
    MSelectionList sList;
    MGlobal::getActiveSelectionList(sList);
    if (sList.isEmpty()) {
        MString msg = MStringResource::getString(kNothingSelected, status);
        MGlobal::displayError(msg);
        return (MS::kFailure);
    }

    if (MS::kSuccess != 
            (status = fReader.readClipboard(animFile, 
            MAnimCurveClipboard::theAPIClipboard()))) {

        return status;
    }

    if (MAnimCurveClipboard::theAPIClipboard().isEmpty()) {
        return (MS::kFailure);
    }

    MString command("pasteKey -cb api ");
    command += pasteFlags;

    int result;
    if (MS::kSuccess != (status =  
        MGlobal::executeCommand(command, result, false, true))) {
        MString msg = MStringResource::getString(kPasteFailed, status);
        MGlobal::displayError(msg);
        return status;
    }

    return status;
}

//-----------------------------------------------------------------------------
//  anim Exporter
//-----------------------------------------------------------------------------

const char *const animExportOptionScript = "animExportOptions";
const char *const animExportDefaultOptions = "precision=8;nodeNames=1;verboseUnits=0;whichRange=1;range=0:10;options=keys;hierarchy=none;controlPoints=0;shapes=1;helpPictures=0;useChannelBox=0;copyKeyCmd=";

const int kDefaultPrecision = 8;    //  float precision.

animExport::animExport()
: MPxFileTranslator()
{
}

animExport::~animExport()
{
}

void *animExport::creator()
{
    return new animExport();
}

MStatus animExport::writer( const MFileObject& file,
                                const MString& options,
                                FileAccessMode mode)
{
    MStatus status = MS::kFailure;

#ifdef MAYA_EVAL_VERSION
    status = MS::kFailure;
    return status;
#endif

    MString fileName = file.fullName();
#if defined (OSMac_)
    char fname[MAXPATHLEN];
    strcpy (fname, fileName.asChar());
    ofstream animFile(fname);
#else
    ofstream animFile(fileName.asChar());
#endif
    //  Defaults.
    //
    MString copyFlags("copyKey -cb api -fea 1 ");
    int precision = kDefaultPrecision;
    bool nodeNames = true;
    bool verboseUnits = false;

    //  Parse the options. The options syntax is in the form of
    //  "flag=val;flag1=val;flag2=val"
    //
    MString exportFlags;
    if (options.length() > 0) {
        const MString flagPrecision("precision");
        const MString flagNodeNames("nodeNames");
        const MString flagVerboseUnits("verboseUnits");
        const MString flagCopyKeyCmd("copyKeyCmd");

        //  Start parsing.
        //
        MStringArray optionList;
        MStringArray theOption;
        options.split(';', optionList);

        unsigned nOptions = optionList.length();
        for (unsigned i = 0; i < nOptions; i++) {
            theOption.clear();
            optionList[i].split('=', theOption);
            if (theOption.length() < 1) {
                continue;
            }

            if (theOption[0] == flagPrecision && theOption.length() > 1) {
                if (theOption[1].isInt()) {
                    precision = theOption[1].asInt();
                }
            } else if ( theOption[0] == 
                        flagNodeNames && theOption.length() > 1) {
                if (theOption[1].isInt()) {
                    nodeNames = (theOption[1].asInt()) ? true : false;
                }
            }
            else if (theOption[0] == 
                    flagVerboseUnits && theOption.length() > 1) {
                if (theOption[1].isInt()) {
                    verboseUnits = (theOption[1].asInt()) ? true : false;
                }
            } else if ( theOption[0] == 
                        flagCopyKeyCmd && theOption.length() > 1) {

                //  Replace any '>' characters with '"'. This is needed
                //  since the file translator option boxes do not handle
                //  escaped quotation marks.
                //
                const char *optStr = theOption[1].asChar();
                size_t nChars = strlen(optStr);
                char *copyStr = new char[nChars+1];

                copyStr = strcpy(copyStr, optStr);
                for (size_t j = 0; j < nChars; j++) {
                    if (copyStr[j] == '>') {
                        copyStr[j] = '"';
                    }
                }
        
                copyFlags += copyStr;
                delete [] copyStr;
            }
        }
    }
    
    //  Set the precision of the ofstream.
    //
    animFile.precision(precision);

    status = exportSelected(animFile, copyFlags, nodeNames, verboseUnits);

    animFile.flush();
    animFile.close();

    return status;
}

bool animExport::haveReadMethod() const
{
    return false;
}

bool animExport::haveWriteMethod() const
{
#ifdef MAYA_EVAL_VERSION
    return false;
#else
    return true;
#endif
}

MString animExport::defaultExtension() const
{
    return MString("anim");
}

MPxFileTranslator::MFileKind animExport::identifyFile(
                                const MFileObject& fileName,
                                const char* buffer,
                                short size) const
{
    const char *name = fileName.name().asChar();
    int   nameLength = (int)strlen(name);

    if ((nameLength > 5) && !strcasecmp(name+nameLength-5, ".anim")) {
        return kIsMyFileType;
    }

    return  kNotMyFileType;
}

MStatus animExport::exportSelected( ofstream &animFile, 
                                    MString &copyFlags,
                                    bool nodeNames /* false */,
                                    bool verboseUnits /* false */)
{
    MStatus status = MS::kFailure;

    //  If the selection list is empty, then there are no anim curves
    //  to export.
    //
    MSelectionList sList;
    MGlobal::getActiveSelectionList(sList);
    if (sList.isEmpty()) {
        MString msg = MStringResource::getString(kNothingSelected, status);
        MGlobal::displayError(msg);
        return (MS::kFailure);
    }

    //  Copy any anim curves to the API clipboard.
    //
    int result = 0;
    MString command(copyFlags);

    if (MS::kSuccess != (status = 
        MGlobal::executeCommand(command, result, false, true))) {
        MStatus stringStat;
        MString msg = MStringResource::getString(kAnimCurveNotFound, stringStat);
        MGlobal::displayError(msg);
        return status;
    }

    if (result == 0 || MAnimCurveClipboard::theAPIClipboard().isEmpty()) {
        MString msg = MStringResource::getString(kAnimCurveNotFound, status);
        MGlobal::displayError(msg);
        return (MS::kFailure);
    }

    if (MS::kSuccess != (   status = 
                            fWriter.writeClipboard(animFile, 
                            MAnimCurveClipboard::theAPIClipboard(),
                            nodeNames, verboseUnits))) {
        return (MS::kFailure);
    }

    return status;
}


MStatus initializePlugin(MObject obj)
{
    MStatus stat = MS::kFailure;
    MFnPlugin impPlugIn(obj, PLUGIN_COMPANY, "3.0", "Any");
    
    // This is done first, so the strings are available. 
    stat = impPlugIn.registerUIStrings(registerMStringResources, "animImportExportInitStrings");
    if (stat != MS::kSuccess)
    {
        stat.perror("registerUIStrings");
        return stat;
    }

    stat = impPlugIn.registerFileTranslator("animImport", "none",
                                            animImport::creator,
                                            (char *)animImportOptionScript,
                                            (char *)animImportDefaultOptions, 
                                            true);

    if (stat != MS::kSuccess) {
        return stat;
    }

    MFnPlugin expPlugIn(obj, PLUGIN_COMPANY, "3.0", "Any");
    stat = expPlugIn.registerFileTranslator("animExport", "",
                                        animExport::creator,
                                        (char *)animExportOptionScript,
                                        (char *)animExportDefaultOptions,
                                        true);

    return stat;
}

MStatus uninitializePlugin(MObject obj)
{
    MStatus stat = MS::kFailure;

    MFnPlugin impPlugIn(obj);
    stat = impPlugIn.deregisterFileTranslator("animImport");

    if (stat != MS::kSuccess) {
        return stat;
    }

    MFnPlugin expPlugIn(obj);
    stat = expPlugIn.deregisterFileTranslator("animExport");

    return stat;
}