API Guide > Writing a Custom Transform Node >

# Example custom transform

The following are examples of custom transform usage.

Custom transform attribute example

This section describes the rockingTransform example that is available in the Maya API Devkit. The rockingTransform example 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 new rockXValue class variable.

Implementing the proxy transformation matrix

The rockingTransformMatrix class inherits from MPxTransformationMatrix and defines a number of virtual methods such as asMatrix().

    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 rockingTransformNode involves deriving from MPxTransform and supporting some required virtuals such as createTransformationMatrix() and validateAndSet(). Also, a new aRockInX attribute 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 MFnPlugin when initializing the plug-in. The unique identifiers that are stored in MTypeIds are 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 the deregisterNode() method of MFnPlugin.

    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 of asMatrix() in the following example calls ParentClass::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, all asMatrix() 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, the aRockInX attribute 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 MPxTransform node provides for testing locking and clamping. For example, to handle locking and clamping correctly with rotation, the methods checkAndSetRotation(), applyRotationLimits() and applyRotationLocks() can be implemented. A similar set of methods exists for other attributes. The rockingTransformCheck example in the Maya API Devkit demonstrates these principles for rotation.