主题:  MEL 语言初级教程 作者:Goomoo(古木)

5D荣誉斑竹

职务:普通成员
等级:5
金币:10.0
发贴:1326
#12002/5/26 19:44:54
************* MEL 语言初级教程(一) Goomoo(古木) 2002/5/16***************
// 如需转载,请注明作者。如欲用于商业用途,请与我联系:zengjinsong@263.net //

首先,我们不讲枯燥的语法,来点实在的。
打开Maya的脚本编辑器(Window->General Editors->Script Editor),脚本编辑器分为上下两栏,上栏为历史栏,显示的是曾经操作的命令及其结果以及错误、警告等。下一栏是命令输入栏,我们可以在其中输入相关命令。请试着输入如下语句:

window -vis true;

然后按下Ctrl+Enter键,是不是立即弹出了一个标题为"window1"的窗口?那么这句话是什么意思呢?window是一个命令,表示我们要创建或修改一个窗口。vis是visible的缩写,即“可见”,-vis是window命令的一个开关,true则是该开关的参数,意为“是”。连起来说就是:创建一个窗口并使它可见。于是我们就得到了一个缺省的窗口,它的标题栏为"window1",名称也为"window1",但注意名称我们看不见,但从历史栏的最后一栏我们可以得知。下面我们更改该窗口的标题,在输入栏中输入:

window -e -t "ChinaDV" window1;

按下Ctrl+Enter键执行命令。我们看到,窗口的标题变成了"ChinaDV"。这句话又是什么意思呢?e是指edit,t是指title,表示我们要编辑名称为window1的窗口,把它的标题栏改为"ChinaDv"。当然,我们也可以用一条语句在生成窗口的同时指定窗口的标题、名称、长宽等属性。试着输入:

window -w 450 -h 600 -vis true -t "Goomoo Studio" myFirstWindow;

并执行之。这次我们得到了一个更大的窗口,它的宽度为450象素,高为600象素,标题为"Goomoo Studio",名称为"myFirstWindow"。

关闭标题为"ChinaDV"的那个窗口,将窗口"Goomoo Studio"调小些(怎么调?用鼠标呗!),使之不挡住主网格。再在命令栏中输入:

sphere;

并执行,看到了什么?视图的中间出现了一个nurbs球体。所以,sphere命令用来生成nurbs球体。
下面我们要实现这样一个目的:在窗口Goomoo Studio上生成一个按钮,每单击一次该按钮,在视图的随机位置创建一个半径随机且小于1的nurbs球体。请输入以下两行:

columnLayout;
button -l "Create Random Sphere" -c " sphere -p (rand(-12.0,12.0)) (rand(-12.0,12.0)) (rand(-12.0,12.0)) -r (rand(1.0));";

输入时请注意大小写和括号、分号和引号,不要输错。执行完后,我们发现,窗口Goomoo Studio上出现了一个标题为"Create Random Sphere"的按钮。那么,这两句话又是什么意思呢?
欲知详情,请听下回分解。

下课!

作业:单击"Create Random Sphere"按钮1000次,体会mel语言带来的乐趣!


************* MEL 语言初级教程(二) Goomoo(古木) 2002/5/15***************
// 如需转载,请注明作者。如欲用于商业用途,请与我联系:zengjinsong@263.net //

上节课的作业完成得怎样?要单击1000次才能生成1000个随机的球,太累!今天我们的目标是只单击一次就生成1000个随机的球,让我们为了此目标而奋斗吧!
先解决上节课悬而未决的问题。在上节课中,我们看到了一个很长的命令:

button -l "Create Random Sphere" -c " sphere -p (rand(-12.0,12.0)) (rand(-12.0,12.0)) (rand(-12.0,12.0)) -r (rand(1.0));";

这里,button表示要创建一个按钮,l即label,也就是要指定按钮上面的文字,而“Create Random Sphere”则是该开关的参数;c是command,表示单击该按钮后所要执行的命令。注意:这些命令要用双引号括起来。这里,我们要执行的命令是:

sphere -p (rand(-12.0,12.0)) (rand(-12.0,12.0)) (rand(-12.0,12.0)) -r (rand(1.0));

p即position,指定球的位置,用三个浮点数(即实数)分别表示它的x,y,z坐标,这里x,y,z的值全为rand(-12.0,12.0),这是什么意思呢?rand()是一个数学函数,它可以带一个或两个参数。当他带一个参数的时候,比如rand(10.0),表示从0到10.0中间随机取一个值,这个值可能是3.14159,也可能是2.71828,当然也可能是7.88414(吃点88试一试,:));当他带两个参数的时候,比如rand(-12.0,12.0),表示从-12到12之间取一个随机的实数,因此有可能是-9.600,3.245,12.0等。你可以在脚本编辑器中试着输入下列命令来体会rand()函数:

print(rand(10));
print(rand(-10,10));

命令的结果将显示在命令历史栏中。
r即radius,表示球的半径。

我们再在命令栏中输入下面一段:

