1 //-*****************************************************************************
2 //
3 // Copyright (c) 2009-2012,
4 // Sony Pictures Imageworks Inc. and
5 // Industrial Light & Magic, a division of Lucasfilm Entertainment Company Ltd.
6 //
7 // All rights reserved.
8 //
9 // Redistribution and use in source and binary forms, with or without
10 // modification, are permitted provided that the following conditions are
11 // met:
12 // * Redistributions of source code must retain the above copyright
13 // notice, this list of conditions and the following disclaimer.
14 // * Redistributions in binary form must reproduce the above
15 // copyright notice, this list of conditions and the following disclaimer
16 // in the documentation and/or other materials provided with the
17 // distribution.
18 // * Neither the name of Sony Pictures Imageworks, nor
19 // Industrial Light & Magic, nor the names of their contributors may be used
20 // to endorse or promote products derived from this software without specific
21 // prior written permission.
22 //
23 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 //
35 //-*****************************************************************************
36
37 #include "MayaUtility.h"
38
39 // this struct is used in function "bool util::isAnimated(MObject & object, bool checkParent)"
40 struct NodesToCheckStruct
41 {
42 MObject node;
43 bool checkParent;
44 };
45
46 // return seconds per frame
spf()47 double util::spf()
48 {
49 static const MTime sec(1.0, MTime::kSeconds);
50 return 1.0 / sec.as(MTime::uiUnit());
51 }
52
isAncestorDescendentRelationship(const MDagPath & path1,const MDagPath & path2)53 bool util::isAncestorDescendentRelationship(const MDagPath & path1,
54 const MDagPath & path2)
55 {
56 unsigned int length1 = path1.length();
57 unsigned int length2 = path2.length();
58 unsigned int diff;
59
60 if (length1 == length2 && !(path1 == path2))
61 return false;
62
63 MDagPath ancestor, descendent;
64 if (length1 > length2)
65 {
66 ancestor = path2;
67 descendent = path1;
68 diff = length1 - length2;
69 }
70 else
71 {
72 ancestor = path1;
73 descendent = path2;
74 diff = length2 - length1;
75 }
76
77 descendent.pop(diff);
78
79 bool ret = (ancestor == descendent);
80
81 if (ret)
82 {
83 MString err = path1.fullPathName() + " and ";
84 err += path2.fullPathName() + " have parenting relationships";
85 MGlobal::displayError(err);
86 }
87 return ret;
88 }
89
90
91 // returns 0 if static, 1 if sampled, and 2 if a curve
getSampledType(const MPlug & iPlug)92 int util::getSampledType(const MPlug& iPlug)
93 {
94 MPlugArray conns;
95
96 iPlug.connectedTo(conns, true, false);
97
98 // it's possible that only some element of an array plug or
99 // some component of a compound plus is connected
100 if (conns.length() == 0)
101 {
102 if (iPlug.isArray())
103 {
104 unsigned int numConnectedElements = iPlug.numConnectedElements();
105 for (unsigned int e = 0; e < numConnectedElements; e++)
106 {
107 int retVal = getSampledType(iPlug.connectionByPhysicalIndex(e));
108 if (retVal > 0)
109 return retVal;
110 }
111 }
112 else if (iPlug.isCompound() && iPlug.numConnectedChildren() > 0)
113 {
114 unsigned int numChildren = iPlug.numChildren();
115 for (unsigned int c = 0; c < numChildren; c++)
116 {
117 int retVal = getSampledType(iPlug.child(c));
118 if (retVal > 0)
119 return retVal;
120 }
121 }
122 return 0;
123 }
124
125 MObject ob;
126 MFnDependencyNode nodeFn;
127 for (unsigned i = 0; i < conns.length(); i++)
128 {
129 ob = conns[i].node();
130 MFn::Type type = ob.apiType();
131
132 switch (type)
133 {
134 case MFn::kAnimCurveTimeToAngular:
135 case MFn::kAnimCurveTimeToDistance:
136 case MFn::kAnimCurveTimeToTime:
137 case MFn::kAnimCurveTimeToUnitless:
138 {
139 nodeFn.setObject(ob);
140 MPlug incoming = nodeFn.findPlug("i", true);
141
142 // sampled
143 if (incoming.isConnected())
144 return 1;
145
146 // curve
147 else
148 return 2;
149 }
150 break;
151
152 case MFn::kMute:
153 {
154 nodeFn.setObject(ob);
155 MPlug mutePlug = nodeFn.findPlug("mute", true);
156
157 // static
158 if (mutePlug.asBool())
159 return 0;
160 // curve
161 else
162 return 2;
163 }
164 break;
165
166 default:
167 break;
168 }
169 }
170
171 return 1;
172 }
173
getRotOrder(MTransformationMatrix::RotationOrder iOrder,unsigned int & oXAxis,unsigned int & oYAxis,unsigned int & oZAxis)174 bool util::getRotOrder(MTransformationMatrix::RotationOrder iOrder,
175 unsigned int & oXAxis, unsigned int & oYAxis, unsigned int & oZAxis)
176 {
177 switch (iOrder)
178 {
179 case MTransformationMatrix::kXYZ:
180 {
181 oXAxis = 0;
182 oYAxis = 1;
183 oZAxis = 2;
184 }
185 break;
186
187 case MTransformationMatrix::kYZX:
188 {
189 oXAxis = 1;
190 oYAxis = 2;
191 oZAxis = 0;
192 }
193 break;
194
195 case MTransformationMatrix::kZXY:
196 {
197 oXAxis = 2;
198 oYAxis = 0;
199 oZAxis = 1;
200 }
201 break;
202
203 case MTransformationMatrix::kXZY:
204 {
205 oXAxis = 0;
206 oYAxis = 2;
207 oZAxis = 1;
208 }
209 break;
210
211 case MTransformationMatrix::kYXZ:
212 {
213 oXAxis = 1;
214 oYAxis = 0;
215 oZAxis = 2;
216 }
217 break;
218
219 case MTransformationMatrix::kZYX:
220 {
221 oXAxis = 2;
222 oYAxis = 1;
223 oZAxis = 0;
224 }
225 break;
226
227 default:
228 {
229 return false;
230 }
231 }
232 return true;
233 }
234
235 // 0 dont write, 1 write static 0, 2 write anim 0, 3 write anim -1
getVisibilityType(const MPlug & iPlug)236 int util::getVisibilityType(const MPlug & iPlug)
237 {
238 int type = getSampledType(iPlug);
239
240 // static case
241 if (type == 0)
242 {
243 // dont write anything
244 if (iPlug.asBool())
245 return 0;
246
247 // write static 0
248 return 1;
249 }
250 else
251 {
252 // anim write -1
253 if (iPlug.asBool())
254 return 3;
255
256 // write anim 0
257 return 2;
258 }
259 }
260
261 // does this cover all cases?
isAnimated(MObject & object,bool checkParent)262 bool util::isAnimated(MObject & object, bool checkParent)
263 {
264 MStatus stat;
265 MItDependencyGraph iter(object, MFn::kInvalid,
266 MItDependencyGraph::kUpstream,
267 MItDependencyGraph::kDepthFirst,
268 MItDependencyGraph::kPlugLevel,
269 &stat);
270
271 if (stat!= MS::kSuccess)
272 {
273 MGlobal::displayError("Unable to create DG iterator ");
274 }
275
276 // MAnimUtil::isAnimated(node) will search the history of the node
277 // for any animation curve nodes. It will return true for those nodes
278 // that have animation curve in their history.
279 // The average time complexity is O(n^2) where n is the number of history
280 // nodes. But we can improve the best case by split the loop into two.
281 std::vector<NodesToCheckStruct> nodesToCheckAnimCurve;
282
283 NodesToCheckStruct nodeStruct;
284 for (; !iter.isDone(); iter.next())
285 {
286 MObject node = iter.currentItem();
287
288 if (node.hasFn(MFn::kPluginDependNode) ||
289 node.hasFn( MFn::kConstraint ) ||
290 node.hasFn(MFn::kPointConstraint) ||
291 node.hasFn(MFn::kAimConstraint) ||
292 node.hasFn(MFn::kOrientConstraint) ||
293 node.hasFn(MFn::kScaleConstraint) ||
294 node.hasFn(MFn::kGeometryConstraint) ||
295 node.hasFn(MFn::kNormalConstraint) ||
296 node.hasFn(MFn::kTangentConstraint) ||
297 node.hasFn(MFn::kParentConstraint) ||
298 node.hasFn(MFn::kPoleVectorConstraint) ||
299 node.hasFn(MFn::kParentConstraint) ||
300 node.hasFn(MFn::kTime) ||
301 node.hasFn(MFn::kJoint) ||
302 node.hasFn(MFn::kGeometryFilt) ||
303 node.hasFn(MFn::kTweak) ||
304 node.hasFn(MFn::kPolyTweak) ||
305 node.hasFn(MFn::kSubdTweak) ||
306 node.hasFn(MFn::kCluster) ||
307 node.hasFn(MFn::kFluid) ||
308 node.hasFn(MFn::kPolyBoolOp))
309 {
310 return true;
311 }
312
313 if (node.hasFn(MFn::kExpression))
314 {
315 MFnExpression fn(node, &stat);
316 if (stat == MS::kSuccess && fn.isAnimated())
317 {
318 return true;
319 }
320 }
321
322 // skip shading nodes
323 if (!node.hasFn(MFn::kShadingEngine))
324 {
325 MPlug plug = iter.thisPlug();
326 MFnAttribute attr(plug.attribute(), &stat);
327 bool checkNodeParent = false;
328 if (stat == MS::kSuccess && attr.isWorldSpace())
329 {
330 checkNodeParent = true;
331 }
332
333 nodeStruct.node = node;
334 nodeStruct.checkParent = checkParent || checkNodeParent;
335 nodesToCheckAnimCurve.push_back(nodeStruct);
336 }
337 else
338 {
339 // and don't traverse the rest of their subgraph
340 iter.prune();
341 }
342 }
343
344 for (size_t i = 0; i < nodesToCheckAnimCurve.size(); i++)
345 {
346 if (MAnimUtil::isAnimated(nodesToCheckAnimCurve[i].node, nodesToCheckAnimCurve[i].checkParent))
347 {
348 return true;
349 }
350 }
351
352 return false;
353 }
354
isDrivenByFBIK(const MFnIkJoint & iJoint)355 bool util::isDrivenByFBIK(const MFnIkJoint & iJoint)
356 {
357 // check joints that are driven by Maya FBIK
358 // Maya FBIK has no connection to joints' TRS plugs
359 // but TRS of joints are driven by FBIK, they are not static
360 // Maya 2012's new HumanIK has connections to joints.
361 // FBIK is a special case.
362 MStatus status = MS::kSuccess;
363 if (iJoint.hikJointName(&status).length() > 0 && status) {
364 return true;
365 }
366 return false;
367 }
368
isDrivenBySplineIK(const MFnIkJoint & iJoint)369 bool util::isDrivenBySplineIK(const MFnIkJoint & iJoint)
370 {
371 // spline IK can drive the starting joint's translate channel but
372 // it has no connection to the translate plug.
373 // we treat the joint as animated in this case.
374 // find the ikHandle node.
375 MPlug msgPlug = iJoint.findPlug("message", false);
376 MPlugArray msgPlugDst;
377 msgPlug.connectedTo(msgPlugDst, false, true);
378 for (unsigned int i = 0; i < msgPlugDst.length(); i++) {
379 MFnDependencyNode ikHandle(msgPlugDst[i].node());
380 if (!ikHandle.object().hasFn(MFn::kIkHandle)) continue;
381
382 // find the ikSolver node.
383 MPlug ikSolverPlug = ikHandle.findPlug("ikSolver", true);
384 MPlugArray ikSolverDst;
385 ikSolverPlug.connectedTo(ikSolverDst, true, false);
386 for (unsigned int j = 0; j < ikSolverDst.length(); j++) {
387
388 // return true if the ikSolver is a spline solver.
389 if (ikSolverDst[j].node().hasFn(MFn::kSplineSolver)) {
390 return true;
391 }
392 }
393 }
394
395 return false;
396 }
397
isIntermediate(const MObject & object)398 bool util::isIntermediate(const MObject & object)
399 {
400 MStatus stat;
401 MFnDagNode mFn(object);
402
403 MPlug plug = mFn.findPlug("intermediateObject", false, &stat);
404 if (stat == MS::kSuccess && plug.asBool())
405 return true;
406 else
407 return false;
408 }
409
isRenderable(const MObject & object)410 bool util::isRenderable(const MObject & object)
411 {
412 MStatus stat;
413 MFnDagNode mFn(object);
414
415 // templated turned on? return false
416 MPlug plug = mFn.findPlug("template", false, &stat);
417 if (stat == MS::kSuccess && plug.asBool())
418 return false;
419
420 // visibility or lodVisibility off? return false
421 plug = mFn.findPlug("visibility", false, &stat);
422 if (stat == MS::kSuccess && !plug.asBool())
423 {
424 // the value is off. let's check if it has any in-connection,
425 // otherwise, it means it is not animated.
426 MPlugArray arrayIn;
427 plug.connectedTo(arrayIn, true, false, &stat);
428
429 if (stat == MS::kSuccess && arrayIn.length() == 0)
430 {
431 return false;
432 }
433 }
434
435 plug = mFn.findPlug("lodVisibility", false, &stat);
436 if (stat == MS::kSuccess && !plug.asBool())
437 {
438 MPlugArray arrayIn;
439 plug.connectedTo(arrayIn, true, false, &stat);
440
441 if (stat == MS::kSuccess && arrayIn.length() == 0)
442 {
443 return false;
444 }
445 }
446
447 // this shape is renderable
448 return true;
449 }
450
stripNamespaces(const MString & iNodeName,unsigned int iDepth)451 MString util::stripNamespaces(const MString & iNodeName, unsigned int iDepth)
452 {
453 if (iDepth == 0)
454 {
455 return iNodeName;
456 }
457
458 MStringArray strArray;
459 if (iNodeName.split(':', strArray) == MS::kSuccess)
460 {
461 unsigned int len = strArray.length();
462
463 // we want to strip off more namespaces than what we have
464 // so we just return the last name
465 if (len == 0)
466 {
467 return iNodeName;
468 }
469 else if (len <= iDepth + 1)
470 {
471 return strArray[len-1];
472 }
473
474 MString name;
475 for (unsigned int i = iDepth; i < len - 1; ++i)
476 {
477 name += strArray[i];
478 name += ":";
479 }
480 name += strArray[len-1];
481 return name;
482 }
483
484 return iNodeName;
485 }
486
getHelpText()487 MString util::getHelpText()
488 {
489 MString ret =
490 "AbcExport [options]\n"
491 "Options:\n"
492 "-h / -help Print this message.\n"
493 "\n"
494 "-prs / -preRollStartFrame double\n"
495 "The frame to start scene evaluation at. This is used to set the\n"
496 "starting frame for time dependent translations and can be used to evaluate\n"
497 "run-up that isn't actually translated.\n"
498 "\n"
499 "-duf / -dontSkipUnwrittenFrames\n"
500 "When evaluating multiple translate jobs, the presence of this flag decides\n"
501 "whether to evaluate frames between jobs when there is a gap in their frame\n"
502 "ranges.\n"
503 "\n"
504 "-v / -verbose\n"
505 "Prints the current frame that is being evaluated.\n"
506 "\n"
507 "-j / -jobArg string REQUIRED\n"
508 "String which contains flags for writing data to a particular file.\n"
509 "Multiple jobArgs can be specified.\n"
510 "\n"
511 "-jobArg flags:\n"
512 "\n"
513 "-a / -attr string\n"
514 "A specific geometric attribute to write out.\n"
515 "This flag may occur more than once.\n"
516 "\n"
517 "-as / -autoSubd\n"
518 "If this flag is present and the mesh has crease edges, crease vertices or holes, \n"
519 "the mesh (OPolyMesh) would now be written out as an OSubD and crease info will be stored in the Alembic \n"
520 "file. Otherwise, creases info won't be preserved in Alembic file \n"
521 "unless a custom Boolean attribute SubDivisionMesh has been added to mesh node and its value is true. \n"
522 "\n"
523 "-atp / -attrPrefix string (default ABC_)\n"
524 "Prefix filter for determining which geometric attributes to write out.\n"
525 "This flag may occur more than once.\n"
526 "\n"
527 "-df / -dataFormat string\n"
528 "The data format to use to write the file. Can be either HDF or Ogawa.\n"
529 "The default is Ogawa.\n"
530 "\n"
531 "-ef / -eulerFilter\n"
532 "If this flag is present, apply Euler filter while sampling rotations.\n"
533 "\n"
534 "-f / -file string REQUIRED\n"
535 "File location to write the Alembic data.\n"
536 "\n"
537 "-fr / -frameRange double double\n"
538 "The frame range to write.\n"
539 "Multiple occurrences of -frameRange are supported within a job. Each\n"
540 "-frameRange defines a new frame range. -step or -frs will affect the\n"
541 "current frame range only.\n"
542 "\n"
543 "-frs / -frameRelativeSample double\n"
544 "frame relative sample that will be written out along the frame range.\n"
545 "This flag may occur more than once.\n"
546 "\n"
547 "-nn / -noNormals\n"
548 "If this flag is present normal data for Alembic poly meshes will not be\n"
549 "written.\n"
550 "\n"
551 "-pr / -preRoll\n"
552 "If this flag is present, this frame range will not be sampled.\n"
553 "\n"
554 "-ro / -renderableOnly\n"
555 "If this flag is present non-renderable hierarchy (invisible, or templated)\n"
556 "will not be written out.\n"
557 "\n"
558 "-rt / -root\n"
559 "Maya dag path which will be parented to the root of the Alembic file.\n"
560 "This flag may occur more than once. If unspecified, it defaults to '|' which\n"
561 "means the entire scene will be written out.\n"
562 "\n"
563 "-s / -step double (default 1.0)\n"
564 "The time interval (expressed in frames) at which the frame range is sampled.\n"
565 "Additional samples around each frame can be specified with -frs.\n"
566 "\n"
567 "-sl / -selection\n"
568 "If this flag is present, write out all all selected nodes from the active\n"
569 "selection list that are descendents of the roots specified with -root.\n"
570 "\n"
571 "-sn / -stripNamespaces (optional int)\n"
572 "If this flag is present all namespaces will be stripped off of the node before\n"
573 "being written to Alembic. If an optional int is specified after the flag\n"
574 "then that many namespaces will be stripped off of the node name. Be careful\n"
575 "that the new stripped name does not collide with other sibling node names.\n\n"
576 "Examples: \n"
577 "taco:foo:bar would be written as just bar with -sn\n"
578 "taco:foo:bar would be written as foo:bar with -sn 1\n"
579 "\n"
580 "-u / -userAttr string\n"
581 "A specific user attribute to write out. This flag may occur more than once.\n"
582 "\n"
583 "-uatp / -userAttrPrefix string\n"
584 "Prefix filter for determining which user attributes to write out.\n"
585 "This flag may occur more than once.\n"
586 "\n"
587 "-uv / -uvWrite\n"
588 "If this flag is present, uv data for PolyMesh and SubD shapes will be written to\n"
589 "the Alembic file. Only the current uv map is used.\n"
590 "\n"
591 "-uvo / -uvsOnly\n"
592 "If this flag is present, only uv data for PolyMesh and SubD shapes will be written\n"
593 "to the Alembic file. Only the current uv map is used.\n"
594 "\n"
595 "-wcs / -writeColorSets\n"
596 "Write all color sets on MFnMeshes as color 3 or color 4 indexed geometry \n"
597 "parameters with face varying scope.\n"
598 "\n"
599 "-wfs / -writeFaceSets\n"
600 "Write all Face sets on MFnMeshes.\n"
601 "\n"
602 "-wfg / -wholeFrameGeo\n"
603 "If this flag is present data for geometry will only be written out on whole\n"
604 "frames.\n"
605 "\n"
606 "-ws / -worldSpace\n"
607 "If this flag is present, any root nodes will be stored in world space.\n"
608 "\n"
609 "-wv / -writeVisibility\n"
610 "If this flag is present, visibility state will be stored in the Alembic\n"
611 "file. Otherwise everything written out is treated as visible.\n"
612 "\n"
613 "-wuvs / -writeUVSets\n"
614 "Write all uv sets on MFnMeshes as vector 2 indexed geometry \n"
615 "parameters with face varying scope.\n"
616 "\n"
617 "-mfc / -melPerFrameCallback string\n"
618 "When each frame (and the static frame) is evaluated the string specified is\n"
619 "evaluated as a Mel command. See below for special processing rules.\n"
620 "\n"
621 "-mpc / -melPostJobCallback string\n"
622 "When the translation has finished the string specified is evaluated as a Mel\n"
623 "command. See below for special processing rules.\n"
624 "\n"
625 "-pfc / -pythonPerFrameCallback string\n"
626 "When each frame (and the static frame) is evaluated the string specified is\n"
627 "evaluated as a python command. See below for special processing rules.\n"
628 "\n"
629 "-ppc / -pythonPostJobCallback string\n"
630 "When the translation has finished the string specified is evaluated as a\n"
631 "python command. See below for special processing rules.\n"
632 "\n"
633 "Special callback information:\n"
634 "On the callbacks, special tokens are replaced with other data, these tokens\n"
635 "and what they are replaced with are as follows:\n"
636 "\n"
637 "#FRAME# replaced with the frame number being evaluated.\n"
638 "#FRAME# is ignored in the post callbacks.\n"
639 "\n"
640 "#BOUNDS# replaced with a string holding bounding box values in minX minY minZ\n"
641 "maxX maxY maxZ space seperated order.\n"
642 "\n"
643 "#BOUNDSARRAY# replaced with the bounding box values as above, but in\n"
644 "array form.\n"
645 "In Mel: {minX, minY, minZ, maxX, maxY, maxZ}\n"
646 "In Python: [minX, minY, minZ, maxX, maxY, maxZ]\n"
647 "\n"
648 "Examples:\n"
649 "\n"
650 "AbcExport -j \"-root |group|foo -root |test|path|bar -file /tmp/test.abc\"\n"
651 "Writes out everything at foo and below and bar and below to /tmp/test.abc.\n"
652 "foo and bar are siblings parented to the root of the Alembic scene.\n"
653 "\n"
654 "AbcExport -j \"-frameRange 1 5 -step 0.5 -root |group|foo -file /tmp/test.abc\"\n"
655 "Writes out everything at foo and below to /tmp/test.abc sampling at frames:\n"
656 "1 1.5 2 2.5 3 3.5 4 4.5 5\n"
657 "\n"
658 "AbcExport -j \"-fr 0 10 -frs -0.1 -frs 0.2 -step 5 -file /tmp/test.abc\"\n"
659 "Writes out everything in the scene to /tmp/test.abc sampling at frames:\n"
660 "-0.1 0.2 4.9 5.2 9.9 10.2\n"
661 "\n"
662 "Note: The difference between your highest and lowest frameRelativeSample can\n"
663 "not be greater than your step size.\n"
664 "\n"
665 "AbcExport -j \"-step 0.25 -frs 0.3 -frs 0.60 -fr 1 5 -root foo -file test.abc\"\n"
666 "\n"
667 "Is illegal because the highest and lowest frameRelativeSamples are 0.3 frames\n"
668 "apart.\n"
669 "\n"
670 "AbcExport -j \"-sl -root |group|foo -file /tmp/test.abc\"\n"
671 "Writes out all selected nodes and it's ancestor nodes including up to foo.\n"
672 "foo will be parented to the root of the Alembic scene.\n"
673 "\n";
674
675 return ret;
676 }
677