maTranslator.cpp

//-
// ==========================================================================
// Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+


// Example Plugin: maTranslator.cpp
//
// This plugin is an example of a file translator.  Although this is not
// the actual code used by Maya when it creates files in MayaAscii format,
// it nonetheless produces a very close approximation of the of that same
// format.  Close enough that Maya can load the resulting files as if they
// were MayaAscii.
//
// Currently, the plugin does not support the following:
//
//   o  Export Selection.  The plugin will only export entire scenes.
//
//   o  Referencing files into the default namespace, or using a renaming
//      prefix.  It only supports referencing files into a separate
//      namespace.
//
//   o  MEL reference files.
//
//   o  Size hints for multi plugs.
//

#include <maya/MDagPath.h>
#include <maya/MDagPathArray.h>
#include <maya/MFileIO.h>
#include <maya/MFileObject.h>
#include <maya/MFnAttribute.h>
#include <maya/MFnCompoundAttribute.h>
#include <maya/MFnDagNode.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnPlugin.h>
#include <maya/MGlobal.h>
#include <maya/MItDag.h>
#include <maya/MItDependencyNodes.h>
#include <maya/MObjectArray.h>
#include <maya/MPlug.h>
#include <maya/MPlugArray.h>
#include <maya/MPxFileTranslator.h>
#include <maya/MString.h>
#include <maya/MStringArray.h>

#include <ctype.h>
#include <maya/MFStream.h>
#include <time.h>

class maTranslator : public MPxFileTranslator
{
public:
    bool        haveReadMethod() const;
    bool        haveWriteMethod() const;
    MString     defaultExtension() const;

    MFileKind   identifyFile(
                    const MFileObject& file,
                    const char* buffer,
                    short size
                ) const;

    MStatus     reader(
                    const MFileObject& file,
                    const MString& options,
                    FileAccessMode mode
                );

    MStatus     writer(
                    const MFileObject& file,
                    const MString& options,
                    FileAccessMode mode
                );

    static void*    creator();
    static void     setPluginName(const MString& name);
    static MString  translatorName();

protected:
    void    getAddAttrCmds(const MObject& node, MStringArray& cmds);
    void    getSetAttrCmds(const MObject& node, MStringArray& cmds);
    void    writeBrokenRefConnections(fstream& f);
    void    writeConnections(fstream& f);
    void    writeCreateNode(fstream& f, const MObject& node);

    void    writeCreateNode(
                fstream& f, const MDagPath& nodePath, const MDagPath& parentPath
            );

    void    writeDagNodes(fstream& f);
    void    writeDefaultNodes(fstream& f);
    void    writeFileInfo(fstream& f);
    void    writeFooter(fstream& f, const MString& fileName);
    void    writeHeader(fstream& f, const MString& fileName);
    void    writeInstances(fstream& f);
    void    writeLockNode(fstream& f, const MObject& node);
    void    writeNodeAttrs(fstream& f, const MObject& node, bool isSelected);
    void    writeNodeConnections(fstream& f, const MObject& node);
    void    writeNonDagNodes(fstream& f);

    void    writeParent(
                fstream& f,
                const MDagPath& parent,
                const MDagPath& child,
                bool addIt
            );

    void    writePlugSizeHint(fstream& f, const MPlug& plug);
    void    writeReferences(fstream& f);
    void    writeReferenceNodes(fstream& f);
    void    writeRefNodeParenting(fstream& f);
    void    writeRequirements(fstream& f);
    void    writeSelectNode(fstream& f, const MObject& node);
    void    writeUnits(fstream& f);

    static MString  comment(const MString& text);
    static MString  quote(const MString& text);

    static MString  fExtension;
    static MString  fFileVersion;
    static MString  fPluginName;
    static MString  fTranslatorName;

private:
    //
    // These are used to keep track of connections which were made within
    // a referenced file but then broken by main scene file.
    //
    MPlugArray      fBrokenConnSrcs;
    MPlugArray      fBrokenConnDests;

    //
    // This is used to keep track of default nodes.
    //
    MObjectArray    fDefaultNodes;

    //
    // These are used to keep track of those DAG nodes which have multiple
    // instances.  'fInstanceParents' holds the first parent, which is
    // usually set up when the child is created.
    //
    MDagPathArray   fInstanceChildren;
    MDagPathArray   fInstanceParents;

    //
    // This is used to keep track of non-reference nodes with referenced
    // parents and referenced nodes with non-referenced parents, as their
    // parenting requires special handling.
    //
    MDagPathArray   fParentingRequired;

    //
    // These are used to store the IDs of the temporary node flags used by 
    // the translator.
    //
    unsigned int    fAttrFlag;
    unsigned int    fCreateFlag;
    unsigned int    fConnectionFlag;
};

//
// Note that this translator writes out 4.5ff01 version Maya ASCII
// files, regardless of the current Maya version.
//
MString maTranslator::fFileVersion = "4.5ff01";
MString maTranslator::fExtension = "pma";
MString maTranslator::fPluginName = "";
MString maTranslator::fTranslatorName = "Maya ASCII (via plugin)";


inline MString maTranslator::defaultExtension() const
{   return fExtension;      }


inline bool maTranslator::haveReadMethod() const
{   return false;           }


inline bool maTranslator::haveWriteMethod() const
{   return true;            }


