???????? 在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?attribute ??
- ????static??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?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?attribute ??
- ????nAttr.setWritable(false);??
- ????//?Attribute?will?not?be?written?to?files?when?this?type?of?node?is?stored ??
- ????nAttr.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 歡迎交流。