Chad Vernon
  • Home
  • Reel/Resume
  • Work
  • Resources
  • About
  • Contact
sideBar

Search

Categories

  • CG
  • cvxporter
  • Maya
    • API
    • Plug-ins
  • Personal

Archives

  • May 2012
  • March 2012
  • September 2011
  • June 2011
  • March 2011
  • December 2010
  • November 2010
  • September 2010
  • August 2010
  • July 2010
  • May 2010
  • April 2010
  • March 2010
  • December 2009
  • October 2009
  • September 2009
  • August 2009
  • July 2009
  • June 2009
  • May 2009
  • April 2009
  • March 2009
  • February 2009
  • January 2009
  • December 2008
  • November 2008
  • October 2008
  • September 2008
  • August 2008
  • July 2008
  • June 2008
  • May 2008
  • April 2008
  • March 2008
  • February 2008
  • January 2008
  • December 2007
  • November 2007
  • October 2007
  • September 2007
  • August 2007

Rss

  • Main Entries RSS
  • Comments RSS
Home » Resources » Maya API Programming » Deformers

Deformers

Deformers are a subset of dependency graph nodes that perform deformation algorithms on input geometry and outputs the deformed geometry to the output attribute of the deformer.  When you think of an algorithm to move points or cvs around on mesh or surface, you usually want to create a deformer.  Example deformers that I have implemented in the past include wrap, jiggle, displacement, blend shape, and skin sliding deformers.  To create a deformer node, we create a class that inherits from the proxy class, MPxDeformerNode.

Since deformers are a subset of dependency nodes, they can be created with the createNode command just like all other nodes, however, deformers have a lot of special functionality that depends on additional connections made to the deformer node.  These connections get made for you when you use the deformer command.  This functionality includes membership editing and reordering of deformation history.

A deformer has the same structure as a normal dependency node except we implement a deform function instead of a compute function.  To show some of the process involved with making deformers, we will create a simple blend shape node that blends one mesh to another mesh.

As inputs to our deformer, we will need a target mesh and a blend weight value:

MStatus BlendNode::initialize()
{
    MFnTypedAttribute tAttr;
    MFnNumericAttribute nAttr;

    aBlendMesh = tAttr.create( "blendMesh", "blendMesh", MFnData::kMesh );
    addAttribute( aBlendMesh );
    attributeAffects( aBlendMesh, outputGeom );

    aBlendWeight = nAttr.create( "blendWeight", "bw", MFnNumericData::kFloat );
    nAttr.setKeyable( true );
    addAttribute( aBlendWeight );
    attributeAffects( aBlendWeight, outputGeom );

    // Make the deformer weights paintable
    MGlobal::executeCommand( "makePaintable -attrType multiFloat -sm deformer blendNode weights;" );

    return MS::kSuccess;
}

Above is our deformers initialize function where we specify all the attributes for our deformer.  To create a mesh attribute, we use MFnTypedAttribute.  A typed attribute is used to create most attributes of non-simple types like meshes, surfaces, curves, arrays, etc.  After the mesh attribute is added, we set it to affect the output geometry attribute, outputGeom.  All deformers have an outputGeom attribute which is part of the MPxDeformerNode class.  Once the mesh attribute is finished, we create the blend weight attribute which is identical to the float attributes we added in the dependency node example.

The last code of interest in the initialize function is the makePaintable call.  makePaintable is a MEL (and Python) command used to make a particular attribute paintable.  All deformers come with a per-vertex weight attribute so we are simple activating the paintability of that attribute.   With our deformer initialized, we can move on to the deform function.