inline void maTranslator::setPluginName(const MString& name)
{   fPluginName = name;     }


inline MString maTranslator::translatorName()
{   return fTranslatorName; }


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


//
// Maya calls this method to find out if this translator is capable of
// handling the given file.
//
MPxFileTranslator::MFileKind maTranslator::identifyFile(
        const MFileObject& file, const char* buffer, short bufferLen
) const
{
    MString tagStr = comment(fTranslatorName);
    int     tagLen = tagStr.length();

    //
    // If the buffer contains enough info to positively identify the file,
    // then use it.  Otherwise we'll base the identification on the file
    // extension.
    //
    if (bufferLen >= tagLen)
    {
        MString initialContents(buffer, bufferLen);
        MStringArray    initialLines;

        initialContents.split('\n', initialLines);

        if (initialLines.length() > 0)
        {
            if (((int)initialLines[0].length() >= tagLen)
            &&  (initialLines[0].substring(0, tagLen-1) == tagStr))
            {
                return kIsMyFileType;
            }
        }
    }
    else
    {
        MString fileName(file.name());
        int     fileNameLen = fileName.length();
        int     startOfExtension = fileName.rindex('.') + 1;

        if ((startOfExtension > 0)
        &&  (startOfExtension < fileNameLen)
        &&  (fileName.substring(startOfExtension, fileNameLen) == fExtension))
        {
            return kIsMyFileType;
        }
    }

    return kNotMyFileType;
}


//
// Maya calls this method to have the translator write out a file.
//
MStatus maTranslator::writer(
        const MFileObject& file,
        const MString& /* options */,
        MPxFileTranslator::FileAccessMode mode
)
{
    //
    // For simplicity, we only do full saves/exports.
    //
    if ((mode != kSaveAccessMode) && (mode != kExportAccessMode))
        return MS::kNotImplemented;

    //
    // Let's see if we can open the output file.
    //
    fstream output(file.fullName().asChar(), ios::out | ios::trunc);

    if (!output.good()) return MS::kNotFound;

    //
    // Get some node flags to keep track of those nodes for which we
    // have already done various stages of processing.
    //
    MStatus status;

    fCreateFlag = MFnDependencyNode::allocateFlag(fPluginName, &status);

    if (status)
        fAttrFlag = MFnDependencyNode::allocateFlag(fPluginName, &status);

    if (status)
        fConnectionFlag = MFnDependencyNode::allocateFlag(fPluginName, &status);

    if (!status)
    {
        MGlobal::displayError(
            "Could not allocate three free node flags."
            "  Try unloading some other plugins."
        );

        return MS::kFailure;
    }

    //
    // Run through all of the nodes in the scene and clear their flags.
    //
    MItDependencyNodes  nodesIter;

    for (; !nodesIter.isDone(); nodesIter.next())
    {
        MObject             node = nodesIter.item();
        MFnDependencyNode   nodeFn(node);

        nodeFn.setFlag(fCreateFlag, false);
        nodeFn.setFlag(fAttrFlag, false);
        nodeFn.setFlag(fConnectionFlag, false);
    }

    //
    // Write out the various sections of the file.
    //
    writeHeader(output, file.name());
    writeFileInfo(output);
    writeReferences(output);
    writeRequirements(output);
    writeUnits(output);
    writeDagNodes(output);
    writeNonDagNodes(output);
    writeDefaultNodes(output);
    writeReferenceNodes(output);
    writeConnections(output);
    writeFooter(output, file.name());

    output.close();

    MFnDependencyNode::deallocateFlag(fPluginName, fCreateFlag);

    return MS::kSuccess;
}


void maTranslator::writeHeader(fstream& f, const MString& fileName)
{
    //
    // Get the current time into the same format as used by Maya ASCII
    // files.
    //
    time_t      tempTime = time(NULL);
    struct tm*  curTime = localtime(&tempTime);
    char        formattedTime[100];

    strftime(
        formattedTime, sizeof(formattedTime), "%a, %b %e, %Y %r", curTime
    );

    //
    // Write out the header information.
    //
    f << comment(fTranslatorName).asChar() << " "
        << fFileVersion.asChar() << " scene" << endl;
    f << comment("Name: ").asChar() << fileName.asChar() << endl;
    f << comment("Last modified: ").asChar() << formattedTime << endl;
}


//
// Write out the "fileInfo" command for the freeform information associated
// with the scene.
//
void maTranslator::writeFileInfo(fstream& f)
{
    //
    // There's no direct access to the scene's fileInfo from within the API,
    // so we have to call MEL's 'fileInfo' command.
    //
    MStringArray    fileInfo;

    if (MGlobal::executeCommand("fileInfo -q", fileInfo))
    {
        unsigned    numEntries = fileInfo.length();
        unsigned    i;

        for (i = 0; i < numEntries; i += 2)
        {
            f << "fileInfo " << quote(fileInfo[i]).asChar() << " "
                    << quote(fileInfo[i+1]).asChar() << ";" << endl;
        }
    }
    else
        MGlobal::displayWarning("Could not get scene's fileInfo.");
}


