![]()
Example custom transform
The following are examples of custom transform usage.
Custom transform attribute example
This section describes the
rockingTransformexample that is available in the Maya API Devkit. TherockingTransformexample introduces an attribute that contains a rocking motion along the X-axis. This rocking motion or rotation is stored separately from the regular Rotate attributes but are incorporated into the transformation matrix. Additionally, get/set methods have been added to the class to help you access the newrockXValueclass variable.Implementing the proxy transformation matrix
The
rockingTransformMatrixclass inherits fromMPxTransformationMatrixand defines a number of virtual methods such asasMatrix().class rockingTransformMatrix : public MPxTransformationMatrix { // A really simple implementation of MPxTransformationMatrix. // The methods include: // - Two accessor methods for getting and setting the // rock // - The virtual asMatrix() method which passes the matrix // back to Maya when the command "xform -q -ws -m" is invoked public: rockingTransformMatrix(); static void *creator(); virtual MMatrix asMatrix() const; virtual MMatrix asMatrix(double percent) const; virtual MMatrix asRotateMatrix() const; // Degrees double getRockInX() const; void setRockInX( double rock ); static MTypeId id; protected: typedef MPxTransformationMatrix ParentClass; // Degrees double rockXValue; };The implementation of the
rockingTransformNodeinvolves deriving fromMPxTransformand supporting some required virtuals such ascreateTransformationMatrix()andvalidateAndSet(). Also, a newaRockInXattribute has been added to the class.class rockingTransformNode : public MPxTransform { // A really simple custom transform. public: rockingTransformNode(); rockingTransformNode(MPxTransformationMatrix *); virtual ~rockingTransformNode(); virtual MPxTransformationMatrix *createTransformationMatrix(); virtual void postConstructor(); virtual MStatus validateAndSetValue( const MPlug& plug, const MDataHandle& handle, const MDGContext& context); virtual void resetTransformation (MPxTransformationMatrix *); virtual void resetTransformation (const MMatrix &); // Utility for getting the related rock matrix pointer rockingTransformMatrix *getRockingTransformMatrix(); const char* className(); static void * creator(); static MStatus initialize(); static MTypeId id; protected: // Degrees static MObject aRockInX; double rockXValue; typedef MPxTransform ParentClass; };Initializing the Plug-in
You need to register the new transform node and its related transformation matrix with
MFnPluginwhen initializing the plug-in. The unique identifiers that are stored inMTypeIdsare required for registering the transform.MStatus initializePlugin( MObject obj ) { MStatus status; MFnPlugin plugin(obj, "Autodesk", "6.5", "Any"); status = plugin.registerTransform( "rockingTransform", rockingTransformNode::id, &rockingTransformNode::creator, &rockingTransformNode::initialize, &rockingTransformMatrix::creator, rockingTransformMatrix::id); if (!status) { status.perror("registerNode"); return status; } return status; }Removing the custom node is done in the
uninitializePlugin()through a call to thederegisterNode()method ofMFnPlugin.MStatus uninitializePlugin( MObject obj) { MStatus status; MFnPlugin plugin(obj); status = plugin.deregisterNode( rockingTransformNode::id ); if (!status) { status.perror("deregisterNode"); return status; } return status; }Implementation of the rockingTransform matrix class
The following examples are very simple implementations of the constructor, creator and get/set methods.
// // Matrix constructor. Initialize any // class variables. // rockingTransformMatrix::rockingTransformMatrix() { rockXValue = 0.0; } // // Creator for matrix // void *rockingTransformMatrix::creator() { return new rockingTransformMatrix(); } // // Utility method for getting the rock // motion in the X axis // double rockingTransformMatrix::getRockInX() const { return rockXValue; } // // Utility method for setting the rcok // motion in the X axis // void rockingTransformMatrix::setRockInX( double rock ) { rockXValue = rock; }The
asMatrix()method is very important to the custom transformation matrix implementation. This method is what Maya calls when it is requesting the transformation matrix from the custom transform. The implementation ofasMatrix()in the following example callsParentClass::asMatrix()to calculate the standard Maya transformation matrix. The rocking motion is then used to add a quaternion rotation to the calculated transformation matrix. This approach allows a custom transform to integrate new attributes into the output of the transformation matrix.// // This method will be used to return information to // Maya. Use the attributes which are outside of // the regular transform attributes to build a new // matrix. This new matrix will be passed back to // Maya. // MMatrix rockingTransformMatrix::asMatrix() const { // Get the current transform matrix MMatrix m = ParentClass::asMatrix(); // Initialize the new matrix we will calculate MTransformationMatrix tm( m ); // Find the current rotation as a quaternion MQuaternion quat = rotation(); // Convert the rocking value in degrees to radians DegreeRadianConverter conv; double newTheta = conv.degreesToRadians( getRockInX() ); quat.setToXAxis( newTheta ); // Apply the rocking rotation to the existing rotation tm.addRotationQuaternion( quat.x, quat.y, quat.z, quat.w, MSpace::kTransform ); // Let Maya know what the matrix should be return tm.asMatrix(); }Note that there is more than one
asMatrix()method. Depending on how the custom transform affects the matrix, allasMatrix()methods may need to be implemented.Implementing the rockingTransformNode
As in other proxy nodes implemented using the API, the
initialize()method is used to add new attributes and configure them. In this example, theaRockInXattribute is added to the node, the attribute is made keyable and is set as affects world space. Also,mustCallValidateAndSet()is called so that Maya will update correctly as the attribute changes.// // Node initialize method. We configure node // attributes here. Static method so // the *this pointer is not available. // MStatus rockingTransformNode::initialize() { MFnNumericAttribute numFn; aRockInX = numFn.create("RockInX", "rockx", MFnNumericData::kDouble, 0.0); numFn.setKeyable(true); numFn.setAffectsWorldSpace(true); addAttribute(aRockInX); // This is required so that the validateAndSet method // is called mustCallValidateAndSet(aRockInX); return MS::kSuccess; }The standard methods for class implementation are the following:
// // Constructor of the transform node // rockingTransformNode::rockingTransformNode() : ParentClass() { rockXValue = 0.0; } // // Constructor of the transform node // rockingTransformNode::rockingTransformNode(MPxTransformationMatrix *tm) : ParentClass(tm) { rockXValue = 0.0; } // // Post constructor method. Has access to *this. Node setup // operations that do not go into the initialize() method should go // here. // void rockingTransformNode::postConstructor() { // Make sure the parent takes care of anything it needs. // ParentClass::postConstructor(); // The baseTransformationMatrix pointer should be setup properly // at this point, but just in case, set the value if it is missing. // if (NULL == baseTransformationMatrix) { MGlobal::displayWarning("NULL baseTransformationMatrix found!"); baseTransformationMatrix = new MPxTransformationMatrix(); } } // // Destructor of the rocking transform // rockingTransformNode::~rockingTransformNode() { } // // Method that returns the new transformation matrix // MPxTransformationMatrix *rockingTransformNode::createTransformationMatrix() { return new rockingTransformMatrix(); } // // Method that returns a new transform node // void *rockingTransformNode::creator() { return new rockingTransformNode(); }The
validateAndSetValue()virtual can be used to make sure that attribute input is correct. For example, it should confirm whether an attribute is locked or clamped. In this simple case, the method can be implemented to ignore locking and clamping. This approach has been taken in the following implementation:MStatus rockingTransformNode::validateAndSetValue( const MPlug& plug, const MDataHandle& handle, const MDGContext& context) { MStatus status = MS::kSuccess; // Make sure that there is something interesting to process. // if (plug.isNull()) return MS::kFailure; MDataBlock block = forceCache(*(MDGContext *)&context); MDataHandle blockHandle = block.outputValue(plug, &status); ReturnOnError(status); if ( plug == aRockInX ) { // Update our new rock in x value double rockInX = handle.asDouble(); blockHandle.set(rockInX); rockXValue = rockInX; // Update the custom transformation matrix to the // right rock value. rockingTransformMatrix *ltm = getRockingTransformMatrix(); if (ltm) ltm->setRockInX(rockXValue); else MGlobal::displayError("Failed to get rock transform matrix"); blockHandle.setClean(); // Mark the matrix as dirty so that DG information // will update. dirtyMatrix(); } // Allow processing for other attributes return ParentClass::validateAndSetValue(plug, handle, context); }There are a number of virtuals that the
MPxTransformnode provides for testing locking and clamping. For example, to handle locking and clamping correctly with rotation, the methodscheckAndSetRotation(),applyRotationLimits()andapplyRotationLocks()can be implemented. A similar set of methods exists for other attributes. TherockingTransformCheckexample in the Maya API Devkit demonstrates these principles for rotation.