MAYA API插件编程--入门篇

来源:互联网 发布:澳大利亚迪肯大学知乎 编辑:程序博客网 时间:2024/04/30 01:23

MAYA API插件编程--入门篇

                                                                                                      作者:华文广          日期:2010.11.28

 

         我们知道,MAYA是一个基于结点的插件式软件架构,这种开放式的软件架构是非常优秀的,它可以让用户非常方便地在其基础上开发一些自已想要的插件,从而实现一些特殊的功能或效果。

         MAYA上开发自已的插件,你有3种选择,第一种是使用MEL语言开发脚本插件,使用MEL语言来编插件的最大优点是方便易学,MEL代码在MAYA上直接可以运行,不用任何辅助工具,在MAYA2008之前,MAYA的整个界面都是用MEL语言来写的,可见MEL语言也足够的强大,但是毕竟它是一个解析型的脚本言语,而且是一种面向过程的语言,因此,当我人要实现一些高性能的,或者是一些代码量非常大,对像关系非常复杂的功能时,MEL语言就是显得有点力不从心。这时候,你就有了第二种选择,基于C++语言的MAYA API插件,API插件的最大优点是高效,一般来说,用C++来写的API插件要比MEL语言插件运行速度要快10倍以上,试想一下,你如果要对一个有100万面的模型的每条边逐一搜索,用MEL来做,肯定要处理很长时间,但是用C++则可以非常轻松实现,可以说,MAYA的核心就是C++OpenGL构建起来的。但是API插件,也有它的缺点,最大的缺点就是用必须要用C++编程,而C++又偏偏是公认的最难学的语言之一,很多计算机专科毕业的人对它都是一知半解,所以对于多数的美术制作人来说,也只能望而却步了。当然,在MAYA 2008之后,我们又有了第三个选择,那就是Python,这是一个在MELC++之间的折中选择,Python本身它是一种脚本语言,因此它也可以和MEL一样直接在MAYA窗口运行,而且也比较好学,同时时呢,它又拥有C++的面向对像的特性,因此呢,你可以用Python来开发足够复杂的程序。

         可见三种方案,各有所长,没有最好,只有最适合,选用哪种方案,得视实际的需求来定夺。在这里,我详细说一下用如何C++来编写MAYA API插件,只为有这方面需求的朋友提供一个入门级的帮助,当然,前提是你要会C++编程。我们选用的编译环境是Maya 2010Microsoft Visual Studio 2005,要编写MAYA API插件就得用到MAYA的开发包,没默认情况下,MAYA SDK会随MAYA程序一起被安装到相应目录下,如:D:/Program Files/Autodesk/Maya2010/includeD:/Program Files/Autodesk/Maya2010/lib,对于入门的朋友,可以使用MAYA API插件编程向导MayaPluginWizard2.0,这个向导能快速地都你在VS2005上搭建插件编程框架。打开文件夹D:/Program Files/Autodesk/Maya2010/devkit/pluginwizard,里面有安装说明,安步骤把MayaPluginWizard2.0.zip安装到VS2005中去。值得注意的是Maya 2010的插件工程向导是基于VS2005的,你如果用的是VS2008或其它VS编译器,这个向导安装上去可能没法正确运行,这是因为版本识别问题,你可以用记事本把文件文件MayaPluginWizard.vszMayaPluginWizard/Templates/1033/plugin.vcproj打开,把里面的8.0改为9.0,就可以在VS2008中运行了。

         如果向导工具安装成功,打开VS2005的新建工程向导,我们可以看到以下的界面

mayaWizrad.png

我们选择MayaPluginWizard来新建一个项目:

mayaWizrad2

默认情况下,developer Kit location是指向C盘的,如果你的MAYA安装在基它地方,则需要指定相应的MAYA安装路径:

mayaWizrad3

我们首先来创建一个最简单的MAYA插件,就是一个不带Undo/Redo功能的maya命令。

Finish之后,工程就创建好了,代码很简单,整个工程只有一个CPP文件,代码如下:

 

#include<maya/MSimple.h>

DeclareSimpleCommand( sayHello,"", "2010");

MStatus sayHello::doIt(const MArgList& args )

