一半君的总结纸

听话只听一半君

#95 MEL初学者简介

只要你在用Maya你就能在各处看到MEL的身影, 他是Maya的嵌入式脚本语言. Maya 的“任何”(严格的说并不是任何)操作都会在MEL脚本编辑器里显示出来,不管是简单的对物体的选择, 调整Soft Body 的权重(weights)亦或是编写一个 shader network. 你立马就能感受到要想深入使用Maya的各种功能,你必须得熟练掌握MEL(这话现在说的好像已经过时了).

注意: 此文原作写于1998年11月. Maya 到现在变化已经很大了, 如果下面的信息有过时或者不准确的话. 你们可以发电邮到: maya@ewertb.com 来告诉我(原作者已经不知所踪,所以有问题的话你只能问lz了,而lz很快也就会挂了,所以你们只能靠自己了)

使用 MEL 脚本(Scripts)

毫无疑问你们做项目的时候肯定会需要写一些 MEL scripts 的. 有很多作者很无私的把自己写的script共享给所有人免费用,如果你想用别人编的script,首先你得把他们安装到一个Maya能找到他们的地方.

MEL scripts 通常需要被复制到Maya home目录下的 ‘scripts’ 目录. IRIX 系统下在:

/usr/people/maya/scripts/

Under NT this is:

C:\Users\屌丝男\Documents\maya\scripts\

Maya 不会扫描这个目录下的任何子目录. 除了这个目录以外,你还可以指定自己的用来放MEL scripts 的文件夹,只需要设置 MAYA_SCRIPT_PATH 环境变量(environment variable)即可.

如果你想写 MEL scripts ,首先你得知道他们是怎样被Maya调用的. 当你第一次运行一个MEL命令的时候, Maya 首先逐个扫描你的脚本文件夹,尝试寻找一个和你输入的MEL命令同名的.mel文件. 举个例子, 如果你输入了 ‘showOnlySelected’ Maya 会首先去找 ‘showOnlySelected.mel.’ 在这个文件里应该有个具有同样名字的全局函数( global procedure ); 如果你打开这个.mel文件,他看起来应该是这样的:

global proc showOnlySelected()
{
   // MEL commands for this script go here
}

只有当上述条件全部满足的时候, Maya才能运行命令showOnlySelected.

当这个script 被装载(loaded)之后, Maya 下次再运行他就是从内存里找到他然后运行他的. 如果这个script 已经被loaded而你去修改了.mel文件的内容,你必须告诉Maya重新load这个.mel文件,方法是使用 ‘source’ 命令:

source showOnlySelected;

上面的命令里你不需要加上 ‘.mel’ 扩展名. Maya 知道你是要source一个 MEL script 文件.

当编你自己的MEL Script的时候, 有以下几点注意事项:

  1. Script 的主函数(main function)必须是一个 global procedure ,像这样 ‘global proc <function_name>’.
  2. .mel 文件也就是script的名字必须得和你的main function一样. 如果你的 main function 叫做 ‘global proc makeBingo()’ ,那你必须把你的script命名成 ‘makeBingo.mel’.
  3. 你想加其他多少个函数( functions)都可以,但是尽在需要的时候才把proc声明成 ‘global’ 的 — 全局函数(global functions)会使用更多的资源. 还有如果一个函数( procedure )需要被某个工具的界面(UI)调用(比如当你按某个UI上的按钮运行一个proc的时候),那这个proc(edure)必须得是一个 global procedure.
  4. 其实上面说的3的前半句在今天lz觉得根本不是问题,就算你随便乱定义一堆global proc 也不会卡的,关键有个namespace pollution的问题,换句话说就是你搞太多global proc之后很可能有重名的proc, 比如你在a.mel里有个global getName proc,同时在b.mel里也有个global getName proc, 视你当时source a或者b的先后而定,当你运行getName();的时候,有可能你运行的是a.mel里的,也有可能是b.mel里的。而不要随意定义一堆没必要的global proc将会很大程度上避免这一点,因为不带global的proc只有在那个.mel文件里的其他proc才能看到,.mel外面的其他mel文件里的proc是看不到他的。