window -vis true;columnLayout;
button -l "Create Random Sphere" -c " int $i;
for($i=0;$i<1000;$i++)
sphere -p (rand(-12.0,12.0)) (rand(-12.0,12.0)) (rand(-12.0,12.0)) -r (rand(1.0)); "

按数字键盘上的Enter键执行之,然后单击窗口上的“Create Random Sphere”按钮。****严正声明****:死机了可别怪我!

哎呀!我要上班了,明天再写。
下课!


************* MEL 语言初级教程(三) Goomoo(古木) 2002/5/16***************
// 如需转载,请注明作者。如欲用于商业用途,请与我联系:zengjinsong@263.net //

执行完上次的命令后,你的机器死过去了吗?没有?那好,这样的机器才有做三维的资格啊。
在上节课的那个更长的命令中,我们看到了三个陌生的东西:“int”、“for(;;)”和“$i”。先从简单的讲起。
$i是一个自定义的变量,它和物体的自定义属性差不多。变量名只能由字母、数字和下划线组成。那么如何理解变量呢?你可以把一个变量想像成一个盒子,这个盒子里可以存放各种数据,包括整数、实数、字符串、矢量等。“int”即“integer”是一个定义符,“int $i”指明了代号为$i这个盒子里只能存放整数,此外比较常用的有:float(实数),string(字符串)等。
注意:在mel中,所有的变量前都需要加一个“$”符号。为什么要这样呢?我想有两个方面的原因:一是与Maya系统所定义的变量区分开来,以免发生冲突;二是A|W公司可能缺钱花,这样规定之后呢,看着mel脚本上满眼的美元,心里那个爽啊!哈哈!!
for(;;)语法可就有点难了,大家要可要认真学罗。还是用实例来讲解吧,输入:

int $i,$sum;
$sum=0;
for($i=1;$i<1001;$i++)
$sum=$sum+$i;
print($sum);

执行完后,我们看到了结果:500500。这就是从1加到1000的和(是否想起了高斯?)。第一句定义了两个整型变量:$i和$sum,$i用来控制循环,$sum用来存放相加的结果。第二句首先将结果置零。
下面就是for循环了,for循环首先执行第一条语句:$i=1,它将变量$i赋值为1;然后执行第二句:$i<1001,判断$i的值是否小于1001,如果是,就执行$sum=$sum+$i;显然,这条语句马上被执行了,所以,$sum=0+1=1;(否则,就跳出循环,执行print($sum);这条语句)然后才执行$i++,这是什么意思呢?这表示变量$i的值自加1,即$i=$i+1,所以$i=1+1=2,然后再次进行判断$i的值是否小于1001,如果是,则再次执行$sum=$sum+$i;显然,这次又执行了,$sum的值由1增为2...如此循环不已,直至$i的值大于1000为止,所以这个循环一共执行了1000次,$sum的值也就从1加到1000。说了这么多,不知大家听懂了没有,如果还没懂,看看附图吧。另外要注意,如果循环体有多条语句,这些语句要用花括号括起来,比如:

float $i,$sum,$multi;
$sum=0;
$multi=1;
for($i=1;$i<51;$i++)
{
$sum+=$i;
$multi*=$i;
}
print($sum);
print("\n");
print($multi);

显示结果:
1275
30414093201713376000000000000000000000000000000000000000000000000

这段程序用来计算1加到50的和与1乘到50的积。Maya可真是厉害,这么大的数都算得出来,比微软的计算器可强多了!
这里“$sum+=$i;”和“$multi*=$i;”是mel的简写法,它们分别等同于“$sum=$sum+$i;”和“$multi=$multi*$i;”

此外,for语句还有许多变异的写法,这里暂时就不涉及了。

以上全是枯燥的语法,下面来点实用的东西。请在脚本编辑器中输入:

window myWindow;
columnLayout;
intSliderGrp -l "Number Of Spheres:" -field true -min 1 -max 1000 -v 50 slider;
button -l "Create Random Sphere" -c " int $k=0;
int $n=`intSliderGrp -q -v slider`;
for(;$k<$n;$k++)
sphere -p (rand(-12.0,12.0)) (rand(-12.0,12.0)) (rand(-12.0,12.0)) -r (rand(1.0));
";
showWindow myWindow;

执行完后,得到了一个有滑动条和按钮的窗口,这样我们就可以通过滑块来控制生成的球体的数目了。
今天埃了老板K,心情不太好,写出来的东西乱糟糟的,大家不要扔鸡蛋罗。
下课!


************* MEL 语言初级教程(四) Goomoo(古木) 2002/5/17***************
// 如需转载,请注明作者。如欲用于商业用途,请与我联系:zengjinsong@263.net //

