Maya Custom Manipulators

Swiss-tool

I. General Introduction

A manipulator is a node that draws itself using 3D graphical
elements that respond to user events. While it is possible to change a node’s attribute values using Channel
Box, Graph Editor and etc., manipulators provides a more intuitive way of changing
attribute values: Manipulators provide a series of visual controls that users can
interactively move or adjust. The modification of these visual controls subsequently
is translated by manipulators into value changes and then applied to the nodes
attributes. The attribute values are modified directly by the manipulator and
not through the standard plug and connection mechanism used by other dependency
graph nodes.

Using the
manipulator, it is easier than typing modified values in the editors such as
Attribute Editor, Channel Box and etc. Users can see the immediate visual
feedback during operations. Therefore, manipulators quite often are used as an
interactive tool for animators to manipulate a specific object or custom nodes
in a scene. With custom manipulators, you will be able to create all types of
interactive custom tools (modeling tool, rigging tool, etc…) for animators in
different stages of animation production pipeline.

There are
several ways to create and activate manipulators: First, you can create a manipulator
on any node in the current scene at any time. You can also create and assign a
manipulator for a specific type of object. Another approach is to create a
context tool and associate the manipulator to the context tool so that whenever
it is active, the manipulator is active and ready to use. Manipulators are only
active when the “Show Manipulator Tool” or the associated context is active,
and the object that they correspond to is selected. For more information, see
Section 5 “Register Manipulators”.

When
manipulators are created, even though they are nodes, they are not visible in
the Hypergraph or Outliner, and they are not added to the Maya selection list.
Additionally, their attributes are not accessible from MEL or the attribute
editor, and they are not written to file.

Manipulators
are designed to operate on data types, ranging from integer and floating point
values to matrix data and can operate on one or more attribute values at the
same time. 

II. Maya Base Manipulators

Maya defines a set of simple manipulators,
called base manipulators. These operate on a range of data types from a
single boolean, integer, or floating point value, to vectors of floating point
values of different lengths. The OpenMaya API
supports 12 base manipulator classes that can be combined to form a composite
manipulator. All the function set classes for these Maya base manipulators are
derived from MFnManip3D.

FreePointTriadManip

Triadmanip
The
FreePointTriadManip provides a moveable point that can be moved anywhere. It
has axes for constrained x, y, and z movement and obeys grid snapping, point
snapping, and curve snapping. The FreePointTriadManip generates the 3D position
of the point. It is useful for specifying the position of an object in space.

One thing to note is that the
FreePointTriadManip is not corresponding to Maya’s internal moveManip, and it
is actually a subset of the moveManip.

The
API class for FreePointTriadManip is MFnFreePointTriadManip. This function set
class can be used to create the manipulator and set up some properties of this
manipulator. For example, MFnFreePointTriadManip::setDrawAxes() can be used to
set whether or not to draw the axes of the FreePointTriadManip.
MFnFreePointTriadManip::setSnapMode() can be used to set whether or not the
FreePointTriadManip should be in snap mode etc…

RotateManip

Rotatemanip
The RotateManip corresponds
to Maya built-in rotate manipulator, and it allows you to manipulate a 3d
rotation vector.  The manipulator
consists of three constrained-axis rotation rings, a view rotation ring, and an
invisible trackball that allows the user to rotate in arbitrary directions on
the sphere. It supports the 3 rotation modes of the built-in rotate manipulator
(object space, global, gimbal) and allows constrained rotation on the x, y, z
and viewing axes. The vector generated by the manipulator is an Euler rotation
that is suitable for input to a rotation plug.  

The API class for
RotateManip is MFnRotateManip. Simliar to MFnFreePointTriadManip, it provides
functions to create the manipulator and set up properties. 

ScaleManip

Scalemanip
The ScaleManip
corresponds to Maya built-in scale manipulator and lets you manipulate relative
x, y, and z scaling values. The scale manipulator provides a central handle for
proportional scaling, and x, y, and z axis handles for non-proportional scaling
on each axis. The vector generated by the manipulator is a relative scaling
vector that is suitable for input to a scale plug.