得到当前选择的所有物体

这可能是你最常用的一个MEL命令了. 大部分 MEL scripts 都需要作用在使用者当前选择的物体上. 你的 scripts 通常需要知道当前选择了哪些物体, 有些时候, 还需要知道选择的物体的类型(type) 或者他们和场景(scene)中其他元素的关系.

得到当前选择了什么物体很容易. 只需要使用 ‘ls’ 命令加 ‘select’ flag即可, 缩写是 ‘−sl’:

string $select[] = `ls -sl`;

如果你编的script在运行过程中选择了别的物体,而你想在他运行完以后再选回最初选择的物体,你可以把’ls -sl‘的返回值储存在一个数组里,然后在你的script运行结束之前,用 ‘select’ 命令来选择所有最初选择的物体:

select -r $select;

ls -sl’ 命令返回的是一个包含所有当前选择了的物体的数组,如果现在什么也没选,那么他返回的将是一个空数组. 如果当前有选中的物体(‘n’个),按么数组也会有‘n’个元素(elements). 注意:就算 ‘ls’ 只返回一个物体, 他还是以数组形式返回的.

如果你想对选择的物体进行操作,你得使用 for 循环(loop)把要影响的物体一个一个拿出来:

for ( $node in $select )     // process each selection
{
   /* … */
}

上面的例子里 $node 自动成为了一个字符串数据类型(string datatype)的变量 ; 你也可以自己先声明一下,但是那不是必需的.

for循环第一次运行的时候, $node 等于 $select[0]; 第二次运行的时候, $node 等于 $select[1], …. 这种写法在 $select[] 是空数组的时候也是对的,  这种情况下for() 循环将不会运行.

两个string可以用”+”连起来,但是string array不可以. 换句话说你不能像下面这样把一个物体的名字加到当前选择的物体的数组里:

$select = $select + "selectThisToo";
// Error: Illegal operation "+" on data of type string[]. //

正确方法是首先用 ‘size’ 函数得到数组里有几个元素,然后把新元素加到数组里的最后一个位置:

int $lastSelect = size( $select );
$select[ $lastSelect ] = "selectThisToo";

ls’ 命令可能是编MEL时候使用频率最高的命令了, 但是他不能告诉你当前选择列表里各个物体是什么类型. 很多时候你可能只是要处理所有的NURBS 物体, 或是particle groups, 或者是 textures 和 shading groups. 不要假设用户会刚好选到你想要的物体类型,你应该自己检查当前选择的物体是不是你的script需要处理的,如果不是,你应该print一些警告信息或者弹出一些对话框提醒用户,因为没选到正确的物体,脚本不能继续运行.

ls’ 命令可以被用来只显示一种或几种类型.  ‘−type’ flag 后面跟需要的类型即可.

ls -sl;
// Result: pSphereShape1 nurbsSphereShape1 emitterShape1 //

ls -sl -type "nurbsSurface";
// Result: nurbsSphereShape1 //

ls -sl -type "mesh";
// Result: pSphereShape1 //

ls -sl -type "nurbsSurface" -type "pointEmitter";
// Result: nurbsSphereShape1 emitterShape1 //

上面的例子里用户特意一开始选的就是shape nodes.  ‘−type’ flag 并不能从shape nodes得到transform node,反之也不行. 这时候需要使用下面的另外一个命令.

如果你不想在ls里限制返回的物体类型,还有其他几种方法可以得到物体的类型. 最简单的方法是用 ‘nodeType’ 或者 ‘objectType’ 命令,这两个命令返回的结果很相似,有时候可以替换使用.  ‘objectType’ 有个选项可以返回布尔值( Boolean )视物体是否符合你指定的类型而定.

string $select[] = `ls -sl`;
// Result: pSphereShape1 nurbsSphereShape1 //

nodeType "pSphereShape1";
// Result: mesh //

nodeType "nurbsSphereShape1"
// Results: nurbsSurface //

objectType -isType "nurbsSurface" nurbsSphereShape1;
// Result: 1 //