//
// Write out the "file" commands which specify the reference files used by
// the scene.
//
void maTranslator::writeReferences(fstream& f)
{
    MStringArray    files;

    MFileIO::getReferences(files);

    unsigned    numRefs = files.length();
    unsigned    i;

    for (i = 0; i < numRefs; i++)
    {
        MString refCmd = "file -r";
        MString fileName = files[i];
        MString nsName = "";

        //
        // For simplicity, we assume that namespaces are always used when
        // referencing.
        //
        MString tempCmd = "file -q -ns \"";
        tempCmd += fileName + "\"";

        if (MGlobal::executeCommand(tempCmd, nsName))
        {
            refCmd += " -ns \"";
            refCmd += nsName + "\"";
        }
        else
            MGlobal::displayWarning("Could not get namespace name.");

        //
        // Is this a deferred reference?
        //
        tempCmd = "file -q -dr \"";
        tempCmd += fileName + "\"";

        int isDeferred;

        if (MGlobal::executeCommand(tempCmd, isDeferred))
        {
            if (isDeferred) refCmd += " -dr 1";
        }
        else
            MGlobal::displayWarning("Could not get deferred reference info.");

        //
        // Get the file's reference node, if it has one.
        //
        tempCmd = "file -q -rfn \"";
        tempCmd += fileName + "\"";

        MString refNode;

        if (MGlobal::executeCommand(tempCmd, refNode))
        {
            if (refNode.length() > 0)
            {
                refCmd += " -rfn \"";
                refCmd += refNode + "\"";
            }
        }
        else
            MGlobal::displayInfo("Could not query reference node name.");

        //
        // Write out the reference command.
        //
        f << refCmd.asChar() << " \"" << fileName.asChar() << "\";" << endl;
    }
}


//
// Write out the "requires" lines which specify the plugins needed by the
// scene.
//
void maTranslator::writeRequirements(fstream& f)
{
    //
    // Every scene requires Maya itself.
    //
    f << "requires maya \"" << fFileVersion.asChar() << "\";" << endl;

    //
    // Write out requirements for each plugin.
    //
    MStringArray    pluginsUsed;

    if (MGlobal::executeCommand("pluginInfo -q -pluginsInUse", pluginsUsed))
    {
        unsigned    numPlugins = pluginsUsed.length();
        unsigned    i;

        for (i = 0; i < numPlugins; i += 2)
        {
            f << "requires " << quote(pluginsUsed[i]).asChar() << " "
                    << quote(pluginsUsed[i+1]).asChar() << ";" << endl;
        }
    }
    else
    {
        MGlobal::displayWarning(
            "Could not get list of plugins currently in use."
        );
    }
}


//
// Write out the units of measurement currently being used by the scene.
//
void maTranslator::writeUnits(fstream& f)
{
    MString args = "";
    MString result;

    //
    // Linear units.
    //
    if (MGlobal::executeCommand("currentUnit -q -fullName -linear", result))
        args += " -l " + result;
    else
        MGlobal::displayWarning("Could not get current linear units.");

    //
    // Angular units.
    //
    if (MGlobal::executeCommand("currentUnit -q -fullName -angle", result))
        args += " -a " + result;
    else
        MGlobal::displayWarning("Could not get current linear units.");

    //
    // Time units.
    //
    if (MGlobal::executeCommand("currentUnit -q -fullName -time", result))
        args += " -t " + result;
    else
        MGlobal::displayWarning("Could not get current linear units.");

    if (args != "")
    {
        f << "currentUnit" << args.asChar() << ";" << endl;
    }
}