执行上节课的最后一段后,我们的程序终于有了人机交互功能,可以由用户来控制生成的球体数目了。我先来逐句讲解上段程序的意思。
第一句,window myWindow;,创建一个名称为myWindow的窗口,但并不立即显示出来。
第二句,columnLayout;,设置窗口的控件布局为列布局。
第三句,intSliderGrp -l "Number Of Spheres:" -field true -min 1 -max 1000 -v 50 slider;,在该窗口上创建一个整数滑动条,其标题文字为"Number Of Spheres:";"-field true" 表示显示数值输入框,默认状态为不显示;"-min 1 -max 1000 -v 50"表示该控件的最小值为1,最大值为1000,默认值为50。连起来说就是:创建一个标题为"Number Of Spheres:",最小值为1,最大值为1000,默认值为50,名称为"slider"的整数滑条,并显示其数字输入框。
第四句,很长啊,我只讲解-c后面的命令序列。int $k=0;,定义一个整型变量$k,并将其值设为0;int $n=`intSliderGrp -q -v slider`;,这个命令有点难理解,定义一个整型变量$n,并将其值设为`intSliderGrp -q -v slider`,***注意***这句两端并不是单引号,而是“左手单引号”,也就是“Esc”键下、“Tab”键上、“1”键左的那个按键,用得很少的一个按键啦,A|W公司思想创新,令该键发挥奇效。该键用来括住一个命令,并捕获该命令执行的结果。有点难理解,还是举个例子来说吧,输入:

string $mySphere[]=`sphere`;

执行之,我们看到视图的中间生成了一个球体。这里,我们定义了一个字符串数组,字符串数组可以用来存放多个字符串。再输入:

print($mySphere);

执行之,得到结果:
nurbsSphere1
makeNurbSphere1

这和历史栏中显示的//result:后显示的结果是一样的。更进一步,在视图的空白处单击,取消对球体的选取,然后输入:

move -a 5 0 0 $mySphere[0];

执行之,该球被移到了世界坐标的(5,0,0)处,可以看到,我们既没有选择该球,也不知道该球的名字,但我们仍然能够通过程序控制它,怎么样,看到“左单引号”的威力了吧!这里,move是移动命令,a即absolute,表示绝对坐标。

再把思路跳回去,那么intSliderGrp -q -v slider执行的结果是什么呢?q表示query,即“查询”,v表示value,即“值”,这句话表示我们要查询名称为“slider”的控件的值,并将查询的结果存入变量$n中。 剩下的就好理解了,for(;$k<$n+1;$k++),这是for循环的一个变种,它的括号中的第一条语句省略了,因为我们前面已将$k的值指定为0;for循环中的第二句把1001改为了$n,这样,这个循环就执行$n次,所以生成了$n个球,也就是用户输入的球体的数目。
第五句,showWindow myWindow;,这句话用来显示第一句创建的窗口。
至此,大功告成。不知我讲的你们是否都懂了。如有不懂,尽管发问,我有问必答。
几天没布置作业了,很高兴吧,今天出个难题。
今天的作业:在该窗体上再添加一个滑条,用以控制生成球体的最大半径。
下课!


************* MEL 语言初级教程(五) Goomoo(古木) 2002/5/19***************
// 如需转载,请注明作者。如欲用于商业用途,请与我联系:zengjinsong@263.net //

上节课布置的作业完成的怎么样啊?没完成的可是要打手心的哟!
以下是上节课练习的解答。

window myWindow;
columnLayout -adj true;
floatSliderGrp -l "max Radius:" -field true -min 0.1 -max 10.0 -v 1.0 rSlider;
intSliderGrp -l "Number of Spheres:" -field true -min 1 -max 1000 -v 50 nSlider;
button -l "Create Ramdom Sphers" -c " int $n=`intSliderGrp -q -v nSlider`;
float $r=`floatSliderGrp -q -v rSlider`;
int $i=0;
for(;$i<$n;$i++)
{ sphere -p (rand(-12.0,12.0)) (rand(-12.0,12.0)) (rand(-12.0,12.0)) -r (rand($r));
}
";
showWindow myWindow;

在上面的程序中,我们看到了一个新的命令:floatSliderGrp,floatSliderGrp和intSliderGrp的用法基本上是一样的,只不过 intSliderGrp 表示的是整数,而 floatSliderGrp 表示的是浮点数,即实数,也就是带有小数部分的数。
另外,我们还发现了一个严重的问题:随着被用户所控制的参数的增多,button命令的-c参数后命令序列也会急剧增加,命令行也会更长,这将给我们阅读、修改和维护程序带来困难。
有没有办法解决这个问题呢?当然有,下面我们来讲讲“函数”(也称为过程)这个重要的东东。当然,函数不仅仅是为了阅读方便而引入的,它是结构化程序设计的一个重要的概念,它把一个长长的程序分成许多独立的模块,每个模块仅通过几个变量(函数的参数和返回值)与其他的模块进行沟通,这样极大地简化了程序的编写,提高了程序代码的可重用性。对于别人已经编好的函数,我们只用采取“拿来主义”,随手拿过来用就行了,不必考虑他是如何实现的。当然,我们也可编写自己的函数。这段话比较枯燥,不过暂时不理解也没关系,等我们把函数用熟了,自然就明白了。我们还是以事实来说话。
我们先来写一个简单的函数,该函数可以返回两个实数中较大的那一个。输入:

