Read this post entirely before you start hacking away in the Maya API - It will save you a lot of time and effort in the long run i can assure you.
GENERAL TIPS
* Check ALL MStatus's returned from the Maya API. I know you might think it's boring, but maya does have a habit of failing silently. If you don't get into the habit of checking for errors, you might not notice that one has occurred! It will speed up development a lot if you get into the habit of checking everything.
* You must write code in the API that works in the way Maya works. Trying to make the API work in the way you want will simply fail to do anything useful.
* Use Maya 2005 or newer. You can't use other compilers. Use gcc under linux/mac
* mel IS more powerful than the C++ api. Hard to believe but true.
* The API is not a C++ version of mel. They both have very different uses.
* Any plugin will consist of a mix of mel and the API. You simply can't avoid using mel.
* IF you don't know how to use Maya, learn to! It's the only way to make sure that your tools will work in a consistent way with the rest of Maya.
* If you have code in mel that you want to port to C++ for speed purposes, try turning off undo, make sure you don't use print() commands, and then see how fast it goes. You may find that you can speed the script up enough to avoid the API completely.
* Keep your nodes small and atomic. Swiss army knife type nodes will not be efficient to evaluate within the DG, so break complex evaluations into multiple nodes.
* Try to use pre-existing nodes if possible, and only add new nodes for something you definately can't do already in the API.
* Understanding the difference between a DAG node, and a DG node is vital if you work within the API. See this post for a description. http://www.highend3d.com/boards/index.php?showtopic=242681
* Only use maya ascii files. For a start they work with version control software, but more importantly you stand a chance of being able to load the files if/when you change plug-in attributes. Maya loads data from the binary file as a raw data block. If you change attributes, or the order in which attributes are defined - you will end up loading corrupted data. Since maya ascii files resolve all attributes by name, you can normally do some minimal modifications and still ensure they load.
FORUM TIPS
* You may find that some questions in this forum recieve the answer "use mel". We are not trying to be annoying when we say that, we are just being very truthful. The question you have asked is either something that is impossible to do in the API, or it is something for which the API is entirely unsuitable (see code example further down this thread). If you recieve the answer "use mel", then please do use mel! A lot of people can get upset when we say that, but honestly the API is nowhere near as powerful as mel, and 99% of the time things should have been done in mel to begin with.
* You may find that people don't have an answer to your question. This is quite common since the API is fairly large, and you can't expect people to know all of it. In those situations, it's best to keep a thread updated with your progress since it forms a google-able record that will help others out in future. Ultimately, the knowledge that we have acquired over the years are down to people doing just that...
* Do not PM me, other moderators, or other forums members API questions. They will not be answered. If you have a problem, the correct place to ask is in the forum. As a rule we do not reply to PM'd questions because we like to keep a google-able record of questions asked, and their eventual answers in the forum.
* Do not PM me, or other forums members a link to a post you have posted in the forum. The moderators here already receive an e-mail for each new topic posted, and we do look at every single question asked. If we can give an answer, we will. Sending a PM with a link to a forum post in it, only serves to annoy us.
EXPORTERS
If you are simply wanting to write an exporter for Maya, you are in luck - it's one of the easiest things to do in the API, and it will give you a nice overview of what maya's all about. I wrote a doc a while ago, that can be found here. It was written for maya 6.5, but should send you in the right direction. (To get the examples to compile on Maya 8.0 or newer, make sure you add the define REQUIRE_IOSTREAM to your pre-processor settings).
IMPORTERS
Writing an importer for maya is a little bit more involved, but not horrific. Typically you'll be looking for a MFn#### class, where #### is the thing of importance (i.e. Transform, Mesh etc). Each MFn class will have a create method, then it's just a case of doing the opposite of this here. It's worth noting, that you may not be able to do everything you need easily within the API itself, so expect to drop to mel occasionally.
NODES
It is worth starting to learn the API by creating your own custom node. Nodes are pivotal to understanding how maya works, and in reality, any task you want to do, is probably best done in a node (and not a command as you may think). Again, i have a few examples to get you started on my website, so take a look here for some examples http://robthebloke.org - the david gould books also give some explanation on how to get started. The things you really want for a new node are:
- the C++ node code itself, MPxNode, MpxEmitter etc
- the AETemplate to define the attribute editor GUI (mel)
- some mel command to create the node and connect it up within the DG (mel or MPxCommand)
- a custom menu item with options box (store options using optionVar)
LOCATOR NODES
These are fairly trivial to do, they may be used for custom markers in the scene etc. There are examples on my site, as well as the david gould books.
CUSTOM POLY/NURBS PRIMITIVES
Want to create a custom shape similar to the default polyCube/polySPhere etc? Use a node! You can create a output attribute of type MPxData::kMesh, or kNurbsSurface etc which will be filled within your compute method.
MODIFYING GEOMETRY OR OTHER DATA
Don't go and attempt to delete a face from a mesh, or add some verts to a mesh, or infact anything that changes data in Maya. There is a 99% chance it wont work. All data modifications should be done using nodes. Consider this example, if you create a poly cube, then from an MPxCommand, you go and delete a face using MFnMesh. All that will happen is that the next DG evaluation will simply overwrite your modified mesh. You actualy need to create a node that modifies the data as it is flowing through the DG!.
You can use the polyModifier examples found in the Maya SDK as a starting point.
COMMANDS
So, you want to convert a slow mel command to run faster in the API? First try turning off the undo queue, make sure you aren't printing anything into the script editor, and then see how fast it goes. That may solve your problem.
The only real reason to write an MPxCommand derived class is because you want to interface with some C++ code or libraries. Chances are, all other uses are a waste of time, and you should be using mel.
GUI
Foget about it. The API does not do GUI, you will have to use mel. The only exception is a custom context which you can write in C++ to handle mouse input for you. You can hoewever use a mel command such as scriptCtx to do this (though it is not as flexible as the C++ version).
STAND ALONE APPS
you might want to use MLibrary to get maya working without the GUI, which is entirely possible without too much effort. Before you do that however, will mayabatch with a mel script be able to do the job quicker? As long as you avoid using all mel's GUI commands, you should be laughing.
MPxCommand
This may be ok to use as a playground to learn the API, but 99 times out of a 100, you'll get nowhere. I strongly suggest that you'd be better of using mel for all commands, since you do not have to worry about implementing undo!
MEL IS MORE POWERFUL THAN THE API
Whilst the maya API is very powerful, it is also very easy to misunderstand when you first start out with it. The idea of any plugin should be to augment mel, not replace it in C++. The following example should hopefully demonstrate that mel is simple, but very flexible:
CODE
$ret = polyCube -n "hello"
;
expression -o $ret[0] -s "tx = time" -ae 1 -uc all;
All that code does is stick a basically connect a couple of node together. The commands polyCube and expression are already implemented in mel, and they are both very powerful, and flexible extremely. To replicate that code EXACTLY in the API, you'll need to worry about the currently set options, you'll need to make sure undo/redo works, you'll need to worry about material assignments, etc etc. In short, the amount of work is actually non trivial. So, the C++ version of those two mel lines is actually 350 lines of code, to simply manipulate a few basic nodes! Extend the idea to say a hundred nodes, your 200 lines of mel would become 35,000 lines of C++!!!
So, try to use mel otherwise you'll end up wasting a lot of time, much like this.....
CODE
// to execute once compiled, call this in mel:
//
// wasteOfTime "hello";
//
// wasteOfTime.h
#ifndef __WHY_MPX_COMMAND_IS_A_WASTE_OF_TIME__H__
#define __WHY_MPX_COMMAND_IS_A_WASTE_OF_TIME__H__
#ifdef WIN32
#define NT_PLUGIN
#define REQUIRE_IOSTREAM
#pragma once
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define CHECK_STAT(X) \
if( (X) != MS::kSuccess) { \
std::cout << __FILE__ << ":" << __LINE__ << std::endl; \
MGlobal::displayError(status.errorString()); \
return (X); \
}
//---------------------------------------------------------------------------------
class WasteOfTime
: public MPxCommand
{
public:
bool isUndoable() const;
MStatus doIt(const MArgList& args);
MStatus redoIt();
MStatus undoIt();
static MSyntax newSyntax();
static void* creator();
private:
MDagModifier mDagMod;
MDagPath mPath;
MObject mExpression;
};
#endif
// wasteOfTime.cpp
CODE
#include "WasteOfTime.h"
//---------------------------------------------------------------------------------
bool WasteOfTime::isUndoable() const
{
return true;
}
//---------------------------------------------------------------------------------
void* WasteOfTime::creator()
{
return new WasteOfTime;
}
//---------------------------------------------------------------------------------
MSyntax WasteOfTime::newSyntax()
{
MSyntax syn;
syn.addArg(MSyntax::kString);
return syn;
}
//---------------------------------------------------------------------------------
MStatus WasteOfTime::doIt(const MArgList& args)
{
MStatus status = MS::kFailure;
// verify args
MArgDatabase db(syntax(),args,&status);
CHECK_STAT(status);
// grab name arg
MString name;
status = db.getCommandArgument(0,name);
CHECK_STAT(status);
// avoid annoying creatNode class with base
MDGModifier& mod = mDagMod;
// create nodes
MObject oT = mDagMod.createNode("transform",MObject::kNullObj,&status);
CHECK_STAT(status);
MObject oM = mDagMod.createNode("mesh",oT,&status);
CHECK_STAT(status);
MObject oP = mod.createNode("polyCube",&status);
CHECK_STAT(status);
// rename based on args
status = mDagMod.renameNode(oM,name + "Shape");
CHECK_STAT(status);
status = mDagMod.renameNode(oT,name);
CHECK_STAT(status);
status = mDagMod.renameNode(oP,name + "Creator");
CHECK_STAT(status);
// connect polycube to mesh
{
MFnDependencyNode fnPC(oP,&status);
CHECK_STAT(status);
MFnDependencyNode fnPM(oM,&status);
CHECK_STAT(status);
MPlug plgI = fnPM.findPlug("i",&status);
CHECK_STAT(status);
MPlug plgO = fnPC.findPlug("out",&status);
CHECK_STAT(status);
status = mDagMod.connect(plgO,plgI);
CHECK_STAT(status);
}
// grab current options for polyCube, and set on node
{
double w=0;
double h=0;
double d=0;
int sdw=0;
int sdh=0;
int sdd=0;
int tex=0;
int axis=0;
status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeWidth\""),w,false,false);
CHECK_STAT(status);
status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeHeight\""),h,false,false);
CHECK_STAT(status);
status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeDepth\""),d,false,false);
CHECK_STAT(status);
status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeSX\""),sdw,false,false);
CHECK_STAT(status);
status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeSY\""),sdh,false,false);
CHECK_STAT(status);
status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeSZ\""),sdd,false,false);
CHECK_STAT(status);
status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeTexture\""),tex,false,false);
CHECK_STAT(status);
status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeAxis\""),axis,false,false);
CHECK_STAT(status);
MPlug plg;
MFnDependencyNode fnP(oP,&status);
plg = fnP.findPlug("w",&status);
CHECK_STAT(status);
status = plg.setValue(w);
CHECK_STAT(status);
plg = fnP.findPlug("h",&status);
CHECK_STAT(status);
status = plg.setValue(h);
CHECK_STAT(status);
plg = fnP.findPlug("d",&status);
CHECK_STAT(status);
status = plg.setValue(d);
CHECK_STAT(status);
plg = fnP.findPlug("sw",&status);
CHECK_STAT(status);
status = plg.setValue(sdw);
CHECK_STAT(status);
plg = fnP.findPlug("sh",&status);
CHECK_STAT(status);
status = plg.setValue(sdh);
CHECK_STAT(status);
plg = fnP.findPlug("sd",&status);
CHECK_STAT(status);
status = plg.setValue(sdd);
CHECK_STAT(status);
plg = fnP.findPlug("tx",&status);
CHECK_STAT(status);
status = plg.setValue(tex);
CHECK_STAT(status);
MPlug axx = fnP.findPlug("axx",&status);
CHECK_STAT(status);
MPlug axy = fnP.findPlug("axy",&status);
CHECK_STAT(status);
MPlug axz = fnP.findPlug("axz",&status);
CHECK_STAT(status);
switch (axis)
{
case 0:
status = axx.setValue(1.0);
CHECK_STAT(status);
status = axy.setValue(0.0);
CHECK_STAT(status);
status = axz.setValue(0.0);
CHECK_STAT(status);
break;
case 1:
status = axx.setValue(0.0);
CHECK_STAT(status);
status = axy.setValue(1.0);
CHECK_STAT(status);
status = axz.setValue(0.0);
CHECK_STAT(status);
break;
default:
case 2:
status = axx.setValue(0.0);
CHECK_STAT(status);
status = axy.setValue(0.0);
CHECK_STAT(status);
status = axz.setValue(1.0);
CHECK_STAT(status);
break;
}
}
// now assign the mesh to the default shading group.
{
MFnDagNode fnM(oM,&status);
CHECK_STAT(status);
status = fnM.getPath(mPath);
CHECK_STAT(status);
}
return redoIt();
}
//---------------------------------------------------------------------------------
MStatus WasteOfTime::redoIt()
{
MStatus status = mDagMod.doIt();
CHECK_STAT(status);
status = MGlobal::selectByName("initialShadingGroup",MGlobal::kReplaceList);
CHECK_STAT(status);
MSelectionList sl;
status = MGlobal::getActiveSelectionList(sl);
CHECK_STAT(status);
MObject o;
status = sl.getDependNode(0,o);
CHECK_STAT(status);
MFnSet fnS(o,&status);
CHECK_STAT(status);
status = fnS.addMember(mPath,MObject::kNullObj);
CHECK_STAT(status);
{
MDagPath p = mPath;
status = p.pop();
CHECK_STAT(status);
MFnExpression fnE;
MObject oT = p.node();
CHECK_STAT(status);
mExpression = fnE.create(MString("tx = time"),oT,&status);
CHECK_STAT(status);
}
return status;
}
//---------------------------------------------------------------------------------
MStatus WasteOfTime::undoIt()
{
MStatus status;
status = MGlobal::selectByName("initialShadingGroup",MGlobal::kReplaceList);
CHECK_STAT(status);
MSelectionList sl;
status = MGlobal::getActiveSelectionList(sl);
CHECK_STAT(status);
MObject o;
status = sl.getDependNode(0,o);
CHECK_STAT(status);
MFnSet fnS(o,&status);
CHECK_STAT(status);
status = fnS.removeMember(mPath,MObject::kNullObj);
CHECK_STAT(status);
status = MGlobal::deleteNode(mExpression);
CHECK_STAT(status);
return mDagMod.undoIt();
}
#include
//---------------------------------------------------------------------------------
#ifdef WIN32
#pragma comment(lib,"Foundation.lib")
#pragma comment(lib,"OpenMaya.lib")
#endif
//---------------------------------------------------------------------------------
#ifdef WIN32
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif
//---------------------------------------------------------------------------------
EXPORT MStatus initializePlugin(MObject obj)
{
MStatus status;
MFnPlugin fnPlugin( obj, "Rob Bateman", "1.0", "Any");
status = fnPlugin.registerCommand( "wasteOfTime",WasteOfTime::creator,WasteOfTime::newSyntax);
CHECK_STAT(status);
return status;
}
//---------------------------------------------------------------------------------
EXPORT MStatus uninitializePlugin(MObject obj)
{
MStatus status;
MFnPlugin fnPlugin(obj);
status = fnPlugin.deregisterCommand( "wasteOfTime" );
CHECK_STAT(status);
return status;
}