MStatus BlendNode::deform( MDataBlock& data, MItGeometry& itGeo, const MMatrix &localToWorldMatrix, unsigned int mIndex )
{
    MStatus status;

    // Get the envelope and blend weight
    float env = data.inputValue( envelope ).asFloat();
    float blendWeight = data.inputValue( aBlendWeight ).asFloat();
    blendWeight *= env;

    // Get the blend mesh
    MObject oBlendMesh = data.inputValue( aBlendMesh ).asMesh();
    if ( oBlendMesh.isNull() )
    {
        // No blend mesh attached so exit node.
        return MS::kSuccess;
    }

    // Get the blend points
    MFnMesh fnBlendMesh( oBlendMesh, &status );
    CHECK_MSTATUS_AND_RETURN_IT( status );
    MPointArray blendPoints;
    fnBlendMesh.getPoints( blendPoints );

    MPoint pt;
    float w = 0.0f;
    for ( ; !itGeo.isDone(); itGeo.next() )
    {
        // Get the input point
        pt = itGeo.position();
        // Get the painted weight value
        w = weightValue( data, mIndex, itGeo.index() );
        // Perform the deformation
        pt = pt + (blendPoints[itGeo.index()] - pt) * blendWeight * w;
        // Set the new output point
        itGeo.setPosition( pt );
    }

    return MS::kSuccess;
}

The deform function is where all the deformation implementation code occurs.  The workflow is similar to normal dependency nodes where we:

  1. Get our inputs from the data block
  2. Perform the deformation
  3. Store the deformed geometry back into the data block.

Our deform function begins with getting the envelope and blend weight input values from the data block.  The envelope is a built-in deformer attribute that you can use as a magnitude multiplier for your deformation.  After we grab the numeric attribute values, we get the blend target mesh.  We need to check if the data is valid because if there is no target mesh connected to the deformer, the MObject will be null and we cannot perform the deformation.  Once we have a valid MObject representing our target mesh, we need to extract its point positions.  We do this by attaching an MFnMesh function set to the MObject.  MFnMesh is the function set used to query, edit, and create polygonal meshes.  Now that we have all the inputs, we can begin the deformation algorithm.

The deform function comes with an MItGeometry parameter that is used to iterate over the components of the input mesh.  If you want to take advantage of deformer set membership, you must use this iterator as it only iterates over the vertices or cvs included in the deformer membership.  You can query the current vertex id with MItGeometry::index().

As we iterate over each vertex, we grab the painted weight value for that vertex.  MPxDeformerNode has a built-in convenience function, weightValue, which we can use to query each painted weight value.  The multiIndex attribute passed into this function is the index of the input geometry.  Some deformers can affect multiple meshes at the same time, each having their own painted weight value map.  This index specifies which index to use.  Most of the time, this is 0.  It is for this reason that all paintable attributes need a parent compound array attribute, which we will learn about in later sections.

After we have the weight value, we can perform the actual deformation, which is on one line.  The blend shape deformation is a simple weighted vector delta addition to the current vertex.  With the new point position calculated, we put it back into the geometry iterator.  Notice we do not have to do any setClean calls.  Unless we add any custom outputs to a deformer, MPxDeformerNode automatically handles that for us when we use the deform function.

You’ll notice that out of all that code, the actual deformation algorithm is just one line.  This is the common case when making plug-ins in the Maya API.  Most of the code is usually all the node setup, event handling, and clean up.  Coming up with the algorithm is almost the easy part.

And here is the full code listing for the blendNode deformer.  Notice when we register the node with Maya, we also specify that it is a deformer node.

#ifndef BLENDNODE_H
#define BLENDNODE_H

#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MGlobal.h>
#include <maya/MItGeometry.h>
#include <maya/MMatrix.h>
#include <maya/MPointArray.h>
#include <maya/MStatus.h>

#include <maya/MFnMesh.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>

#include <maya/MPxDeformerNode.h>

class BlendNode : public MPxDeformerNode
{
public:
                    BlendNode() {};
    virtual MStatus deform( MDataBlock& data, MItGeometry& itGeo, const MMatrix &localToWorldMatrix, unsigned int mIndex );
    static  void*   creator();
    static  MStatus initialize();

    static MTypeId      id;
    static MObject      aBlendMesh;
    static MObject      aBlendWeight;
};
#endif
#include "include/BlendNode.h"
#include <maya/MFnPlugin.h>

