A more complex dependency graph example

This page provides a slightly more complex example of a dependency graph node plugin.

The code fragments on this page are taken from the simpleLoftNode/simpleLoftNode.cpp example, which takes a curve as input and generates a surface.

MObject simpleLoft::inputCurve;
MObject simpleLoft::outputSurface;

This example has only two attributes, an input curve and an output surface.

MStatus simpleLoft::initialize()
{
     MStatus stat;
     MFnTypedAttribute typedAttr;

The previous example used MFnNumericAttribute since the attributes were simply floating point numbers. Since this example uses more complex data, MFnTypedAttribute is used.

    inputCurve = typedAttr.create( "inputCurve", "in", 
    MFnData::kNurbsCurve, &stat );
    if( !stat )
        return stat;

This creates an attribute to hold curve objects. The Type enumeration in MFnData lists the types of data which can be created using a typed attribute. This list includes curves, surfaces, meshes, strings, user-defined data, etc.

    outputSurface = typedAttr.create( "outputSurface", "out",
    MFnData::kNurbsSurface, &stat );
    if( !stat )
        return stat;

This creates an attribute to hold the generated surface object.

    typedAttr.setStorable( false );

Since the surface is a generated object, it isn’t necessary to store it when storing the node to a file.

    addAttribute( inputCurve );
    addAttribute( outputSurface );
    attributeAffects( inputCurve, outputSurface );

Finally, the two attributes are added to the node and attributeAffects() is used to indicate that when the input curve is modified the resulting surface will have to be regenerated.

    return MS::kSuccess;
}
MStatus simpleLoft::compute( const MPlug& plug, MDataBlock& data )
{
    MStatus stat;
    if ( plug == outputSurface )
    {

This ensures that the computation of the node is only done for the appropriate attribute.

        MDataHandle inputData = data.inputValue( inputCurve, &stat );
        if( !stat )
            return stat;

As before, the data block contains all the data for the node in an efficient manner. The data handle is required to access this data.

        else
        {
            MObject curve = inputData.asNurbsCurve();
            MFnNurbsCurve curveFn( curve, &stat );
            if( !stat )
                return stat;

With the data handle you can then get the input curve which you can then pass on to an MFnNurbsCurve function set to operate on.

            else
            {
                MDataHandle surfHandle = data.outputValue( outputSurface, &stat );
                if( !stat )
                    return stat;

A second data handle is used to access the surface’s portion of the data block.

                MFnNurbsSurfaceData dataCreator;
                MObject newSurfData = dataCreator.create( &stat );
                if ( !stat )
                    return stat;

Notice that you don’t use MFnNurbsSurface in this example, but rather MFnNurbsSurfaceData. This is necessary since you want to create a data object to pass through the dependency graph and not a DAG object. This will always be the case when creating objects in dependency graph nodes. There are special nodes used which connect surface data into the DAG, and therefore any node that you create which creates geometry need not also create a DAG node for it, just the data.

                MObject newSurf = loft( curve, newSurfData, stat );
                if( !stat )
                    return stat;

This calls some user-written code which creates a lofted surface from the curve. The method would use MFnNurbsSurface to operate on the surface data object. (MFnNurbsSurface determines whether it is operating on a surface in the DAG or not. If not, some of it’s methods will not succeed. For example, since the surface data object isn’t in the DAG, determining the world position of it does not make sense, so that method would fail.)

                surfHandle.set( newSurfData );

Add the new surface to the data block so that the output changes.

                stat = data.setClean( plug );
                if( !stat )
                    return stat;

Tell the system that the plug has been successfully recomputed and is now clean.

            }
        }
    }
    else
    {
         cerr << "unknown plug\n";
         return MS::kUnknownParameter;
    } 

You must return MS::kUnknownParameter if the plug is not recognized or if there is no computation occurring on a given keyable plug. This causes the compute of the base class to be called which will implement default data handling and cause:

     return MS::kSuccess;
}