Maya API How-To #05

Back · Previous · Next Maya

How do I "constrain" my custom manipulator so it moves with its parent node?

Method #1: For function sets with a .setStartPoint() method (e.g. MFnDistanceManip)

First, declare a ‘MDagPath’ and ‘MPoint’ object within your manipulator class.

We'll also be using a utility function ‘nodeTranslation()’, which we'll declare here as well.

class myManip : public MPxManipContainer
{
  // ...

private:
        MDagPath        fNodePath;
        MPoint          nodePos;

        MVector         nodeTranslation() const;

  // ...

};

Within the ‘connectToDependNode()’ method, set fNodePath to the DAG path for your manipulator. Use fNodePath in the ‘nodeTranslation()’ function to determine the offset required; assign this offset to the nodePos member variable.

MStatus myManip::connectToDependNode(const MObject &node)
{
  // ...

  // Get the DAG path for this manipulator
  MFnDagNode dagNodeFn(node);
  dagNodeFn.getPath(fNodePath);

  // Set member variable to node position
  nodePos =  nodeTranslation();

  MPlug myPlug = nodeFn.findPlug("myPlug", &stat);

  if ( stat != MS::kFailure )
  {
    MFnDistanceManip distanceManipFn( fDistanceManip );
    distanceManipFn.connectToDistancePlug( myPlug );

    // Set the "home" pos for this manipulator
    distanceManipFn.setStartPoint( startPoint );
  }

  finishAddingManips();
  MPxManipContainer::connectToDependNode(node);

  // ...
}

The ‘nodeTranslation()’ method returns the world-space translation of the manipulator.

MVector myManip::nodeTranslation() const
{
  MFnDagNode dagFn(fNodePath);
  MDagPath path;
  dagFn.getPath(path);
  path.pop();  // pop from the shape to the transform
  MFnTransform transformFn(path);
  return transformFn.translation(MSpace::kWorld);
}

If you are providing any custom GL calls for your manipulator, you can use the stored nodePos value. Add its matrix to any text or GL draw calls.

void myManip::draw(M3dView & view,
           const MDagPath & path,
           M3dView::DisplayStyle style,
           M3dView::DisplayStatus status)
{
  // ...

  view.beginGL();
  glMatrixMode(GL_MODELVIEW);

  glPushMatrix();

  // Move manip to node's position
  glTranslated( nodePos.x, nodePos.y, nodePos.z );
  // convert to Y-up
  glRotatef(90.0f, 1.0f, 0.0f, 0.0f);

  glBegin( GL_LINE_LOOP );

    // Draw a 1.0-unit circle around node/manip
    for ( r = 0; r < SEGMENTS; r++ )
    {
      double angle = ( 2 * PI ) / SEGMENTS * r;
      GLdouble radiusX = cos(angle);
      GLdouble radiusY = sin(angle);

      glVertex2d( radiusX, radiusY );
    }

  glEnd();

  MString manipText( "Pick me!" );
  MPoint textPos( 0.0, 0.0, 0.0 );

  // offset text by node's position
  textPos += nodePos;
  view.drawText( manipText, textPos, M3dView::kRight );

  glPopMatrix();

  view.endGL();

  // ...
}

Method #2: For function sets without a .setStartPoint() method (e.g. MFnStateManip)

This technique is almost identical to the one above -- we just have to use a plugToManipConversionCallback to get the "startPoint" (or equivalent) is all.

The declaration of your manipulator will still include a MDagPath object and nodeTranslation() method, and also the callback (as exemplified in footPrintManip.cpp in the DevKit):

class myManip : public MPxManipContainer
{
  // ...

private:
        MDagPath        fNodePath;

        MManipData      startPointCallback( unsigned index ) const;
        MVector         nodeTranslation() const;

  // ...

};

The ‘startPointCallback()’ method uses ‘nodeTranslation()’ to pass the world-space offset of the node to the manipulators startPoint.

MManipData containerManip::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);
}

MVector myManip::nodeTranslation() const
{
  MFnDagNode dagFn(fNodePath);
  MDagPath path;
  dagFn.getPath(path);
  path.pop();  // pop from the shape to the transform
  MFnTransform transformFn(path);
  return transformFn.translation(MSpace::kWorld);
}

Within the ‘connectToDependNode()’ method, set fNodePath to the DAG path for your manipulator. Also required in this case is to set up the plugToManipConversionCallback for the manipulator's positionIndex.

MStatus myManip::connectToDependNode(const MObject &node)
{
  // ...

  // Get the DAG path for this manipulator
  MFnDagNode dagNodeFn(node);
  dagNodeFn.getPath(fNodePath);

  MFnStateManip containerStateManipFn( fContainerStateManip );
  positionIndex = containerStateManipFn.positionIndex();

  // Use the callback to set the startPoint for this manipulator
  addPlugToManipConversionCallback(positionIndex, (plugToManipConversionCallback) startPointCallback);

  // ...

  finishAddingManips();
  MPxManipContainer::connectToDependNode(node);

  // ...
}

Note: I've had problems (read: Maya crashes) initiating multiple startPointCallbacks for multiple manipulators in the same MPxManipContainer. I know it's possible, because A|w does so in their examples. I think the crashes were due to the fact that the manipulators were all of the same type (in my case, MFnDistanceManip). This was the reason I started using the "direct" method of using setStartPoint() instead of the callback. Your mileage may vary.


Related How-To's