MTypeId BlendNode::id( 0x00000002 );
MObject BlendNode::aBlendMesh;
MObject BlendNode::aBlendWeight;

void* BlendNode::creator() { return new BlendNode; }

MStatus BlendNode::deform( MDataBlock& data, MItGeometry& itGeo, const MMatrix &localToWorldMatrix, unsigned int mIndex )
{
    MStatus status;

    // Get the envelope and blend weight
    float env = data.inputValue( envelope ).asFloat();
    float blendWeight = data.inputValue( aBlendWeight ).asFloat();
    blendWeight *= env;

    // Get the blend mesh
    MObject oBlendMesh = data.inputValue( aBlendMesh ).asMesh();
    if ( oBlendMesh.isNull() )
    {
        // No blend mesh attached so exit node.
        return MS::kSuccess;
    }

    // Get the blend points
    MFnMesh fnBlendMesh( oBlendMesh, &status );
    CHECK_MSTATUS_AND_RETURN_IT( status );
    MPointArray blendPoints;
    fnBlendMesh.getPoints( blendPoints );

    MPoint pt;
    float w = 0.0f;
    for ( ; !itGeo.isDone(); itGeo.next() )
    {
        // Get the input point
        pt = itGeo.position();
        // Get the painted weight value
        w = weightValue( data, mIndex, itGeo.index() );
        // Perform the deformation
        pt = pt + (blendPoints[itGeo.index()] - pt) * blendWeight * w;
        // Set the new output point
        itGeo.setPosition( pt );
    }

    return MS::kSuccess;
}

MStatus BlendNode::initialize()
{
    MFnTypedAttribute tAttr;
    MFnNumericAttribute nAttr;

    aBlendMesh = tAttr.create( "blendMesh", "blendMesh", MFnData::kMesh );
    addAttribute( aBlendMesh );
    attributeAffects( aBlendMesh, outputGeom );

    aBlendWeight = nAttr.create( "blendWeight", "bw", MFnNumericData::kFloat );
    nAttr.setKeyable( true );
    addAttribute( aBlendWeight );
    attributeAffects( aBlendWeight, outputGeom );

    // Make the deformer weights paintable
    MGlobal::executeCommand( "makePaintable -attrType multiFloat -sm deformer blendNode weights;" );

    return MS::kSuccess;
}

MStatus initializePlugin( MObject obj )
{
    MStatus status;
    MFnPlugin plugin( obj, "Chad Vernon", "1.0", "Any");

    // Specify we are making a deformer node
    status = plugin.registerNode( "blendNode", BlendNode::id, BlendNode::creator, BlendNode::initialize, MPxNode::kDeformerNode );
    CHECK_MSTATUS_AND_RETURN_IT( status );

    return status;
}

MStatus uninitializePlugin( MObject obj )
{
    MStatus	  status;
    MFnPlugin plugin( obj );

    status = plugin.deregisterNode( BlendNode::id );
    CHECK_MSTATUS_AND_RETURN_IT( status );

    return status;
}

And here is the corresponding Python implementation:

import maya.OpenMayaMPx as OpenMayaMPx
import maya.OpenMaya as OpenMaya
import maya.cmds as cmds

class BlendNode(OpenMayaMPx.MPxDeformerNode):
    kPluginNodeId = OpenMaya.MTypeId(0x00000002)

    aBlendMesh = OpenMaya.MObject()
    aBlendWeight = OpenMaya.MObject()

    def __init__(self):
        OpenMayaMPx.MPxDeformerNode.__init__(self)

    def deform(self, data, itGeo, localToWorldMatrix, mIndex):
        envelope = OpenMayaMPx.cvar.MPxDeformerNode_envelope
        env = data.inputValue(envelope).asFloat()
        blendWeight = data.inputValue(BlendNode.aBlendWeight).asFloat()
        blendWeight *= env

        oBlendMesh = data.inputValue(BlendNode.aBlendMesh).asMesh()
        if oBlendMesh.isNull():
            return OpenMaya.MStatus.kSuccess

        fnBlendMesh = OpenMaya.MFnMesh(oBlendMesh)
        blendPoints = OpenMaya.MPointArray()
        fnBlendMesh.getPoints(blendPoints)

        while not itGeo.isDone():
            pt = itGeo.position()
            w = self.weightValue(data, mIndex, itGeo.index())
            pt = pt + (blendPoints[itGeo.index()] - pt) * blendWeight * w
            itGeo.setPosition(pt)
            itGeo.next()

        return OpenMaya.MStatus.kSuccess

def creator():
    return OpenMayaMPx.asMPxPtr(BlendNode())

def initialize():
    tAttr = OpenMaya.MFnTypedAttribute()
    nAttr = OpenMaya.MFnNumericAttribute()

    BlendNode.aBlendMesh = tAttr.create('blendMesh', 'bm', OpenMaya.MFnData.kMesh)
    BlendNode.addAttribute( BlendNode.aBlendMesh )

    outputGeom = OpenMayaMPx.cvar.MPxDeformerNode_outputGeom
    BlendNode.attributeAffects(BlendNode.aBlendMesh, outputGeom)

    BlendNode.aBlendWeight = nAttr.create('blendWeight', 'bw', OpenMaya.MFnNumericData.kFloat)
    nAttr.setKeyable(True)
    BlendNode.addAttribute(BlendNode.aBlendWeight)
    BlendNode.attributeAffects(BlendNode.aBlendWeight, outputGeom)

    # Make deformer weights paintable
    cmds.makePaintable('blendNode', 'weights', attrType='multiFloat', shapeMode='deformer')

def initializePlugin(obj):
    plugin = OpenMayaMPx.MFnPlugin(obj, 'Chad Vernon', '1.0', 'Any')
    try:
        plugin.registerNode('blendNode', BlendNode.kPluginNodeId, creator, initialize, OpenMayaMPx.MPxNode.kDeformerNode)
    except:
        raise RuntimeError, 'Failed to register node'

def uninitializePlugin(obj):
    plugin = OpenMayaMPx.MFnPlugin(obj)
    try:
        plugin.deregisterNode(BlendNode.kPluginNodeId)
    except:
        raise RuntimeError, 'Failed to deregister node'

The only difference besides syntax here is the access of the built-in static variables, outputGeom and envelope, of the proxy class, MPxDeformerNode.  We cannot just use self.outputGeom or self.envelope; instead, we can use similar code:

outputGeom = OpenMayaMPx.cvar.MPxDeformerNode_outputGeom
envelope = OpenMayaMPx.cvar.MPxDeformerNode_envelope

You can read more about scripted plug-in workflow in the Maya Python API section of the documentation.

5 Responses to “Deformers”

Subscribes to this topic Comment RSS or TrackBack URL

hello, are you using the UV parameter space of the spheres for the deformer shown here http://www.youtube.com/watch?v=XGYdT4p3S58&feature=channel_video_title
or is it a blend shape like explained in the notes above?

Cheers

nikos on June 8th, 2011 at 2:30 am

No, it’s not using the UVs and it’s not like a blendshape. It’s mostly matrix calculations.

Chad on June 8th, 2011 at 8:14 am

Cheers!

nikos on June 20th, 2011 at 2:05 am

Thanks Chad! Thanks to your detailed explanation, now my custom python deformer is now paintable! :)
Still very slow though… :(

Richard on August 11th, 2011 at 3:05 pm

Just wanted to thank you for the great resources! Have been looking for something like this all over the place.

MWilliams on October 13th, 2011 at 9:47 am

Leave A Reply

Allowed tag : <blockquote>, <p>, <code>, <em>, <small>, <ul>, <li>, <ol>, <a href=>..

 Username

 Email Address

 Website

Sticky: Always double check your comment before posting Please Note: Comment Moderation Maybe Active So There Is No Need To Resubmit Your Comments
Home » Resources » Maya API Programming » Deformers

© 2011 Chad Vernon