C++ API Reference
manipOverride/manipOverride.cpp
//-
// ==========================================================================
// Copyright 2020 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+
//
// manipOverride.cpp
//
// This plug-in demonstrates how to create user-defined manipulators
// from a user-defined context and apply the manipulator to a custom attribute
// defined on a custom transform node. The custom transform node has a
// custom attribute defined, RockInX. A distance base manip is defined as
// the custom manipulator and gets attached to the RockInX attribute when selected.
//
// The attachment of the manipulator is performed by an event callback that
// is registered for PostToolChanged and SelectionChanged events.
//
#include <stdio.h>
#include <stdlib.h>
#include <maya/M3dView.h>
#include <maya/MArgList.h>
#include <maya/MCursor.h>
#include <maya/MDagPath.h>
#include <maya/MEventMessage.h>
#include <maya/MFn.h>
#include <maya/MFnCamera.h>
#include <maya/MFnDistanceManip.h>
#include <maya/MFnPlugin.h>
#include <maya/MFnTransform.h>
#include <maya/MGlobal.h>
#include <maya/MIOStream.h>
#include <maya/MItSelectionList.h>
#include <maya/MModelMessage.h>
#include <maya/MPxContext.h>
#include <maya/MPxContextCommand.h>
#include <maya/MPxManipContainer.h>
#include <maya/MPxNode.h>
#include <maya/MPxSelectionContext.h>
#include <maya/MPxToolCommand.h>
#include <maya/MPlug.h>
#include <maya/MPoint.h>
#include <maya/MQuaternion.h>
#include <maya/MSelectionList.h>
#include <maya/MVector.h>
#include "rockingTransform2.h"
#include "customTriadManip.h"
#define customAttributeString "rockx"
MCallbackId cid1 = 0;
MCallbackId cid2 = 0;
static bool isSetting = false;
// This constant is used to translate mouse delta values into floating point delta
// values to modify the attached attributes.
static double scaleFactor = 0.01;
// Callback function for messages. This callback will be registered for the PostToolChanged
// and SelectionChanged events.
//
static void eventCB(void * data);
//
// The customAttr tool command
//
// - This command is used to turn the interactions with the manip
// or the context into an undoable action.
//
#define ATTR_CMD_NAME "customAttrToolCmd"
#define DOIT 0
#define UNDOIT 1
#define REDOIT 2
class customAttrCmd : public MPxToolCommand
{
public:
customAttrCmd();
~customAttrCmd() override;
MStatus doIt( const MArgList& args ) override;
MStatus redoIt() override;
MStatus undoIt() override;
bool isUndoable() const override;
MStatus finalize() override;
public:
static void* creator();
void setDelta(double d);
void setDragX() {dragX = true;}
private:
double delta;
bool dragX;
MStatus action( int flag ); // do the work here
void parseDrag(int axis);
};
customAttrCmd::customAttrCmd( )
{
setCommandString( ATTR_CMD_NAME );
dragX = false;
}
customAttrCmd::~customAttrCmd()
{}
void* customAttrCmd::creator()
{
return new customAttrCmd;
}
bool customAttrCmd::isUndoable() const
//
// Description
// Set this command to be undoable.
//
{
return true;
}
void customAttrCmd::setDelta(double d)
//
// Description
// This method sets the delta value that will be used when
// the command is executed.
//
{
delta = d * scaleFactor;
}
MStatus customAttrCmd::finalize()
//
// Description
// This method constructs the final command syntax which will be called
// to execute/undo/redo the action. The syntax of the generated command
// will be:
//
// customAttrToolCmd <deltaVal>
//
// where <deltaVal> is the most recently set value from the call to
// customAttrCmd::setDelta().
//
{
MArgList command;
command.addArg( commandString() );
command.addArg( delta );
// This call adds the command to the undo queue and sets
// the journal string for the command.
//
return MPxToolCommand::doFinalize( command );
}
MStatus customAttrCmd::doIt( const MArgList& args )
//
// Description
// This method executes the command given the passed arguments.
// The arguments consist of a delta value and one or more axes
// along which the delta value will be applied.
//
{
MStatus stat;
delta = args.asDouble( 0, &stat );
return action( DOIT );
}
MStatus customAttrCmd::undoIt( )
//
// Description
// Undo last delta value.
//
{
return action( UNDOIT );
}
MStatus customAttrCmd::redoIt( )
//
// Description
// Redo last delta value.
//
{
return action( REDOIT );
}
MStatus customAttrCmd::action( int flag )
//
// Description
// Do the actual work here to move the objects by the
// command's delta value. The objects will come from those
// on the active selection list.
//
{
MStatus stat;
double d = delta;
switch( flag )
{
case UNDOIT: // undo
d = -d;
break;
case REDOIT: // redo
break;
case DOIT: // do command
break;
default:
break;
}
// Create a selection list iterator
//
MItSelectionList iter( slist, MFn::kInvalid, &stat );
if ( MS::kSuccess == stat ) {
MDagPath mdagPath; // Item dag path
MObject mComponent; // Current component
// Processs all selected objects
//
for ( ; !iter.isDone(); iter.next() )
{
// Get path and possibly a component
//
iter.getDagPath( mdagPath, mComponent );
MFnTransform transFn( mdagPath, &stat );
if ( MS::kSuccess == stat ) {
// If the selected object is of type rockingTransform2,
// then set the appropriate plug value depending on which axes
// the command is operating on.
if (transFn.typeId() == rockingTransform2Node::id)
{
MPlug plg = transFn.findPlug(customAttributeString, true);
double val;
plg.getValue(val);
plg.setValue(val+d);
}
continue;
}
} // for
}
else {
cerr << "Error creating selection list iterator" << endl;
}
return MS::kSuccess;
}
//
// The customAttrManip2 manipulator
//
// - This class defines the manipulator which will be used
// when the tool becomes the active context. It consists of
// three distance base manips aligned along the X, Y, and Z
// axes of the attached transform's coordinate system. The
// internals of the manipulator base class handle the management
// of command information so that undo/redo are handled.
//
class customAttrManip2 : public MPxManipContainer
{
public:
customAttrManip2();
~customAttrManip2() override;
static void * creator();
static MStatus initialize();
MStatus createChildren() override;
MStatus connectToDependNode(const MObject &node) override;
MVector nodeTranslation() const;
MQuaternion nodeRotation() const;
void updateManipLocations();
MDagPath fManip;
MDagPath fNodePath;
public:
static MTypeId id;
};
MTypeId customAttrManip2::id( 0x80025 );
customAttrManip2::customAttrManip2()
{
// Do not call createChildren from here -
// MayaPtr has not been set up yet.
}
customAttrManip2::~customAttrManip2()
{
}
void *customAttrManip2::creator()
{
return new customAttrManip2();
}
MStatus customAttrManip2::initialize()
{
MStatus stat;
return stat;
}
MStatus customAttrManip2::createChildren()
//
// Description
// Create the geometry of the manip. This consists of a single
// distance manip.
//
{
fManip = addDistanceManip("customtManip", "customPoint");
}
MQuaternion customAttrManip2::nodeRotation() const
//
// Description
// Query and return the rotation values for the attached transform node.
//
{
MFnDagNode dagFn(fNodePath);
MDagPath path;
dagFn.getPath(path);
MFnTransform transformFn(path);
transformFn.getRotation( q, MSpace::kWorld );
return q;
}
MVector customAttrManip2::nodeTranslation() const
//
// Description
// Query and return the translation values for the attached transform node.
//
{
MFnDagNode dagFn(fNodePath);
MDagPath path;
dagFn.getPath(path);
MFnTransform transformFn(path);
return transformFn.getTranslation(MSpace::kWorld);
}
void customAttrManip2::updateManipLocations()
//
// Description
// This method places the manip in the scene according to the information
// obtained from the attached transform node. The position and orientation
// of the distance manip is determined.
//
{
MVector trans = nodeTranslation();
MQuaternion q = nodeRotation();
MFnDistanceManip freePointManipFn(fManip);
MVector vecX(1.0, 0.0, 0.0);
freePointManipFn.setDirection(vecX);
freePointManipFn.rotateBy(q);
freePointManipFn.setTranslation(trans, MSpace::kWorld);
}
MStatus customAttrManip2::connectToDependNode(const MObject &node)
//
// Description
// This method activates the manip on the given transform node.
//
{
// Get the DAG path
//
MFnDagNode dagNodeFn(node);
dagNodeFn.getPath(fNodePath);
// Connect the plugs to the manip.
MFnDependencyNode nodeFn(node);
MFnDistanceManip distManipFn(fManip);
MPlug cPlug = nodeFn.findPlug(customAttributeString, true, &stat);
if( stat == MStatus::kSuccess )
distManipFn.connectToDistancePlug(cPlug);
finishAddingManips();
updateManipLocations();
return stat;
}
//
// The customAttrManip2 Context
//
// - Tool contexts are custom event handlers and are used to
// process mouse interactions. The context subclass
// allows you to override press/drag/release events.
//
// This context contains the customAttrManip2 defined above and
// also performs its own mouse processing by handling the middle
// mouse. When the middle mouse button is lifted at the end of
// a drag, a command is constructed for use in undo/redo.
//
#define MOVEHELPSTR "Drag the distance manips to change values on custom attributes"
#define MOVETITLESTR "customAttrManip2"
class customAttrCtx : public MPxSelectionContext
{
public:
customAttrCtx();
void toolOnSetup(MEvent &event) override;
void toolOffCleanup() override;
MStatus doEnterRegion(MEvent &event) override;
MStatus doPress(MEvent &event) override;
MStatus doDrag(MEvent &event) override;
MStatus doRelease(MEvent &event) override;
customAttrManip2 * caManip;
private:
M3dView view;
short startPos_x, endPos_x;
short startPos_y, endPos_y;
double delta;
customAttrCmd * cmd;
};
void updateManipulators(void * data);
MCallbackId id1;
customAttrCtx::customAttrCtx()
{
MString str(MOVETITLESTR);
setTitleString(str);
}
void customAttrCtx::toolOnSetup(MEvent &)
{
MString str(MOVEHELPSTR);
setHelpString(str);
updateManipulators(this);
MStatus status;
updateManipulators,
this, &status);
if (!status) {
cerr << "Model addCallback failed\n";
}
}
void customAttrCtx::toolOffCleanup()
//
// Description
// This method is called when the context is no longer the current context.
// The manipulator is removed from the scene.
//
{
MStatus status;
if (!status) {
cerr << "Model remove callback failed\n";
}
}
MStatus customAttrCtx::doPress( MEvent & event )
//
// Description
// This method is called when a mouse button is pressed while this context is
// the current context.
//
{
// Let the parent class handle the event first in case there is no object
// selected yet. The parent class will perform any necessary selection.
// If an object has been selected, then process the event. Otherwise,
// ignore it as there is nothing to do.
if ( !isSelecting() ) {
{
setCursor(MCursor::handCursor);
// Create an instance of the customAttrCmd tool command and initialize
// its delta value to 0. As the mouse drags, the delta value will change.
// when the mouse is lifted, a final command will be constructed with the
// most recently set delta value and axis specifications.
cmd = (customAttrCmd *)newToolCommand();
cmd->setDelta(0.0);
event.getPosition( startPos_x, startPos_y );
// Determine the channel box attribute which will be operated on by the
// dragging motion and set the state of the command accordingly.
unsigned int i;
MStringArray result;
"channelBox -q -selectedMainAttributes $gChannelBoxName", result);
for (i=0; i<result.length(); i++)
{
if (result[i] == customAttributeString)
{
cmd->setDragX();
break;
}
}
}
}
return stat;
}
MStatus customAttrCtx::doDrag( MEvent & event )
//
// Description
// This method is called when a mouse button is dragged while this context is
// the current context.
//
{
MStatus stat;
// If an object has been selected, then process the drag. Otherwise, pass the
// event on up to the parent class.
if ((!isSelecting()) && (event.mouseButton() == MEvent::kMiddleMouse)) {
event.getPosition( endPos_x, endPos_y );
// Undo the command to erase the previously set delta value from the
// node, set a new delta value in the command and redo the command to
// set the values in the node.
cmd->undoIt();
cmd->setDelta(endPos_x - startPos_x);
stat = cmd->redoIt();
view.refresh( true );
}
else
stat = MPxSelectionContext::doDrag( event );
return stat;
}
MStatus customAttrCtx::doRelease( MEvent & event )
//
// Description
// This method is called when a mouse button is released while this context is
// the current context.
//
{
// Let the parent class handle the event.
// If an object is selected, process the event if the middle mouse button
// was lifted.
if ( !isSelecting() ) {
{
event.getPosition( endPos_x, endPos_y );
// Delete the move command if we have moved less then 2 pixels
// otherwise call finalize to set up the journal and add the
// command to the undo queue.
//
if ( abs(startPos_x - endPos_x) < 2 ) {
delete cmd;
view.refresh( true );
}
else {
stat = cmd->finalize();
view.refresh( true );
}
}
}
return stat;
}
void updateManipulators(void * data)
{
//
// Description
// This callback function is called when the selection changes so that the manip can
// be reinitialized on the new current selection.
//
// Delete any previously existing manipulators.
customAttrCtx * ctxPtr = (customAttrCtx *) data;
ctxPtr->deleteManipulators();
// iterate through the selected objects:
//
MItSelectionList iter(list, MFn::kInvalid, &stat);
if (MS::kSuccess == stat) {
for (; !iter.isDone(); iter.next()) {
// create the customAttrManip2 for each object selected:
//
MString manipName ("customAttrManip2");
MObject manipObject;
ctxPtr->caManip = (customAttrManip2 *) customAttrManip2::newManipulator(manipName, manipObject);
if (NULL != ctxPtr->caManip) {
MObject dependNode;
iter.getDependNode(dependNode);
MFnDependencyNode dependNodeFn(dependNode);
ctxPtr->addManipulator(manipObject);
dependNodeFn.findPlug(customAttributeString, true, &stat);
if (MStatus::kSuccess != stat) {
ctxPtr->deleteManipulators();
return;
}
ctxPtr->caManip->connectToDependNode(dependNode);
}
}
}
}
MStatus customAttrCtx::doEnterRegion(MEvent &event)
//
// Print the tool description in the help line.
//
{
MString str(MOVEHELPSTR);
return setHelpString(str);
}
//
// Context creation command
//
// This is the command that will be used to create instances
// of our context.
//
#define CREATE_CTX_NAME "customAttrManipContext2"
class customAttrCtxCommand : public MPxContextCommand
{
public:
customAttrCtxCommand() {};
MPxContext * makeObj() override;
public:
static void* creator();
};
MPxContext *customAttrCtxCommand::makeObj()
{
customAttrCtx *newC = new customAttrCtx();
return newC;
}
void *customAttrCtxCommand::creator()
{
return new customAttrCtxCommand;
}
//
// The following routines are used to register/unregister
// the commands we are creating within Maya
//
MStatus initializePlugin(MObject obj)
{
MStatus status;
MFnPlugin plugin(obj, PLUGIN_COMPANY, "3.0", "Any");
status = plugin.registerContextCommand(
CREATE_CTX_NAME, &customAttrCtxCommand::creator,
ATTR_CMD_NAME, &customAttrCmd::creator);
if (!status)
{
status.perror("registerContextCommand");
return status;
}
status = plugin.registerContextCommand(
CREATE_TRIAD_CTX_NAME, &customTriadCtxCommand::creator);
if (!status)
{
status.perror("registerContextCommand");
return status;
}
// Classify the node as a transform. This causes Viewport
// 2.0 to treat the node the same way it treats a regular
// transform node.
const MString classification = "drawdb/geometry/transform/rockingTransform2";
status = plugin.registerTransform( "rockingTransform2",
rockingTransform2Node::id,
&rockingTransform2Node::creator,
&rockingTransform2Node::initialize,
&rockingTransform2Matrix::creator,
rockingTransform2Matrix::id,
&classification);
if (!status) {
status.perror("registerNode");
return status;
}
status = plugin.registerNode("customAttrManip2", customAttrManip2::id,
&customAttrManip2::creator, &customAttrManip2::initialize,
if (!status)
{
status.perror("registerManip");
return status;
}
status = plugin.registerNode("customTriadManip", customTriadManip::id,
&customTriadManip::creator, &customTriadManip::initialize,
if (!status)
{
status.perror("registerManip");
return status;
}
// Register a callback for the PostToolChanged and SelectionChanged events.
cid1 = MEventMessage::addEventCallback("PostToolChanged", eventCB, NULL, &status);
cid2 = MEventMessage::addEventCallback("SelectionChanged", eventCB, NULL, &status);
MGlobal::executeCommandOnIdle("customAttrManipContext2 myCustomAttrContext");
MGlobal::executeCommandOnIdle("customTriadManipContext myCustomTriadContext");
return status;
}
MStatus uninitializePlugin(MObject obj)
{
MStatus status;
MFnPlugin plugin(obj);
// Unregister the event callbacks.
status = plugin.deregisterContextCommand(CREATE_CTX_NAME, ATTR_CMD_NAME);
if (!status) {
status.perror("deregisterContextCommand");
return status;
}
status = plugin.deregisterContextCommand(CREATE_TRIAD_CTX_NAME);
if (!status) {
status.perror("deregisterContextCommand");
return status;
}
status = plugin.deregisterNode(customAttrManip2::id);
if (!status) {
status.perror("deregisterManip");
return status;
}
status = plugin.deregisterNode(customTriadManip::id);
if (!status) {
status.perror("deregisterManip");
return status;
}
status = plugin.deregisterNode( rockingTransform2Node::id );
if (!status) {
status.perror("deregisterNode");
return status;
}
return status;
}
//
// Callback functions
//
//
// This callback gets called for the PostToolChanged and SelectionChanged events.
// It checks to see if the current context is the dragAttrContext, which is the context
// applied by default when a custom numeric attribute is selected in the channel box.
// In this case, the customAttrManip2 context is set.
//
static void eventCB(void * data)
{
// This check prevents recursion from happening when overriding the manip.
if (isSetting)
return;
MSelectionList selList;
MString curCtx = "";
MGlobal::executeCommand("currentCtx", curCtx);
MDagPath path;
MObject dependNode;
for (unsigned int i=0; i<selList.length(); i++)
{
if ((selList.getDependNode(i, dependNode)) == MStatus::kSuccess)
{
if (node.hasObj(dependNode))
node.setObject(dependNode);
else
continue;
if (node.typeId() == rockingTransform2Node::id)
{
// If the current context is the dragAttrContext, check to see
// if the custom channel box attributes are selected. If so,
// attach the custom manipulator.
if ((curCtx == "dragAttrContext") || (curCtx == ""))
{
// Make sure that the correct channel box attributes are selected
// before setting the tool context.
unsigned int c;
MStringArray cboxAttrs;
"channelBox -q -selectedMainAttributes $gChannelBoxName", cboxAttrs);
for (c=0; c<cboxAttrs.length(); c++)
{
if (cboxAttrs[c] == customAttributeString)
{
isSetting = true;
MGlobal::executeCommand("setToolTo myCustomAttrContext");
isSetting = false;
return;
}
}
}
if ((curCtx == "moveSuperContext") || (curCtx == "manipMoveContext") ||
(curCtx == ""))
{
isSetting = true;
MGlobal::executeCommand("setToolTo myCustomTriadContext");
isSetting = false;
return;
}
}
}
}
}