//- // ========================================================================== // 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. // // ========================================================================== //+ // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 true; } 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; }