proc float zjsMax(float $a,float $b)
{ if($a>=$b)
return $a;
else
return $b;
}

执行之。咦?怎么一点反应都没有?反应是有的,只是我们看不见,我们已经定义了一个名为zjsMax的函数存到maya的内存中去了(不知这样说准不准确?),下面我们再输入:

zjsMax(147.258,258.369);

执行之。咳!有反应了!历史栏中显示出:// Result: 258.369 //。这表示我们的第一个函数编写成功!下面我们来剖析这个函数。
proc即procedure,意为“函数”、“过程”,告诉Maya下面我们要定义一个函数了。
float,前面讲过,意为“浮点数”、“实数”,表示这个函数的返回值是一个实数。
zjsMax,这是函数名。函数名必须由字母、数字和下划线组成,第一个字符不能为数字,前不用加美元符号。
float $a,float $b,表示这个函数需要两个参数,而且都是浮点数。参数必须用圆括号括起来。最后不要加分号。
下面是函数体,函数体由多条语句组成,这些语句必须用花括号括起来。
if,意为“如果”,后面是条件。条件语句后不要带分号。
($a>=$b),$a大于或等于$b。此外还有>(大于)、<(小于)、<=(小于或等于)、!=(不等于)、==(等于)。
return,返回;else,否则。
这段函数体翻译成中文就是:如果$a大于或等于$b,就返回$a ,否则,返回$b。所以,它返回的是较大的那个实数。

下面我们再编写一个函数,它需要两个参数,一个表示球体的最大半径,另一个表示球体的数目。请输入:

proc zjsRandomSphere(float $r,int $n)
{ int $i=0;
for(;$i<$n;$i++)
{ sphere -p (rand(-12.0,12.0)) (rand(-12.0,12.0)) (rand(-12.0,12.0)) -r $r; }
}

执行之,照旧没反应。不过我们已有心理准备啦。再输入:

zjsRandomSphere(1,100);

视图中立即出现了100个最大半径为1的球体。哈哈,连前面的界面都不用了。这个函数没有声明返回的数据类型,也没有任何返回值。

这样,我们上节课的练习答案可以改成:

proc zjsRandomSphere(float $r,int $n)
{ int $i=0;
for(;$i<$n;$i++)
{ sphere -p (rand(-12.0,12.0)) (rand(-12.0,12.0)) (rand(-12.0,12.0)) -r $r;
}
}

window myWindow;
columnLayout -adj true;
floatSliderGrp -l "max Radius:" -field true -min 0.1 -max 10.0 -v 1.0 rSlider;
intSliderGrp -l "Number of Spheres:" -field true -min 1 -max 1000 -v 50 nSlider;
button -l "Create Ramdom Sphers" -c " int $n=`intSliderGrp -q -v nSlider`;
float $r=`floatSliderGrp -q -v rSlider`;
zjsRandomSphere($r,$n);
";
showWindow myWindow;

这段程序看上去比前面那段更长,但更易于阅读和修改。你有这样的感觉吗?如果没有,那是你还没有习惯,等习惯了就好了。
今天的作业:写一个函数,它需要3个参数,返回这三个数中最大的那一个。

----------------------------------------------------------
*****附:为响应米侬朋友的要求,下面讲一下全局变量。*****
----------------------------------------------------------

以下只是我个人对全局变量的理解,也许不够准确,但希望能够释疑。
全局变量可以认为是多个函数共同享用的变量。如何理解呢?请看例子:

global float $gVar=12345;
proc asignvalue(float $value)
{
global float $gVar; //全局变量
$gVar=$value;
}

proc outPut()
{
global float $gVar; //全局变量
print($gVar);
}

proc outPut1()
{
float $gVar; //局部变量
print($gVar);
}

执行之。这里定义了一个全局变量$gVar和三个函数asignvalue(float)、outPut()和outPut1()。按照书写约定,全局变量的前面要加一个g字母。任何一个函数在使用全局变量前都必须先声明该变量。在函数asignvalue和函数outPut中,使用的是全局变量$gVar,而在函数outPut1中,使用的是局部变量$gVar,他们的变量名相同,下面我们来看看他们有什么差别:
输入outPut();并执行之,结果:12345;输入outPut1();并执行之,结果:0。
再输入:asignvalue(-1000);并执行之。
输入:outPut();并执行之,结果:-1000;输入outPut1();并执行之,结果:0,不变。


************* MEL 语言初级教程(六) Goomoo(古木) 2002/5/21***************
// 如需转载,请注明作者。如欲用于商业用途,请与我联系:zengjinsong@263.net //

这是上节课的习题解答:
proc float zjsMax(float $a,float $b,float $c)
{
float $t; //定义一个临时变量$t
if($a>=$b)
$t=$a;
else
$t=$b;
if($t>=$c)
return $t;
else
return $c;
}

如果熟悉了mel以后,这个函数还可以写成一句话:

proc float zjsMax(float $a,float $b,float $c)
{
return $a>($b>$c?$b:$c)?$a:($b>$c?$b:$c);
}

嘿嘿,看不懂吧。这是mel中的一个独特的语句:a>z?a:z,它表示:a大于z吗?如果a大于z,则值为a,否则,值为z。上面那句只是把z换为了“b>c?b:c”,所以返回的是三个数中的最大值。

在看别人写的脚本的时候,我们经常会看到这样的符号“//”、“/* …… */”这样的符号,这是什么意思呢?这是mel的注释符号。在“//”后面的语句和“/* …… */”之间的语句是给人看的,maya在执行脚本时是不理会这些语句的。“//”一次只能注释它后面的一行语句,而“/* …… */”则能注释包含在其中的多行语句。注释语句有什么作用呢?当我们写一个很长很复杂的脚本的时候,当时看起来也许很简单,但经过很长一段时间后再转回来看时,也许连自己也看不懂了;另一方面,当多人合作开发一个程序时,注释能使别人更容易理解你的程序和思路,从而提高程序开发的效率。

我们的创建随机球的程序基本上已经完成了,现在我们要提供给其他人实用。当然,我们不能指望别人在每次执行这个程序时都将代码重输或复制粘贴一遍,所以我们还需要继续完善。Let's go!

首先,我们要把我们创建随机球的函数zjsRandomSphere改成全局函数,这样,我们就能在maya的任何地方调用它了,与你一起合作的其他人也可方便地调用它。如下:

global proc zjsRandomSphere(float $r,int $n)
{ int $i=0;
for(;$i<$n;$i++)
{ sphere -p (rand(-12.0,12.0)) (rand(-12.0,12.0)) (rand(-12.0,12.0)) -r $r;
}
}

只用在函数名前加上单词“global”即可。“global”是“全局的”、“全球的”之意。另外,我们还需把创建窗口的代码放入另一个全局函数中,如下:
global proc createRandomSphere()
{
window myWindow;
columnLayout -adj true;
floatSliderGrp -l "max Radius:" -field true -min 0.1 -max 10.0 -v 1.0 rSlider;
intSliderGrp -l "Number of Spheres:" -field true -min 1 -max 1000 -v 50 nSlider;
button -l "Create Ramdom Sphers" -c " int $n=`intSliderGrp -q -v nSlider`;
float $r=`floatSliderGrp -q -v rSlider`;
zjsRandomSphere($r,$n);
";
showWindow myWindow;
}

将以上两个函数复制粘贴到一个文本文件中,将文件存盘。**注意**:存盘后必须将文件名改为“createRandomSphere.mel”,也就是文件名必须与创建用户界面的函数名相同,还要注意大小写。然后把createRandomSphere.mel文件拷贝到以下地方,就可以方便的使用它了:
1.Windows 2000 或 WindowsXP
<我的文档>\maya\scripts
2.Windows NT
C:/WINNT/Profiles/my-login-name/maya/scripts"
3.UNIX
~username/maya/scripts

拷贝完毕后,请重新启动maya,按一下“左手单引号”(也就是A|W公司令其发挥奇效的那个键),将焦点跳到maya的命令行中,输入“createRandomSphere;”,然后回车。瞧!我们编的窗口出现了,Enjoy it!为省去每次输入的麻烦,你可以打开脚本编辑器,在历史栏中,选中“createRandomSphere;”这一行,将其拖到maya的工具架上,形成一个按钮,以后只用单击该按钮就行啦,maya真是方便呀!如果你记得创建随机球的那个函数,你还可以直接在命令行中输入:zjsRandomSphere(0.5,500);来创建最大半径为0.5的500个随机球,这就是在函数前加上“global”所起的作用。
今天讲的东西很简单,就不布置作业了。
下课!

************* MEL 语言初级教程(六) Goomoo(古木) 2002/5/24***************
// 如需转载,请注明作者。如欲用于商业用途,请与我联系:zengjinsong@263.net //

作为本初级教程的最后一课,我们讲一下经典实用的弹簧曲线的生成方法。这里要涉及到一点三角函数的知识,高中学的,大家应该还没忘吧。
先看看如何生成曲线。输入:

curve -d 3 -p 1 0 0 -p 0.707 0.2 -0.707 -p 0 0.4 -1 -p -0.707 0.6 -0.707 -p -1 0.8 0;

执行之,视图中出现了一个圆弧,这是一个半圈的螺旋线。打开它的CV点,可以看到,一共有5个CV。在上面的这行命令中,curve表示要创建一条曲线;-d 3,指定曲线的度数(degree)为3;-p x y z,指定CV的位置。在以上的命令中,我们一共指定了5个CV。
下面我们用循环语句生成一个半径为3的圆。生成一个圆需要8个CV。

int $i=0;float $deg=0.0,$rad=0.0; //$deg:角度数,$rad:弧度数
string $curve=`curve -p 3 0 0`; //定义第一点,同时捕捉生成的曲线的名称,以便追加CV
for(;$i<7;$i++) //进入循环
{ $deg+=45; //即:$deg=$deg+45;旋转45度
$rad=deg_to_rad($deg); //deg_to_rad()用来将度数转为弧度
curve -a -p (3*cos($rad)) 0 (3*sin($rad)) $curve; //三角函数的参数必须使用弧度数
}
closeCurve -ps 0 -rpo 1;

在这段语句中,curve命令的-a开关表示追加CV(append),最后要指定追加的曲线名,这里为$curve。closeCurve为关闭曲线命令,-ps 0,表示保护形状(preserve shape)为否,即不保护形状;-rpo 1,表示替换原有曲线(replace orignal)。

我们先考虑生成一个弹簧曲线需要哪些参数。首先,应该是弹簧曲线的圈数;其次,如果弹簧曲线的上下底半径不同,还要考虑它的上下底半径;再次,就是弹簧的旋转方向。这里,弹簧的高度是可以不予考虑的,因为我们通过简单的缩放就可以调整。假设上半径为r1,下半径为r2,圈数为n,则该曲线所需的CV数为(8*n+1),每次半径的递增量为(r2-r1)/(8*n+1)当然,这个值可能为正数,也可能为负数。于是,生成弹簧曲线的函数可以写成:

global proc string spiral(float $r1,float $r2,int $turns,int $direction)
{ string $curve=`curve -p $r2 0 0`;
float $r=$r2; float $y=0; float $deg=0.0,$rad=0.0;
int $numOfCVs=8*$turns+1;
float $r_inc=($r2-$r1)/$numOfCVs;
int $i=0;
for(;$i<$numOfCVs-1;$i++)
{ $y+=0.1; //y坐标递增
$r-=$r_inc;
if($direction==1)
$deg+=45; //顺时针
else
$deg-=45; //逆时针
$rad=deg_to_rad($deg);
curve -a -p ($r*cos($rad)) $y ($r*sin($rad)) $curve;
}
return $curve;
}

spiral(1,3,8,1);

执行之,视图中出现了一个上半径为1,下半径为3,圈数为8,方向为顺时针的弹簧线。再执行
spiral(2,2,10,2);
视图中出现了一个上下半径均为2、圈数为10、方向为逆时针的弹簧线。
一切就这么简单!
至于其他类型的曲线,只需要动一点点脑筋,再加上一点点数学知识,就可以全部写出来啦。有兴趣的朋友可以试试看。

我们的mel初级教程到这里就要结束了,以后我的水平提高了,再给大家写中级、高级教程吧。但愿我写的这些东西能给大家一点帮助,至少不辜负我瞒着老板偷偷打这些文字!大家如果以后在mel方面有什么问题,可以发EMail(zengjinsong@263.net)给我,我将尽我所能帮你解答。

今天的作业,为弹簧曲线编写图形界面。
解散!

goomoo.diy.163.com/zjsCurves.zip
goomoo.diy.163.com/zjsCurves02.zip
goomoo.diy.163.com/03cn.zip
goomoo.diy.163.com/gu03bcn/gu03bcn.zip
goomoo.diy.163.com/gu03bcn/gu033bcn.zip

编辑历史:[这消息被koodoo编辑过(编辑时间2002-05-26 19:45:33)]


goomoo

职务:普通成员
等级:1
金币:0.0
发贴:55
#22002/5/30 18:16:12
想不到我写的东西跑到这里来了。
多谢版主看得起!



5D荣誉斑竹

职务:普通成员
等级:5
金币:10.0
发贴:1326
#32002/5/30 18:27:18
谢谢古木
希望goomoo兄有后续文章让我们学习



goomoo

职务:普通成员
等级:1
金币:0.0
发贴:55
#42002/5/30 18:49:55
过两天闲下来会写的。



5DJSP技术版主

职务:版主
等级:2
金币:10.0
发贴:436
#52002/5/30 21:51:16
goomoo在上个帖子中说
引用:
过两天闲下来会写的。


期待大作啊~~~!!!



王睿

职务:普通成员
等级:1
金币:0.0
发贴:118
#62002/5/31 17:36:06
mel的作用是什么啊?



goomoo

职务:普通成员
等级:1
金币:0.0
发贴:55
#72002/6/4 14:37:53
王睿:
看看我写的插件就知道啦。
点击下载:
goomoo.diy.163.com/gu03bcn/gu036bcn.zip



goomoo

职务:普通成员
等级:1
金币:0.0
发贴:55
#82002/6/11 12:39:40
************* MEL 语言初级教程(八) Goomoo(古木) 2002/6/10***************
//    如需转载,请注明作者。如欲用于商业用途,请与我联系:zengjinsong@263.net    zjs3d@hotmail.com//

终于能够挤点时间来写写教程了。前几天忙着给公司赶活,又忙着升级GU(也就是Goomoo Utilities啦),再加上看WC(不要误会,是指“World Cup”,我又不是所长兼收银元,哈!),所以抽不出时间,大家不要怪我啊。
最近一段时间,我的mel水平也没多大长进,还是凑合着叫“初级教程”,就算作为续集吧。都说电影电视的续集不会比原集好看,希望我的教程不会如此。
好了,废话少说,言归正传!今天我要讲一讲我的得意之作(当前阶段),也就是GU中的“沿曲线创建弹簧”的功能,这是我的首创啊,希望大家能够喜欢。
说它是我的得意之作,并不是指它在编程方面有多复杂,而是思想创新,值得借鉴(用来形容A|W公司的词全用在自己身上了,真会自我吹捧,:))。
在看源代码之前,先有必要说一说思想方法。在上一讲中,我们编了一个生成弹簧曲线的函数,当时的思想是通过半径和三角函数以及y轴的增量计算空间CV点的坐标,然后将这些CV点逐一连成曲线。想想今天这个方法还行得通吗?我们需要沿着一条曲线创建弹簧,注意,这是一条三维空间的曲线。按照前面的思路,我们需要在曲线上等距离地取点,然后计算曲线上该点的切线方向,并在以该点为圆心,指定半径的圆上取点的坐标,而且该圆的法线方向要与曲线上该点的切线方向相同,而且这不仅仅是一个点,想想有多难吧!按照这个思路显然行不通,至少我是编不出来的。看来得另辟他径。经过一段时间的冥思苦想,终于想到了一个巧妙的办法:使用曲面作为辅助面,通过曲面的uv坐标来定点,进而获取点的坐标,这样,连三角函数都不用了,怎一个爽字了得!下面看看源代码:

global proc string zjsSpiralAlongCurve(int $turns,float $radius,int $direction)
{
    string $curve[]=`filterExpand -sm 9`;
    if($curve[0]=="") error "请选择一条nurbs曲线。";
    rebuildCurve -ch 1 -rpo 1 -rt 4 -end 1 -kr 0 -kcp 0 -kep 1 -kt 0 -s 8 -d 3 -tol 0.01 $curve[0];
    string $tmpCircle[]=`circle -r $radius`;
    string $tmpSurface[]=`extrude -ch true -rn false -po 0 -et 2 -ucp 1 -fpt 1 -upn 1 -rotation 0 -scale 1 -rsp 1 $tmpCircle[0] $curve[0]`;
    hide $tmpSurface[0];
    string $tmpSurfaceShape[]=`listRelatives -s $tmpSurface[0]`;
    float $surfaceV[]=`eval("getAttr "+$tmpSurfaceShape[0]+".minMaxRangeV")`;
    float $init_v=(($surfaceV[0]))<(($surfaceV[1]))?(($surfaceV[0])):(($surfaceV[1]));
    float $end_v=(($surfaceV[0]))>(($surfaceV[1]))?(($surfaceV[0])):(($surfaceV[1]));
    int $n_CVs=$turns*8+1;
    float $V_inc=(float)($end_v-$init_v)/($turns*8);
    float $position[]=`eval("pointPosition "+$tmpSurfaceShape[0]+".uv[0]["+$init_v+"]")`;
    string $resultCurve=`curve -p $position[0] $position[1] $position[2] -n "spiral"`;
    float $u=0,$v=$init_v;
    int $n=0;
    if($direction==1)    //clock wise
    {    for(;$n<$n_CVs;$n++)
        {    $u++;
            $u=$u%8;
            $v+=$V_inc;
            float $position[]=`eval("pointPosition "+$tmpSurfaceShape[0]+".uv["+$u+"]["+$v+"]")`;
            curve -a -p $position[0] $position[1] $position[2] $resultCurve;
        }
    }
    else            //counter clockwise
    {    for(;$n<$n_CVs;$n++)
        {    $u--;
            $u+=8;
            $u=$u%8;
            $v+=$V_inc;
            float $position[]=`eval("pointPosition "+$tmpSurfaceShape[0]+".uv["+$u+"]["+$v+"]")`;
            curve -a -p $position[0] $position[1] $position[2] $resultCurve;
        }
    }
    delete $tmpSurface $tmpCircle;
    select -r $resultCurve;
    return $resultCurve;
}

我来逐句讲解。
第一句用来定义一个全局的返回值为字符串的函数,该函数需要三个参数:圈数,半径和方向。
接下来是函数体,定义了一个字符串数组$curve[],该数组存放的是一个命令返回的结果。该命令是“filterExpand -sm 9”,这个命令是干啥的呢?filterExpand命令用来过滤选择,sm即select mask,选择屏蔽,代号9是指“nurbs curve”。这个命令的意思是:不管用户选择了那些物体,该命令只将被选物体中的曲线的名称存入数组$curve中。请查阅mel command reference了解其他的代号(一定要查奥!)。
if句用来判断用户是否选择了nurbs曲线,如果没有,则数组$curve的第一个元素的值应该为空,即""。error命令用来显示一条错误信息,同时阻止程序继续运行。错误信息最好用双引号括起来。
为了生成好的弹簧线,rebuildCurve语句将曲线重构。rebuildCurve命令的参数很多,当然我并没有把它们记下来,只是临要用时将一条曲线按需重构,再从ScriptWindow的历史栏中拷贝出来,粘贴到我的源代码中,然后作适当的修改即可。我只是把最后一个参数改为了$curve[0],即用户选择的第一条曲线。如果你非要了解rebuildCurve命令的参数,可以查阅Mel command reference。
接下来的语句用来生成一个临时的nurbs圆,并将其名称存入的字符串数组$tmpCurve中。注意,缺省的nurbs圆的最大U值为8。
下面的语句用来把临时的圆沿用户选择的第一条曲线挤压成一个nurbs曲面,并将该曲面的名称存入字符串数组$tmpSurface中,以备后用。
hide语句用来将临时曲面隐藏起来。
这一句用来得到曲面的形状节点。listRelatives用来获取于指定物体相关的节点,s即shape,形状节点。为什么要获取曲面的形状节点呢?因为我们要通过形状节点来获取曲面V值的取值范围,并通过它来计算V向的增量(相当于上一课中y轴的增量)。
float $surfaceV[]=`eval("getAttr "+$tmpSurfaceShape[0]+".minMaxRangeV")`;这一句很重要,得仔细讲讲。这里出现了一个新的函数eval(),这是一个很重要的函数,而且不太好理解。eval()函数将一段字符串翻译成指令并执行改指令。什么意思呢?举个例子来说:
我们知道,sphere -r 5; 用来生成一个半径为5的球体。那么eval("sphere -r 5");执行的效果与它相同;eval("sphere"+" "+"-r 5");执行的效果也与它相同。这不是在画蛇添足吗?在这种情况下,应该说是。但请看下面的语句:

string $goomoo="sphere";
eval($goomoo+" -r 5");

执行的结果与上面同,但你如果输入$goomoo -r 5;,maya则会提示说有语法错误。也许你还是不太明白,但关系不大,你只要记住:“eval()函数将一段字符串翻译成指令并执行改指令”就够了。再回来看看该语句,假设生成的临时曲面的名称为extrudeSurface1,则eval执行的命令为“getAttr extrudeSurface1.minMaxRangeV”,即曲面的最大最小V值,该值在曲面的属性编辑器中可以看到(请参见附图),这些值是很重要的。getAttr指令用来获取物体的属性。那么,你怎么知道曲面有minMaxRangeV属性呢?猜的,呵呵~~
接下来的两个以float开头的语句用来获取曲面的初始V值和终止V值,即V向的最小最大值。这两句不懂的同志请看前面的第六课。要注意的是,曲面的V值有可能是负值。
int $n_CVs=$turns*8+1;用来计算弹簧曲线上CV点的数目。
float $V_inc=(float)($end_v-$init_v)/($turns*8);用来计算V值的增量。(float)表示强制类型转换,因为整数与整数相除的结果是没有小数的,所以这里需要将被除数强制转换成实数。请运行:print(5/4);的结果为1;而print(5.0/4);的结果为1.25,看到差别了吧。
float $position......这一句用来获取曲面上起始点的坐标,并将这些坐标存入浮点数组$position中。pointPosition可以用来获取任何点的坐标,请参阅Mel command reference。
string $result......语句绘出弹簧曲线的起始点,并将曲线命名为“spiral”,如果场景中已存在一个名为“spiral”的对象,maya会自动在名称后面加上递增数字。
初始化曲面的UV值。
初始化循环变量。
如果$direction为1,顺时针。
进入循环体。在循环体中,$u逐渐加1,然后计算除以8的余数(%为取余运算),因为nurbs圆的最大U值为8,且0与8重合,所以$u不会大于等于8。逆时针时,$u逐渐减1,然后加8,确保其不是负数,然后再与8求余,确保其不大于8。
剩下的语句就简单了。
最后删除临时曲面和临时圆,选取生成的弹簧曲线,返回该曲线的名称。

怎么样?不算太难吧?这一课够抵上前面两三课了。

作业:编写生成球形弹簧的函数。这是GU中还没有的功能啊。



5D荣誉斑竹

职务:普通成员
等级:5
金币:10.0
发贴:1326
#92002/6/14 15:47:42
再次感谢古木,能否编个群组动画的插件?



goomoo

职务:普通成员
等级:1
金币:0.0
发贴:55
#102002/7/19 13:55:20
在思索中,不知能否成功哦.



5D荣誉斑竹

职务:普通成员
等级:5
金币:10.0
发贴:1326
#112002/10/13 11:47:36
goomoo.diy.163.com/
古木兄的主页



vitty

职务:普通成员
等级:1
金币:0.0
发贴:1
#122004/12/5 1:02:06
多谢!