//  Return Value:

//      MS::kSuccess - command succeeded

//      MS::kFailure - command failed (returning this value will cause the

//                    MEL script that is being run to terminate unless the

//                    error is caught using a "catch" statement.

//

{

    MStatus stat = MS::kSuccess;

    displayInfo("Hello World!");

    // Since this class is derived off of MPxCommand, you can use the

    // inherited methods to return values and set error messages

    //

    setResult( "sayHello command executed!/n" );

    return stat;

}

我们在doIt()函数中加入一行:displayInfo("Hello World!");

这个对于程序员来说近乎圣经般入门代码。然后进行编译,如果一切顺利,在我们工程的Debug文件夹中就生成了一个叫sayHello.mll文件,这就是一个MAYA插件了,安装MAYA插件也挺简单,把sayHello.mll文件拷贝到D:/Program Files/Autodesk/Maya2010/bin/plug-ins目录下,然后重新打开maya2010,从菜单window->settings/preferences->Plug-In Manager打开插件加载窗口:

loadPlugin

把我们的sayHello.mll插件加载进来,然后在我们的maya命令行窗口中输入sayHello;命令对插件进行测试。

sayHelloCmd

久违的Hello World!问候最终是成功地显示。

MAYA的插件大体上分为两大类型,命令(Command)和结点(Node),多数情况下,命令都是为结点服务的,下面我们来说一下如何编写一个简单的Maya结点。那么什么maya的结点呢?我们可以把结点想像为一个数据流处理器,每个结点,它都有输入接口,输出接口,及对数据进行处理的内核,如图:

node
我们说MYAY是基于结点的插件式软件架构,所以在MAYA底层,对所有的数据都是通过把大量这的结点连接起来,一层层地进行运算和处理才得到最终的结果。这种基于结点的软件架构,其最大的好处就是制作人员可以根据需求把各种随意地连接起来,从而实现让制作人员可以最大限度在发挥自已的想像空间和创意能力。

        下面我们来实现一个功能简单的结点,该结点只有一个输入接口和一个输出接口(注:一个结点可以有多个输入接口和输出接口),要实现的功能是把输入的数据乘以0.5变成原来的一半,然后输出。

        打开MayaPluginWizard,新建一个Dependency Graph Node插件

halfScaleNode

 

 

//// Copyright (C) // // File: pluginMain.cpp//// Author: Maya Plug-in Wizard 2.0//#include "halfScaleNodeNode.h"#include <maya/MFnPlugin.h>MStatus initializePlugin( MObject obj )////Description://this method is called when the plug-in is loaded into Maya.  It //registers all of the services that this plug-in provides with //Maya.////Arguments://obj - a handle to the plug-in object (use MFnPlugin to access it)//{ MStatus   status;MFnPlugin plugin( obj, "", "2010", "Any");status = plugin.registerNode( "halfScaleNode", halfScaleNode::id, halfScaleNode::creator,  halfScaleNode::initialize );if (!status) {status.perror("registerNode");return status;}return status;}MStatus uninitializePlugin( MObject obj)////Description://this method is called when the plug-in is unloaded from Maya. It //deregisters all of the services that it was providing.////Arguments://obj - a handle to the plug-in object (use MFnPlugin to access it)//{MStatus   status;MFnPlugin plugin( obj );status = plugin.deregisterNode( halfScaleNode::id );if (!status) {status.perror("deregisterNode");return status;}return status;}


 

halfScaleNodeNode.h

#ifndef _halfScaleNodeNode#define _halfScaleNodeNode//// Copyright (C) // // File: halfScaleNodeNode.h//// Dependency Graph Node: halfScaleNode//// Author: Maya Plug-in Wizard 2.0//#include <maya/MPxNode.h>#include <maya/MFnNumericAttribute.h>#include <maya/MTypeId.h>  class halfScaleNode : public MPxNode{public:halfScaleNode();virtual~halfScaleNode(); virtual MStatuscompute( const MPlug& plug, MDataBlock& data );static  void*creator();static  MStatusinitialize();public:// There needs to be a MObject handle declared for each attribute that// the node will have.  These handles are needed for getting and setting// the values later.//static  MObjectinput;// Example input attributestatic  MObjectoutput;// Example output attribute// The typeid is a unique 32bit indentifier that describes this node.// It is used to save and retrieve nodes of this type from the binary// file format.  If it is not unique, it will cause file IO problems.//staticMTypeIdid;};#endif


 

halfScaleNodeNode.cpp

 

//// Copyright (C) // // File: halfScaleNodeNode.cpp//// Dependency Graph Node: halfScaleNode//// Author: Maya Plug-in Wizard 2.0//#include "halfScaleNodeNode.h"#include <maya/MPlug.h>#include <maya/MDataBlock.h>#include <maya/MDataHandle.h>#include <maya/MGlobal.h>// You MUST change this to a unique value!!!  The id is a 32bit value used// to identify this type of node in the binary file format.  ////#error change the following to a unique value and then erase this line MTypeId     halfScaleNode::id( 0x02010 );// Example attributes// MObject     halfScaleNode::input;        MObject     halfScaleNode::output;       halfScaleNode::halfScaleNode() {}halfScaleNode::~halfScaleNode() {}MStatus halfScaleNode::compute( const MPlug& plug, MDataBlock& data )////Description://This method computes the value of the given output plug based//on the values of the input attributes.////Arguments://plug - the plug to compute//data - object that provides access to the attributes for this node//{MStatus returnStatus; // Check which output attribute we have been asked to compute.  If this // node doesn't know how to compute it, we must return // MS::kUnknownParameter.// if( plug == output ){// Get a handle to the input attribute that we will need for the// computation.  If the value is being supplied via a connection // in the dependency graph, then this call will cause all upstream  // connections to be evaluated so that the correct value is supplied.// MDataHandle inputData = data.inputValue( input, &returnStatus );if( returnStatus != MS::kSuccess )MGlobal::displayError( "Node halfScaleNode cannot get value\n" );else{// Read the input value from the handle.//float result = inputData.asFloat();result *= 0.5;// Get a handle to the output attribute.  This is similar to the// "inputValue" call above except that no dependency graph // computation will be done as a result of this call.// MDataHandle outputHandle = data.outputValue( halfScaleNode::output );// This just copies the input value through to the output.  // outputHandle.set( result );// Mark the destination plug as being clean.  This will prevent the// dependency graph from repeating this calculation until an input // of this node changes.// data.setClean(plug);}} else {return MS::kUnknownParameter;}return MS::kSuccess;}void* halfScaleNode::creator()////Description://this method exists to give Maya a way to create new objects//      of this type. ////Return Value://a new object of this type//{return new halfScaleNode();}MStatus halfScaleNode::initialize()////Description://This method is called to create and initialize all of the attributes//      and attribute dependencies for this node type.  This is only called //once when the node type is registered with Maya.////Return Values://MS::kSuccess//MS::kFailure//{// This sample creates a single input float attribute and a single// output float attribute.//MFnNumericAttribute nAttr;MStatusstat;input = nAttr.create( "input", "in", MFnNumericData::kFloat, 0.0 );// Attribute will be written to files when this type of node is stored nAttr.setStorable(true);// Attribute is keyable and will show up in the channel box nAttr.setKeyable(true);output = nAttr.create( "output", "out", MFnNumericData::kFloat, 0.0 );// Attribute is read-only because it is an output attributenAttr.setWritable(false);// Attribute will not be written to files when this type of node is storednAttr.setStorable(false);// Add the attributes we have created to the node//stat = addAttribute( input );if (!stat) { stat.perror("addAttribute"); return stat;}stat = addAttribute( output );if (!stat) { stat.perror("addAttribute"); return stat;}// Set up a dependency between the input and the output.  This will cause// the output to be marked dirty when the input changes.  The output will// then be recomputed the next time the value of the output is requested.//stat = attributeAffects( input, output );if (!stat) { stat.perror("attributeAffects"); return stat;}return MS::kSuccess;}


 

 

我们可以看到,现在的工程代码比刚才的命令插件要复杂多了,除了有对应的halfScaleNode.cpphalfScaleNode.h文件之外,还有一个pluginMain.cpp文件,这是每个MAYA插件的入口,MAYA在加载该结点的时候,会自动运行initializePlugin( MObject obj )函数,因此我们就可以在这里做一些初始化的操作,其中最重要的就是要注册这个maya结点。

status = plugin.registerNode("halfScaleNode", halfScaleNode::id, halfScaleNode::creator,

                                      halfScaleNode::initialize );

if (!status) {

    status.perror("registerNode");

    return status;

}

所有自定义的maya结点及命令,都必须在初始化的时候注册,才能被MAYA识别和使用。注册的时候我们要注意的是,新的结点名和结点ID不能与已有的结点冲突,也就是说结点名和结点ID在整个MAYA系统中都必须是唯一的。所以在halfScaleNodeNode.cpp文件的开始有这样一个定义结点ID的代码

// You MUST change this to a unique value!!! The id is a 32bit value used

// to identify this type of node in the binary file format. 

#error change the following to a unique value and then erase this line

MTypeId    halfScaleNode::id( 0x00001 );

这里就提示我们必须要给该结点分配一个唯一的ID,要不就没法通过编译。我们可以把以上代码改为

//#error change the following to a unique value and then erase this line

MTypeId    halfScaleNode::id( 0x02010 );

这样我们就可以正常地编译代码了。

 

接下来我们来详细说一下每个结点的核心运算函数:compute(const MPlug& plug, MDataBlock& data );前面我们说过,一个结点是由输入接口、输出接口及运算核心组成,这里的运算核心就是compute()函数,而输入输出接口则被封装在MDataBlock& data这个对像里面,我们通过相应的函数,就可以取得输入口和输出口所对应的地址,然后对这些数据进行操作:

MDataHandle inputData = data.inputValue( input, &returnStatus );

MDataHandle outputHandle = data.outputValue( halfScaleNode::output );

 

float result = inputData.asFloat();

这里,result所得到的就是输入数据的原始值,如果我们不作任何运算,直接把它赋值给输出接口

outputHandle.set( result );

那么得到的输出结果,也不会有任何改变。现在我们把输入数据乘以0.5然后再赋给输出接口:

float result = inputData.asFloat();

result = result * 0.5;

outputHandle.set( result );

很容易地,我们就达到了我们所想要实现的功能。把工程编译一下,得到一个叫halfScaleNode.mllMAYA插件,和前面所说的安装方式一样,我们把halfScaleNode.mll拷贝到plug-in文件夹中,然后在MAYA插件管理器中加载该插件。

我们来验检一下该结点插件是否能正确运行。我们要在maya场景中建两个小球,在命令窗口中输入并运行以下mel代码:

polySphere;

createNode halfScaleNode;

connectAttr halfScaleNode1.output pSphere2.translateX;

scenescene2

 

hypergraph

从超图上我们可以后清晰地看到,pSphere1translateX属性被连接到halfScaleNode1input输入口,经过运算后,输出给pSphere2translateX属性。现在我们选择pSphere1然后沿X轴平称,我们可以看到,pSphere2会跟随pSphere1一起移动,但总是慢半拍,这正是我们想要的效果。

    这样,一个简单的MAYA插件也就完成了。从上面的操作,我们也可以看到,一般一就,单独一个MAYA结点,如果用手工,是很难被正确连接起来的,所以多数情况下,结点插件都会结合mel脚本或API命令来一起使用。

    以上,只是简单地介绍了编写MAYA API插件的入门知识,在实际应用中,一个MAYA API插件要比这个复杂得多,一个MAYA结点,它可以包括多个接口,而每个接口可以是各种类型的参数,如,浮点、整型、向量、矩阵等等,甚至可以是一个mesh对像或是一个二维数组。这些,我们都可以在每个结点的initialize()函数中生成和指定。

    为了管理上的方便,我们可以在一个MAYA API插件中包含多个结点和命令,也就是说一个mll文件中可能有多个nodecommand,我们只要把它们都在pluginMain.cpp中的MStatus initializePlugin( MObject obj )函数进行注册就可以了。

有问题可以给我发邮件 huawenguang@sina.com欢迎交流。