用上面这两个命令你可以事先检查将要处理的物体是不是你假设的类型. 如果你的script只处理某种类型,那么你应该忽略其他类型,或者提示用户要使用你的script,必须先选择某某类型的物体.

上面的例子里的两个节点(nodes)都是 shape nodes. 不幸的是,并不能保证用户刚好选的就是shape nodes,通常情况下,如果你在视窗(viewport)里框选一下,你很有可能选到的是 transform node .

比较下面的运行结果:

string $select[] = `ls -sl`;
// Result: pSphere1 nurbsSphere1 //

nodeType "pSphere1";
// Result: transform //

nodeType "nurbsSphere1"
// Results: transform //

上面的实验都是先选了shape nodes的,但实际用的时候,可能初始选择是 polygon object 或者NURBS object,又或者是camera. 这时候你需要自己找到shape node.

listRelatives’ 命令可以找到transform 层级之下的shape nodes . 一个 a transform node下面可以有多于一个的shape nodes; (就像rigging中经常使用的nurbs curve做的控制器,箭头之类的),像 ‘ls’ 命令一样, ‘listRelatives’ 返回一个字符串数组( string array).

string $relatives[] = `listRelatives -shapes "pSphere1"`;
// Result: pSphereShape1 //

注意,如果对shape node再次使用 ‘listRelatives’ 命令得shape nodes将返回一个空数组:

listRelatives -shapes "pSphereShape1";
// Result: //

Alias|wavefront 的Paul Anand 提供了一个小技巧,你可以用 ‘pickWalk’ 命令来选中shape node:

pickWalk -d down;

如果当前选择的node是transform,那么运行完过上面的命令后,就会选到shape node. 如果一开是你已经选的是 shape node 了,那运行完后你还是选的他,没变化. 这个命令会改变当前选择的物体,记得用完后恢复以前的选择.

有个简单的办法可以一次得到所有当前选择的物体的shape nodes,在用 ‘ls’ 命令的时候加上 ‘−dagObjects’ 和 ‘−leaf’ flags:

ls -sl;
// Result: pCube1 pSphereShape1 //
ls -sl -dag -lf;
// Result: pCubeShape1 pSphereShape1 //

(此方法由 Julian Mann 提供)

还有个办法,和 ‘ls -type’ 类似, 可以用来返回指定类型的物体. 就是使用 ‘filterExpand’ 命令,他比 ‘ls -type’ 好用的地方在于,即使你一开是选的是transform node, ‘filterExpand’ 仍然可以返回指定的类型.

filterExpand’ 命令用的时候需要指定想返回的物体的类型,是一个int的数值.

string $select[] = `ls -sl`;
// Result: pSphere1 nurbsSphereShape1 //

filterExpand -selectionMask 12;   // Poly Mesh
// Result: pSphere1 //

filterExpand -selectionMask 10;   // Nurbs Surfaces
// Result: nurbsSphere1;

上面的例子里,虽然当前选的是shape,但是 ‘filterExpand‘ 命令仍然能返回指定的 transform node 类型.

此命令还有一个好处是他返回的结果是“展开”的. 如果你想解析(parse)返回的字符串,这会省不少事. Maya 通常返回相邻的结果时会使用这种格式 “[s:e]” ,  ‘s’ 表示范围的起始 ‘e’ 表示范围的结束. 虽然这样可读性提高了,可是你想parse返回值字符串的时候就麻烦了,比如:

ls -sl;
// Result: nurbsSphereShape1.cv[4][3:5] //

filterExpand -sm 28   // Control Vertices (CVs)
// Result: nurbsSphere1.cv[4][3] nurbsSphere1.cv[4][4] nurbsSphere1.cv[4][5] //

下表是Maya 的帮助(documentation)里的 ‘filterExpand’ 命令支持的部分类型,虽然帮助没写(可能当年作者用的Maya里没写,不过现在2014是有的),但其实是支持的 :

Selection Mask Component Type
31 Polygon Vertices
32 Polygon Edges
34 Polygon Facets
35 Polygon UV Map Coordinates
46 Lattice Points
47 Particle Components
70 Polygon VtxFace

你可以用原作者的 H2O MEL Script 里的‘findFilterExpand.mel’ 来做例子看看”隐藏的“ Selection Mask 有哪些.(估计也下不到了)

注意: 从 Maya 3 开始 filterExpand selectionMasks 已经被写进帮助里了

遍历场景层级(Traversing Scene Hierarchies)

除了知道选择的物体是什么类型之外,我们经常需要知道这些物体和场景中其他物体的关系. 一个 transform node 可以控制许多他下面的节点. 当使用骨骼( skeletons )的时候,一个关节( joints )是在其他 joints 之上或之下和他最后怎么运动是有关系的.

有两种方法可以知道层级信息( hierarchy information )- 下面将要介绍他们的不同之处,后者会改变当前选择物体列别,而前者不会.

得到一个物体的所有子物体(children):

string $children[] = `listRelatives -children pSphere1`;

找到一个物体的父物体( parent ):

string $parent[] = `listRelatives -parent pSphere1`;

listRelatives’ 命令不影响当前选择列表,而 ‘pickWalk’ 命令则会改变当前选择的物体,并同时以数组形式返回新选择的物体.

选择同一层级的前一个或下一个物体:

// Pick next object on same level
string newSelect[] = `pickWalk -direction right`;

// Pick previous object on same level
string newSelect[] = `pickWalk -direction left`;

选择当前物体的下一个或者上一个层级:

// Move down to and select child
string newSelect[] = `pickWalk -direction down`;

// Move up to and select parent
string newSelect[] = `pickWalk -direction up`;

查找场景信息

大部分情况下,让使用者自己选择你的script要作用在哪些物体上就行了,但有些时候,你可能希望你的script作用在场景中一系列特定的物体上,或者先检测这些你感兴趣的物体是否已经存在. 如果想知道某物体,或者某种类型的物体是否存在很简单; 想知道一个物体上有有没有某个属性稍微复杂一点.

想知道某物体是否存在,用 ‘ls’ 命令加上物体的名字即可. 比如,我在我的一个script里想要创建一个新node来储存某些数据,但是我又不想每次运行script都创建一个新node. 那么我可以先检测下是不是已经有这个node了。如果你不管有没有,总是创建的话,每次创建新的node,Maya会在指定的名字后面加上一个数字,然后每次增加1.

测试:

if ( `objExists "recallData"` )
{
   // The data node exists
}

如果物体存在, ‘objExists’ 命令返回 1 ,不存在则返回 0.

如果想要更具体的测试,可以用 ‘ls’ 命令. 当加上 ‘−type’ flag 的时候‘ls’ 命令可以用来查询某种特殊类型的node是不是已经存在了. 比如, 想知道场景中所有的 NURBS geometry 上有没有trims历史:

if ( size( `ls -type trim` ) > 0 )
{
   // Trim performed on NURBS geometry
}

ls’ 命令返回一个数组,其中包含所有的类型是 “trim.”的node,如果没有这种node,那返回的是空数组,长度是0.

Maya 有个功能是你可以把自己定义的属性(custom attributes)加到物体上. 然后可以用 ‘getAttr’ 命令得到这个属性的值. 显然,你用在还不存在的属性(attribute)上的时候会看到错误提示,这时script会自动终止.

float $myAttr = `getAttr pSphere1.noAttr`;
// Error: No object matches name: pSphere1.noAttr //

所以,当你给任何custom attribute赋值或者查询值的时候,你得先确保这个属性已经存在了,这时候可以用 ‘attributeQuery‘ 命令,比如想知道在物体’leftFoot’ 上,custom attribute ‘toeCurl‘ 是不是已经存在了:

if ( `attributeQuery -node "leftFoot" -exists "toeCurl"` )
{
  // Safe to use 'toeCurl' attribute
}

attributeQuery‘ 命令还可以用来查询attribute的最大值和最小值. 返回结构是浮点数组( float array).

addAttr -ln "blink" -at double -min 0 -max 100 -dv 0 eyeRight;
attributeQuery -node "eyeRight" -range "blink";
// Result: 0 100 //

但不幸的是, 该命令不能用来改变属性的值的范围.

写出更好的MEL的10条注意事项

    1. 尽可能在script运行完后,恢复开始运行之前 已经选择了的物体
    2. 开始任何操作之前,检查当前选择的物体的类型. 不要假设使用者选择的物体刚好就是你想要的. 跳过不是你想要的物体类型,提示使用者你希望他在点按钮之前选中哪些物体.
    3. 在script头上写点注释( comments ),简略描述一下怎么用,有哪些功能.如果你的 script 需要参数( parameters), 写清楚需要哪些参数才能正常使用.
    4. 尽可能写的比较通用. 比如, 如果script需要使用者选择物体或者某种特定类型物体,那就用上面介绍的方法判断使用者选的物体是否符合要求,或者使用pickwalk命令得到需要的物体,再或者从初始选择的物体得到script需要的物体.
    5. 如果使用了scriptJobs,而又有 UI 的话,把scriptJob parent给UI的window. 这样能确保当UI关闭的时候,scriptJob会跟着一起被删除.
    6. 创建窗口(window )前首先检查window是不是已经存在了,如果存在,那么你要么跳过创建新窗口,运行一个‘showWindow’ ,要么就先把他删了,然后重新创建‘window -exists’ 命令可以检测一个window是不是已经存在了:
      if ( `window -exists myWindowName` )
         deleteUI myWindowName;
      
    7. 当你连接( concatenate)两个字符串(strings)的时候, 或者一个字符串和一个数字的时候, 你应该像在用print的时候一样用括号把他们括起来:
      string $objectName = "nurbsSphere";
      
      print ( $objectName + ".rotateY" );
      // Result: nurbsSphere.rotateY //
      
      float $ry = `getAttr ( $objectName + ".rotateY" )`;
      // Result: 45.0 //
      
    8. 如果你的script里选择了 isoparms 或者 surface上的point,你可能会看到不停显示的警告信息:
      // Warning:  Some items cannot be moved //
      

      这是因为当前的 manipulator tool 是一个能移动的工具比如 Move Tool. 如果不想看到这些信息,你可以事先换成选择工具:

      global string $gSelect;   // Sacred Select Tool
      setToolTo $gSelect;
      
    9. 不要滥用全局字符串( global strings)作为UI的名字,尽量用描述性强的和独特的名字.
      proc getValue()
      {
         int $number = `intField -q -value int_recallItemsInList`;
      }
      
      global proc recall()
      {
         /* … */
         intField int_recallItemsInList;
         /* … */
      }
      

      当然你可以把UI UI widgets的名字作为参数( parameter)传给其他的函数( functions). 如果你选择这么做,你最后在注释里写出这个 parameter的作用,以及这个 UI element的出处,以备日后回顾.

      proc getValue( string $field )
      // Parameters: $field = intField that holds value;
      //             defined in global proc recall()
      {
         int $number = `intField -q -value $field`;
      }
      
      global proc recall()
      {
         /* … */
         string $field = `intField`;
         // Must build $field into command, and encapsulate in quotes
         intField -e -changeCommand ( "getValue( \"" + $field + "\" )" ) $field;
         /* … */
      }
      
    10. 随着你不断修改你的scripts ,你肯定需要经常回来修复 bugs 或者增加功能. 但是, 一旦 Maya 把你的 script Load到内存里了以后,他是不会自动去检查你的script改了没有并重新reload的,为了方便,你可以在Shelf上放一个临时的图标来重新source你的script,然后调用script的主函数. 比如你的script 叫做”recall.mel” ,那你可以在 Script Editor里输入下面几行:
      // Update and execute Recall script
      source recall;
      recall;
      

      高亮选中这3行,然后把他们拖到shelf上成为一个按钮,现在只要你按下这按钮,那么Maya就会把你最新修改的script load进内存了,你测试运行的就是你最新的版本了. 你把鼠标移动到shelf icon上还可以看到tooltip或者comment,(这3行的前几行,也许), 这个方法是Rhonda Graphics在SIGGRAPH’98 上演示过的.(lz觉得这段是废话)

December 05, 2000

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: