int DtCameraGetAnimKeys( int cameraID, MIntArray *keyFrames ) { MStatus status; MObject shapeNode; MObject anim; MFnDependencyNode dgNode; MDagPath dagPath; int currKey, numKeys, keyTime, stat; MDagPath shapeDagPath; MItDependencyGraph::Direction direction = MItDependencyGraph::kUpstream; MItDependencyGraph::Traversal traversalType = MItDependencyGraph::kBreadthFirst; MItDependencyGraph::Level level = MItDependencyGraph::kNodeLevel; MFn::Type filter = MFn::kAnimCurve; // A quick check to see if the user has actually given us a valid // pointer. if ( !keyFrames ) { return 0; } stat = DtExt_CameraGetShapeNode( cameraID, shapeNode ); if ( 1 != stat ) { cerr << "Problems in cameraGetShapeNode" << endl; return 0; } MItDependencyGraph dgIter( shapeNode, filter, direction, traversalType, level, &status ); for ( ; !dgIter.isDone(); dgIter.next() ) { anim = dgIter.thisNode( &status ); MFnAnimCurve animCurve( anim, &status ); if ( MS::kSuccess == status ) { numKeys = animCurve.numKeyframes( &status ); for ( currKey = 0; currKey < numKeys; currKey++ ) { // Truncating values here; may need more control keyTime = (int) animCurve.time( currKey, &status ).value(); addElement( keyFrames, keyTime ); } } } return 1; }
void Exporter::RecursiveJointExtraction(MFnTransform& joint, int parentIndex){ Bone output; output.parent = parentIndex; output.invBindPose = joint.transformation().asMatrixInverse().matrix; MItDependencyNodes matIt(MFn::kAnimCurve); while (!matIt.isDone()) { MFnAnimCurve animCurve(matIt.item()); if (!strcmp(animCurve.name().substring(0, joint.name().length() - 1).asChar(), joint.name().asChar())){ cout << animCurve.name().asChar() << endl; std::string type = animCurve.name().substring(joint.name().length(), animCurve.name().length()).asChar(); output.frames.resize(animCurve.time(animCurve.numKeys() - 1).value()); for (int i = 0; i < output.frames.size(); i++) { MTime time; time.setValue(i); output.frames[i].time = time.value(); if (!strcmp(type.c_str(), "_translateX")){ cout << animCurve.evaluate(time) << endl; output.frames[i].trans.x = animCurve.evaluate(time); } if (!strcmp(type.c_str(), "_translateY")){ cout << animCurve.evaluate(time) << endl; output.frames[i].trans.y = animCurve.evaluate(time); } if (!strcmp(type.c_str(), "_translateZ")){ cout << animCurve.evaluate(time) << endl; output.frames[i].trans.z = animCurve.evaluate(time); } if (!strcmp(type.c_str(), "_rotateX")){ cout << animCurve.evaluate(time) << endl; output.frames[i].rot.x = animCurve.evaluate(time); } if (!strcmp(type.c_str(), "_rotateY")){ cout << animCurve.evaluate(time) << endl; output.frames[i].rot.y = animCurve.evaluate(time); } if (!strcmp(type.c_str(), "_rotateZ")){ cout << animCurve.evaluate(time) << endl; output.frames[i].rot.z = animCurve.evaluate(time); } } } matIt.next(); } scene_.skeleton.push_back(output); int children = joint.childCount(); int parent = scene_.skeleton.size() - 1; for (int i = 0; i < children; i++) RecursiveJointExtraction(MFnTransform(joint.child(i)), parent); };
bool animWriter::writeAnimCurve(ofstream &clip, const MObject *animCurveObj, bool verboseUnits /* false */) // // Description: // Write out the anim curve from the clipboard item into the // ofstream. The actual anim curve data is written out. // // This method returns true if the write was successful. // { if (NULL == animCurveObj || animCurveObj->isNull() || !clip) { return true; } MStatus status = MS::kSuccess; MFnAnimCurve animCurve(*animCurveObj, &status); if (MS::kSuccess != status) { cerr << "Error: Could not read the anim curve for export." << endl; return false; } clip << kAnimData << kSpaceChar << kBraceLeftChar << endl; clip << kTwoSpace << kInputString << kSpaceChar << boolInputTypeAsWord(animCurve.isUnitlessInput()) << kSemiColonChar << endl; clip << kTwoSpace << kOutputString << kSpaceChar << outputTypeAsWord(animCurve.animCurveType()) << kSemiColonChar << endl; clip << kTwoSpace << kWeightedString << kSpaceChar << (animCurve.isWeighted() ? 1 : 0) << kSemiColonChar << endl; // These units default to the units in the header of the file. // if (verboseUnits) { clip << kTwoSpace << kInputUnitString << kSpaceChar; if (animCurve.isTimeInput()) { MString unitName; animUnitNames::setToShortName(timeUnit, unitName); clip << unitName; } else { // The anim curve has unitless input. // clip << kUnitlessString; } clip << kSemiColonChar << endl; clip << kTwoSpace << kOutputUnitString << kSpaceChar; } double conversion = 1.0; MString unitName; switch (animCurve.animCurveType()) { case MFnAnimCurve::kAnimCurveTA: case MFnAnimCurve::kAnimCurveUA: animUnitNames::setToShortName(angularUnit, unitName); if (verboseUnits) clip << unitName; { MAngle angle(1.0); conversion = angle.as(angularUnit); } break; case MFnAnimCurve::kAnimCurveTL: case MFnAnimCurve::kAnimCurveUL: animUnitNames::setToShortName(linearUnit, unitName); if (verboseUnits) clip << unitName; { MDistance distance(1.0); conversion = distance.as(linearUnit); } break; case MFnAnimCurve::kAnimCurveTT: case MFnAnimCurve::kAnimCurveUT: animUnitNames::setToShortName(timeUnit, unitName); if (verboseUnits) clip << unitName; break; default: if (verboseUnits) clip << kUnitlessString; break; } if (verboseUnits) clip << kSemiColonChar << endl; if (verboseUnits) { MString angleUnitName; animUnitNames::setToShortName(angularUnit, angleUnitName); clip << kTwoSpace << kTanAngleUnitString << kSpaceChar << angleUnitName << kSemiColonChar << endl; } clip << kTwoSpace << kPreInfinityString << kSpaceChar << infinityTypeAsWord(animCurve.preInfinityType()) << kSemiColonChar << endl; clip << kTwoSpace << kPostInfinityString << kSpaceChar << infinityTypeAsWord(animCurve.postInfinityType()) << kSemiColonChar << endl; clip << kTwoSpace << kKeysString << kSpaceChar << kBraceLeftChar << endl; // And then write out each keyframe // unsigned numKeys = animCurve.numKeyframes(); for (unsigned i = 0; i < numKeys; i++) { clip << kTwoSpace << kTwoSpace; if (animCurve.isUnitlessInput()) { clip << animCurve.unitlessInput(i); } else { clip << animCurve.time(i).value(); } clip << kSpaceChar << (conversion*animCurve.value(i)); clip << kSpaceChar << tangentTypeAsWord(animCurve.inTangentType(i)); clip << kSpaceChar << tangentTypeAsWord(animCurve.outTangentType(i)); clip << kSpaceChar << (animCurve.tangentsLocked(i) ? 1 : 0); clip << kSpaceChar << (animCurve.weightsLocked(i) ? 1 : 0); clip << kSpaceChar << (animCurve.isBreakdown(i) ? 1 : 0); if (animCurve.inTangentType(i) == MFnAnimCurve::kTangentFixed) { MAngle angle; double weight; animCurve.getTangent(i, angle, weight, true); clip << kSpaceChar << angle.as(angularUnit); clip << kSpaceChar << weight; } if (animCurve.outTangentType(i) == MFnAnimCurve::kTangentFixed) { MAngle angle; double weight; animCurve.getTangent(i, angle, weight, false); clip << kSpaceChar << angle.as(angularUnit); clip << kSpaceChar << weight; } clip << kSemiColonChar << endl; } clip << kTwoSpace << kBraceRightChar << endl; clip << kBraceRightChar << endl; return true; }
//********************************************************* // Name: processConnections // Desc: //********************************************************* MStatus BreakdownCommand::processConnections( MPlugArray &connections, unsigned int objID, MString objName ) { status = MS::kSuccess; bool skipProcessing = false; // Under certain conditions, the specified weight must be modified double actualBreakdownWeight = breakdownWeight; bool isBooleanValue = false; for( unsigned int j = 0; j < connections.length(); j++ ) { // If the attribute is a boolean or enum, keep its // breakdown value the same as its previous key value. // Weirdness can occur in things like visibility if( isBooleanDataType(connections[j]) || isEnumDataType(connections[j]) ) { // favour the previous key completely actualBreakdownWeight = 0.0; isBooleanValue = true; } else { actualBreakdownWeight = breakdownWeight; isBooleanValue = false; } // When the selectedAttrOnly flag is set, only process // attributes that have been selected in the channel box if( !selectedAttrOnly || isStrOnSelectedAttrList( connections[j].partialName() )) { if( connections[j].isKeyable() && !connections[j].isLocked() ) { MPlug currentPlug = connections[j]; MItDependencyGraph dgIter( currentPlug, MFn::kAnimCurve, MItDependencyGraph::kUpstream, MItDependencyGraph::kBreadthFirst, MItDependencyGraph::kNodeLevel, &status ); for( ; !dgIter.isDone(); dgIter.next() ) { MObjectArray nodePath; dgIter.getNodePath( nodePath ); /* // *** Original solution that didn't support Blend/Charater nodes *** // Only use the anim curves directly connected to the attributes // >1 on a breath first search is level 2. Root will always be // first in the path if( nodePath.length() > 2 ) break; */ // At a depth of 1 in the DAG, the animation nodes are directly // connected to animated object. However, if the depth is greater // than one then we must accomodate both PairBlend nodes and // Character Set nodes (which sit between the transform node and // the anim nodes. int nodeParentIndex = 1; if( nodePath.length() <= 2 || (nodePath.length() == 3 && (nodePath[nodeParentIndex].apiType() == MFn::kPairBlend || nodePath[nodeParentIndex].apiType() == MFn::kCharacter )) ) { MObject anim = dgIter.thisNode( &status ); MFnAnimCurve animCurve( anim, &status ); // Avoid adding duplicate anim curves to the list // Important when dealing with blend nodes MString curveName = animCurve.name(); bool nameExists = false; breakdownList.iterBegin(); Breakdown* bkdn = breakdownList.getCurrent(); while( bkdn != NULL ) { if( bkdn->getAnimCurveFn().name() == curveName ) { nameExists = true; break; } bkdn = breakdownList.getNext(); } if( !nameExists ) { // Create a breakdown and add it to the list Breakdown* newBreakdown = new Breakdown( animCurve, breakdownWeight, breakdownMode, tickDrawSpecial, currentAnimationFrame, isBooleanValue, objID, &status ); // On success, add new breakdown to the list if( status == MS::kSuccess ) { // pluginTrace( "BreakdownCommand", "processConnections", "***Adding Breakdown****" ); breakdownList.add( newBreakdown ); } // If the breakdown failed, determine how to proceed // from the invalidAttrOp flag else { status = MS::kSuccess; // The command fails if there is an invalid // attribute. No breakdowns are set. if( invalidAttrOp == kSkipAll ) { pluginTrace( "BreakdownCommand", "processConnections", "Skipping all objects" ); MGlobal::displayInfo( connections[j].partialName(true) + " --> " + newBreakdown->getErrorMsg()); MGlobal::displayError( "Skipping All Objects (See Script Editor for Invalid Attribute)" ); skipProcessing = true; status = MS::kFailure; break; } // The object is skipped. All breakdowns already // added for attributes on this object will need // to be removed from the list else if( invalidAttrOp == kSkipObject ) { pluginTrace( "BreakdownCommand", "processConnections", "Skipping object: " + objName ); MGlobal::displayInfo( "Skipping Object: " + objName ); breakdownList.deleteBreakdowns( objID ); skipProcessing = true; objectsSkipped = true; break; } // If only invalid attributes are to be skipped // just delete the breakdown and carry on else if( invalidAttrOp == kSkipAttr ) { pluginTrace( "BreakdownCommand", "processConnections", "Skipping attribute: " + connections[j].partialName( true )); MGlobal::displayInfo( "Skipping Attribute: " + connections[j].partialName( true ) + " (" + newBreakdown->getErrorMsg() + ")" ); attributesSkipped = true; } // Clean up memory for discarded breakdowns delete newBreakdown; } } } } } } // Don't keep processing connections if the object // should be skipped (invalid attribute flag) if( skipProcessing ) break; } return status; }
// In order for the new animation curve positions to match the controller's previous global position, // first we need to get the controller's global transform matrix. The new local position of the controller // will be the controller's global transform matrix multiplied by the inverse of the new parent group's // global transform matrix. // So if: // [A] = controller's global transformation matrix // [B] = parent group's global transformation matrix // [X] = controller's new local transformation matrix // Then: // [X] = [A]inverse([B]) // // The controller's new local transformation matrix needs to be calculated per keyframe, which is why // a map of world position matrices for the controller are passed in as a parameter. The key for the map // is the time at which the associated world matrix was stored. MStatus lrutils::updateAnimCurves(MObject transformObj, std::map<double, MMatrix> ctlWorldMatrices, MMatrix ctlGroupMatrix) { MStatus status = MS::kFailure; MFnTransform transformFn( transformObj, &status ); MyCheckStatusReturn(status, status.errorString() ); //get all of the connections from the transform node MPlugArray transformConnections; status = transformFn.getConnections( transformConnections ); MyCheckStatusReturn(status, status.errorString() ); for(unsigned int i = 0; i < transformConnections.length(); i++) { //get all of the plugs this plug is connected to as a destination MPlugArray connectedPlugs; transformConnections[i].connectedTo(connectedPlugs,true,false,&status); MyCheckStatusReturn(status, status.errorString() ); MString plugName = transformConnections[i].partialName(false,false,false,false,false,true); for(unsigned int j = 0; j < connectedPlugs.length(); j++) { MObject connectedObj = connectedPlugs[j].node(); MFn::Type animType = connectedObj.apiType(); //if the connected object is an animCurveT[L,A], then transform it if( (animType == MFn::kAnimCurveTimeToDistance) || (animType == MFn::kAnimCurveTimeToAngular) ) { MFnAnimCurve animCurve(connectedObj, &status); MyCheckStatusReturn(status, status.errorString() ); //iterate through every key in the curve for(unsigned int k = 0; k < animCurve.numKeys(); k++) { double keyTime = animCurve.time(k, &status).value(); MyCheckStatusReturn( status, status.errorString() ); double keyVal = animCurve.value(k, &status); MyCheckStatusReturn( status, status.errorString() ); float tangentX, tangentY; status = animCurve.getTangent(k, tangentX, tangentY, false); MyCheckStatusReturn(status, status.errorString() ); //stringstream tmp; //tmp << "out tangent = (" << tangentX << "," << tangentY << ")"; //MGlobal::displayInfo(tmp.str().c_str()); //calculate the controller's local transformation matrix //first retrieve the corresponding world matrix for this key time MMatrix ctlWorldMatrix = ctlWorldMatrices[keyTime]; //if the transformation matrices are the same, don't update the curve //if( ctlWorldMatrix == ctlGroupMatrix) { // continue; //} MTransformationMatrix ctlGroupTransMatrix(ctlGroupMatrix); MMatrix ctlGroupMatrixInverse = ctlGroupTransMatrix.asMatrixInverse(); MMatrix newCtlLocalMatrix = ctlWorldMatrix * ctlGroupMatrixInverse; MTransformationMatrix newCtlLocalTransMatrix(newCtlLocalMatrix); //determine if the curve is for translation data if(animType == MFn::kAnimCurveTimeToDistance) { MVector vTransform = newCtlLocalTransMatrix.translation(MSpace::kTransform); if( plugName.indexW( MString("X") ) != -1 ) { keyVal = vTransform.x; } else if ( plugName.indexW( MString("Y") ) != -1 ) { keyVal = vTransform.y; } else if ( plugName.indexW( MString("Z") ) != -1 ) { keyVal = vTransform.z; } //if the curve is for rotation data } else if (animType == MFn::kAnimCurveTimeToAngular) { double* rotation = new double[3]; MTransformationMatrix::RotationOrder rotOrder = MTransformationMatrix::kXYZ; newCtlLocalTransMatrix.getRotation(rotation,rotOrder); if( plugName.indexW( MString("X") ) != -1 ) { keyVal = rotation[0]; } else if ( plugName.indexW( MString("Y") ) != -1 ) { keyVal = rotation[1]; } else if ( plugName.indexW( MString("Z") ) != -1 ) { keyVal = rotation[2]; } } animCurve.setValue(k, keyVal); status = MS::kSuccess; } } } } return status; }