1 /*
2     Copyright (c) 2008-2009 NetAllied Systems GmbH
3 
4 	This file is part of COLLADAMaya.
5 
6     Portions of the code are:
7     Copyright (c) 2005-2007 Feeling Software Inc.
8     Copyright (c) 2005-2007 Sony Computer Entertainment America
9     Copyright (c) 2004-2005 Alias Systems Corp.
10 
11     Licensed under the MIT Open Source License,
12     for details please see LICENSE file or the website
13     http://www.opensource.org/licenses/mit-license.php
14 */
15 
16 #include "COLLADAMayaStableHeaders.h"
17 #include "COLLADAMayaReferenceManager.h"
18 #include "COLLADAMayaExportOptions.h"
19 #include "COLLADAMayaDagHelper.h"
20 
21 #include "maya/MFileIO.h"
22 #include "maya/MFnDagNode.h"
23 #include "maya/MFnTransform.h"
24 #if MAYA_API_VERSION >= 201400
25 #include "maya/MFnReference.h"
26 #endif
27 
28 namespace COLLADAMaya
29 {
30 
31     // --------------------------------------
ReferenceManager()32     ReferenceManager::ReferenceManager()
33     : mReferences (0)
34     , mFiles (0)
35     {
36 
37     }
38 
39     // --------------------------------------
~ReferenceManager()40     ReferenceManager::~ReferenceManager()
41     {
42         deleteReferences();
43         deleteFiles();
44     }
45 
46     // --------------------------------------
deleteReferences()47     void ReferenceManager::deleteReferences ()
48     {
49         if ( mReferences.size() > 0 )
50         {
51             for ( uint i=0; i<mReferences.size(); ++i )
52             {
53                 Reference* reference = mReferences[i];
54                 delete reference;
55             }
56             mReferences.clear();
57         }
58     }
59 
60     // --------------------------------------
deleteFiles()61     void ReferenceManager::deleteFiles ()
62     {
63         if ( mFiles.size() > 0 )
64         {
65             for ( uint i=0; i<mFiles.size(); ++i )
66             {
67                 ReferenceFile* file = mFiles[i];
68                 delete file;
69             }
70             mFiles.clear();
71         }
72     }
73 
74     // --------------------------------------
initialize()75     void ReferenceManager::initialize()
76     {
77         deleteReferences ();
78         deleteFiles ();
79 
80         if ( !ExportOptions::exportXRefs() || ExportOptions::dereferenceXRefs() ) return;
81 
82 #if MAYA_API_VERSION >= 600
83 
84         MStatus status;
85         MStringArray referenceFilenames;
86         MFileIO::getReferences ( referenceFilenames );
87 
88         uint referenceCount = referenceFilenames.length();
89         mReferences.reserve( referenceCount );
90         for (uint i = 0; i < referenceCount; ++i)
91         {
92             MString& filename = referenceFilenames[i];
93             MObject referenceNode = getReferenceNode ( filename );
94             if ( referenceNode != MObject::kNullObj ) processReference ( referenceNode );
95         }
96 #endif
97     }
98 
99     // --------------------------------------
getReferenceNode(const MString & filename)100     MObject	ReferenceManager::getReferenceNode ( const MString& filename )
101     {
102         MString referenceNodeName;
103         MString command = MString ( "file -q -rfn \"" ) + filename + "\"";
104         MGlobal::executeCommand ( command, referenceNodeName );
105         return DagHelper::getNode ( referenceNodeName );
106     }
107 
108     // --------------------------------------
processReference(const MObject & referenceNode)109     void ReferenceManager::processReference ( const MObject& referenceNode )
110     {
111         MStatus status;
112         MFnDependencyNode referenceNodeFn ( referenceNode, &status );
113         if (status != MStatus::kSuccess) return;
114 
115 #if MAYA_API_VERSION >= 600
116         MString referenceNodeName = MFnDependencyNode( referenceNode ).name();
117 
118         Reference* reference = new Reference();
119         reference->referenceNode = referenceNode;
120         mReferences.push_back ( reference );
121 
122         // Get the paths of the root transforms included in this reference
123         MObjectArray subReferences;
124         getRootObjects ( referenceNode, reference->paths, subReferences );
125         uint pathCount = reference->paths.length();
126 
127         // Process the sub-references first
128         uint subReferenceCount = subReferences.length();
129         for (uint i = 0; i < subReferenceCount; ++i)
130         {
131             MObject& subReference = subReferences[i];
132             if ( subReference != MObject::kNullObj ) processReference ( subReference );
133         }
134 
135         // Retrieve the reference node's filename
136         MString command = MString("reference -rfn \"") + referenceNodeFn.name() + MString("\" -q -filename;");
137         MString filename;
138         status = MGlobal::executeCommand ( command, filename );
139         if (status != MStatus::kSuccess || filename.length() == 0) return;
140 
141         // Strip the filename of the multiple file token
142         int stripIndex = filename.index('{');
143         if (stripIndex != -1) filename = filename.substring(0, stripIndex - 1);
144 
145         // Avoid transform look-ups on COLLADA references.
146         int extLocation = filename.rindex('.');
147         if (extLocation > 0)
148         {
149             MString ext = filename.substring(extLocation + 1, filename.length() - 1).toLowerCase();
150             if (ext == "dae" || ext == "xml") return;
151         }
152 
153         // Check for already existing file information
154         // Otherwise create a new file information sheet with current node names
155         for ( ReferenceFileList::iterator it = mFiles.begin(); it != mFiles.end(); ++it )
156         {
157             if ((*it)->filename == filename)
158             {
159                 reference->file = (*it);
160                 break;
161             }
162         }
163 
164         if ( reference->file == NULL ) reference->file = processReferenceFile(filename);
165 
166         // Get the list of the root transform's first child's unreferenced parents.
167         // This is a list of the imported nodes!
168         for (uint j = 0; j < pathCount; ++j)
169         {
170             MDagPath path = reference->paths[j];
171             if (path.childCount() > 0)
172             {
173                 path.push ( path.child(0) );
174                 MFnDagNode childNode ( path );
175                 if (!childNode.object().hasFn(MFn::kTransform)) continue;
176 
177                 uint parentCount = childNode.parentCount();
178                 for (uint k = 0; k < parentCount; ++k)
179                 {
180                     MFnDagNode parentNode(childNode.parent(k));
181                     if (parentNode.object() == MObject::kNullObj || parentNode.isFromReferencedFile()) continue;
182 
183                     MDagPath parentPath = MDagPath::getAPathTo(parentNode.object());
184                     if (parentPath.length() > 0)
185                     {
186                         ReferenceRootList::iterator it =
187                             reference->reroots.insert( reference->reroots.end(), ReferenceRoot() );
188                         (*it).index = j;
189                         (*it).reroot = parentPath;
190                     }
191                 }
192             }
193         }
194 #endif
195     }
196 
197     // --------------------------------------
processReferenceFile(const MString & filename)198     ReferenceFile* ReferenceManager::processReferenceFile(const MString& filename)
199     {
200         ReferenceFile* file = new ReferenceFile();
201         file->filename = filename;
202         mFiles.push_back(file);
203 
204 #if MAYA_API_VERSION >= 800
205         return file; // Versions 8.00 and 8.50 of Maya don't allow us to create references inside a plug-in.
206 
207 #elif MAYA_API_VERSION >= 600
208 
209         // Get the original transformations for this file.
210         // 1. Create a new reference
211         MString tempFilename;
212         MObject tempReferenceNode;
213 
214         {
215             MString command = MString("file -r -type \"COLLADA importer\" -namespace \"_TEMP_EXP_NAMESPACE\" \"") + filename + "\";";
216             MGlobal::executeCommand(command, tempFilename);
217 
218             tempFilename = getLastReferenceFilename ( tempFilename );
219 
220             MObject tempReferenceNode = getReferenceNode ( tempFilename );
221             MString tempNodeName = MFnDependencyNode(tempReferenceNode).name();
222             command = MString("file -loadReference \"") + tempNodeName + "\" \"" + tempFilename + "\";";
223             MGlobal::executeCommand(command);
224         }
225 
226         // 2. Get the original transformations for the root transforms of the temporary reference object
227         MDagPathArray tempRoots;
228         MObjectArray subReferences;
229         getRootObjects ( tempReferenceNode, tempRoots, subReferences );
230         uint tempRootCount = tempRoots.length();
231         for (uint j = 0; j < tempRootCount; ++j)
232         {
233             MFnTransform tempT(tempRoots[j]);
234             file->originalTransformations.push_back( tempT.transformation() );
235         }
236 
237         // 3. Get the original node names. This will be used as the URL for export
238         file->rootNames.setLength(tempRootCount);
239         for (uint j = 0; j < tempRootCount; ++j)
240         {
241             MString& originalName = file->rootNames[j];
242             originalName = tempRoots[j].partialPathName();
243             originalName = originalName.substring ( originalName.index(':') + 1, originalName.length() );
244         }
245 
246         // 4. Cleanup: remove this reference
247         MString command = MString("file -rr \"") + tempFilename + "\";";
248         MGlobal::executeCommand ( command );
249 
250 #endif // MAYA >= 600
251 
252         return file;
253     }
254 
255     // --------------------------------------
getLastReferenceFilename(const MString & referenceResult)256     MString	ReferenceManager::getLastReferenceFilename( const MString& referenceResult )
257     {
258 #if MAYA_API_VERSION >= 650
259         return referenceResult;
260 #else
261         MStringArray filenames;
262         MGlobal::executeCommand("file -q -r;", filenames);
263         // TODO
264 //         if ( ImportOptions::isOpenMode() )
265 //         {
266 //             return (filenames.length() > 0) ? filenames[filenames.length() - 1] : "";
267 //         }
268 //         else
269         {
270             return referenceResult;
271         }
272 #endif
273     }
274 
275     // --------------------------------------
getRootObjects(const MObject & referenceNode,MDagPathArray & rootPaths,MObjectArray & subReferences)276     void ReferenceManager::getRootObjects(
277         const MObject& referenceNode,
278         MDagPathArray& rootPaths,
279         MObjectArray& subReferences)
280     {
281         rootPaths.clear();
282         subReferences.clear();
283 
284         MFnDependencyNode referenceNodeFn(referenceNode);
285 
286         // Get the paths of all the dag nodes included in this reference
287         MStringArray nodeNames;
288         MString command = MString("reference -rfn \"") + referenceNodeFn.name() + "\" -q -node -dp;";
289         MGlobal::executeCommand(command, nodeNames);
290 
291         uint nodeNameCount = nodeNames.length();
292         MDagPathArray nodePaths;
293         for (uint j = 0; j < nodeNameCount; ++j)
294         {
295             MObject o = DagHelper::getNode(nodeNames[j]);
296             MDagPath p = DagHelper::getShortestDagPath(o);
297             if (p.length() > 0)
298             {
299                 nodePaths.append(p);
300             }
301             else
302             {
303                 if (o != MObject::kNullObj && o.apiType() == MFn::kReference
304                     && strstr(nodeNames[j].asChar(), "_UNKNOWN_REF_NODE") == NULL)
305                 {
306                     subReferences.append(o);
307                 }
308             }
309         }
310 
311         // Keep only the root transform for the reference in our path arrays
312         uint nodePathCount = nodePaths.length();
313         for (uint j = 0; j < nodePathCount; ++j)
314         {
315             const MDagPath& p = nodePaths[j];
316             if ( !isRootTransform ( nodePaths, p ) ) continue;
317             rootPaths.append(p);
318         }
319     }
320 
321     // --------------------------------------
isRootTransform(const MDagPathArray & allPaths,const MDagPath & testPath)322     bool ReferenceManager::isRootTransform ( const MDagPathArray& allPaths, const MDagPath& testPath )
323     {
324         MStatus status;
325         MFnTransform transform(testPath, &status);
326         if (status != MStatus::kSuccess) return false;
327         uint pathCount = allPaths.length();
328 
329         uint parentCount = transform.parentCount();
330         for (uint k = 0; k < parentCount; ++k)
331         {
332             MFnDependencyNode parentNode(transform.parent(k));
333             if (!parentNode.isFromReferencedFile()) continue;
334 
335             for (uint m = 0; m < pathCount; ++m)
336             {
337                 if ( allPaths[m].node() == parentNode.object() ) return false;
338             }
339         }
340 
341         return true;
342     }
343 
344     // --------------------------------------
getReferenceFilename(const MDagPath & path)345     MString ReferenceManager::getReferenceFilename ( const MDagPath& path )
346     {
347         MString command = MString("reference -q -f ") + path.fullPathName();
348         MString filename;
349         MGlobal::executeCommand(command, filename);
350         return filename;
351     }
352 
353     // --------------------------------------
getReferenceFilename(const MObject & dgNode)354     MString ReferenceManager::getReferenceFilename ( const MObject& dgNode )
355     {
356         MString command = MString("reference -q -f ") + MFnDependencyNode(dgNode).name();
357         MString filename;
358         MGlobal::executeCommand(command, filename);
359         return filename;
360     }
361 
362 #if MAYA_API_VERSION < 201400
isIn(const MDagPath & dagPath,const MObject & referenceNode)363     bool isIn(const MDagPath & dagPath, const MObject & referenceNode)
364     {
365         MDagPathArray rootDagPaths;
366         MObjectArray subReferences;
367         ReferenceManager::getRootObjects(referenceNode, rootDagPaths, subReferences);
368         for (unsigned int i = 0; i < rootDagPaths.length(); ++i) {
369             if (rootDagPaths[i] == dagPath) {
370                 return true;
371             }
372         }
373         for (unsigned int i = 0; i < subReferences.length(); ++i) {
374             if (isIn(dagPath, subReferences[i])) {
375                 return true;
376             }
377         }
378         return false;
379     }
380 
getTopLevelReferenceNode(const MDagPath & dagPath,MObject & outReferenceNode)381     MStatus ReferenceManager::getTopLevelReferenceNode(const MDagPath & dagPath, MObject & outReferenceNode)
382     {
383         MStringArray references;
384         MStatus status = MFileIO::getReferences(references);
385         unsigned int referenceCount = references.length();
386         for (unsigned int referenceIndex = 0; referenceIndex < referenceCount; ++referenceIndex)
387         {
388             MString referenceFilename = references[referenceIndex];
389             MObject referenceNode = ReferenceManager::getReferenceNode(referenceFilename);
390 
391             if (isIn(dagPath, referenceNode)) {
392                 outReferenceNode = referenceNode;
393                 return MS::kSuccess;
394             }
395         }
396         return MS::kFailure;
397     }
398 #else //#if MAYA_API_VERSION < 201400
getTopLevelReferenceNode(const MDagPath & dagPath,MObject & outReferenceNode)399 	MStatus ReferenceManager::getTopLevelReferenceNode(const MDagPath & dagPath, MObject & outReferenceNode)
400 	{
401         MStatus status;
402 
403         MFnDagNode fnDagNode(dagPath, &status);
404         if (!status) return status;
405 
406         bool isFromReferencedFile = fnDagNode.isFromReferencedFile(&status);
407         if (!status) return status;
408 
409         if (!isFromReferencedFile) {
410             // dagPath is not a reference
411             return MS::kFailure;
412         }
413 
414         // Get full dag path
415         MString fullPathName;
416         fullPathName = dagPath.fullPathName(&status);
417         if (!status) return status;
418 
419         // Get nearest reference node
420         MString RNPath;
421         MString command = MString("referenceQuery -referenceNode ") + fullPathName;
422         status = MGlobal::executeCommand(command, RNPath);
423         if (!status) return status;
424 
425         MObject RN = DagHelper::getNode(RNPath);
426         if (RN.isNull()) return MS::kFailure;
427 
428         MFnReference fnRN(RN, &status);
429         if (!status) return status;
430 
431         // Get parent most reference node
432         MObject parentRN = fnRN.parentReference(&status);
433         if (!status) return status;
434         while (!parentRN.isNull())
435         {
436             RN = parentRN;
437 
438             status = fnRN.setObject(parentRN);
439             if (!status) return status;
440 
441             parentRN = fnRN.parentReference(&status);
442             if (!status) return status;
443         }
444 
445         outReferenceNode = RN;
446         return MS::kSuccess;
447 	}
448 #endif //#if MAYA_API_VERSION < 201400
449 
getReferenceFilename(const MObject & referenceNode,MString & referenceFilename)450 	MStatus ReferenceManager::getReferenceFilename(const MObject & referenceNode, MString & referenceFilename)
451 	{
452 		MString command = MString("referenceQuery -filename ") + MFnDependencyNode(referenceNode).name();
453 		return MGlobal::executeCommand(command, referenceFilename);
454 	}
455 }