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