事实上着就是MAYA4.5完全手册插件篇的内容

来源:互联网 发布:电大基地干部网络培训 编辑:程序博客网 时间:2024/05/16 10:34


不过着好象侵权了,因为ALIAS声明不得一任何方式传播该手册的部分或全部_炙墨追零

Maya不为插件提供二进制兼容性。每当发布新版本时,旧插件的源代码要重新编译。然而,我们的目标是保持源代码兼容性,在大多情况下,不需要修改源代码。也许我们以后会提供支持二进制兼容性的API。
IRIX,Windows,Linux和Mac OS X上的Maya API
Maya API是提供从内部访问Maya的C++ API,它由一系列与Maya不同功能有关的库组成。这些库是:
OpenMaya包含定义节点和命令的基本类
OpenMayaUI包含用于创建用户界面元素(如manipulators, contexts, and locators)的类
OpenMayaAnim用于创建动画的类,包括deformers和inverse kinematics
OpenMayaFX包含用于dynamics的类
OpenMayaRender包含用于执行渲染的类
载入插件
有两种装载和卸载插件的方法。最简单的是用Plug-in Manager,另一种是用loadPlugin命令。二者都搜索环境变量MAYA_PLUG_IN_PATH指定的路径。
卸载插件
用unloadPlugin命令带上插件名卸载插件。
注释
插件必须在重编译前被卸载,否则Maya会崩溃。
卸载插件前必须移除场景中所有的参考和插件中定义的节点,还要清空undo队列,因为插件的影响还可能被保留其中。
当你在插件使用中强制卸载它,就无法重载。因为现存的节点被转换为Unknown,重载插件时这些节点不会恢复。
写一个简单的插件
下面演示如何写一个简单的Hello World插件。

#include <maya/MSimple.h>
DeclareSimpleCommand( helloWorld, "Alias", "6.0");
MStatus helloWorld::doIt( const MArgList& )
{
printf("Hello World\n");
return MS::kSuccess;
}

编译后在命令窗口键入helloWorld,按数字键盘上的Enter执行。
重要的插件特性
以上例子展示了很多重要特性。
MSimple.h
用于简单的command plug-in的头文件,通过DeclareSimpleCommand宏注册新command,但这样你只能创建一个command。
注释
可能经常需要创建执行许多特性的插件,例如dependency graph nodes和commands,在这种情况下MSimple.h不再可用,你要自己写注册代码告知Maya插件的功能。
这个宏的主要缺陷是只能创建不能撤销的命令。
MStatus
指明方法是否成功。API类中大部分方法返回MStatus。为防止命名空间冲突,我们用MS名字空间,如MS::kSuccess
注释
API很少用status codes,但如果错误记录被开启,额外的错误细节会被写入日志文件。
DeclareSimpleCommand
DeclareSimpleCommand宏需要三个参数:类名,作者名,命令版本号
注释
不支持undo的命令不应该改变场景状态,否则Maya的undo功能会失效。
写一个与Maya交互的插件
只需要在helloWorld例子上做很少的修改。
以下程序输出Hello后跟输入字符。

#include <maya/MSimple.h>
DeclareSimpleCommand( hello, "Alias", "6.0");
MStatus hello::doIt( const MArgList& args )
{
printf("Hello %s\n", args.asString( 0 ).asChar() );
return MS::kSuccess;
}

MArgList
MArgList类提供和C或C++程序中argc/argv相似的作用。该类提供获得多种类型参数(如integer,double,string,vector)的方法。
下面的例子用于创建螺旋线,有pitch和radius两个参数。

#include <math.h>

#include <maya/MSimple.h>
#include <maya/MFnNurbsCurve.h>
#include <maya/MPointArray.h>
#include <maya/MDoubleArray.h>
#include <maya/MPoint.h>

DeclareSimpleCommand( helix, "Alias - Example", "3.0");

MStatus helix::doIt( const MArgList& args )
{
MStatus stat;

const unsigned deg = 3; // Curve Degree
const unsigned ncvs = 20; // Number of CVs
const unsigned spans = ncvs - deg; // Number of spans
const unsigned nknots = spans+2*deg-1;// Number of knots
double radius = 4.0; // Helix radius
double pitch = 0.5; // Helix pitch
unsigned i;

// Parse the arguments.
for ( i = 0; i < args.length(); i++ )
if ( MString( "-p" ) == args.asString( i, &stat )
&& MS::kSuccess == stat)
{
double tmp = args.asDouble( ++i, &stat );
if ( MS::kSuccess == stat )
pitch = tmp;
}
else if ( MString( "-r" ) == args.asString( i, &stat )
&& MS::kSuccess == stat)
{
double tmp = args.asDouble( ++i, &stat );
if ( MS::kSuccess == stat )
radius = tmp;
}

MPointArray controlVertices;
MDoubleArray knotSequences;

// Set up cvs and knots for the helix
//
for (i = 0; i < ncvs; i++)
controlVertices.append( MPoint( radius * cos( (double)i ),
pitch * (double)i, radius * sin( (double)i ) ) );

for (i = 0; i < nknots; i++)
knotSequences.append( (double)i );

// Now create the curve
//
MFnNurbsCurve curveFn;

MObject curve = curveFn.create( controlVertices,
knotSequences, deg,
MFnNurbsCurve::kOpen,
false, false,
MObject::kNullObj,
&stat );

if ( MS::kSuccess != stat )
printf("Error creating curve.\n");

return stat;
}

提示
argc/argv和MArgList重要的不同是MArgList中的零元素是参数而不像argc/argv中是命令名。
用插件创建曲线
#include <math.h>
#include <maya/MSimple.h>
#include <maya/MPoint.h>
#include <maya/MPointArray.h>
#include <maya/MDoubleArray.h>
#include <maya/MFnNurbsCurve.h>

DeclareSimpleCommand( doHelix, "Alias - Example", "6.0");

MStatus doHelix::doIt( const MArgList& )
{
MStatus stat;

const unsigned deg = 3; // Curve Degree
const unsigned ncvs = 20; // Number of CVs
const unsigned spans = ncvs - deg; // Number of spans
const unsigned nknots = spans+2*deg-1; // Number of knots
double radius = 4.0; // Helix radius
double pitch = 0.5; // Helix pitch
unsigned i;

MPointArray controlVertices;
MDoubleArray knotSequences;

// Set up cvs and knots for the helix
//
for (i = 0; i < ncvs; i++)
controlVertices.append( MPoint( radius * cos( (double)i ),
pitch * (double)i, radius * sin( (double)i ) ) );

for (i = 0; i < nknots; i++)
knotSequences.append( (double)i );

// Now create the curve
//
MFnNurbsCurve curveFn;

MObject curve = curveFn.create( controlVertices,
knotSequences, deg, MFnNurbsCurve::kOpen, false, false, MObject::kNullObj, &stat );

if ( MS::kSuccess != stat )
printf("Error creating curve.\n");

return stat;
}

返回页首

LEN3D
二星视客

注册时间: 2002-02-10
帖子: 499

视币:643

发表于: 2005-12-31 11:03 发表主题:

--------------------------------------------------------------------------------

同Maya交互
Maya API包含四种C++对象用于和Maya交互,它们是wrappers, objects, function sets和proxies。
API中的物体所有权
object和function set的结合类似于wrapper,但区别在所有权上。API中物体所有权很重要,如果没有很好定义,你可能会删除一些系统需要或者已经删除的东西。wrappers,objects,function sets解决了这类问题。
MObject
对所有Maya物体(curves, surfaces, DAG nodes, dependency graph nodes, lights, shaders, textures等)的访问是通过句柄MObject完成的。这个句柄提供了一些简单的方法来辨别物体类型。MObject的析构函数并不真正删除它所参考的Maya物体,调用析构函数只会删除句柄本身而维持所有权。
重要提示
绝对不要在插件调用外储存指向MObject的指针。
Wrappers
wrappers为像vectors,matrices这样的简单对象存在。它们一般都被完整的实现,带有公用的构造和析构函数。API方法可能会返回一个接下来由你负责的wrapper。你可以任意分配和删除它们。前面例子中的MPointArray和MDoubleArray就是wrappers,你拥有wrappers的所有权。
Objects和Function Sets
objects和function sets总被一同使用。它们相对独立是因为所有权问题,objects总被Maya拥有,而function sets属于你。
Function Sets
function sets是操作对象的C++类。在前面的例子中MFnNurbsCurve就是function set,MFn前缀说明了这一点。
下面两行创建新曲线。

MFnNurbsCurve curveFn;
MObject curve = curveFn.create( ... );

MFnNurbsCurve curveFn;创建包含操作曲线方法的function set, create方法用来创建新曲线。
MObject curve = curveFn.create( ... );创建随后你可以任意使用的Maya对象。

如果你再加上一行:

curve = curveFn.create( ... );

另一条曲线会被创建,名为curve的MObject现在指向新创建的这条曲线,但原先创建的曲线仍然存在,只不过不被句柄参考。
Proxies
Maya通过proxy对象创建新对象类型。proxy对象是你创建的但是被Maya占有。
一个普遍的误解是以为可以通过继承现存的function set来创建新对象类型,例如MFnNurbsSurface从MFnDagNode派生。但实际上这样不行,因为function set完全被你拥有,Maya根本不使用它们,Maya只用MObject指向的物体。
无类型
分开objects和function sets带来的一个有趣结果,API操作对象时可以不顾类型,例如:

