Monday, September 15, 2014

Simple Cartoon Squash Deformer in Maya Api -- Part I

Hi,

I thought i would give it a try making a simple squash deformer on a nurbs surface.

Here's what i made so far.  Basically i wanted to get an understanding of first compiling a deformer that uses cv data.  Here it just accesses cv data and moves all cvs above the world y = 0 line, down by half the total height of the nurbs surface.
before:

after:


I highly recommend David Gould's Maya Complete programming book.  His examples on melt node and rolling node were very very very helpful for getting me started with deformers using Maya api.

To get your feet wet i highly recommend first getting a simple example that uses a header file and .cpp file to work.  For example see Chad Vernon's hello command program (chadvernon dot com).

So here is the current draft there are 5 files in total

//Algorithm of node files
testNode.cpp  //this is the one to look in to see how cvs, .. were actually moved in the "compute"
testNode.h

//Files to do mostly book keeping and communicate with user
testNodeCmd.h
testNodeCmd.cpp //if you use MEL it does stuff similar to connectAttr and what we see with listConnections
testNodeMain.cpp //this one kind of ties all the pieces together

I'm still fairly new to this stuff, but i think separating out headers makes each file a little more simple to debug.  A tip for getting includes is to do search of includes by searching for class name in Maya's api examples (example edit .bash_profile with a function that can search a directory for occurence of a name),  this way we can see what we need to include when we use stuff.

Hope this is helpful,
cheers,
Nate

Modify at your own risk (please remove space in "< maya" i added space so could show code in html, note I did little error checking, also the id 0x00334 is temporary you may want to change this):

//what i ran to compile this thing on a macbook

g++ -c testNodeCmd.cpp -arch i386 -DMAC_PLUGIN -DOSMac_MachO_ -DBits32_ -m32 -DUNIX -D_BOOL -DOSMac_ -DFUNCPROTO -D_GNU_SOURCE -fPIC -fno-strict-aliasing -DREQUIRE_IOSTREAM -Wno-deprecated -Wall -Wno-multichar -Wno-comment -Wno-sign-compare -funsigned-char -Wno-reorder -fno-gnu-keywords -ftemplate-depth-25 -pthread -Wno-deprecated -fno-gnu-keywords -g -I/Applications/Autodesk/maya2008/devkit/include -o testNodeCmd.o g++ -c testNode.cpp -arch i386 -DMAC_PLUGIN -DOSMac_MachO_ -DBits32_ -m32 -DUNIX -D_BOOL -DOSMac_ -DFUNCPROTO -D_GNU_SOURCE -fPIC -fno-strict-aliasing -DREQUIRE_IOSTREAM -Wno-deprecated -Wall -Wno-multichar -Wno-comment -Wno-sign-compare -funsigned-char -Wno-reorder -fno-gnu-keywords -ftemplate-depth-25 -pthread -Wno-deprecated -fno-gnu-keywords -g -I/Applications/Autodesk/maya2008/devkit/include -o testNode.o g++ testNodeMain.o testNodeCmd.o testNode.o -ldl -shared -L/Applications/Maya.app/Contents/MacOS -lOpenMaya -lFoundation -Wl,-executable_path,/Applications/Maya.app/Contents/MacOS -lFoundation -lOpenMaya -framework OpenGL -o testNode.bundle


//testNode.cpp
//last updated: 09-15-2014  nate -- initial release

//Inspired by David Gould's Complete Maya Programming, rolling node and melting node examples
#include "testNode.h"
#include < maya/MPlug.h>
#include < maya/MDataBlock.h>
#include < maya/MTypeId.h>
#include < maya/MDataHandle.h>
#include < maya/MObject.h>
#include < maya/MFnNurbsSurfaceData.h>
#include < maya/MFnNurbsSurface.h>
#include < maya/MPointArray.h>
#include < maya/MFnTypedAttribute.h>

//for some error printing
#include < string.h>
#include < maya/MIOStream.h>

MTypeId testNode::id( 0x00334);

MObject testNode::inputSurface;
MObject testNode::outputSurface;


//little error checking
//because we are using a bigger structure that has lots of other plugs we dont see
//we need to be specific about our plugs/attr
MStatus testNode::compute( const MPlug& plug, MDataBlock& data)
{
    MStatus stat;
    
    if( plug == outputSurface )
    {
        //get handles to all attributes    
        MDataHandle inputSurfaceHnd = data.inputValue( inputSurface, &stat );
        if(stat != MS::kSuccess){cerr << "error MDataHandle" << stat << endl; return stat;}
        MDataHandle outputSurfaceHnd = data.outputValue( outputSurface, &stat );
        if(stat != MS::kSuccess){cerr << "error MDataHandle" << stat << endl; return stat;}
        
        //using wrong as will probably cause maya to crash
        MObject inputSurfaceObj = inputSurfaceHnd.asNurbsSurface();
        
        //make a place to store original input surface
        //nurbs surface need specific function set for its data see MFnGeometryData to find polys etc
        //im guessing its kind of like beginning of making a nurbs sphere in maya 
        MFnNurbsSurfaceData surfaceDataFn;
        MObject newSurfaceData = surfaceDataFn.create(&stat);
        if(stat != MS::kSuccess){cerr << "error making new output surface" << stat << endl; return stat;}

        //copy original input surface to new output surface
        MFnNurbsSurface surfaceFn;
        surfaceFn.copy( inputSurfaceObj, newSurfaceData ); //im guessing its analagous to using hypergraph and connecting create of one nurbssurface into create of a second nurbas surface
        
        //we can now deform the copy without affecting our original
        surfaceFn.setObject( newSurfaceData ); //giving it an mobject
        
        //all surface cvs retrieved and stored in an array at once to save time
        MPointArray pts; //they should be vectors in worldspace i think from origin to location of cv, it might be from pivot center
        
        stat = surfaceFn.getCVs(pts);
        if(stat != MS::kSuccess){cerr<<"Error getting cvs"<< stat<< endl;return stat;}
        
        //figure out have height of nurbs by first getting whole distance
        unsigned int i;
        
        double halfDistance;
        double minHeight = -1, maxHeight = 1, y;
        
        for( i=0; i < pts.length(); i++ )
        {
            y = pts[i].y;
            if( y < minHeight ){ minHeight = y; }
            if( y > maxHeight ){ maxHeight = y; }
        }
        halfDistance = 0.5*(maxHeight - minHeight);
        //should have our height
              
        //go over cvs this time deforming them
        MVector vec;
        for( i = 0; i < pts.length(); i++ ){
            MPoint &p = pts[i];
            //move each point down vertically by half distance if it was above 0 to start
            if( p.y > 0 ){ p.y -= halfDistance; }
            //p.y -= halfDistance;
           
        }//DONE WITH DEFORMATION
        
        //UPDATE with deformation
        surfaceFn.setCVs(pts);
        
        //Nurbssurface has been changed tell Maya this or else get crash
        surfaceFn.updateSurface();
        
        //set the output surface attribute to the new data
        outputSurfaceHnd.set(newSurfaceData);
        
        //since attribute has now been recomputed tell plug its clean or get crash
        stat = data.setClean(plug);
        if( stat != MS::kSuccess ){cerr<<"error cleaning"<< stat<< endl; return stat;}
    }//done with plug
    else{
        //for any other plugs we didnt make let Maya know to do its thing
        stat = MS::kUnknownParameter; //very important
    }
    
    
    return stat;
}


//little error checking
MStatus testNode::initialize()
{
    MStatus stat;
    
    MFnTypedAttribute tAttr;//for nurbs surface attribute
    inputSurface = tAttr.create( "inputSurface", "is", MFnNurbsSurfaceData::kNurbsSurface, &stat );
    if( !stat ){stat.perror("error creating -- inputSurface");return stat;}
    tAttr.setHidden(true);//hide it, by default its visible

    outputSurface = tAttr.create( "outputSurface", "os", MFnNurbsSurfaceData::kNurbsSurface, &stat );
    //since this can be derived from just input dont need to store it
    tAttr.setStorable(false);
    tAttr.setHidden(true);//hide it, by default its visible
    
    //add attributes to node
    
    stat = addAttribute( inputSurface );
    if (!stat) {stat.perror("addAttribute");return stat;}
    stat = addAttribute( outputSurface );
    if (!stat) {stat.perror("addAttribute");return stat;}
    
    //the input surface affects output surface this is required or get crash
    attributeAffects( inputSurface, outputSurface);
    
    return MS::kSuccess;
}
//testNode.h
#ifndef TESTNODE_H
#define TESTNODE_H

#include < maya/MObject.h>
#include < maya/MGlobal.h>
#include < maya/MPxNode.h>
#include < maya/MPlug.h>
#include < maya/MDataBlock.h>
#include < maya/MTypeId.h>

//Inspired by David Gould RollingNode and Melt Node example
//move when deformer applied it translates all cvs above 0 in y by half height
class testNode : public MPxNode
{
 
public:
    virtual MStatus compute( const MPlug& plug, MDataBlock& data);
    static void *creator(){return new testNode();}
    static MStatus initialize();
    //attributes
    static MObject inputSurface;
    static MObject outputSurface;
    
    //id
    static MTypeId id;
};
#endif
//testNodeCmd.cpp
#include "testNodeCmd.h"
#include "testNode.h"


#include < maya/MItSelectionList.h>
#include < maya/MPlugArray.h>
#include < maya/MPlug.h>
#include < maya/MFnDependencyNode.h>

MStatus testNodeCmd::doIt( const MArgList &args ){
    
    MStatus stat;
    MSelectionList selection;
    MGlobal::getActiveSelectionList( selection );
    
    //so can apply deformer to any number of selected nurbs
    MItSelectionList iter( selection, MFn::kNurbsSurface );
    for( ; !iter.isDone(); iter.next() )
    {
        MObject shapeNode; //get shape
        iter.getDependNode(shapeNode);
        
        //get plug to create attribute on nurbs shape
        MFnDependencyNode shapeFn( shapeNode );
        MPlug createPlug = shapeFn.findPlug( "create" );//need correct name here
        
        //like listconnections to create attribute
        MPlugArray srcPlugs;
        createPlug.connectedTo( srcPlugs, true, false );
        MPlug srcPlug = srcPlugs[0];//assuming only one input connection to create attribute
        
        
        MObject testNode = dgMod.createNode( testNode::id );//it will need header of testnode
        
        
        //create plugs to input and output surface
        MFnDependencyNode testFn( testNode );
        
        MPlug outputSurfacePlug = testFn.findPlug("outputSurface");
        MPlug inputSurfacePlug = testFn.findPlug("inputSurface");
        
        
        //before new connection to break existing connection
        //kind of like getting mel erro attribute locked etc
        dgMod.disconnect(srcPlug, createPlug);
        
        
        //HOW TO PRINT DATA TYPE OF PLUG
        //MString outputSurfacePlugInfo = outputSurfacePlug.info();
        //MString inputSurfacePlugInfo = inputSurfacePlug.info();
        //MString createPlugInfo = createPlug.info();
        //MGlobal::displayInfo(createPlugInfo);
        //MGlobal::displayInfo(outputSurfacePlugInfo);
        // MGlobal::displayInfo(inputSurfacePlugInfo);
        
        
        dgMod.connect( srcPlug, inputSurfacePlug);//important deformer input surface comes from what created our nurbs
        dgMod.connect( outputSurfacePlug, createPlug);//important output surface goes into create plug
        //
        
        
        //automatically assign new node a unique name
        static int i = 0;
        MString name = MString("GreatDay_testNode") + i++;
        dgMod.renameNode( testNode, name );
        
        //if needed to run any mel do it here    
    }
    
    //finally call redoit to do the real work
    return redoIt();   
}

MStatus testNodeCmd::redoIt()
{
    return dgMod.doIt();//undoing handled by MDGModifier
}

MStatus testNodeCmd::undoIt()
{
    return dgMod.undoIt();
}
//testNodeCmd.h
#ifndef TESTNODECMD_H
#define TESTNODECMD_H

#include < maya/MArgList.h>
#include < maya/MObject.h>
#include < maya/MGlobal.h>
#include < maya/MPxCommand.h>
#include < maya/MDGModifier.h>

class testNodeCmd : public MPxCommand
{
public:
    virtual MStatus doIt ( const MArgList& );
    virtual MStatus undoIt();
    virtual MStatus redoIt();
    virtual bool isUndoable() const{return true; }
    
    static void *creator() {return new testNodeCmd; }
    //static MSyntax newSyntax();
private:
    MDGModifier dgMod;
      
};
#endif
//testNodeMain.cpp
#include "testNodeCmd.h"
#include "testNode.h"

#include < maya/MFnPlugin.h>

MStatus initializePlugin(MObject obj)
{
    MStatus stat;
    
    MFnPlugin pluginFn(obj, "Nate Test Nodes", "1.0", "Any");
    
    stat = pluginFn.registerCommand( "testNodeCmd", testNodeCmd::creator);
    if(!stat){stat.perror("error registering command");}
    
    //need more stuff in initialize plugin for deformer node
    //this is need to put blue print of nodes attributes its done once
    stat = pluginFn.registerNode("testNode", 
            testNode::id, 
            testNode::creator, 
            testNode::initialize );
            
    if(!stat){stat.perror("error registering node");}
    
    return stat;
}

MStatus uninitializePlugin(MObject obj)
{
    MStatus stat;
    MFnPlugin pluginFn( obj );
    
    stat = pluginFn.deregisterCommand("testNodeCmd");
    if(!stat){stat.perror("error deregistering command");}
    stat = pluginFn.deregisterNode(testNode::id);//needs more than just name
    if(!stat){stat.perror("error deregistering node");}
    
    return stat;
}


Inspired by David Gould

Inspired by Chad Vernon (chadvernon dot com)