void maTranslator::writeDagNodes(fstream& f)
{
    fParentingRequired.clear();

    MItDag      dagIter;

    dagIter.traverseUnderWorld(true);

    MDagPath    worldPath;

    dagIter.getPath(worldPath);

    //
    // We step over the world node before starting the loop, because it
    // doesn't get written out.
    //
    for (dagIter.next(); !dagIter.isDone(); dagIter.next())
    {
        MDagPath    path;
        dagIter.getPath(path);

        //
        // If the node has already been written, then all of its descendants
        // must have been written, or at least checked, as well, so prune
        // this branch of the tree from the iteration.
        //
        MFnDagNode  dagNodeFn(path);

        if (dagNodeFn.isFlagSet(fCreateFlag))
        {
            dagIter.prune();
            continue;
        }

        //
        // If this is a default node, it will be written out later, so skip
        // it.
        //
        if (dagNodeFn.isDefaultNode()) continue;

        //
        // If this node is not writable, and is not a shared node, then mark
        // it as having been written, and skip it.
        //
        if (!dagNodeFn.canBeWritten() && !dagNodeFn.isShared())
        {
            dagNodeFn.setFlag(fCreateFlag, true);
            continue;
        }

        unsigned int    numParents = dagNodeFn.parentCount();

        if (dagNodeFn.isFromReferencedFile())
        {
            //
            // We don't issue 'creatNode' commands for nodes from referenced
            // files, but if the node has any parents which are not from
            // referenced files, other than the world, then make a note that
            // we'll need to issue extra 'parent' commands for it later on.
            //
            unsigned int i;

            for (i = 0; i < numParents; i++)
            {
                MObject     altParent = dagNodeFn.parent(i);
                MFnDagNode  altParentFn(altParent);

                if (!altParentFn.isFromReferencedFile()
                &&  (altParentFn.object() != worldPath.node()))
                {
                    fParentingRequired.append(path);
                    break;
                }
            }
        }
        else
        {
            //
            // Find the node's parent.
            //
            MDagPath    parentPath = worldPath;

            if (path.length() > 1)
            {
                //
                // Get the parent's path.
                //
                parentPath = path;
                parentPath.pop();

                //
                // If the parent is in the underworld, then find the closest
                // ancestor which is not.
                //
                if (parentPath.pathCount() > 1)
                {
                    //
                    // The first segment of the path contains whatever
                    // portion of the path exists in the world.  So the closest
                    // worldly ancestor is simply the one at the end of that
                    // first path segment.
                    //
                    path.getPath(parentPath, 0);
                }
            }

            MFnDagNode  parentNodeFn(parentPath);

            if (parentNodeFn.isFromReferencedFile())
            {
                //
                // We prefer to parent to a non-referenced node.  So if this
                // node has any other parents, which are not from referenced
                // files and have not already been processed, then we'll
                // skip this instance and wait for an instance through one
                // of those parents.
                //
                unsigned i;

                for (i = 0; i < numParents; i++)
                {
                    if (dagNodeFn.parent(i) != parentNodeFn.object())
                    {
                        MObject     altParent = dagNodeFn.parent(i);
                        MFnDagNode  altParentFn(altParent);

                        if (!altParentFn.isFromReferencedFile()
                        &&  !altParentFn.isFlagSet(fCreateFlag))
                        {
                            break;
                        }
                    }
                }

                if (i < numParents) continue;

                //
                // This node only has parents within referenced files, so
                // create it without a parent and note that we need to issue
                // 'parent' commands for it later on.
                //
                writeCreateNode(f, path, worldPath);

                fParentingRequired.append(path);
            }
            else
            {
                writeCreateNode(f, path, parentPath);

                //
                // Let's see if this node has any parents from referenced
                // files, or any parents other than this one which are not
                // from referenced files.
                //
                unsigned    int i;
                bool        hasRefParents = false;
                bool        hasOtherNonRefParents = false;

                for (i = 0; i < numParents; i++)
                {
                    if (dagNodeFn.parent(i) != parentNodeFn.object())
                    {
                        MObject     altParent = dagNodeFn.parent(i);
                        MFnDagNode  altParentFn(altParent);

                        if (altParentFn.isFromReferencedFile())
                            hasRefParents = true;
                        else
                            hasOtherNonRefParents = true;

                        //
                        // If we've already got positives for both tests,
                        // then there's no need in continuing.
                        //
                        if (hasRefParents && hasOtherNonRefParents) break;
                    }
                }

                //
                // If this node has parents from referenced files, then
                // make note that we will have to issue 'parent' commands
                // later on.
                //
                if (hasRefParents) fParentingRequired.append(path);

                //
                // If this node has parents other than this one which are
                // not from referenced files, then make note that the
                // parenting for the other instances still has to be done.
                //
                if (hasOtherNonRefParents)
                {
                    fInstanceChildren.append(path);
                    fInstanceParents.append(parentPath);
                }
            }

            //
            // Write out the node's 'addAttr', 'setAttr' and 'lockNode'
            // commands.
            //
            writeNodeAttrs(f, path.node(), true);
            writeLockNode(f, path.node());
        }

        //
        // Mark the node as having been written.
        //
        dagNodeFn.setFlag(fCreateFlag, true);
    }

    //
    // Write out the parenting for instances.
    //
    writeInstances(f);
}


//
// If a DAG node is instanced (i.e. has multiple parents), this method
// will put it under its remaining parents.  It will already have been put
// under its first parent when it was created.
//
void maTranslator::writeInstances(fstream& f)
{
    unsigned int numInstancedNodes = fInstanceChildren.length();
    unsigned int i;

    for (i = 0; i < numInstancedNodes; i++)
    {
        MFnDagNode  nodeFn(fInstanceChildren[i]);

        unsigned int numParents = nodeFn.parentCount();
        unsigned int p;

        for (p = 0; p < numParents; p++)
        {
            //
            // We don't want to issue a 'parent' command for the node's
            // existing parent.
            //
            if (nodeFn.parent(i) != fInstanceParents[i].node())
            {
                MObject     parent = nodeFn.parent(i);
                MFnDagNode  parentFn(parent);

                if (!parentFn.isFromReferencedFile())
                {
                    //
                    // Get the first path to the parent node.
                    //
                    MDagPath    parentPath;

                    MDagPath::getAPathTo(parentFn.object(), parentPath);

                    writeParent(f, parentPath, fInstanceChildren[i], true);
                }
            }
        }
    }

    //
    // We don't need this any more, so free up the space.
    //
    fInstanceChildren.clear();
    fInstanceParents.clear();
}


//
// Write out a 'parent' command to parent one DAG node under another.
//
void maTranslator::writeParent(
        fstream& f, const MDagPath& parent, const MDagPath& child, bool addIt
)
{
    f << "parent -s -nc -r ";
 
    //
    // If this is not the first parent then we have to include the "-a/add"
    // flag.
    //
    if (addIt) f << "-a ";

    //
    // If the parent is the world, then we must include the "-w/world" flag.
    //
    if (parent.length() == 0) f << "-w ";

    f << "\"" << child.partialPathName().asChar() << "\"";

    //
    // If the parent is NOT the world, then give the parent's name.
    //
    if (parent.length() != 0)
        f << " \"" << parent.partialPathName().asChar() << "\"";

    f << ";" << endl;
}


void maTranslator::writeNonDagNodes(fstream& f)
{
    MItDependencyNodes  nodeIter;

    for (; !nodeIter.isDone(); nodeIter.next())
    {
        MObject             node = nodeIter.item();
        MFnDependencyNode   nodeFn(node);

        //
        // Save default nodes for later processing.
        //
        if (nodeFn.isDefaultNode())
        {
            fDefaultNodes.append(node);
        }
        else if (!nodeFn.isFromReferencedFile()
        &&  !nodeFn.isFlagSet(fCreateFlag))
        {
            //
            // If this node is either writable or shared, then write it out.
            // Otherwise don't, but still mark it as having been written so
            // that we don't end up processing it again at some later time.
            //
            if (nodeFn.canBeWritten() || nodeFn.isShared())
            {
                writeCreateNode(f, node);
                writeNodeAttrs(f, node, true);
                writeLockNode(f, node);
            }

            nodeFn.setFlag(fCreateFlag, true);
            nodeFn.setFlag(fAttrFlag, true);
        }
    }
}


void maTranslator::writeDefaultNodes(fstream& f)
{
    //
    // For default nodes we don't write out a createNode statement, but we
    // still write added attributes and changed attribute values.
    //
    unsigned int    numNodes = fDefaultNodes.length();
    unsigned int    i;

    for (i = 0; i < numNodes; i++)
    {
        writeNodeAttrs(f, fDefaultNodes[i], false);

        MFnDependencyNode   nodeFn(fDefaultNodes[i]);

        nodeFn.setFlag(fAttrFlag, true);
    }
}


//
// Write out the 'addAttr' and 'setAttr' commands for a node.
//
void maTranslator::writeNodeAttrs(
        fstream& f, const MObject& node, bool isSelected
)
{
    MFnDependencyNode   nodeFn(node);

    if (nodeFn.canBeWritten())
    {
        MStringArray    addAttrCmds;
        MStringArray    setAttrCmds;

        getAddAttrCmds(node, addAttrCmds);
        getSetAttrCmds(node, setAttrCmds);

        unsigned int    numAddAttrCmds = addAttrCmds.length();
        unsigned int    numSetAttrCmds = setAttrCmds.length();

        if (numAddAttrCmds + numSetAttrCmds > 0)
        {
            //
            // If the node is not already selected, then issue a command to
            // select it.
            //
            if (!isSelected) writeSelectNode(f, node);

            unsigned int i;

            for (i = 0; i < numAddAttrCmds; i++)
                f << addAttrCmds[i].asChar() << endl;

            for (i = 0; i < numSetAttrCmds; i++)
                f << setAttrCmds[i].asChar() << endl;
        }
    }
}


void maTranslator::writeReferenceNodes(fstream& f)
{
    //
    // We don't write out createNode commands for reference nodes, but
    // we do write out parenting between them and non-reference nodes,
    // as well as attributes added and attribute values changed after the
    // referenced file was loaded
    //
    writeRefNodeParenting(f);

    //
    // Output the commands for DAG nodes first.
    //
    MItDag  dagIter;

    for (dagIter.next(); !dagIter.isDone(); dagIter.next())
    {
        MObject             node = dagIter.item();
        MFnDependencyNode   nodeFn(node);

        if (nodeFn.isFromReferencedFile()
        &&  !nodeFn.isFlagSet(fAttrFlag))
        {
            writeNodeAttrs(f, node, false);

            //
            // Make note of any connections to this node which have been
            // broken by the main scene.
            //
            MFileIO::getReferenceConnectionsBroken(
                node, fBrokenConnSrcs, fBrokenConnDests, true, true
            );

            nodeFn.setFlag(fAttrFlag, true);
        }
    }

    //
    // Now do the remaining, non-DAG nodes.
    //
    MItDependencyNodes  nodeIter;

    for (; !nodeIter.isDone(); nodeIter.next())
    {
        MObject             node = nodeIter.item();
        MFnDependencyNode   nodeFn(node);

        if (nodeFn.isFromReferencedFile()
        &&  !nodeFn.isFlagSet(fAttrFlag))
        {
            writeNodeAttrs(f, node, false);

            //
            // Make note of any connections to this node which have been
            // broken by the main scene.
            //
            MFileIO::getReferenceConnectionsBroken(
                node, fBrokenConnSrcs, fBrokenConnDests, true, true
            );

            nodeFn.setFlag(fAttrFlag, true);
        }
    }
}


//
// Write out all of the connections in the scene.
//
void maTranslator::writeConnections(fstream& f)
{
    //
    // If the scene has broken any connections which were made in referenced
    // files, handle those first so that the attributes are free for any new
    // connections which may come along.
    //
    writeBrokenRefConnections(f);

    //
    // We're about to write out the scene's connections in three parts: DAG
    // nodes, non-DAG non-default nodes, then default nodes.
    //
    // It's really not necessary that we group them like this and would in
    // fact be more efficient to do them all in one MItDependencyNodes
    // traversal.  However, this is the order in which the normal MayaAscii
    // translator does them, so this makes it easier to compare the output
    // of this translator to Maya's output.
    //

    //
    // Write out connections for the DAG nodes first.
    //
    MItDag  dagIter;
    dagIter.traverseUnderWorld(true);

    for (dagIter.next(); !dagIter.isDone(); dagIter.next())
    {
        MObject     node = dagIter.item();
        MFnDagNode  dagNodeFn(node);

        if (!dagNodeFn.isFlagSet(fConnectionFlag)
        &&  dagNodeFn.canBeWritten()
        &&  !dagNodeFn.isDefaultNode())
        {
            writeNodeConnections(f, dagIter.item());
            dagNodeFn.setFlag(fConnectionFlag, true);
        }
    }

    //
    // Now do the non-DAG, non-default nodes.
    //
    MItDependencyNodes  nodeIter;

    for (; !nodeIter.isDone(); nodeIter.next())
    {
        MFnDependencyNode   nodeFn(nodeIter.item());

        if (!nodeFn.isFlagSet(fConnectionFlag)
        &&  nodeFn.canBeWritten()
        &&  !nodeFn.isDefaultNode())
        {
            writeNodeConnections(f, nodeIter.item());
            nodeFn.setFlag(fConnectionFlag, true);
        }
    }

    //
    // And finish up with the default nodes.
    //
    unsigned int    numNodes = fDefaultNodes.length();
    unsigned int    i;

    for (i = 0; i < numNodes; i++)
    {
        MFnDependencyNode   nodeFn(fDefaultNodes[i]);

        if (!nodeFn.isFlagSet(fConnectionFlag)
        &&  nodeFn.canBeWritten()
        &&  nodeFn.isDefaultNode())
        {
            writeNodeConnections(f, fDefaultNodes[i]);
            nodeFn.setFlag(fConnectionFlag, true);
        }
    }
}


//
// Write the 'disconnectAttr' statements for those connections which were
// made in referenced files, but broken in the main scene.
//
void maTranslator::writeBrokenRefConnections(fstream& f)
{
    unsigned int    numBrokenConnections = fBrokenConnSrcs.length();
    unsigned int    i;

    for (i = 0; i < numBrokenConnections; i++)
    {
        f << "disconnectAttr \""
          << fBrokenConnSrcs[i].partialName(true).asChar()
          << "\" \""
          << fBrokenConnDests[i].partialName(true).asChar()
          << "\"";

        //
        // If the destination plug is a multi for which index does not
        // matter, then we must add a "-na/nextAvailable" flag to the
        // command.
        //
        MObject         attr = fBrokenConnDests[i].attribute();
        MFnAttribute    attrFn(attr);

        if (!attrFn.indexMatters()) f << " -na";

        f << ";" << endl;
    }
}


//
// Write the 'connectAttr' commands for all of a node's incoming
// connections.
//
void maTranslator::writeNodeConnections(fstream& f, const MObject& node)
{
    MFnDependencyNode   nodeFn(node);
    MPlugArray          plugs;

    nodeFn.getConnections(plugs);

    unsigned int        numBrokenConns = fBrokenConnSrcs.length();
    unsigned int        numPlugs = plugs.length();
    unsigned int        i;

    for (i = 0; i < numPlugs; i++)
    {
        //
        // We only care about connections where we are the destination.
        //
        MPlug       destPlug = plugs[i];
        MPlugArray  srcPlug;

        destPlug.connectedTo(srcPlug, true, false);

        if (srcPlug.length() > 0)
        {
            MObject             srcNode = srcPlug[0].node();
            MFnDependencyNode   srcNodeFn(srcNode);

            //
            // Don't write the connection if the source is not writable...
            //
            if (!srcNodeFn.canBeWritten()) continue;

            //
            // or the connection was made in a referenced file...
            //
            if (destPlug.isFromReferencedFile()) continue;

            //
            // or the plug is procedural...
            //
            if (destPlug.isProcedural()) continue;

            //
            // or it is a connection between a default node and a shared
            // node (because those will get set up automatically).
            //
            if (srcNodeFn.isDefaultNode() && nodeFn.isShared()) continue;

            f << "connectAttr \"";

            //
            // Default nodes get a colon at the start of their names.
            //
            if (srcNodeFn.isDefaultNode()) f << ":";

            f << srcPlug[0].partialName(true).asChar()
              << "\" \"";

            if (nodeFn.isDefaultNode()) f << ":";

            f << destPlug.partialName(true).asChar()
              << "\"";

            //
            // If the src plug is also one from which a broken
            // connection originated, then add the "-rd/referenceDest" flag
            // to the command.  That will help Maya to better adjust if the
            // referenced file has changed the next time it is loaded.
            //
            if (srcNodeFn.isFromReferencedFile())
            {
                unsigned int j;

                for (j = 0; j < numBrokenConns; j++)
                {
                    if (fBrokenConnSrcs[j] == srcPlug[0])
                    {
                        f << " -rd \""
                          << fBrokenConnDests[j].partialName(true).asChar()
                          << "\"";

                        break;
                    }
                }
            }

            //
            // If the plug is locked, then add a "-l/lock" flag to the
            // command.
            //
            if (destPlug.isLocked()) f << " -l on";

            //
            // If the destination attribute is a multi for which index
            // does not matter, then we must add the "-na/nextAvailable"
            // flag to the command.
            //
            MObject         attr = destPlug.attribute();
            MFnAttribute    attrFn(attr);

            if (!attrFn.indexMatters()) f << " -na";

            f << ";" << endl;
        }
    }
}


//
// Write out a 'createNode' command for a DAG node.
//
void maTranslator::writeCreateNode(
        fstream& f, const MDagPath& nodePath, const MDagPath& parentPath
)
{
    MObject     node(nodePath.node());
    MFnDagNode  nodeFn(node);

    //
    // Write out the 'createNode' command for this node.
    //
    f << "createNode " << nodeFn.typeName().asChar();

    //
    // If the node is shared, then add a "-s/shared" flag to the command.
    //
    if (nodeFn.isShared()) f << " -s";

    f << " -n \"" << nodeFn.name().asChar() << "\"";

    //
    // If this is not a top-level node, then include its first parent in the
    // command.
    //
    if (parentPath.length() > 0)
        f << " -p \"" << parentPath.partialPathName().asChar() << "\"";
   
    f << ";" << endl;
}


//
// Write out a 'createNode' command for a non-DAG node.
//
void maTranslator::writeCreateNode(fstream& f, const MObject& node)
{
    MFnDependencyNode   nodeFn(node);

    //
    // Write out the 'createNode' command for this node.
    //
    f << "createNode " << nodeFn.typeName().asChar();

    //
    // If the node is shared, then add a "-s/shared" flag to the command.
    //
    if (nodeFn.isShared()) f << " -s";

    f << " -n \"" << nodeFn.name().asChar() << "\";" << endl;
}


//
// Write out a "lockNode" command.
//
void maTranslator::writeLockNode(fstream& f, const MObject& node)
{
    MFnDependencyNode   nodeFn(node);

    //
    // By default, nodes are not locked, so we only have to issue a
    // "lockNode" command if the node is locked.
    //
    if (nodeFn.isLocked()) f << "lockNode;" << endl;
}


//
// Write out a "select" command.
//
void maTranslator::writeSelectNode(fstream& f, const MObject& node)
{
    MStatus             status;
    MFnDependencyNode   nodeFn(node);
    MString             nodeName;

    //
    // If the node has a unique name, then we can just go ahead and use
    // that.  Otherwise we will have to use part of its DAG path to to
    // distinguish it from the others with the same name.
    //
    if (nodeFn.hasUniqueName())
        nodeName = nodeFn.name();
    else
    {
        //
        // Only DAG nodes are allowed to have duplicate names.
        //
        MFnDagNode  dagNodeFn(node, &status);

        if (!status)
        {
            MGlobal::displayWarning(
                MString("Node '") + nodeFn.name()
                + "' has a non-unique name but claimes to not be a DAG node.\n"
                + "Using non-unique name."
            );

            nodeName = nodeFn.name();
        }
        else
            nodeName = dagNodeFn.partialPathName();
    }

    //
    // We use the "-ne/noExpand" flag so that if the node is a set, we
    // actually select the set itself, rather than its members.
    //
    f << "select -ne ";

    //
    // Default nodes get a colon slapped onto the start of their names.
    //
    if (nodeFn.isDefaultNode()) f << ":";

    f << nodeName.asChar() << ";\n";
}


//
// Deal with nodes whose parenting is between referenced and non-referenced
// nodes.
//
void maTranslator::writeRefNodeParenting(fstream& f)
{
    unsigned int numNodes = fParentingRequired.length();
    unsigned int i;

    for (i = 0; i < numNodes; i++)
    {
        MFnDagNode  nodeFn(fParentingRequired[i]);

        //
        // Find out if this node has any parents from referenced or
        // non-referenced files.
        //
        bool            hasRefParents = false;
        bool            hasNonRefParents = false;
        unsigned int    numParents = nodeFn.parentCount();
        unsigned int    p;

        for (p = 0; p < numParents; p++)
        {
            MObject     parent = nodeFn.parent(p);
            MFnDagNode  parentFn(parent);

            if (parentFn.isFromReferencedFile())
                hasRefParents = true;
            else
                hasNonRefParents = true;

            if (hasRefParents && hasNonRefParents) break;
        }

        //
        // If this node is from a referenced file and it has parents which
        // are also from a referenced file, then it already has its first
        // parent and all others are added instances.
        //
        // Similarly if the node is not from a referenced file and has
        // parents which are also not from referenced files.
        //
        bool    alreadyHasFirstParent =
            (nodeFn.isFromReferencedFile() ? hasRefParents : hasNonRefParents);

        //
        // Now run through the parents again and output any parenting
        // which involves a non-referenced node, either as parent or child.
        //
        for (p = 0; p < numParents; p++)
        {
            MObject     parent = nodeFn.parent(p);
            MFnDagNode  parentFn(parent);

            if (parentFn.isFromReferencedFile() != nodeFn.isFromReferencedFile())
            {
                //
                // Get the first path to the parent.
                //
                MDagPath    parentPath;
                MDagPath::getAPathTo(parentFn.object(), parentPath);

                writeParent(
                    f, parentPath, fParentingRequired[i], alreadyHasFirstParent
                );

                //
                // If it didn't have its first parent before, it does now.
                //
                alreadyHasFirstParent = true;
            }
        }
    }
}


void maTranslator::writeFooter(fstream& f, const MString& fileName)
{
    f << comment(" End of ").asChar() << fileName.asChar() << endl;
}


void maTranslator::getAddAttrCmds(const MObject& node, MStringArray& cmds)
{
    //
    // Run through the node's attributes.
    //
    MFnDependencyNode   nodeFn(node);
    unsigned int        numAttrs = nodeFn.attributeCount();
    unsigned int        i;

    for (i = 0; i < numAttrs; i++)
    {
        //
        // Use the attribute ordering which Maya uses when doing I/O.
        //
        MObject attr = nodeFn.reorderedAttribute(i);

        //
        // If this attribute has been added since the node was created,
        // then we may want to write out an addAttr statement for it.
        //
        if (nodeFn.isNewAttribute(attr))
        {
            MFnAttribute    attrFn(attr);

            //
            // If the attribute has a parent then ignore it because it will
            // be processed when we process the parent.
            //
            MStatus status;

            attrFn.parent(&status);

            if (status == MS::kNotFound)
            {
                //
                // If the attribute is a compound, then we can do its entire
                // tree at once.
                //
                MFnCompoundAttribute    cAttrFn(attr, &status);

                if (status)
                {
                    MStringArray    newCmds;

                    cAttrFn.getAddAttrCmds(newCmds);

                    unsigned int    numCommands = newCmds.length();
                    unsigned int    c;

                    for (c = 0; c < numCommands; c++)
                    {
                        if (newCmds[c] != "")
                            cmds.append(newCmds[c]);
                    }
                }
                else
                {
                    MString newCmd = attrFn.getAddAttrCmd();

                    if (newCmd != "") cmds.append(newCmd);
                }
            }
        }
    }
}


void maTranslator::getSetAttrCmds(const MObject& node, MStringArray& cmds)
{
    //
    // Get rid of any garbage already in the array.
    //
    cmds.clear();

    //
    // Run through the node's attributes.
    //
    MFnDependencyNode   nodeFn(node);
    unsigned int        numAttrs = nodeFn.attributeCount();
    unsigned int        i;

    for (i = 0; i < numAttrs; i++)
    {
        //
        // Use the attribute ordering which Maya uses when doing I/O.
        //
        MObject         attr = nodeFn.reorderedAttribute(i);
        MFnAttribute    attrFn(attr);
        MStatus         status;

        attrFn.parent(&status);

        bool            isChild = (status != MS::kNotFound);

        //
        // We don't want attributes which are children of other attributes
        // because they will be processed when we process the parent.
        //
        // And we only want storable attributes which accept inputs.
        //
        if (!isChild && attrFn.isStorable() && attrFn.isWritable())
        {
            //
            // Get a plug for the attribute.
            //
            MPlug   plug(node, attr);

            //
            // Get setAttr commands for this attribute, and any of its
            // children, which have had their values changed by the scene.
            //
            MStringArray    newCmds;

            plug.getSetAttrCmds(newCmds, MPlug::kChanged, false);

            unsigned int    numCommands = newCmds.length();
            unsigned int    c;

            for (c = 0; c < numCommands; c++)
            {
                if (newCmds[c] != "")
                    cmds.append(newCmds[c]);
            }
        }
    }
}


MStatus maTranslator::reader(
        const MFileObject& /* file */,
        const MString& /* options */,
        MPxFileTranslator::FileAccessMode /* mode */
)
{
    return MS::kNotImplemented;
}


MString maTranslator::comment(const MString& text)
{
    MString result("//");
    result += text;

    return result;
}


//
// Convert a string into a quoted, printable string.
//
MString maTranslator::quote(const MString& str)
{
    const char* cstr = str.asChar();
    int strLen = str.length();
    int i;

    MString result("\"");

    for (i = 0; i < strLen; i++)
    {
        int c = cstr[i];

        if (isprint(c))
        {
            //
            // Because backslash and double-quote have special meaning
            // within a printable string, we have to turn those into escape
            // sequences.
            //
            switch (c)
            {
                case '"':
                    result += "\\\"";
                break;

                case '\\':
                    result += "\\\\";
                break;

                default:
                    result += MString((const char*)&c, 1);
                break;
            }
        }
        else
        {
            //
            // Convert non-printable characters into escape sequences.
            //
            switch (c)
            {
                case '\n':
                    result += "\\n";
                break;

                case '\t':
                    result += "\\t";
                break;

                case '\b':
                    result += "\\b";
                break;

                case '\r':
                    result += "\\r";
                break;

                case '\f':
                    result += "\\f";
                break;

                case '\v':
                    result += "\\v";
                break;

                case '\007':
                    result += "\\a";
                break;

                default:
                {
                    //
                    // Encode it as an octal escape sequence.
                    //
                    char buff[5];
                    sprintf(buff, "\\%.3o", c);
                    result += MString(buff, 4);
                }
            }
        }
    }

    //
    // Add closing quote.
    //
    result += "\"";

    return result;
}


// ****************************************

MStatus initializePlugin(MObject obj)
{
    MFnPlugin plugin(obj, PLUGIN_COMPANY, "1.0", "Any");

    maTranslator::setPluginName(plugin.name());

    plugin.registerFileTranslator(
        maTranslator::translatorName(),
        NULL,
        maTranslator::creator,
        NULL,
        NULL,
        false
    );

    return MS::kSuccess;
}


MStatus uninitializePlugin(MObject obj)
{
    MFnPlugin plugin( obj );

    plugin.deregisterFileTranslator(maTranslator::translatorName());

    return MS::kSuccess;
}