MFnNurbsCurve curveFn;
MObject curve = curveFn.create( ... );
MFnNurbsSurface surface( curve );

MFnNurbsSurface只操作surface对象,以上的例子是错的,但是你可能没有发觉。API中的错误检查代码会处理这种初始化错误。
function sets接受任何类型的MObjects,如果它不认识该物体,就忽略物体并返回错误值。
命名习惯
Maya API使用以下前缀来区别不同类型的对象。

MFn

表示function set

MIt

迭代器,功能和function set类似但用于操作单独的对象。

MPx

表示proxy对象,你可以继承它们来创建自己的对象类型。

M classes

大多是wrappers但也有例外。MGlobal是包含很多静态方法的类用于全局操作,并不需要MObject。
添加参数
上面的螺旋线插件生成简单的曲线,但每次结果都一样。
为曲线的例子添加参数
通过一些改变可以添加如radius和pitch这样的参数,注意下面函数参数的声明多了一个args:

MStatus doHelix::doIt( const MArgList& args )

Add the following lines after the variable declarations:

// Parse the arguments.
for ( i = 0; i < args.length(); i++ )
if ( MString( "-p" ) == args.asString( i ) )
pitch = args.asDouble( ++i );
else if ( MString( "-r" ) == args.asString( i ) )
radius = args.asDouble( ++i );

for循环遍历所有MArgList中的参数,两个if语句把参数转换成MString并与可能的值做比较。
如果匹配,后一个参数就被转换成double并且赋给对应的值,例如:

doHelix -p 0.5 -r 5

会生成一个pitch为0.5单位长,radius为5单位长的螺旋线。
错误检查
在上面的例子中还未涉及错误检查问题。
大部分的方法提供一个可选参数,是一个指向MStatus的指针,用于返回值。
下面的参数解析代码包含了错误检查:

// Parse the arguments.
for ( i = 0; i < args.length(); i++ )
if ( MString( "-p" ) == args.asString( i, &stat )
&& MS::kSuccess == stat )
{
double tmp = args.asDouble( ++i, &stat );
// argument can be retrieved as a double
if ( MS::kSuccess == stat )
pitch = tmp;
}
else if ( MString( "-r" ) == args.asString( i, &stat )
&& MS::kSuccess == stat )
{
double tmp = args.asDouble( ++i, &stat );
// argument can be retrieved as a double
if ( MS::kSuccess == stat )
radius = tmp;
}

注意asString()和asDouble()增加的最后一个&stat参数,用于检查操作是否成功。
例如args.asString(i, &stat)可能会返回MS::kFailure,索引值大于参数数量或者当不能转换成double值时args.asDouble(++i, &stat)会失败。
MStatus类
MStatus类用于确定方法是否成功。
Maya API既返回MStatus也给可选的MStatus参数赋值。MStatus类包含error方法并重载了bool操作符,它们都能用来检查错误,例如:

MStatus stat = MGlobal::clearSelectionList;
if (!stat) {
// Do error handling
...
}

如果MStatus实例包含错误,你可以做以下事:

用statusCode方法获得具体的错误原因。
用errorString方法获得错误细节描述。
用perror打印错误细节描述。
用重载的相等和不相等操作符来和某个MStatusCode做比较。
用clear方法把MStatus实例设置为成功状态。
错误记录
同使用MStatus一样,你也可以用错误记录检查错误。
开启和关闭错误记录:
1. 在MEL中用openMayaPref命令加上-errlog标识。
2. 在插件中调用MGlobal::startErrorLogging()和MGlobal::stopErrorLogging()方法。
一旦开启错误记录,每次插件发生错误都会被Maya记录在文件中,还会包含错误在何处引发的描述。
默认文件名是OpenMayaErrorLog,在当前目录下。
可以用MGlobal::setErrorLogPathName改变错误记录的目录。
提示
插件可以用MGlobal::doErrorLogEntry()向记录文件中添加自己的记录。

--------------------------------------------------------------------------------

用API选择
概览用API选择
一个命令经常从选择列表获取输入。MGlobal::getActiveSelectionList() 方法的结果包含所有当前被选择的物体。通过MSelectionList和MItSelectionList两个API类可以容易的检查和修改选择列表。
MGlobal::setActiveSelectionList()
可以通过MGlobal::setActiveSelectionList()获得当前全局选择列表的拷贝。
在调用MGlobal::setActiveSelectionList()前,你对返回的选择列表的任何改变都不会真正影响到场景。
你可以创建自己的MSelectionList来与其它列表合并,列表可以用来创建对象集合。
MSelectionList
MSelectionList提供向选择列表中添加、删除和遍历对象的方法。
下面的例子打印所有被选中的DAG节点的名字。
简单的插件例子

#include <maya/MSimple.h>
#include <maya/MGlobal.h>
#include <maya/MString.h>
#include <maya/MDagPath.h>
#include <maya/MFnDagNode.h>
#include <maya/MSelectionList.h>

MStatus pickExample::doIt( const MArgList& )
{
MDagPath node;
MObject component;
MSelectionList list;
MFnDagNode nodeFn;

MGlobal::getActiveSelectionList( list );
for ( unsigned int index = 0; index < list.length(); index++ )
{
list.getDagPath( index, node, component );
nodeFn.setObject( node );
printf("%s is selected\n", nodeFn.name().asChar() );
}

return MS::kSuccess;
}
DeclareSimpleCommand( pickExample, "Alias", "1.0" );

MFnDagNode中的setObject()方法被MFnBase的所有子类继承,用于设置该function set将要操作的对象。通常这在构造函数中完成,但如果function set已经被创建并且你想改变它要操作的对象,就使用setObject(),这比每次都重新创建和销毁function set有效率的多。
当你选择CVs并调用该插件时,不会得到CVs的名字,而是得到父对象(如curve,surface,mesh)的名字,并且打印名字的数量和选择物体的数量不同。
Maya简化像CVs这样的对象组件的选择,不是把每个组件都添加到选择列表,而是添加父对象并把组件当成一个组。
例如,当nurbSphereShape1的很多CVs被选中,list.getDagPath() 会返回一个指向nurbSphereShape1 的MDagPath和一个包含了所有被选中的CVs的MObject。这里的MDagPath和MObject可以被传递给MItSurfaceCV迭代器来遍历所有选中的CVs。
只要你继续选择一个对象的组件,选择列表中的对象只会有一个。然而如果你选择了一个对象中的某些组件和另一个对象中的某些组件,然后再选择第一个对象中的某些组件,那么第一个对象会在选择列表中出现两次。这样你可以决定物体被选择的顺序。所有组件也被按它们被选中的顺序排列。
MItSelectionList
MltSelectionList是一个包含被选择物体的wrapper类,它可以通过过滤让你只看到特定类型的对象,而MSelectionList不行。

MGlobal::getActiveSelectionList( list );
for ( MItSelectionList listIter( list ); !listIter.isDone(); listIter.next() )
{
listIter.getDagPath( node, component );
nodeFn.setObject( node );
printf("%s is selected\n", nodeFn.name().asChar() );
}

上面MSelectionList的例子可以用这些代码代替,结果完全相同。
当把MItSelectionList的构造函数改成:

MItSelectionList listIter( list, MFn::kNurbsSurface )

这时只会访问NURBS Surface,也会忽略surface CVs。如果你只想访问surface CVs,可以这么写:

MItSelectionList listIter( list, MFn::kSurfaceCVComponent )
setObject()方法
限制
Mesh vertices, faces或edges并不按被选中的顺序返回。
MFn::Type枚举
到处都会用到MFn::Type枚举来确定物件类型。
function sets类有apiType()方法用来确定MObject参考的对象类型,还有一个type()方法用来确定function set本身的类型。
MGlobal::getFunctionSetList()返回字符串数组,描述了function sets的类型。
MGlobal::selectByName()
MGlobal::selectByName()方法将所有匹配的对象添加到当前选择列表,例如:

MGlobal::selectByName( "*Sphere*" );

选择所有名字中包含Sphere的东西。
提示
你也可以不必创建MSelectionList而直接用MGlobal::select()往全局选择列表添加对象。

--------------------------------------------------------------------------------

Command plug-ins
向Maya添加commands概览
Plug-ins
API支持多种类型的插件:
Command plug-ins提供命令来扩展MEL
Tool commands获得鼠标输入的插件
Dependency graph plug-ins添加新的操作,如dependency graph nodes
Device plug-ins允许新的设备与Maya交互
注册commands
你必须知道在Maya中如何正确注册commands,MFnPlugin类用来做这件事。
MFnPlugin
下面的helloWorld用MFnPlugin注册而不像前面那样用宏。

#include <stdio.h>
#include <maya/MString.h>
#include <maya/MArgList.h>
#include <maya/MFnPlugin.h>
#include <maya/MPxCommand.h>

class hello : public MPxCommand
{
public:
MStatus doIt( const MArgList& args );
static void* creator();
};

MStatus hello::doIt( const MArgList& args ) {
printf("Hello %s\n", args.asString( 0 ).asChar() );
return MS::kSuccess;
}

void* hello::creator() {
return new hello;
}

MStatus initializePlugin( MObject obj ) {
MFnPlugin plugin( obj, "Alias", "1.0", "Any" );
plugin.registerCommand( "hello", hello::creator );
return MS::kSuccess;
}

MStatus uninitializePlugin( MObject obj ) {
MFnPlugin plugin( obj );
plugin.deregisterCommand( "hello" );

return MS::kSuccess;
}

注释
所有插件都必须有initializePlugin()和uninitializePlugin(),否则不会被载入,creator是必须的,它允许Maya创建类的实例。
initializePlugin()
initializePlugin()可以被当作C或C++函数定义。你不定义它插件就不会被装载。
它包含了注册commands, tools, devices等的代码,只在插件装载时被立即调用一次。
例如commands和tools通过实例化一个操作MObject的MFnPlugin来注册。这个MObject包含Maya私有信息如插件名字和目录,它被传递给MFnPlugin的构造函数。"Any"表示Maya API的版本,是默认值。
MFnPlugin::registerCommand()用来注册"hello"命令。如果初始化不成功,插件会被自动卸载。
uninitializePlugin()
uninitializePlugin()用来注销initializePlugin()注册的所有东西,只在插件被卸载时被调用一次。Maya会自己管理的对象不需要在这里销毁。
Creator methods
插件要注册的东西Maya事先是不知道的,所以没办法决定分配它需要的空间,这时creator用来帮助Maya创建这些东西的实例。
MPxCommand
从MPxCommand继承的类至少要定义两个函数,doIt和creator。
doIt()和redoIt()方法
doIt()方法是纯虚的,而且基类中没有定义creator,所以两个都要定义。
简单的command可以让doIt做所有事,但在复杂的例子中,doIt()用来解析参数列表和选择列表等,并用解析的结果设置内部数据,最后再调用redoIt()来做真正的工作。这样避免了doIt()和redoIt()间的代码重复。
校验
下面的例子用来演示方法被调用的顺序。

#include <stdio.h>
#include <maya/MString.h>
#include <maya/MArgList.h>
#include <maya/MFnPlugin.h>
#include <maya/MPxCommand.h>

class commandExample : public MPxCommand
{
public:
commandExample();
virtual ~commandExample();
MStatus doIt( const MArgList& );
MStatus redoIt();
MStatus undoIt();
bool isUndoable() const;
static void* creator();
};

commandExample::commandExample() {
printf("In commandExample::commandExample()\n");
}
commandExample::~commandExample() {
printf("In commandExample::~commandExample()\n");
}
MStatus commandExample::doIt( const MArgList& ) {
printf("In commandExample::doIt()\n");
return MS::kSuccess;
}
MStatus commandExample::redoIt() {
printf("In commandExample::redoIt()\n");
return MS::kSuccess;
}
MStatus commandExample::undoIt() {
printf("In commandExample::undoIt()\n");
return MS::kSuccess;
}
bool commandExample::isUndoable() const {
printf("In commandExample::isUndoable()\n");
return true;
}
void* commandExample::creator() {
printf("In commandExample::creator()\n");
return new commandExample();
}

MStatus initializePlugin( MObject obj )
{
MFnPlugin plugin( obj, "My plug-in", "1.0", "Any" );
plugin.registerCommand( "commandExample", commandExample::creator );
printf("In initializePlugin()\n");
return MS::kSuccess;
}

MStatus uninitializePlugin( MObject obj )
{
MFnPlugin plugin( obj );
plugin.deregisterCommand( "commandExample" );
printf("In uninitializePlugin()\n");
return MS::kSuccess;
}

第一次载入插件时"In initializePlugin()"被立即打印,在命令窗口键入"commandExample"会得到:

In commandExample::creator()
In commandExample::commandExample()
In commandExample::doIt()
In commandExample::isUndoable()

注意析构函数并没有被调用,因为command还有可能被undo和redo。
这是Maya的undo机制的工作方式。Command objects负责保留undo自己需要的信息。析构函数在command掉到undo队列的末尾或插件被卸载时才被调用。
如果你修改一下,让isUndoable()返回false,将得到如下结果:

In commandExample::creator()
In commandExample::commandExample()
In commandExample::doIt()
In commandExample::isUndoable()
In commandExample::~commandExample()

在这种情况下析构函数立即被调用,因为不能undo了,Maya认为这样的command不会改变场景,所以使用undo和redo时undoIt()和redoIt()不会被调用。
带undo和redo的螺旋线例子
这个插件会把被选中的曲线变成螺旋线。

#include <stdio.h>
#include <math.h>

#include <maya/MFnPlugin.h>
#include <maya/MFnNurbsCurve.h>
#include <maya/MPointArray.h>
#include <maya/MDoubleArray.h>
#include <maya/MPoint.h>
#include <maya/MSelectionList.h>
#include <maya/MItSelectionList.h>
#include <maya/MItCurveCV.h>
#include <maya/MGlobal.h>
#include <maya/MDagPath.h>
#include <maya/MString.h>
#include <maya/MPxCommand.h>
#include <maya/MArgList.h>

class helix2 : public MPxCommand {
public:
helix2();
virtual ~helix2();
MStatus doIt( const MArgList& );
MStatus redoIt();
MStatus undoIt();
bool isUndoable() const;
static void* creator();

这边的和前面都差不多。

private:
MDagPath fDagPath;
MPointArray fCVs;
double radius;
double pitch;
};

这个command储存原始的曲线信息用来undo,并储存螺旋线的描述用来redo。
非常值得注意的是它不储存MObject,而是用一个MDagPath来指向要操作的曲线。因为命令下一次被执行时无法保证MObject继续有效,Maya可能会核心转储。然而MDagPath却能保证任何时候都指向正确的曲线。

void* helix2::creator() {
return new helix2;
}

creator简单返回对象实例。

helix2::helix2() : radius( 4.0 ), pitch( 0.5 ) {}

初始化radius和pitch的值。

helix2::~helix2() {}

这时析构函数不需要做任何事。
注释
不应该删除Maya占有的数据。

MStatus helix2::doIt( const MArgList& args ) {
MStatus status;

// Parse the arguments.
for ( int i = 0; i < args.length(); i++ )
if ( MString( "-p" ) == args.asString( i, &status )
&& MS::kSuccess == status )
{
double tmp = args.asDouble( ++i, &status );
if ( MS::kSuccess == status )
pitch = tmp;
}
else if ( MString( "-r" ) == args.asString( i, &status )
&& MS::kSuccess == status )
{
double tmp = args.asDouble( ++i, &status );
if ( MS::kSuccess == status )
radius = tmp;
}
else
{
MString msg = "Invalid flag: ";
msg += args.asString( i );
displayError( msg );
return MS::kFailure;
}

在doIt()里面解析参数并且设置radius和pitch,它们将被用在redoIt()中。
从MPxCommand继承的displayError()方法用来在命令窗口输出消息,消息都会带上"Error:"前缀,如果用displayWarning()就会带上"Warning:"前缀。

// Get the first selected curve from the selection list.
MSelectionList slist;
MGlobal::getActiveSelectionList( slist );
MItSelectionList list( slist, MFn::kNurbsCurve, &status );
if (MS::kSuccess != status) {
displayError( "Could not create selection list iterator" );
return status;
}

if (list.isDone()) {
displayError( "No curve selected" );
return MS::kFailure;
}

MObject component;
list.getDagPath( fDagPath, component );

这段代码从选择列表中取出第一条曲线。

return redoIt();
}

最后调用redoIt()。

MStatus helix2::redoIt()
{
unsigned i, numCVs;
MStatus status;
MFnNurbsCurve curveFn( fDagPath );

numCVs = curveFn.numCVs();
status = curveFn.getCVs( fCVs );
if ( MS::kSuccess != status )
{
displayError( "Could not get curve's CVs" );
return MS::kFailure;
}

从被选中的曲线获取CVs并储存在command内的MPointArray中,用来在调用undoIt()时把曲线恢复为原状。

MPointArray points(fCVs);
for (i = 0; i < numCVs; i++)
points[i] = MPoint( radius * cos( (double)i ),
pitch * (double)i, radius * sin( (double)i ) );
status = curveFn.setCVs( points );
if ( MS::kSuccess != status )
{
displayError( "Could not set new CV information" );
fCVs.clear();
return status;
}

这段代码把曲线变为螺旋线。
updateCurve()用来通知Maya几何已被更改,需要重绘。

return MS::kSuccess;
}

MStatus helix2::undoIt()
{
MStatus status;

MFnNurbsCurve curveFn( fDagPath );
status = curveFn.setCVs( fCVs );
if ( MS::kSuccess != status)
{
displayError( "Could not set old CV information" );
return status;
}

这里将曲线的CVs恢复了。
注释
不需要担心CVs数量改变或已被删除,你可以假设command执行后的一切改变都被撤销了,模型维持不变。

status = curveFn.updateCurve();
if ( MS::kSuccess != status )
{
displayError( "Could not update curve" );
return status;
}

fCVs.clear();
return MS::kSuccess;
}

只是为了以防万一才调用clear。

bool helix2::isUndoable() const
{
return true;
}

指明该command是可撤销的。

MStatus initializePlugin( MObject obj )
{
MFnPlugin plugin( obj, "Alias", "1.0", "Any");
plugin.registerCommand( "helix2", helix2::creator );

return MS::kSuccess;
}

MStatus uninitializePlugin( MObject obj )
{
MFnPlugin plugin( obj );
plugin.deregisterCommand( "helix2" );

return MS::kSuccess;
}

这里和前面差不多。
Returning results to MEL
commands也可以向MEL返回结果,这通过叫"setResult"和"appendToResult"的一系列从MPxCommand继承的重载函数完成。例如可以这样返回值为4的整数:

int result =4;
clearResult();
setResult( result );

也可以多次调用appendToResult来返回数组,如:

MPoint result (1.0, 2.0, 3.0);
...
clearResult();
appendToResult( result.x );
appendToResult( result.y );
appendToResult( result.z );

或者直接返回数组:

MDoubleArray result;
MPoint point (1.0, 2.0, 3.0);

result.append( point.x );
result.append( point.y );
result.append( point.z );

clearResult();
setResult( result );
Syntax objects
当你写语法对象时需要用到MSyntax和MArgDatabase,这些类是定义和处理命令标记输入时必需的。
MSyntax用来指定传递给commands的标记和参数。
MArgDatabase用于解析传递给commands的所有标记、参数和对象的类。它接受MSyntax对象来把命令参数解析成容易访问的形式。
注释
MArgParser和MArgDatabase类似但是用于context commands的而不是commands。
Flags
Syntax objects需要flags,你既要定义短flags又要定义长flags,短flags小于等于三个字母,长flags大于等于4个字母。
用#define来声明这些flags,例如scanDagSyntax使用以下flags:

#define kBreadthFlag "-b"
#define kBreadthFlagLong "-breadthFirst"
#define kDepthFlag "-d"
#define kDepthFlagLong "-depthFirst"

创建Syntax Object
在你的command类中需要写newSyntax方法建立你的语法。它应该是返回MSyntax的静态函数。
在该方法中你应该为syntax object添加必要的flags并返回它。
scanDagSyntax类的newSyntax定义如下:

class scanDagSyntax: public MPxCommand
{
public:
...
static MSyntax newSyntax();
...
};

MSyntax scanDagSyntax::newSyntax()
{
MSyntax syntax;

syntax.addFlag(kBreadthFlag, kBreadthFlagLong);
syntax.addFlag(kDepthFlag, kDepthFlagLong);
...
return syntax;
}

解析参数
按习惯一般在parseArgs方法中解析参数并由doIt调用,该方法创建一个局部的用syntax object和命令参数初始化的MArgDatabase。MArgDatabase提供了决定哪些flags被设置的方便方法。
注释
除特别情况外,所有API都用Maya内部的厘米和弧度作为单位。

MStatus scanDagSyntax::parseArgs(const MArgList &args,
MItDag::TraversalType &
traversalType,
MFn::Type &filter,
bool &quiet)
{
MArgDatabase argData(syntax(), args);

if (argData.isFlagSet(kBreadthFlag))
traversalType = MItDag::kBreadthFirst;
else if (argData.isFlagSet(kDepthFlag))
traversalType = MItDag::kDepthFirst;
...
return MS::kSuccess;
}

注册
创建syntax object的方法同command一起在initializePlugin中被注册。

MStatus initializePlugin( MObject obj )
{
MStatus status;
MFnPlugin plugin(obj, "Alias - Example",
"2.0", "Any");
status = plugin.registerCommand("scanDagSyntax",
scanDagSyntax::creator,
scanDagSyntax::newSyntax);
return status;
}
Contexts
Maya中的contexts用于决定鼠标动作如何被解释。context可以执行commands,改变当前选择集,或执行绘图操作等。context还能绘制不同的鼠标指针。在Maya中context被表述为tool。
MPxContext
MPxContext允许你创建自己的context。
在Maya中context通过一个特殊的command来创建。在这点上,context类似shape,它通过command创建和修改并具有决定其行为和外观的状态。当你通过继承MPxContext来写一个context时,你也应该为它定义一个从MPxContextCommand派生的command。
以下是选取框的例子,它会绘制一个OpenGL选取框并选择物体。

const char helpString[] =
"Click with left button or drag with middle button to select";

class marqueeContext : public MPxContext
{
public:
marqueeContext();
virtual void toolOnSetup( MEvent & event );
virtual MStatus doPress( MEvent & event );
virtual MStatus doDrag( MEvent & event );
virtual MStatus doRelease( MEvent & event );
virtual MStatus doEnterRegion( MEvent & event );

如果虚方法没有被重载,MPxContext将执行默认的动作,所以你只要写必要的方法即可。

private:
short start_x, start_y;
short last_x, last_y;
MGlobal::ListAdjustment listAdjustment
M3dView view;
};

marqueeContext::marqueeContext()
{
setTitleString ( "Marquee Tool" );
}

构造函数设置了标题文字,当工具被选中时会显示在界面上。

void marqueeContext::toolOnSetup ( MEvent & )
{
setHelpString( helpString );
}

当工具被选中该方法会被调用,在提示行显示帮助信息。

MStatus marqueeContext::doPress( MEvent & event )
{

当工具被选中并且你按下鼠标键时该方法会被调用。MEvent对象包含了鼠标动作的相关信息,如单击的坐标。

if (event.isModifierShift() || event.isModifierControl() ) {
if ( event.isModifierShift() ) {
if ( event.isModifierControl() ) {
// both shift and control pressed, merge new selections
listAdjustment = MGlobal::kAddToList;
} else {
// shift only, xor new selections with previous ones
listAdjustment = MGlobal::kXORWithList;
}
} else if ( event.isModifierControl() ) {
// control only, remove new selections from the previous list
listAdjustment = MGlobal::kRemoveFromList;
}
}
else
listAdjustment = MGlobal::kReplaceList;

这里根据键盘动作来决定选择类型。

event.getPosition( start_x, start_y );

获得开始选择处的屏幕坐标。

view = M3dView::active3dView();
view.beginGL();

决定激活视图并开启OpenGL渲染。

view.beginOverlayDrawing();
return MS::kSuccess;
}

MStatus marqueeContext::doDrag( MEvent & event )
{

当你拖动鼠标时该方法会被调用。

event.getPosition( last_x, last_y );
view.clearOverlayPlane();

每次绘制新选择框前该方法都会被调用来清空overlay planes。

glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluOrtho2D(
0.0, (GLdouble) view.portWidth(),
0.0, (GLdouble) view.portHeight()
);
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef(0.375, 0.375, 0.0);

执行视图变换。

glLineStipple( 1, 0x5555 );
glLineWidth( 1.0 );
glEnable( GL_LINE_STIPPLE );
glIndexi( 2 );

选定直线类型。

// Draw marquee
//
glBegin( GL_LINE_LOOP );
glVertex2i( start_x, start_y );
glVertex2i( last_x, start_y );
glVertex2i( last_x, last_y );
glVertex2i( start_x, last_y );
glEnd();

绘制选择框。

#ifndef _WIN32
glXSwapBuffers(view.display(), view.window() );
#else
SwapBuffers(view.deviceContext() );
#endif

交换双缓冲区。

glDisable( GL_LINE_STIPPLE );

恢复绘图模式。

return MS::kSuccess;
}

MStatus marqueeContext::doRelease( MEvent & event )
{

鼠标键释放时该方法会被调用。

MSelectionList incomingList, marqueeList;
MGlobal::ListAdjustment listAdjustment;

view.clearOverlayPlane();
view.endOverlayDrawing();
view.endGL();

绘图完成,清空overlay planes,关闭OpenGL渲染。

event.getPosition( last_x, last_y );

决定鼠标释放处的坐标。

MGlobal::getActiveSelectionList(incomingList);

获取并保存当前选择列表。

if ( abs(start_x - last_x) < 2 && abs(start_y - last_y) < 2 )
MGlobal::selectFromScreen( start_x, start_y, MGlobal::kReplaceList );

如果开始和结束选择的坐标相同,则用单击选取代替包围盒选取。

else
// Select all the objects or components within the marquee.
MGlobal::selectFromScreen( start_x, start_y, last_x, last_y,
MGlobal::kReplaceList );

包围盒选取。

// Get the list of selected items
MGlobal::getActiveSelectionList(marqueeList);

获取刚才选定的列表。

MGlobal::setActiveSelectionList(incomingList, \
MGlobal::kReplaceList);

恢复原选择列表。

MGlobal::selectCommand(marqueeList, listAdjustment);

按指定方式修改选择列表。

return MS::kSuccess;
}

MStatus marqueeContext::doEnterRegion( MEvent & )
{
return setHelpString( helpString );
}

当你把鼠标移到任何视图时都会调用该方法。

class marqueeContextCmd : public MPxContextCommand
{
public:
marqueeContextCmd();
virtual MPxContext* makeObj();
static void* creator();
};
MPxContextCommand
MPxContextCommand类用来定义创建contexts的特殊command。context commands和一般的commands一样可以用命令行调用或者写入MEL脚本。它们拥有修改context属性的编辑和访问选项。它们创建一个context实例并传给Maya。context commands不能撤销。
创建context command
下面是用来创建前面的选择框context的context command。

marqueeContextCmd::marqueeContextCmd() {}

MPxContext* marqueeContextCmd::makeObj()
{
return new marqueeContext();
}

用来为Maya创建context实例的方法。

void* marqueeContextCmd::creator()
{
return new marqueeContextCmd;
}

MStatus initializePlugin( MObject obj )
{
MStatus status;
MFnPlugin plugin( obj, "Alias", "1.0", "Any");
status = plugin.registerContextCommand( \
"marqueeToolContext", marqueeContextCmd::creator );

用MFnPlugin::registerContextCommand()而不是MFnPlugin::registerCommand()注册context command。

return status;
}

MStatus uninitializePlugin( MObject obj )
{
MStatus status;
MFnPlugin plugin( obj );
status = plugin.deregisterContextCommand( \
"marqueeToolContext" );

用MFnPlugin::deregisterContextCommand()注销context command。

return status;
}

创建context就是这么简单。
把context command添加到Maya工具架
有两种方法激活context为当前context。第一种是用setToolTo命令后跟context的名字。
第二种方法就是把context的图表添加到Maya工具架上。Maya工具架可以储存两种按钮,command按钮和tool按钮。
下面是用来创建context按钮和tool按钮的MEL命令。

marqueeToolContext marqueeToolContext1;
setParent Shelf1;
toolButton -cl toolCluster
-t marqueeToolContext1
-i1 "marqueeTool.xpm" marqueeTool1;

这些MEL代码创建一个marqueeToolContext实例并添加到"Common"工具架。
marqueeTool.xpm是工具的图标名,必须在XBMLANGPATH目录下,否则就不会显示,但工具依然可用。
这些代码既可以手工键入,又可以在initializePlugin()中用MGlobal::sourceFile()调用。
Tool property sheets
Tool property sheets是用来显示和编辑context属性的交互式编辑器,和用来编辑dependency graph node属性的attribute editors类似。
实现一个tool properly sheet必须写两个MEL文件,一个用来编辑context,一个用来访问context。
一个文件叫<yourContextName>Properties.mel,另一个叫<yourContextName>Values.mel,<yourContextName>是context的名字,可以用getClassName()方法获得。
<>Properties.mel用来定义编辑器外观。
<>Values.mel用来从编辑器获得数值。
要有效实现tool property sheet,你必须在context command中实现足够的编辑和访问选项,并在MPxContext实现充分的访问方法来设置和获得内部参数。
MPxToolCommand
MPxToolCommand是创建用来在context中执行的commands的基类。tool commands和一般的commands一样用command flags定义可以用命令行调用。但它们要做额外的工作,因为它们不从Maya调用而是从MPxContext调用。这些工作用来让Maya正确执行undo/redo和日志策略。
如果context想执行自己的command,必须在注册context和context command时注册这个command。一个context只能和一个tool command对应。
下面是综合了螺旋线和选取框的tool command的例子。

#define NUMBER_OF_CVS 20

class helixTool : public MPxToolCommand
{
public:
helixTool();
virtual ~helixTool();
static void* creator();

MStatus doIt( const MArgList& args );
MStatus redoIt();
MStatus undoIt();
bool isUndoable() const;
MStatus finalize();
static MSyntax newSyntax();

这些方法都差不多,但是加了finalize()用来提供command用法的帮助。

void setRadius( double newRadius );
void setPitch( double newPitch );
void setNumCVs( unsigned newNumCVs );
void setUpsideDown( bool newUpsideDown );

当tool command属性由context设置时这些方法是必要的。

private:
double radius; // Helix radius
double pitch; // Helix pitch
unsigned numCV; // Helix number of CVs
bool upDown; // Helis upsideDown
MDagPath path; // Dag path to the curve.
// Don't save the pointer!
};

void* helixTool::creator()
{
return new helixTool;
}

helixTool::~helixTool() {}

这里和前面的螺旋线例子一样。

helixTool::helixTool()
{
numCV = NUMBER_OF_CVS;
upDown = false;
setCommandString( "helixToolCmd" );
}

构造函数保存MEL command的名字以待在finalize()中使用。

MSyntax helixTool::newSyntax()
{
MSyntax syntax;

syntax.addFlag(kPitchFlag, kPitchFlagLong,
MSyntax::kDouble);
syntax.addFlag(kRadiusFlag, kRadiusFlagLong,
MSyntax::kDouble);
syntax.addFlag(kNumberCVsFlag, kNumberCVsFlagLong,
MSyntax::kUnsigned);
syntax.addFlag(kUpsideDownFlag, kUpsideDownFlagLong,
MSyntax::kBoolean);

return syntax;
}

MStatus helixTool::doIt( const MArgList &args )
{
MStatus status;

status = parseArgs(args);

if (MS::kSuccess != status)
return status;

return redoIt();
}

MStatus helixTool::parseArgs(const MArgList &args)
{
MStatus status;
MArgDatabase argData(syntax(), args);

if (argData.isFlagSet(kPitchFlag)) {
double tmp;
status = argData.getFlagArgument(kPitchFlag, 0, tmp);
if (!status) {
status.perror("pitch flag parsing failed.");
return status;
}
pitch = tmp;
}

if (argData.isFlagSet(kRadiusFlag)) {
double tmp;
status = argData.getFlagArgument(kRadiusFlag, 0, tmp);
if (!status) {
status.perror("radius flag parsing failed.");
return status;
}
radius = tmp;
}

if (argData.isFlagSet(kNumberCVsFlag)) {
unsigned tmp;
status = argData.getFlagArgument(kNumberCVsFlag,
0, tmp);
if (!status) {
status.perror("numCVs flag parsing failed.");
return status;
}
numCV = tmp;
}

if (argData.isFlagSet(kUpsideDownFlag)) {
bool tmp;
status = argData.getFlagArgument(kUpsideDownFlag,
0, tmp);
if (!status) {
status.perror("upside down flag parsing failed.");
return status;
}
upDown = tmp;
}

return MS::kSuccess;
}

MStatus helixTool::redoIt()
{
MStatus stat;

const unsigned deg = 3; // Curve Degree
const unsigned ncvs = NUMBER_OF_CVS;// Number of CVs
const unsigned spans = ncvs - deg; // Number of spans
const unsigned nknots = spans+2*deg-1;// Number of knots
unsigned i;
MPointArray controlVertices;
MDoubleArray knotSequences;

int upFactor;
if (upDown) upFactor = -1;
else upFactor = 1;

// Set up cvs and knots for the helix
//
for (i = 0; i < ncvs; i++)
controlVertices.append( MPoint(
radius * cos( (double)i ),
upFactor * pitch * (double)i,
radius * sin( (double)i ) ) );

for (i = 0; i < nknots; i++)
knotSequences.append( (double)i );

// Now create the curve
//
MFnNurbsCurve curveFn;

MObject curve = curveFn.create( controlVertices,
knotSequences, deg, MFnNurbsCurve::kOpen,
false, false, MObject::kNullObj, &stat );

if ( !stat )
{
stat.perror("Error creating curve");
return stat;
}

stat = curveFn.getPath( path );

return stat;
}

MStatus helixTool::undoIt()
{
MStatus stat;
MObject transform = path.transform();
stat = MGlobal::removeFromModel( transform );
return stat;
}

bool helixTool::isUndoable() const
{
return true;
}

这里和前面的例子类似,其实只要做很少的修改就可以把command变成tool。

MStatus helixTool::finalize()
{
MArgList command;
command.addArg( commandString() );
command.addArg( MString(kRadiusFlag) );
command.addArg( radius );
command.addArg( MString(kPitchFlag) );
command.addArg( pitch );
command.addArg( MString(kNumberCVsFlag) );
command.addArg( (int)numCV );
command.addArg( MString(kUpsideDownFlag) );
command.addArg( upDown );
return MPxToolCommand::doFinalize( command );
}

因为tool从UI调用而不是命令行,所以不能像一般command那样输出日志,所以需要finalize()来做这件事。

void helixTool::setRadius( double newRadius )
{
radius = newRadius;
}

void helixTool::setPitch( double newPitch )
{
pitch = newPitch;
}

void helixTool::setNumCVs( unsigned newNumCVs )
{
numCV = newNumCVs;
}

void helixTool::setUpsideDown( double newUpsideDown )
{
upDown = newUpsideDown;
}

const char helpString[] = "Click and drag to draw helix";

class helixContext : public MPxContext
{

用来执行helixTool command的context。

public:
helixContext();
virtual void toolOnSetup( MEvent & event );
virtual MStatus doPress( MEvent & event );
virtual MStatus doDrag( MEvent & event );
virtual MStatus doRelease( MEvent & event );
virtual MStatus doEnterRegion( MEvent & event );

private:
short startPos_x, startPos_y;
short endPos_x, endPos_y;
unsigned numCV;
bool upDown;
M3dView view;
GLdouble height,radius;
};

helixContext::helixContext()
{
setTitleString( "Helix Tool" );
}

void helixContext::toolOnSetup( MEvent & )
{
setHelpString( helpString );
}

MStatus helixContext::doPress( MEvent & event )
{
event.getPosition( startPos_x, startPos_y );
view = MGlobal::active3dView();
view.beginGL();
view.beginOverlayDrawing();
return MS::kSuccess;
}

和前面的例子差不多,只是在doPress()方法中不需要检测键盘。

MStatus helixContext::doDrag( MEvent & event )
{
event.getPosition( endPos_x, endPos_y );
view.clearOverlayPlane();
glIndexi( 2 );

int upFactor;
if (upDown) upFactor = 1;
else upFactor = -1;

// Draw the guide cylinder
//
glMatrixMode( GL_MODELVIEW );
glPushMatrix();
glRotatef( upFactor * 90.0, 1.0f, 0.0f, 0.0f );
GLUquadricObj *qobj = gluNewQuadric();
gluQuadricDrawStyle(qobj, GLU_LINE);
GLdouble factor = (GLdouble)numCV;
radius = fabs(endPos_x - startPos_x)/factor + 0.1;
height = fabs(endPos_y - startPos_y)/factor + 0.1;
gluCylinder( qobj, radius, radius, height, 8, 1 );
glPopMatrix();

绘制表示螺旋线轮廓的圆柱。

#ifndef _WIN32
glXSwapBuffers(view.display(), view.window() );
#else
SwapBuffers(view.deviceContext() );
#endif

return MS::kSuccess;
}

MStatus helixContext::doRelease( MEvent & )
{
// Clear the overlay plane & restore from overlay drawing
//
view.clearOverlayPlane();
view.endOverlayDrawing();
view.endGL();

helixTool * cmd = (helixTool*)newToolCommand();
cmd->setPitch( height/NumCVs );
cmd->setRadius( radius );
cmd->setNumCVs( numCV );
cmd->setUpsideDown( upDown );
cmd->redoIt();
cmd->finalize();

调用helixTool::creator创建真正的command,调用redoIt() 生成数据,调用finalize()输出日志。

return MS::kSuccess;
}

MStatus helixContext::doEnterRegion( MEvent & )
{
return setHelpString( helpString );
}

void helixContext::getClassName( MString &name ) const
{
name.set("helix");
}

下面的四个方法用于context和context command的编辑和访问方法间的交互,会被tool property sheet调用。MToolsInfo::setDirtyFlag()告诉tool property sheet参数改变需要重绘。

void helixContext::setNumCVs( unsigned newNumCVs )
{
numCV = newNumCVs;
MToolsInfo::setDirtyFlag(*this);
}

void helixContext::setUpsideDown( bool newUpsideDown )
{
upDown = newUpsideDown;
MToolsInfo::setDirtyFlag(*this);
}

unsigned helixContext::numCVs()
{
return numCV;
}

bool helixContext::upsideDown()
{
return upDown;
}

下面的类和选取框例子相仿。

class helixContextCmd : public MPxContextCommand
{
public:
helixContextCmd();
virtual MStatus doEditFlags();
virtual MStatus doQueryFlags();
virtual MPxContext* makeObj();
virtual MStatus appendSyntax();
static void* creator();

protected:
helixContext * fHelixContext;
};

helixContextCmd::helixContextCmd() {}

MPxContext* helixContextCmd::makeObj()
{
fHelixContext = new helixContext();
return fHelixContext;
}

void* helixContextCmd::creator()
{
return new helixContextCmd;
}

下面的方法做参数解析。有两类参数,一类修改context属性,一类访问context属性。
注释
参数解析通过MPxContextCommand::parser()方法完成,它返回的MArgParser类与MArgDatabase类相似。

MStatus helixContextCmd::doEditFlags()
{
MArgParser argData = parser();

if (argData.isFlagSet(kNumberCVsFlag)) {
unsigned numCVs;
status = argData.getFlagArgument(kNumberCVsFlag,
0, numCVs);
if (!status) {
status.perror("numCVs flag parsing failed.");
return status;
}
fHelixContext->setNumCVs(numCVs);
}

if (argData.isFlagSet(kUpsideDownFlag)) {
bool upsideDown;
status = argData.getFlagArgument(kUpsideDownFlag,
0, upsideDown);
if (!status) {
status.perror("upsideDown flag parsing failed.");
return status;
}
fHelixContext->setUpsideDown(upsideDown);
}

return MS::kSuccess;
}

MStatus helixContextCmd::doQueryFlags()
{
MArgParser argData = parser();

if (argData.isFlagSet(kNumberCVsFlag)) {
setResult((int) fHelixContext->numCVs());
}
if (argData.isFlagSet(kUpsideDownFlag)) {
setResult(fHelixContext->upsideDown());
}

return MS::kSuccess;
}

MStatus helixContextCmd::appendSyntax()
{
MStatus status;

MSyntax mySyntax = syntax();

if (MS::kSuccess != mySyntax.addFlag(kNumberCVsFlag,
kNumberCVsFlagLong, MSyntax::kUnsigned)) {
return MS::kFailure;
}

if (MS::kSuccess != mySyntax.addFlag(kUpsideDownFlag,
kUpsideDownFlagLong, MSyntax::kBoolean)) {
return MS::kFailure;
}

return MS::kSuccess;
}

MStatus initializePlugin( MObject obj )
{
MStatus status;

MFnPlugin plugin( obj, "Alias", "1.0", "Any");

// Register the context creation command and the tool
// command that the helixContext will use.
//
status = plugin.registerContextCommand(
"helixToolContext", helixContextCmd::creator,
"helixToolCmd", helixTool::creator,
helixTool::newSyntax);
if (!status) {
status.perror("registerContextCommand");
return status;
}

return status;
}

initializePlugin()同时注册command和对应的context。

MStatus uninitializePlugin( MObject obj)
{
MStatus status;
MFnPlugin plugin( obj );

// Deregister the tool command and the context
// creation command.
//
status = plugin.deregisterContextCommand(
"helixToolContext" "helixToolCmd");
if (!status) {
status.perror("deregisterContextCommand");
return status;
}

return status;
}

必须使用和选取框例子中类似的MEL代码把helixTool添加到用户界面。

--------------------------------------------------------------------------------

DAG层级
DAG层级概览
在Maya中,有向无环图(DAG)用来定义如坐标、方向、几何体尺寸等元素。DAG包含两种DAG节点,transforms和shapes。
Transform nodes保存变换信息(平移、旋转、缩放等)和父子关系信息。例如,你有一个手的模型,你只想通过一次变换操作同时完成对手掌和手指的旋转,而不是单独操作它们,这时手掌和手指就共享一个父级transform节点。
Shape nodes参考几何体,不提供父子关系信息和变换信息。
在最简单的情形下,DAG描述了一个几何体如何构成对象实例。例如,当你创建一个球体,你既创造了表示球体本身的shape node,又创造了允许你指定球体位置、大小和旋转的transform node,shape node是transform node的子节点。
节点(Nodes)
transform nodes可拥有多个子节点,这些子节点被组合起来放在transform node之下。对节点的组合允许节点间共享变换信息,并被当作一个单元处理。
实例化
transform node和shape nodes都可以拥有多个父节点,这些节点是已被实例化的。实例化可以减少模型的几何体储存量。例如,当你建一棵树的模型时,你可以创建许多独立的叶子,但这会带来很大的数据量,因为每片叶子都会有自己的transform nodes和shape nodes及NURBS或多边形数据。但是你也可以只创建一片叶子并将它实例化许多次来创建一系列相同的叶子,并把它们各自正确摆放在树枝上,这样树叶的shape node和NURBS或多边形数据就被共享了。

--------------------------------------------------------------------------------

PlugInsDAGhierarchya.jpg
描述:

文件大小: 5.89 KB
看过的: 文件被下载或查看 229 次

--------------------------------------------------------------------------------

返回页首

LEN3D
二星视客

注册时间: 2002-02-10
帖子: 499

视币:643

发表于: 2005-12-31 11:08 发表主题:

--------------------------------------------------------------------------------

图示的DAG层级有三个transform nodes(Transform1, Transform2, Transform3)和一个shape node(Leaf),当Transform3和Leaf被实例化后会显示两片叶子。
Transforms和Shapes
DAG节点是DAG中的简单实体。它可能拥有双亲、兄弟和孩子并掌握它们的信息,但它不必知道变换和几何信息。Transforms和Shapes是从DAG节点派生的两种节点。transform nodes只负责变换(平移、旋转和缩放)而不包含几何信息,shape nodes只负责几何信息而不包含变换。这意味着一个几何体需要两个节点,一个shape node直接位于其父级,一个transform node位于shape node的父级。
例如:
MFnDagNode用来决定节点有哪些父节点。
MFnTransformNode继承自MFnDagNode的用来操作transform nodes的function set,可以获取并设置变换。
MFnNurbsSurface是多种操作不同类型的shape nodes的function set中的一种,也从MFnDagNode派生,有获取和设置曲面CVs等的方法。
DAG路径(paths)
路径通过一系列节点指定某个节点或节点实例在DAG中的唯一位置,这一系列节点从根节点开始,直到目标节点。到同一个目标节点的不同路径对应于一个目标节点的实例。路径的显示形式是从根节点开始的一系列节点的名字,用"|"符号隔开。
DAG路径和API中世界空间的操作
因为DAG路径表示一个shape是如何被插入场景的,所以世界空间的任何操作都要通过DAG路径完成。如果试图用MObject句柄获得某个节点组件的世界空间坐标,只会导致失败,因为没有DAG路径Maya无法决定对象的世界空间坐标,只有DAG路径能唯一指定节点的某个实例。几乎所有能返回MObject的方法都会返回MDagPath句柄用于指明路径。所有MFn类都既可以用MObject又可以用MDagPath构造。用MObject进行世界空间操作会失败,换作MDagPath则会成功。
添加或删除节点
MDagPath用一个节点的堆栈来表示路径,根节点在堆栈底部。push()和pop()方法可以用来添加或删除节点。
注释
这些方法不会改变真正的DAG结构,只会改变MDagPath对象本身。
包含和排除矩阵
因为DAG中的节点存在于不同的DAG层级,所以每个节点上可能有不同的变换积累。MDagPath允许把这些变换通过inclusiveMatrix()和exclusiveMatrix()两种模式返回。
"inclusive matrix"考虑了最后一个节点对变换的影响。
"exclusive matrix"不考虑最后一个节点对变换的影响。
例如,如果一条路径定义如下:

|RootTransform|Transform1|Transform2|Shape

inclusive matrix考虑RootTransform, Transform1和Transform2的影响,而exclusive matrix只考虑RootTransform和Transform1的影响。
为何将shape node添加到DAG路径
在Maya中,物体级别的选择实际上是选择了shape node的父级transform node,当用MGlobal::getActiveSelectionList()访问选择集时,MDagPath只会返回transform node而不是真正的shape。extendToShape()可以方便的把shape node添加到路径末端。
应用于某种MDagPath的function set通过路径的最后一个node获得。如果最后一个node是transform node,就可以获得用来操作MDagPath实例的function set。如果最后一个node是shape node,就可以获得用来操作shape的function set。
唯一的名字
使用DAG路径后对象名字可以重用,只要同名的对象不在相同的父级节点下。

返回页首

--------------------------------------------------------------------------------

Generalized instancing
Maya支持generalized instancing,意即实例化同一个节点的节点不必互为

--------------------------------------------------------------------------------

返回页首

--------------------------------------------------------------------------------

Node 2和Node 4不是兄弟,但它们都实例化Node 3。可以创造更复杂的层级,只要不破坏DAG的无环性质即可。
带有多Shapes的Transforms
一个transform node可以有任意数目的子transform nodes。一般上一个transform node只能有一个子shape node,当通过交互窗口查看DAG时总是可以发现这一点。然而通过API检查DAG时你会发现transform node有多个子shape node,这在原始shape被dependency graph修改后发生。为了保持dependency graph的结果,修改后的shape会被放在和原始shape同一个transform node之下。这时,只有最终的结果会被显示在交互窗口。

--------------------------------------------------------------------------------

|Transform1|Shape1是原始对象,而|Transform1|Shape1a是在交互窗口中真正可见的对象。|Transform1|Shape1也被称为中间对象。这点对后面的dependency graph很重要。
警告
如果你对一个最后一个transform node包含多个子shape node的路径使用MDagPath::extendToShape(),第一个shape node会被添加到路径末端,如果这不是你想要的,建议你用MDagPath::child()和MDagPath::childCount() 方法代替extendToShape()方法。
The Underworld
The "underworld"指的是shape node的参数空间,例如NURBS曲面的UV空间。节点和整个子图都可能定义在这样的underworld空间。
例如,在NURBS曲面上定义一条曲线的transform node和shape node。曲线上的控制点位于曲面的UV空间。指定underworld中节点的路径位于shape node之下。underworld路径的第一个节点定义在shape的参数空间,大多属于transform node。
underworld路径和正常路径的表示类似,都使用"|"符号,不同点是在shape node和underworld路径的根节点间用"->"符号隔开。
例如NURBS曲面上一条曲线的路径完整表示为
|SurfaceTransform|NURBSSurface->UnderworldTransform|CurvesShape。underworlds可以被递归定义,只要一个underworld还有参数空间,就可以有更底层的underworld。
MDagPath包括了访问underworld不同路径的方法。MDagPath::pathCount()返回MDagPath表示的路径总数。在上面的曲面上的曲线的例子中,如果MDagPath表示到curve shape的路径,那么pathCount为2。MDagPath::getPath()既返回underworld又返回3D空间的路径。Path 0总指定3D空间,Path 1指定Path 0后的underworld路径,Path 2指定Path 1后的underworld路径,依此

--------------------------------------------------------------------------------

返回页首

遍历DAG实例
下面的scanDagSyntaxCmd例子示范了如何以深度优先或广度优先的方式遍历DAG,这对文件转换器之类的要遍历DAG的插件编写很有帮助。

class scanDagSyntax: public MPxCommand
{
public:
scanDagSyntax() {};
virtual ~scanDagSyntax();
static void* creator();
static MSyntax newSyntax();
virtual MStatus doIt( const MArgList& );

这是一个简单的例子,所以不提供undoIt()和redoIt()。

private:
MStatus parseArgs( const MArgList& args,
MItDag::TraversalType& traversalType,
MFn::Type& filter, bool & quiet);
MStatus doScan( const MItDag::TraversalType traversalType,
MFn::Type filter, bool quiet);
void printTransformData(const MDagPath& dagPath, bool quiet);
};

scanDagSyntax::~scanDagSyntax() {}

void* scanDagSyntax::creator()
{
return new scanDagSyntax;
}

MSyntax scanDagSyntax::newSyntax()
{
MSyntax syntax;

syntax.addFlag(kBreadthFlag, kBreadthFlagLong);
syntax.addFlag(kDepthFlag, kDepthFlagLong);
syntax.addFlag(kCameraFlag, kCameraFlagLong);
syntax.addFlag(kLightFlag, kLightFlagLong);
syntax.addFlag(kNurbsSurfaceFlag, kNurbsSurfaceFlagLong);
syntax.addFlag(kQuietFlag, kQuietFlagLong);

return syntax;
}

MStatus scanDagSyntax::doIt( const MArgList& args )
{
MItDag::TraversalType traversalType = MItDag::kDepthFirst;
MFn::Type filter = MFn::kInvalid;
MStatus status;
bool quiet = false;

DAG迭代器可以被设定为只访问特定的类型,如果用MFn::kInvalid模式,则访问所有DAG节点。

status = parseArgs ( args, traversalType, filter, quiet );
if (!status)
return status;

return doScan( traversalType, filter, quiet);
};

doIt()方法简单的调用一些做真正工作的辅助方法。
MStatus scanDagSyntax::parseArgs( const MArgList& args,
MItDag::TraversalType& traversalType,
MFn::Type& filter,
bool & quiet)
{
MStatus stat;
MArgDatabase argData(syntax(), args);

MString arg;

if (argData.isFlagSet(kBreadthFlag))
traversalType = MItDag::kBreadthFirst;
else if (argData.isFlagSet(kDepthFlag))
traversalType = MItDag::kDepthFirst;

if (argData.isFlagSet(kCameraFlag))
filter = MFn::kCamera;
else if (argData.isFlagSet(kLightFlag))
filter = MFn::kLight;
else if (argData.isFlagSet(kNurbsSurfaceFlag))
filter = MFn::kNurbsSurface;

if (argData.isFlagSet(kQuietFlag))
quiet = true;

return stat;
}

DAG迭代器能以深度优先或广度优先的方式访问DAG。这个例子只访问cameras, lights和NURBS surfaces,但事实上可以访问MFn::Type中的任何类型。

MStatus scanDagSyntax::doScan( const MItDag::TraversalType traversalType,
MFn::Type filter,
bool quiet)
{
MStatus status;

MItDag dagIterator( traversalType, filter, &status);
if ( !status) {
status.perror("MItDag constructor");
return status;
}
// Scan the entire DAG and output the name and depth of each node

if (traversalType == MItDag::kBreadthFirst)
if (!quiet)
cout << endl << "Starting Breadth First scan of the Dag";
else
if (!quiet)
cout << endl << "Starting Depth First scan of the Dag";

广度优先遍历意思是先访问兄弟再访问孩子,深度优先遍历先访问孩子再访问兄弟。

switch (filter) {
case MFn::kCamera:
if (!quiet)
cout << ": Filtering for Cameras\n";
break;
case MFn::kLight:
if (!quiet)
cout << ": Filtering for Lights\n";
break;
case MFn::kNurbsSurface:
if (!quiet)
cout << ": Filtering for Nurbs Surfaces\n";
break;
default:
cout << endl;
}

int objectCount = 0;
for ( ; !dagIterator.isDone(); dagIterator.next() ) {

MDagPath dagPath;

status = dagIterator.getPath(dagPath);
if ( !status ) {
status.perror("MItDag::getPath");
continue;
}

MItDag::getPath()获取迭代器的当前对象的参考。DAG路径可提供给function set以操作对象。不推荐用迭代器重新排列DAG。

MFnDagNode dagNode(dagPath, &status);
if ( !status ) {
status.perror("MFnDagNode constructor");
continue;
}

if (!quiet)
cout << dagNode.name() << ": " << dagNode.typeName() << endl;

if (!quiet)
cout << " dagPath: " << dagPath.fullPathName() << endl;

objectCount += 1;
if (dagPath.hasFn(MFn::kCamera)) {

这里检查当前对象是否为摄像机,是则输出摄像机信息。

MFnCamera camera (dagPath, &status);
if ( !status ) {
status.perror("MFnCamera constructor");
continue;
}

// Get the translation/rotation/scale data
printTransformData(dagPath, quiet);

// Extract some interesting Camera data
if (!quiet)
{
cout << " eyePoint: "
<< camera.eyePoint(MSpace::kWorld) << endl;
cout << " upDirection: "
<< camera.upDirection(MSpace::kWorld) << endl;
cout << " viewDirection: "
<< camera.viewDirection(MSpace::kWorld) << endl;
cout << " aspectRatio: " << camera.aspectRatio() << endl;
cout << " horizontalFilmAperture: "
<< camera.horizontalFilmAperture() << endl;
cout << " verticalFilmAperture: "
<< camera.verticalFilmAperture() << endl;
}
} else if (dagPath.hasFn(MFn::kLight)) {

若对象为灯光,则输出灯光信息。

MFnLight light (dagPath, &status);
if ( !status ) {
status.perror("MFnLight constructor");
continue;
}

// Get the translation/rotation/scale data
printTransformData(dagPath, quiet);

// Extract some interesting Light data
MColor color;

color = light.color();
if (!quiet)
{
cout << " color: ["
<< color.r << ", "
<< color.g << ", "
<< color.b << "]\n";
}
color = light.shadowColor();
if (!quiet)
{
cout << " shadowColor: ["
<< color.r << ", "
<< color.g << ", "
<< color.b << "]\n";

cout << " intensity: " << light.intensity() << endl;
}
} else if (dagPath.hasFn(MFn::kNurbsSurface)) {

若对象为NURBS曲面,则输出其信息。
MFnNurbsSurface surface (dagPath, &status);
if ( !status ) {
status.perror("MFnNurbsSurface constructor");
continue;
}

// Get the translation/rotation/scale data
printTransformData(dagPath, quiet);

// Extract some interesting Surface data
if (!quiet)
{
cout << " numCVs: "
<< surface.numCVsInU()
<< " * "
<< surface.numCVsInV()
<< endl;
cout << " numKnots: "
<< surface.numKnotsInU()
<< " * "
<< surface.numKnotsInV()
<< endl;
cout << " numSpans: "
<< surface.numSpansInU()
<< " * "
<< surface.numSpansInV()
<< endl;
}
} else {

为其它类型,则只输出变换信息。

// Get the translation/rotation/scale data
printTransformData(dagPath, quiet);
}
}

if (!quiet)
{
cout.flush();
}
setResult(objectCount);
return MS::kSuccess;
}

void scanDagSyntax::printTransformData(const MDagPath& dagPath, bool quiet)
{

该方法用于打印DAG节点的变换信息。

MStatus status;
MObject transformNode = dagPath.transform(&status);
// This node has no transform - i.e., it's the world node
if (!status && status.statusCode () == MStatus::kInvalidParameter)
return;
MFnDagNode transform (transformNode, &status);
if (!status) {
status.perror("MFnDagNode constructor");
return;
}
MTransformationMatrix matrix (transform.transformationMatrix());

if (!quiet)
{
cout << " translation: " << matrix.translation(MSpace::kWorld)
<< endl;
}
double threeDoubles[3];
MTransformationMatrix::RotationOrder rOrder;

matrix.getRotation (threeDoubles, rOrder, MSpace::kWorld);
if (!quiet)
{
cout << " rotation: ["
<< threeDoubles[0] << ", "
<< threeDoubles[1] << ", "
<< threeDoubles[2] << "]\n";
}
matrix.getScale (threeDoubles, MSpace::kWorld);
if (!quiet)
{
cout << " scale: ["
<< threeDoubles[0] << ", "
<< threeDoubles[1] << ", "
<< threeDoubles[2] << "]\n";
}
}

MStatus initializePlugin( MObject obj )
{
MStatus status;

MFnPlugin plugin ( obj, "Alias - Example", "2.0", "Any" );
status = plugin.registerCommand( "scanDagSyntax",
scanDagSyntax::creator,
scanDagSyntax::newSyntax );

return status;
}

MStatus uninitializePlugin( MObject obj )
{
MStatus status;

MFnPlugin plugin( obj );
status = plugin.deregisterCommand( "scanDagSyntax" );

return status;
}

Dependency graph插件
Dependency graph插件概览
Dependency graph是Maya的中心,它被用在动画和构造记录中。你可以为它添加新的节点来支持全新的操作。
父类描述
可以从12个父类派生新节点,这些类是:
MPxNode 允许创建新的dependency node,从最基本的DG节点派生,无继承行为。
MPxLocatorNode 允许创建新的locator node,这是DAG对象,不能渲染,但可以绘制在3D视图中。
MPxIkSolverNode 允许创建新类型的IK解析器。
MPxDeformerNode 允许创建新的变形器。
MPxFieldNode 允许创建新类型的动态场。
MPxEmitterNode 允许创建新类型的动态发射器。
MPxSpringNode 允许创建新类型的动态弹簧。
MPxManipContainer 允许创建新类型的操纵器。
MPxSurfaceShape 允许创建新的DAG对象,经常用来创建新类型的shape,也可以用在其它方面。
MPxObjectSet 允许创建新类型的集合。
MPxHwShaderNode 允许创建新类型的硬件着色器。
MPxTransform 允许创建新类型的变换矩阵。
基本例子
下面的例子是简单的用户定义的DG节点,输入浮点数,输出该数的正弦值。

#include <string.h>
#include <iostream.h>
#include <math.h>

#include <maya/MString.h>
#include <maya/MFnPlugin.h>

这还是一个插件所以需要MFnPlugin.h,但用不同的方法注册节点。

#include <maya/MPxNode.h>
#include <maya/MTypeId.h>
#include <maya/MPlug.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>

大多插件DG节点都要用到这些头文件。

#include <maya/MFnNumericAttribute.h>

有多种不同的attributes,你需要什么依赖于你的节点类型,在本例中只用到数字数据。

class sine : public MPxNode
{

从MPxNode派生用户定义DG节点。

public:
sine();

每当该节点实例被创建时都会调用构造函数,可能发生在createNode命令被调用,或MFnDependencyNode::create()被调用等时候。

virtual ~sine();

析构函数只在节点真正被删除时被调用。由于Maya的undo队列,删除节点并不真正调用析构函数,因此如果删除被撤销,就可以不重新创建节点而直接恢复它。一般只当undo队列被清空时已删除的节点的析构函数才会被调用。

virtual MStatus compute( const MPlug& plug,
MDataBlock& data );

compute()是节点的关键部分,用来真正通过节点的输入产生输出。

static void* creator();

creator()方法的职责和command中的creator一样,它允许Maya创建节点实例。每次需要新节点实例时它都可能由createNode或MFnDependencyNode::create()方法。

static MStatus initialize();

initialize()方法由注册机制在插件被载入时立即调用,用来定义节点的输入和输出(如attributes)。

public:
static MObject input;
static MObject output;

这两个MObject是正弦节点的attributes,你可以给节点的attributes起任何名字,这里用input和output只是为了清晰。

static MTypeId id;

每个节点都需要一个唯一的标识符,用在MFnDependencyNode::create()中以指明创建哪个节点,并用于Maya文件格式。
局部测试中你可以使用任何介于0x00000000和0x0007ffff之间的标识符,但对于任何想永久使用的节点,你应该从Alias技术支持获得唯一的id。

};

MTypeId sine::id( 0x80000 );

初始化节点标识符。

MObject sine::input;
MObject sine::output;

初始化attributes为NULL。

void* sine::creator() {
return new sine;
}

这里creator()方法简单返回新节点实例。在更复杂的情况中可能需要相互连接很多节点,可以只定义一个creator来分配和连接所有节点。

MStatus sine::initialize() {

MFnNumericAttribute nAttr;

本例只用到数字数据所以只需要MFnNumericAttribute。

output = nAttr.create( "output", "out",
MFnNumericData::kFloat, 0.0 );
nAttr.setWritable(false);
nAttr.setStorable(false);

定义输出attribute。定义attribute时你必须指明一个长名字(大于等于四个字符)和一个短名字(小于等于三个字符)。这些名字用在MEL脚本和UI编辑器中以区别特殊的attributes。除特殊情况外,建议attribute的长名字和其C++中的名字相同,本例中都取为"output"。
create方法还指明了attribute的类型,这里为浮点数(MFnNumericData::kFloat)并初始化为0,a  评论这张


 
 
 
 
 
 

原创粉丝点击