You can create a manipulator container programmatically and add one or more base manipulators to the container. This approach involves the following steps:
The parent class of user-defined container manipulators is MPxManipContainer. There are a number of methods in MPxManipContainer that allow you to add a variety of base manipulators to the container. You also need to implement a number of methods in the custom manipulator.
You can also override the draw method to customize the way your container manipulator is drawn.
The creator method needs to return a new instance of the manipulator, and it is registered in the initializePlugin() function with a call to the MFnPlugin::registerNode() method.
The initialize method performs any necessary initializations for the manipulator as well as calling the method in the parent class MPxManipContainer::initialize(). Similar to the creator method, the initialize method is static and registered rather than derived.
The base manipulators are added to the manipulator container classes using the MPxManipContainer::createChildren() method.
For example, in the moveManip::createChildren() method:
MStatus moveManip::createChildren() { ... fDistanceManip = addDistanceManip(manipName, distanceName); fFreePointManip = addFreePointTriadManip(pointManipName, pointName); ... }
This method is usually called after the custom manipulator container class is set up. The MPxManipContainer class provides a set of member functions to add individual base manipulators, most of them are named as addXYZManip, where XYZ represents the manipulator name. The function returns an MDagPath object representing the created base manipulator. Following is an example from the footPrintManip plug-in for adding distance manipulator to a custom manipulator 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); }
The connectToDependNode() method is where the association is made between the manipulator and the plug with which it will communicate. All the operations to set up communication between manipulators and plugs (including one-on-one association and conversion functions) must be put in this method. After mapping relationships between manipulators and plugs are set, there are two additional methods to be called. These methods are MPxManipContainer::finishAddingManips() and MPxManipContainer::connectToDependNode(), and must be called in that order. It is important to note that MPxManipContainer::finishAddingManips() must be called after all calls to connect to plugs have been made. Furthermore, finishAddingManips() must be called only once.
For example, in the moveManip::connectToDependNode() method:
MStatus moveManip::connectToDependNode(const MObject &node) { ... distanceManipFn.connectToDistancePlug(syPlug); ... freePointTriadManipFn.connectToPointPlug(tPlug); ... finishAddingManips(); ... MPxManipContainer::connectToDependNode(node); ... }
The footPrintManip plug-in example demonstrates the following operations: creating a distance manipulator, applying MFnDistanceManip function set class on the created distance manipulator, setting up one-on-one association between plug and distance manipulator value, and setting up plugToManip conversion to set the manipulator 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); }
The draw method is an optional method which can be used to customize the drawing of a container manipulator. If you are overriding the draw method, you must first call MPxManipContainer::draw() to draw all the children.
For example, the moveManip::draw() method draws a label in addition to the base manipulators.
void moveManip::draw(M3dView &view, const MDagPath &path, M3dView::DisplayStyle style, M3dView::DispalyStatus status) { MPxManipContainer::draw(view, path, style, status); view.beginGL(); MPoint textPos(0, 0, 0); char str[100]; sprintf(str, "Stretch Me!"); MString distanceText(str); view.drawText(distanceText, textPos, M3dView::kLeft); view.endGL(); }
The shape and drawing of custom manipulators is restricted to base manipulators because the MPxManipContainer::draw() method only provides a M3dView parameter to add supplementary drawing to the manipulator. The custom manipulator’s shape is already decided by the combination of base manipulator shapes that are added to the current manipulator container. However, there are situations where you want to have more control over different perspectives of a manipulator. The MPxManipulatorNode class, which was introduced in Maya 2009, provides a new way to implement custom manipulators with custom OpenGL drawing code. It offers options for selecting components (activating different handles) in custom manipulators. This class can either work alone or work with the MPxManipContainer class.
The MPxManipulatorNode::draw() method is overridden to implement custom drawing. In OpenGL, drawing and picking are done together. There are several important methods like the following:
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(); }
You can use MPxManipulatorNode to update the plug values in the following ways:
To connect plugs directly to manipulator values, the manipulator values must be first created on the manipulator. There are several add*Value() methods to add manipulator values of different types. The one-on-one association can be achieved by calling connectPlugToValue() method within MPxManipulatorNode::connectToDependNode(). In one of the do*() functions, which is triggered when the manipulator is receiving the corresponding mouse event, calling set*Value() sets the corresponding manipulator value that consequently sets the plug value.
Because manipulator containers are derived from nodes, user-defined manipulators can be registered and deregistered like any other node, with the exception that the MPxNode::Type argument in MFnPlugin::registerNode() is set to MPxNode::kManipContainer instead of the default MPxNode::kDependNode.
For example, in moveToolManip.cpp:
MStatus initializePlugin(MObject obj) { ... plugin.registerNode("moveManip", moveManip::id, &moveManip::creator, &moveManip::initialize, MPxNode::kManipContainer); ... } MStatus uninitializePlugin(MObject obj) { ... plugin.deregisterNode(moveManip::id); ... }