The API class for
scaleManip is MFnScaleManip. Simliar to MFnFreePointTriadManip, it provides
functions to create the manipulator and set up properties. 

DirectionManip

Dirmanip
The DirectionManip lets
you specify a direction as defined by the vector from the start point to the
manipulator position. It uses a FreePointTriadManip to specify the end point of
a vector relative to a given start point. This manipulator generates a vector
from the start point to the end point.

The API class for DirectionManip
is MFnDirectionManip. Simliar to MFnFreePointTriadManip, it provides functions
to create the manipulator and set up properties of the manipulator.

DistanceManip

Distmanip
The DistanceManip lets
you manipulate a point that is constrained to move along a line. The distance
value is calculated from the start point of the line to the manipulated point.
This manipulator generates a single floating point value. Scaling factors can
be used to determine how long the manipulator appears when it is drawn. The API
class for DistanceManip is MFnDistanceManip.

PointOnCurveManip

The PointOnCurveManip
lets you manipulate a point constrained to move along a curve, in order to
specify the “u” curve parameter value. This manipulator generates a
single floating point value corresponding to the curve parameter. The API class
for PointOnCurveManip is MFnPointOnCurveManip.

PointOnSurfaceManip

Pointonsurf
The PointOnSurfaceManip
lets you manipulate a point constrained to move along a surface, in order to
specify the (u, v) surface parameter values. This manipulator generates two
floating point values corresponding to the surface (u, v) parameters. The API
class for PointOnSurfaceManip is MFnPointOnSurfaceManip.

DiscManip

Discmanip
The DiscManip lets you
rotate a disc in order to specify a rotation about an axis. This manipulator
generates a single floating point value corresponding to the rotation. The API
class for DiscManip is MFnDiscManip.

CircleSweepManip

Circlesweep
The CircleSweepManip
lets you manipulate a point constrained to move around a circle, in order to
specify a sweep angle. This manipulator generates a single floating point value
corresponding to the sweep angle. Although very similar to DiscManip, it
provides more options for specifying rotating angles. The API class for CircleSweepManip
is MFnCircleSweepManip.

ToggleManip

Togglemanip
The ToggleManip lets
you switch between two modes or some on/off state. It is drawn as a circle with
or without a dot. When the mode is on, the dot is drawn in the circle and when
the mode is off, the circle is drawn without the dot. This manipulator
generates a boolean value corresponding to whether or not the mode is on or
off. The API class for ToggleManip is MFnToggleManip.

StateManip

Statemanip
The StateManip lets you
switch between multiple states. It is drawn as a circle with a notch. Each
click on the circle increments the value of the state (modulo the maximum
number of states). This manipulator generates an integer value corresponding to
the state of the manipulator. The API class for StateManip is MFnStateManip.

CurveSegmentManip

The CurveSegmentManip
lets you manipulate two points on a curve to specify a curve segment. This
manipulator generates two floating point values, which correspond to the
parameters of the start and end of the curve segment. The API class for CurveSegmentManip
is MFnCurveSegmentManip.

III. Custom Manipulators

There are several ways to work with manipulators, you could
either create a base manipulator
on any node in the current scene on the fly, or you could also create a
manipulator container and compose a custom manipulator by adding Maya base
manipulators into the container, furthermore, you can also create a brand new
type of manipulator node by using MPxManipulatorNode class and defining custom
drawing and picking of the manipulator node. Before we jump into more advanced topics
of creating a manipulator with MPxManipulatorNode, let’s first examine how to
work with Maya base manipulators to create the simplest manipulator on a node.

a. Working with Maya Base Manipulators

