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,这是一个在MEL与C++之间的折中选择,Python本身它是一种脚本语言,因此它也可以和MEL一样直接在MAYA窗口运行,而且也比较好学,同时时呢,它又拥有C++的面向对像的特性,因此呢,你可以用Python来开发足够复杂的程序。

可见三种方案,各有所长,没有最好,只有最适合,选用哪种方案,得视实际的需求来定夺。在这里,我详细说一下用如何C++来编写MAYA API插件,只为有这方面需求的朋友提供一个入门级的帮助,当然,前提是你要会C++编程。我们选用的编译环境是Maya 2010和Microsoft Visual Studio 2005,要编写MAYA API插件就得用到MAYA的开发包,没默认情况下,MAYA SDK会随MAYA程序一起被安装到相应目录下,如:D:/Program Files/Autodesk/Maya2010/include和D:/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.vsz及MayaPluginWizard/Templates/1033/plugin.vcproj打开,把里面的8.0改为9.0,就可以在VS2008中运行了。

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

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

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

我们首先来创建一个最简单的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打开插件加载窗口:

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

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

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


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

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

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

//
// 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 MStatus       compute( const MPlug& plug, MDataBlock& data );static  void*        creator();static  MStatus       initialize();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  MObject     input;      // Example input attributestatic  MObject       output;     // 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.//static  MTypeId     id;
};#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;MStatus                stat;input = nAttr.create( "input", "in", MFnNumericData::kFloat, 0.0 );// Attribute will be written to files when this type of node is storednAttr.setStorable(true);// Attribute is keyable and will show up in the channel boxnAttr.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.cpp和halfScaleNode.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.mll的MAYA插件,和前面所说的安装方式一样,我们把halfScaleNode.mll拷贝到plug-in文件夹中,然后在MAYA插件管理器中加载该插件。

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

polySphere;

createNode halfScaleNode;

connectAttr halfScaleNode1.output pSphere2.translateX;

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

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

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

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

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

MAYA API插件编程--入门篇相关推荐

  1. 初识Django —Python API接口编程入门

    初识Django -Python API接口编程入门 一.WEB架构的简单介绍 Django是什么? Django是一个开放源代码的Web应用框架,由Python写成.我们的目标是用Python语言, ...

  2. python机器人视觉编程——入门篇(下)

    目录 1 全篇概要 2 图像的读取与运算基础 2.1图像的读取 2.1.1 从磁盘的图像(.jpg,.npg,.gif等等)读取 2.1.2 从摄像头里读取图像 2.2图像的运算 2.2.1 图像的数 ...

  3. ​​Python少儿编程入门篇(2)算术运算和赋值运算

    Python少儿编程小课堂(二) 入门篇(2)算术运算和赋值运算 标识符 就是一个名字,就好像我们每个人都有自己的名字一样,主要作用就给变量.函数.类.模块以及其他对象起名字. 命名规则 1. 标识符 ...

  4. Python少儿编程入门篇(3)比较运算和逻辑运算

    Python少儿编程小课堂(三) 入门篇(3)比较运算和逻辑运算 运算符(2) 上一节课讲了算术运算符和赋值运算符,本节继续讲其它运算符: 比较运算符 ==.!=.>.<.>= .& ...

  5. 编程入门篇之零基础入门(通用)

    为什么写这一篇? 编程一途,最难的莫过于入门.如果你想学,那么现在就开始,编程远没有想象中那么复杂. 从变量说起 2x + 1 = y 这是一个数学中的二元一次方程,其中x和y就是变量,在编程中的变量 ...

  6. python机器人视觉编程——入门篇(上)

    目录 1 全篇概要(主要阅读对象及内容提要) 2 python知识点之--环境及依赖的库安装简述 2.1 Python开发环境安装 2.2 Python 机器视觉模块安装 2.3 写第一个Python ...

  7. Chrome拓展(插件)入门篇

    学习三部曲: 第一步:WHAT 是什么? 第二步:HOW 怎么实现? 第三步:WHY 为什么这么实现? 本篇介绍了拓展基本概念.配置清单manifest.json以及清单中的action字段 什么是扩 ...

  8. python连接eplan的api_EPLAN API开发之入门篇

    EPLAN P8 API开发入门 运行Microsoft Visual Studio并新建一个C#类库项目,如下图所示 新建项目窗口上部选择.NET版本,并设置好项目名称和路径. 重命名C#源文件名为 ...

  9. Cookie编程入门篇

    Cookie就是所谓的" 小甜饼" ,他最早出现是在Netscape Navigator .0中.Cookie其实就是由Web服务器创建的.将信息存储在计算机上的文件.那么为什么W ...

最新文章

  1. Ubuntu 查看隐藏的文件
  2. Python Twisted系列教程16:Twisted 进程守护
  3. Java提高篇——Java 异常处理
  4. 实现WIFI客户端上网设置说明
  5. python爬虫在哪里学_学完了python基础想学习python爬虫从哪里学起呢?
  6. 51cto mysql下载_安装MySQL
  7. akka应用_处理Akka应用程序中的每个事件
  8. 【LeetCode - 1765】. 地图中的最高点
  9. h.264视频文件封装
  10. mysql 常用数据库连接池_常见的数据库连接池
  11. 动手学深度学习(PyTorch实现)(十一)--GoogLeNet模型
  12. 运筹学与计算机知识,计算机、数学、运筹学等领域的36个重要算法
  13. TensorRT加速ENet
  14. Java-web下使用RSA进行加密解密操作
  15. linux内存管理方式,简要概括Linux内存管理的方式
  16. 这款完全开源可自主DIY的小程序商城太强大了,直接可给客户搭建赚米
  17. matlab绘图工具
  18. mac安装homebrew失败的处理方法
  19. html屏幕3分,使用CSS3的background-size优化苹果的Retina屏幕的图像显示
  20. 阿里云相关——高速通道

热门文章

  1. svg+js走路吹泡泡动画js特效代码
  2. leaflet 加载GPX数据,显示图形(示例代码048)
  3. 3dmax导入Sketchup 模型位置错乱的解决方法
  4. 深入理解C# 中 “$” 符号的作用以及用法
  5. links for 2007-09-12
  6. 基于EasyX的贪吃蛇游戏(C语言)
  7. 会计实务:用友U8各种采购暂估处理会计分录案例
  8. 网络安全应急响应具体操作流程
  9. 怎么看删掉的聊天记录?微信删掉的聊天记录怎么恢复?
  10. 国货之光—白象方便面