In the “Maya Base Manipulators”
section, we listed all the function set classes for Maya base manipulators. The
function set classes are responsible to create the base manipulators with corresponding
create() functions, and also set up some properties of the base manipulators depending
on their individual types. Most importantly, the function set classes are used
to set up relationships between values of visual controls on the manipulators
and values of the attributes (plugs) on nodes. In other words, these base
manipulator function set classes are responsible to communicate with attribute
(plugs) on nodes to set their values based on visual control values on
manipulator and also to set manipulator visual control values appropriately
with respect to the values of the attributes (plugs). In the following section,
we will take a closer look at how this communication works. We will use MFnDistanceManip
as an example.

i. Communication between
Manipulators and Plugs on Nodes

The communication between manipulators
and nodes can be done in one of two ways: simple one-to-one associations, or
through the use of more complex conversion functions.

The properties of the visual controls on a manipulator are
represented by float values or other types of data values, and these properties
are all registered on the manipulator with unique identifiers. The values are
called manip values, and the unique identifiers are manip indices. Some of the values
define the key property relate directly to an affordance of the manipulator.
For example, the MFnDistanceManip::distanceIndex() returns the index of the
manipulator property that relates directly to the distance of a DiscManip.  Other values do not relate to an affordance of
the manipulator, but provide important information on the position or
orientation of the manipulator such as MFnDistanceManip::startPointIndex() and MFnDistanceManip::directionIndex().

Manipplugs
One-to-One Mapping

As the name indicates, one-to-one mapping basically synchronizes
manip values with attribute/plug values directly. In all the function set classes
for Maya base manipulators, there is always one function that has some form of
“connectToXXXPlug”. This is the function to establish the one-to-one mapping
relationship between manip values and attribute/plug values. In other words,
the manip value equals to the attribute/plug value, and vice versa.

In the case of MFnDistanceManip, the function
MFnDistanceManip::connectToDistancePlug() sets up the one-to-one relationship
between the manip value of distance manip and the corresponding “distance” attribute/plug
on a node. The following code in devkit project footPrintManip demonstrates a
good example of using this one-to-one mapping.

MFnDistanceManip distanceManipFn (fDistanceManip) ;
MFnDependencyNode nodeFn (node) ;
MPlug sizePlug =nodeFn.findPlug ("size", &stat) ;
if ( stat != MStatus::kFailure ) {
distanceManipFn.connectToDistancePlug (sizePlug) ;
…
}

The
following graph demonstrates the one-to-one mapping relationship between the
distance manip value and the value of “size” plug on a node.

Onetoone
After you set up the one-to-one mapping relationship,
whenever the manip value changes, the corresponding plug/attribute value
changes with it and vice versa.

One-to-one mapping is a very straightforward and convenient
approach to set up relationship between manip values and attribute/plug values,
however it may not be acceptable for more complex requirements. For example, in the above case what if you want to set
up the manip value to be 5 * the value of plug “size”? There is no way you can
set this up with one-to-one mapping. Also, when you move the node, the distance
manipulator will appear not showing up at the center of the node, and there is
no way to make the manipulator move along with the position of the node. In
situations where the position of a manipulator needs to be affected by the
position of an object, or a group of manipulators need to move together in a
specific way; a new approach (conversion functions) needs to be implemented.

Conversion Functions

Conversion functions are used to set up more
complex relationship between manip values and attribute/plug values. As the
name indicates, they are responsible to convert manip values to attribute/plug
values, and vice versa. In C++, they are implemented as callback methods.

There are two
types of conversion callback functions: 

plugToManip conversion

A plugToManip conversion callback is used to convert
attribute/plug values to corresponding manip values. The implementation of
plugToManip conversion is achieved by calling MPxManipContainer::addPlugToManipConversionCallback().
Using manipulator container is the recommended approach to create a custom
manipulator from May base manipulators. In the following section “Manipulator
Containers”, all aspects of using MPxManipContainer class will be discussed in
detail.

As discussed earlier, every property of visual
controls has a corresponding index registered within the manipulator. In order
to set up plugToManip conversion relationship, when MPxManipContainer::addPlugToManipConversionCallback()
is called, the index of the manip value needs to be passed in. In every base
manipulator function set class, there are functions to retrieve different
indices for individual properties.

The following code in devkit project footPrintManip
illustrates how to retrieve index of the starting point of the manipulator and
pass it into addPlugToManipConversionCallback function call. The second
parameter of this function call is the callback conversion function, which is
responsible to calculate the manip value of this starting point based on some
attribute/plug values. 

unsigned startPointIndex =distanceManipFn.startPointIndex () ;
addPlugToManipConversionCallback (startPointIndex, (plugToManipConversionCallback)&footPrintLocatorManip::startPointCallback) ;

The plugToManip conversion callback is responsible to
access attribute/plug values, calculate manip values based on custom algorithm
and return them. In this specific case, the callback function doesn’t request
attribute/plug values, instead, it retrieves the node translation in world
space and return this value to Maya. By doing so, it actually sets the start
point manip value to be the same as the node translation. The outcome of this
setting is that whenever the node moves, the manipulator moves along with it
and always appears in the center of the node.

MManipData footPrintLocatorManip::startPointCallback (unsigned index) const {
MFnNumericData numData ;
MObject numDataObj =numData.create (MFnNumericData::k3Double) ;
MVector vec =nodeTranslation () ;
numData.setData (vec.x, vec.y, vec.z) ;
return (MManipData (numDataObj)) ;
}

DistanceManip

One thing to note is that the conversion callback returns
a data type called MManipData. By returning this to Maya, the conversion
callback sets the manip value to what the MManipData represents. MManipData can
represent data that is either simple or complex. When it comes to complex data
types (such as such as matrices, curves, and arrays of data), MFnData or its
derived classes need to be used to create the data.

manipToPlug conversion

A manipToPlug conversion
callback is used to convert manip values to attribute/plug values. The
implementation of manipToPlug conversion is achieved by calling MPxManipContainer::addManipToPlugConversionCallback().The
corresponding plug needs to be passed in as the first parameter because the
callback needs to set attribute/plug values, it needs to know which
attribute/plug values to change.

In general, manipToPlug conversions are less
commonly used and there are several examples in the devkit. The following code
from rotateManip example project demonstrates using this technique with the
node’s “rotate” plug passed in.

MFnDependencyNode nodeFn (node) ;
MPlug rPlug =nodeFn.findPlug ("rotate", &stat) ;
…
rotatePlugIndex =addManipToPlugConversionCallback (rPlug, (manipToPlugConversionCallback)&exampleRotateManip::rotationChangedCallback) ;

The addManipToPlugConversionCallback function call returns
the index (“rotatePlugIndex”) to identify which plug has been registered with
this callback. When the actual callback function is invoked, the index of the
plug value to be calculated will be passed in as the parameter of the function
call. The callback function then needs to distinguish if the passed-in value is
the index that is registered for this callback. If there is more than one plug
registered with this callback, a condition statement can be used to distinguish
different passed-in plug index and calculate corresponding manip values.  In the rotationChangedCallback() below,  the passed-in “index” is compared with
“rotatePlugIndex”. If they are equal, getConverterManipValue() is called with rotate
manip index to retrieve rotate manip value. By returning the rotate manip value
to Maya as a MManipData object, this callback function is setting the rotate
plug value to be the same as the rotate manip value. Depending on your specific
requirement, more complex value relationship between rotate plug value and
rotate manip value can be set up with this conversion callback function.

MManipData exampleRotateManip::rotationChangedCallback (unsigned index) {
MObject obj =MObject::kNullObj ;
// If we entered the callback with an invalid index, print an error and
// return.  Since we registered the callback only for one plug, all
// invocations of the callback should be for that plug.
if ( index != rotatePlugIndex ) {
MGlobal::displayError ("Invalid index in rotation changed callback!") ;
// For invalid indices, return vector of 0's
MFnNumericData numericData ;
obj =numericData.create (MFnNumericData::k3Double) ;
numericData.setData (0.0, 0.0, 0.0) ;
return (obj);
}
MFnNumericData numericData ;
obj =numericData.create (MFnNumericData::k3Double) ;
MEulerRotation manipRotation ;
if ( !getConverterManipValue (rotateManip.rotationIndex (), manipRotation) ) {
MGlobal::displayError ("Error retrieving manip value") ;
numericData.setData (0.0, 0.0, 0.0) ;
} else {
numericData.setData (manipRotation.x, manipRotation.y, manipRotation.z) ;
}
return MManipData (obj) ;
}

RotateManip

For more complete and in-depth explanation, you can go to
Maya API documentationà“API
Guideӈ
“Manipulators”à
“Communication between manipulators and nodes”.

Tips:

  1. C++
    Conversion Functions vs. Python Conversion Functions
    When looking at the MPxManipContainer class
    documentation, people usually get confused by the many conversion and
    conversion callback function names.  Here
    is a clarification: If you are writing python plug-in,
    addManipToPlugConversion() and addPlugToManipConversion() (that are
    specifically designed for writing python plug-ins) are the only functions to
    register conversion callback functions. If you are writing C++ plug-ins, you
    can either use addPlugToManipConversionCallback()/addManipToPlugConversionCallback()
    functions or addManipToPlugConversion()/addPlugToManipConversion() functions to
    register conversion callback functions.
  2. Manip
    index vs. plug index
    Properties of visual controls have individual
    index registered within the manipulator. For example, MFnDistanceManip::startPointIndex()
    returns the index of the start point of the DistanceManip, MFnRotateManip::rotationIndex()
    returns the index of rotation manip Value of the manipulator, etc. In order to
    specify which manip value is set up with plugToManip conversion callback, manip
    index needs to used when addPlugToManipConversionCallback() or addPlugToManipConversion()
    is called.

    There is another concept of index called
    plug index that is returned by addManipToPlugConversionCallback() and
    addManipToPlugConversion() to identify which plug has been registered with
    manipToPlug conversion callback.  This
    index usually is saved as a member variable and whenever the callback function
    is invoked, it is used to compare with the requested-value plug index to decide
    what values need to return for the requested-value plug.

    Manip indexes also are used to retrieve manip
    values from getConverterManipValue() function call, and plug indexes are used
    to retrieve plug values from getConverterPlugValue() function call.

b. Manipulator Containers

It is recommended to create a manipulator
container and add one or more base manipulators to the container to compose a
new type of manipulator. It is also the most common way to create a custom
manipulator. This approach involves the following steps:

i. create a custom manip container class derived from MPxManipContainer

ii.  add base manipulators to the
manip container class

iii. define associations between the manipulator and the attribute on
the nodes they affect

Let’s examine these steps in detail:

i. Create a custom
manip container class derived from MPxManipContainer

In order to create
a custom manipulator you need to derive from MPxManipContainer and implement
some common functions of MPxNode such as the create() and initialize() functions.
Also when registering this custom node with MFnPlugin::registerNode in
initializePlugin() function, MPxNode::kManipContainer needs to used.

ii. Add base
manipulators to the manip container class

Maya base manipulators are added into custom manipulator
container classes in the MPxManipContainer::createChildren() function. This
method is usually called by Maya after this custom manip container class is set
up. MPxManipContainer class provides a set of member functions to add
individual Maya base manipulators, most of them named like this “addXXXManip”
with XXX being the manip name. The function returns an MDagPath object
representing the created base manipulator, which you can then use the corresponding
function set class to operate with later.  Here is an example in the devkit project
footPrintManip adding distance manip into a custom manip container:

MStatus footPrintLocatorManip::createChildren () {
MStatus stat =MStatus::kSuccess ;
MString manipName ("distanceManip") ;
MString distanceName ("distance") ;
MPoint startPoint (0.0, 0.0, 0.0) ;
MVector direction (0.0, 1.0, 0.0) ;
fDistanceManip =addDistanceManip (manipName,distanceName) ;
MFnDistanceManip distanceManipFn (fDistanceManip) ;
distanceManipFn.setStartPoint (startPoint) ;
distanceManipFn.setDirection (direction) ;
return (stat) ;
}

iii. Define
associations between the manipulator and the attribute on the nodes they affect

As its name indicates,
MPxManipContainer::connectToDependNode() method connects the manipulator to the
dependency node. It is also where the association is made between the
manipulator and the attribute(s)/plug(s) that it will communicate with. All the
operations to set up communication between manipulators and attribute(s)/plugs
(including one-on-one mapping and conversion functions) need to put into this
function call.

After mapping
relationships between manipulators and plugs are set up, there are two
additional methods that need to be called. The methods are
MPxManipContainer::finishAddingManips() and MPxManipContainer::connectToDependNode()
and must be called in that order. Also, MPxManipContainer::finishAddingManips()
must be called after all calls to connect to plugs have been made. MPxManipContainer::finishAddingManips
must be called only once. Devkit sample footPrintManip demonstrates the
following operations: creating a distance manip; applying MFnDistanceManip
function set class on the created distance manip; setting up one-on-one mapping
relationship between plug “size” and distance manip value; and setting up
plugToManip conversion to set up manip start point position.

MStatus footPrintLocatorManip::connectToDependNode (const MObject &node) {
MStatus stat ;
// Get the DAG path
MFnDagNode dagNodeFn (node) ;
dagNodeFn.getPath (fNodePath) ;
// Connect the plugs
//
MFnDistanceManip distanceManipFn (fDistanceManip) ;
MFnDependencyNode nodeFn (node) ;
MPlug sizePlug =nodeFn.findPlug ("size", &stat) ;
if ( stat != MStatus::kFailure ) {
distanceManipFn.connectToDistancePlug (sizePlug) ;
unsigned startPointIndex =distanceManipFn.startPointIndex () ;
addPlugToManipConversionCallback (startPointIndex, (plugToManipConversionCallback)&footPrintLocatorManip::startPointCallback) ;
finishAddingManips () ;
MPxManipContainer::connectToDependNode (node) ;
}
return (stat) ;
}

c. Custom Drawing of Custom Manipulators

Constructing a manipulator by creating a manipulator container and
adding one or more base manipulators to it is the most common approach to
create a custom manipulator, and Maya base manipulators provide a great variety
of possibilities of custom manipulator. However there are still situations
where you want to have more customized control over different perspectives of a
manipulator. For example, with the above approach, the shape and drawing of
custom manipulators is limited/restricted to Maya base manipulators because MPxManipContainer::draw()
function only provides a M3dView parameter to add supplementary drawing onto
the manipulator. The custom manipulator’s shape is already decided by the
combination of Maya base manipulator shapes that are added into the current manip
container. Also, you don’t have control over the selection
on the manipulator components and the selecting behavior when user clicks on
the manipulator that is defined by Maya base manipulators internal
implementation.

A new manipulator class MPxManipulatorNode was introduced
with Maya 2009 to provide a new way to implement custom manipulators with
custom OpenGL drawing code. It offers developers with options for selecting
manipulator components (activating different handles) on custom manipulators.
This class can either work alone or work with the MPxManipContainer class.

i. Custom Drawing

MPxManipulatorNode::draw() method
is overridden to implement custom drawing of this custom manipulator. In OpenGL,
drawing and picking is done together. This method is also responsible to set up
for selection. Several functions are important to use within this method:

glFirstHandle(): When drawing a OpenGL pickable component,  a name uniquely representing the OpenGL
component must be set. This method returns the unsigned int value which
represents the first available OpenGL handle to use. When one OpenGL component
drawing is finished, the int value can be added by 1 to represent the next OpenGL
component.

colorAndName(): This
method is used to set the color of the OpenGL component that is being drawn
next. It is also used to set the OpenGL name of the component so that picking
can be supported. The second parameter “glName” is the OpenGL name that
represents the component you are going to draw in the code next. The last
parameter “colorIndex” presents the color you want to use on the GL
component.  In MPxManipulatorNode class,
there are several color methods which return color indexes that Maya use to
allow custom manipulators to have a similar look. 

void triadScaleManip::draw (M3dView &view, const MDagPath &path, M3dView::DisplayStyle style, M3dView::DisplayStatus status) {
……
// Begin the drawing
view.beginGL () ;
// Place before you draw the manipulator component that can
// be pickable.
MGLuint glPickableItem ;
glFirstHandle (glPickableItem) ;
// Top
topName =glPickableItem ;
colorAndName (view, glPickableItem, true, mainColor ()) ;
gGLFT->glBegin (GL_LINES) ;
gGLFT->glVertex3fv (tl) ;
gGLFT->glVertex3fv (tr) ;
gGLFT->glEnd () ;
// Right
glPickableItem++;
rightName =glPickableItem ;
colorAndName (view, glPickableItem, true, mainColor ()) ;
gGLFT->glBegin (GL_LINES) ;
gGLFT->glVertex3fv (tr) ;
gGLFT->glVertex3fv (br) ;
gGLFT->glEnd () ;
// ...
// End the drawing
view.endGL () ;
}

ii.
Updating Attributes/Plugs Values on Nodes

There are 2 ways for MPxManipulatorNode to
update attributes/plugs values:

  1. Update attribute/plugs values directly by
    implementing functions related with mouse movement event:
    In MPxManipulator class there are three functions to use to watch for
    mouse movement events:
      MPxManipulatorNode::doPress (M3dView &view);
      MPxManipulatorNode::doDrag (M3dView &view);
      MPxManipulatorNode::doRelease (M3dView &view);
    These are called when the manipulator receives
    a corresponding mouse event including mouse
    down, mouse drag and mouse release. Algorithms can be implemented to update
    attributes/plugs values when these functions are called. For example, when
    doPress() gets called, record mouse position as original position; when
    doDrag() gets called, record mouse movement (direction and distance); when
    doRelease() gets called, calculate the actual mouse movement values and apply
    to node attributes/plugs.
  2. Connect to a dependent node:
    In order to
    connect to a dependent node, the following steps need to be taken:
      Call add*Value() in the postConstructor() of the manipulator node;
      Call conectPlugToValue() in connectToDependNode();
      Call set*Value() in one of the do*() functions;
    In order to connect attributes/plugs directly to
    manipulator values, manipulator values need to be created first on the
    manipulator. There are several add*Value() to add manipulator values of
    different types. After the manipulator values are created and added onto the
    manipulator, one-to-one mapping relationship can be achieved by calling
    connectPlugToValue() method within MPxManipulatorNode::connectToDependNode(). In
    one of the do*() function, which gets triggered when the manipulator is
    receiving corresponding mouse event, calling set*Value() will set the
    corresponding manipulator value that will consequently set the attributes/plugs
    value which is connected to current manipulator value.

V. Apply Manipulators

After a
custom manipulator is created, there are two ways to apply the manipulator to
nodes. The manipulator can either be applied to work only on one specific type
of node, or it can be added to a custom context/tool so that it is applicable
to any node.

a. Apply on one type of node

In order to make a custom
manipulator work on one specific type of node, there are several steps to set
up the relationship between custom manipulator and node type:

i. The manipulator must be named
after the node type appended with “Manip”: For example, if the node type name
is “myNode”, the custom manipulator name should be “myNodeManip”.

ii. Register the node type in the
manipulator connect table in node class initialization. In the node class initialize()
function, add one function call:

MPxManipContainer::addToManipConnectTable(id);  where “id” is the custom node type ID.

After you select the custom node in
the Maya scene and go to “Show Manipulator Tool”, the custom manipulator will show
up on the custom node. 

b. Apply in user-defined context

Another approach to apply custom
manipulators is adding them into a custom context. Custom contexts are proxy
classes where you can implement interactive tools based on mouse events occurring within an interactive panel in Maya.

In MPxContext class, there are two functions to
work with manipulators: addManipulators() and deleteManipulators().  MPxContext::addManipulators() takes a
parameter of MObject that represents a custom manipulator. MPxManipContainer::newManipulator()
or MPxManipulatorNode::newManipulator() can be used to create a custom
manipulator that is passed into MPxContext::addManipulator() call.

In addition, MPxSelectionContext::toolOnSetup()
and MPxContext::toolOffCleanup() should be overridden so that toolOnSetup adds
a callback to update custom manipulators to work on different objects, and
toolOffCleanup removes the callback when the current tool is inactive.

VI. Manipulator Examples

Here is the list of manipulator examples available in the Maya
installation devkit folder:

customAttrManip:

This plug-in
demonstrates how to create user-defined manipulators on custom attributes of
nodes within a user-defined context. This custom manipulator is called customAttrManip
and is composed of three DistanceManips.

customTriadManip:

This plug-in
demonstrates how to create user-defined manipulators from a user-defined
context and apply the manipulator to custom attributes defined on a custom
transform node.  The custom transform
node has three custom attributes define, TnoiseX, TnoiseY, and TnoiseZ.  Three distance base manips are defined as the
custom manipulator and get attached to the noise attributes when selected. The
attachment of the manipulator is performed by an event callback that is registered
for toolOnSetup and SelectionChanged events. 

moveManip:

It shows how
to create a manipulator from a context. The user-defined manipulator in
moveToolManip.cpp is called moveManip and consists of two base manipulators: a
FreePointTriadManip and a DistanceManip.

footPrintManip:

This plug-in example demonstrates how to use the
Show Manipulator Tool with a user-defined node and a user-defined manipulator.
The user-defined manipulator consists of a DistanceManip. It also demonstrates
how to write plugToManip conversion callback functions so that DistanceManip is
always following the location of the foot.

rotateManip:

This plug-in demonstrates the different modes of
the rotate base manipulator. The user-defined manipulator in rotateManip.cpp
consists of a rotate base manipulator and a state base manipulator. The state
manipulator is used to control the mode of the rotate manipulator: object mode,
world space mode, gimbal mode, and object mode with snapping.

componentScaleManip:

This plug-in demonstrates how to use the scale base
manipulator and also demonstrates a method for manipulating components. The
plug-in componentScaleManip.cpp consists of a scale base manipulator. The
manipulator works by attaching manipToPlug conversion callbacks for every
selected vertex. The conversion function computes the new vertex positions
using stored initial vertex positions and the scale manipValue.

surfaceBumpManip:

This plug-in example demonstrates how the
pointOnSurface base manipulator can be used to modify vertices close to the
param manipValue. The plug-in uses a manipToPlug conversion function as a
callback to update vertex positions on the NURBS surface.

swissArmyManip:

This plug-in is an
example of a user-defined manipulator, that is comprised of a variety of the
base manipulators: CircleSweepManip, DirectionManip, DiscManip, DistanceManip,
FreePointTriadManip, StateManip, ToggleManip, RotateManip, ScaleManip.

lineManip:

This example
demonstrates how to use the MPxManipulatorNode class along with a command to
create a user defined manipulator.  The
manipulator created is a simple line which is an OpenGL pickable
component.  As you move the pickable
component, selected transforms have their scale attribute modified. The line’s
movements are restricted in a plane. A corresponding command is used to create
and delete the manipulator node and to support undo/redo etc.

squareScaleManip:

This example demonstrates how to use the
MPxManipulatorNode class along with a command to create a user defined
manipulator.  The manipulator created is
a simple square with the 4 sides as OpenGL pickable components.  As you move the pickable component, selected
transforms have their scale attribute modified. A corresponding command is used
to create and delete the manipulator node and to support undo/redo etc.

Find here a conpanion sample for this post.

This article was written by Naiqi Weng who is a member of the ADN team working with me at Autodesk. Naiqi is an expert on the Maya and MotionBuilder API.


Comments

2 responses to “Maya Custom Manipulators”

  1. Great write-up on manips. Appreciate all the ‘behind the scenes’ info.

  2. Jens Jebens Avatar
    Jens Jebens

    Thank you for posting this! Appreciated as well ;)

Leave a Reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading