mel语初解之二-多边型建模

众所周知,maya的多边形建模能力是不如人意的,因此这时mel会派上很大的用� O嘈藕芏嗳硕加霉恍└ㄖ缘慕9ぞ撸鏜JPolyTools、BPT、icePolyTools…我在教程中会对这些程序的关键功能的编写方法作出详细说明,希望大家能在掌握这些功能的基础之上编写出自己称

Maya教程:顶级灯光环境设置教程(一)

这教程全部使用Autodesk Maya8.5的MentalRay进行渲染 幸福就像阳光:有光线的环境才让人感到舒适。让我们开始这次由Otto Ludwing(奥托.路德)提供的案例练习. 欢迎共同讨论关于挑战制作写实3D室内环境的可行性。

众所周知,maya的多边形建模能力是不如人意的,因此这时mel会派上很大的用场。相信很多人都用过一些辅助性的建模工具,例如MJPolyTools、BPT、icePolyTools…我在教程中会对这些程序的关键功能的编写方法作出详细说明,希望大家能在掌握这些功能的基础之上编写出自己称心如意的Poly工具。

现在继续mel的教程,如果你对mel还一无所知的话,请看mel语初解之一-基础和界面篇:
http:///photoshop/jiaocheng/106791.html

众所周知,maya的多边形建模能力是不如人意的,因此这时mel会派上很大的用场。相信很多人都用过一些辅助性的建模工具,例如MJPolyTools、BPT、icePolyTools、CPS、drawSplit、rockGen…我在教程中会对这些程序的关键功能的编写方法作出详细说明,希望大家能在掌握这些功能的基础之上编写出自己称心如意的Poly工具。

mel作为脚本语言使用非常方便,在工作中会很容易地把你的一些简单想法付诸实践。

在讲Poly建模之前,需要复习一下以前的知识。

首先要复习一下数组(Array):一群变量放到了一起,这群变量就成了一个数组变量。不过这些变量不是随便放的,每个变量都有一个房间,每个房间都有顺次的门牌号,我们就是根据门牌号来访问任何一个数组成员的。请看这个字符串数组的例子:

选择几个场景中的物体。

mel语初解之二-多边型建模插图

// 获取场景中的每一个物体,分别放入数组$objects的每个房间中
string $objects[] = `ls -sl`;

这时数组的状态如图所示。$objects可以看作是公寓的名称,[]里的红色数字为房间的门牌号,也叫作索引号(index)。数组的索引号总是从0开始的。也就是说$objects[0]为数组的第一个成员,它的值为”pSphere1″;而$objects[1]为数组的第二个成员,他的值为”pCube1″;以此类推。

我们可以从数组中取值,例如:
string $obj = $objects[0];
// 此时变量$obj的值为”pSphere1″

也可以给数组的成员赋值,例如:
$objects[1] = “pBox1”;
// 此时数组$objects的值为{“pSphere1”, “pBox1”, “pCone1”}

mel语初解之二-多边型建模插图(1)

要想遍历数组中的每个成员,可以用for语句,有两种方法。

// 方法一
string $objects[] = `ls -sl`;
for ($i = 0; $i < size($objects); $i++)
{
string $obj = $objects[$i];
// do something …
}

// 方法二
string $objects[] = `ls -sl`;
for ($obj in $objects)
{
// do something …
}
[注] mel的for…in语句和JavaScript有所不同,$obj是字符串,指的是
当前的数组成员,等同于”string $obj = $objects[$i];”

再复习一下函数(Function):

如果你编写比较复杂的程序,就会发现有很多经常用到的语句,这些语句经常以相同的组合出现。这样的语句编写起来有些麻烦,看起来也不太直观。为了提高工作效率,增加可读性,我们可以使用函数把它们封装起来。下面举例说明。

还记得前面讲过的filterExpand获取多边形面的方法吧?

string $faces[] = `filterExpand -ex 1 -sm 34`;

对初学者来说,看到”-sm 34″后,总是很难联想到多边形的面。当然你可以用maya的全局变量$gSelectMeshFaces来替代34,不过这样做有些麻烦。我们编一个新的函数来做与上面代码同样的事情。

proc string[] getSelFaces()
{
return `filterExpand -ex true -sm 34`;
}
// [注] Sel为Selected的缩写

有了这个函数,我们以后再获取多边形的面时,就可以这样写:string $faces[] = `getSelFaces`;

也可以这样写:string $faces[] = getSelFaces();

return为返回的意思,proc后面的字代表返回值的类型,return后面的字(变量或表达式)代表返回值,也就是函数的输出值。

对初学者来说,看到”-sm 34″后,总是很难联想到多边形的面。当然你可以用maya的全局变量$gSelectMeshFaces来替代34,不过这样做有些麻烦。我们编一个新的函数来做与上面代码同样的事情。

proc string[] getSelFaces()
{
return `filterExpand -ex true -sm 34`;
}
// [注] Sel为Selected的缩写

有了这个函数,我们以后再获取多边形的面时,就可以这样写:string $faces[] = `getSelFaces`;

也可以这样写:string $faces[] = getSelFaces();

return为返回的意思,proc后面的字代表返回值的类型,return后面的字(变量或表达式)代表返回值,也就是函数的输出值。

再看一个例子:

proc string[] getPolySel(string $type)
{
if ($type == “vert”)
return `filterExpand -ex true -sm 31`;

if ($type == “edge”)
return `filterExpand -ex true -sm 32`;

if ($type == “face”)
return `filterExpand -ex true -sm 34`;

// 假如输入参数是非预期的,就返回一个空数组
string $null[];
return $null;
}
想要获取多边形的面时,可以这样写: string $faces[] = getSelFaces(“face”);

或: string $faces[] = `getPolySel “face”`;

这回用到了函数的输入参数(string $type),根据输入参数的不同,产成不同的返回值。

mel语初解之二-多边型建模插图(2)

一个函数可以既没有输入参数也没有返回值,也可以只有其一。参数可以是多个,返回值只能是一个。

return语句执行之后,后面的语句将不再执行。例如:
 

proc myProc()
{
// 获取选择的物体
string $objects[] = `ls -sl`;
// 如果什么都没选择,就返回(什么也不做)。
if (!size($objects))
return;

// do something ...
}
proc myProc()
{
// 获取选择的物体
string $objects[] = `ls -sl`;
// 如果什么都没选择,就返回(什么也不做)。
if (!size($objects))
return;

// do something ...
}

或: string $faces[] = `getPolySel “face”`;

这回用到了函数的输入参数(string $type),根据输入参数的不同,产成不同的返回值。

mel语初解之二-多边型建模插图(2)

一个函数可以既没有输入参数也没有返回值,也可以只有其一。参数可以是多个,返回值只能是一个。

return语句执行之后,后面的语句将不再执行。例如:
 

proc myProc()
{
// 获取选择的物体
string $objects[] = `ls -sl`;
// 如果什么都没选择,就返回(什么也不做)。
if (!size($objects))
return;

// do something ...
}
proc myProc()
{
// 获取选择的物体
string $objects[] = `ls -sl`;
// 如果什么都没选择,就返回(什么也不做)。
if (!size($objects))
return;

// do something ...
}

global procproc的区别

proc是局部函数,局部函数只能在编写这个函数的mel文件中使用,不能在其他mel文件中使用,不能作为菜单和按钮命令,不占用内存空间。

global proc是全局函数,没有proc那些局限。使用全局函数应注意,函数名不能与Maya中已有的全局函数或mel命令相同,否则会把原来的覆盖掉,可以通过使用函数名前缀来避免重复命名。关于全局函数的使用,最好了解一些Maya的运行方式。Maya启动时一般只把指定scripts路径中的*.mel文件名(*)载入内存,这样Maya运行时就可以调用这个文件中的同名函数,而当调用这个同名函数时,这个mel文件中的所有全局函数将被载入内存,直到Maya退出。

如果还不明白,那就统统使用global proc好了,没什么大不了的。

下面提供几个多边形建模常用到的函数,因为后面经常用到,所以应该熟练掌握,至少对于每个函数做什么事要很清楚。
// 获取选择的多边形顶点
proc string[] getSelVerts()
{
return `filterExpand -ex true -sm 31`;
}

// 获取选择的多边形边
proc string[] getSelEdges()
{
return `filterExpand -ex true -sm 32`;
}

// 获取选择的多边形面
proc string[] getSelFaces()
{
return `filterExpand -ex true -sm 34`;
}

// 获取选择的多边形UV点
proc string[] getSelUVs()
{
return `filterExpand -ex true -sm 35`;
}

用法范例:

// 获取选择的所有面,存放到数组$faces[]中
string $faces[] = getSelFaces();
    这四个函数是maya内置的,也是菜单命令,经常用到。

// 菜单命令:Edit Polygons->Selection->Convert Selection to Vertices
// 转换当前选择为顶点

ConvertSelectionToVertices();

// 菜单命令:Edit Polygons->Selection->Convert Selection to Edges
// 转换当前选择为边

ConvertSelectionToEdges();

// 菜单命令:Edit Polygons->Selection->Convert Selection to Faces
// 转换当前选择为面

ConvertSelectionToFaces();

// 菜单命令:Edit Polygons->Selection->Convert Selection to UVs
// 转换当前选择为UV点

ConvertSelectionToUVs();

用法范例:

// 获取选择的所有面,存放到数组$faces[]中
string $faces[] = getSelFaces();
    这四个函数是maya内置的,也是菜单命令,经常用到。

// 菜单命令:Edit Polygons->Selection->Convert Selection to Vertices
// 转换当前选择为顶点

ConvertSelectionToVertices();

// 菜单命令:Edit Polygons->Selection->Convert Selection to Edges
// 转换当前选择为边

ConvertSelectionToEdges();

// 菜单命令:Edit Polygons->Selection->Convert Selection to Faces
// 转换当前选择为面

ConvertSelectionToFaces();

// 菜单命令:Edit Polygons->Selection->Convert Selection to UVs
// 转换当前选择为UV点

ConvertSelectionToUVs();

这四个函数在maya的scripts/others目录中,可以直接调用。

// 转换当前选择为顶点,并获取这些顶点的名称
global proc string[] getVerts()
{
select -r `polyListComponentConversion -tv`;
string $result[]=`filterExpand -ex true -sm 31`;
return $result;
}

// 转换当前选择为边,并获取这些点的名称
global proc string[] getEdges()
{
select -r `polyListComponentConversion -te`;
string $result[]=`filterExpand -ex true -sm 32`;
return $result;
}

// 转换当前选择为面,并获取这些面的名称
global proc string[] getFaces()
{
select -r `polyListComponentConversion -tf`;
string $result[]=`filterExpand -ex true -sm 34`;
return $result;
}

// 转换当前选择为UV点,并获取这些UV点的名称
global proc string[] getUVs()
{
string $uvs[];
$uvs=`polyListComponentConversion -tuv`;
if (size($uvs) == 0) return $uvs;
select -r $uvs;
string $result[]=`filterExpand -ex true -sm 35`;
return $result;
}
// 根据点、边、面、UV点的名称得出多边形的名称
// 例如多边形一条边的名称为"pSphere1.e[637]",则这个多边形的
// 名称为"pSphere1"

proc string getBaseName(string $item)
{
string $buffer[];
if ($item != "")
{
tokenize($item, ".", $buffer);
}
return $buffer[0];
}

用法范例:

string $polyName = getBaseName("pSphere1.e[637]");

// 返回值:pSphere1
// 根据点、边、面、UV点的名称得出它们的索引号
// 例如多边形一条边的名称为"pSphere1.e[637]",则这个多边形的
// 索引号为637

proc int getIndex(string $indexString)
{
string $buffer[];
tokenize($indexString, "[]", $buffer);
int $index = (int)$buffer[1];
return $index;
}

用法范例:

int $index = getIndex("pSphere1.e[637]");
// 返回值:637

首先介绍一个有用的函数(intersectStringArray)。这个函数可以找到两个数组的共同部分,比如数组1为{“兔子”, “老虎”, “山羊”, “虫子”},数组2为{“虫子”, “刀子”, “梳子”, “兔子”, “珠子”},你可以获得一个新数组包含它们的共同部分{“兔子”, “虫子”}。

// 获得两个数组的共同部分
proc string[] intersectStringArray(string $array1[], string $array2[])
{
global string $m_arrayIntersector;
if ($m_arrayIntersector == "")
$m_arrayIntersector = `stringArrayIntersector`;

stringArrayIntersector -edit -intersect $array1 $m_arrayIntersector;
stringArrayIntersector -edit -intersect $array2 $m_arrayIntersector;
string $result[] = `stringArrayIntersector -query $m_arrayIntersector`;
stringArrayIntersector -edit -reset $m_arrayIntersector;
return $result;
}
    [注] global string代表一个全局字符串变量,以前讲过全局变量应当尽量避免命名冲突。maya中的全局变量都是以
小写字母"g"开头,为避免冲突,本教程中的全局变量一律使用"m_"作为前缀。
    前面介绍过的函数可以看作是工具函数,这些函数几乎在以后的每个程序中都要用到。如果编写某一功能,还需要编写
一些有针对性的专用函数。现在我们来编一个多边形的导角功能,来看看一个完整的程序是怎样完成的。
mel语初解之二-多边型建模插图(4)
			
 
			
		
这是一些必须记住的单词,相信所有学过Maya的人都不会感到陌生。

单词 缩写 解释
polygon poly 多边形
vertex v;ver;vert;vtx 多边形顶点
edge e;ed 多边形边线
face f 多边形面
split 切割
index idx 索引
    要编写一个比较复杂的程序,我们首先考虑的是应该怎样把这个程序做最大程度的简化,要把一个庞大的东西拆成一小块
一小块的分别去处理。今天我们需要完成第一小块,就是当你选择一条边时,程序可以在这条边的两侧各切一刀,如图。
mel语初解之二-多边型建模插图(5)

要做到这一点,需要分成四步。

第一步,我们需要做一点准备工作,要了解一下切割命令polySplit和边的构造顺序。

为了更直观的说明程序的原理,我尽量多放一些插图。

选择菜单Polygons->Create Polygon Tool,从左上角开始,画一个正方形。这时看看mel历史窗,可以看到polyCreateFacet命令,这个命令目前还用不到,先不去管他。

mel语初解之二-多边型建模插图(6)

依次选择正方形的四个顶点,看看每个顶点的名称和索引号。

mel语初解之二-多边型建模插图(7)

可以发现索引号是按照创建时的顺序指定的。分别为0,1,2,3。

mel语初解之二-多边型建模插图(8)

再看看每条边的索引号,也是按照创建时的顺序指定的。一条边有两个点,分别为起点和终点,这两个点决定了边的构造顺序。

mel语初解之二-多边型建模插图(9)

使用Edit Polygons->Split Polygon Tool在正方形上切一刀。

mel语初解之二-多边型建模插图(10)

我们看一下polySplit的用法,-ep后面有两个参数,第一个参数(3)是边的索引号,第二个参数(0.263489)是百分比,如果边的长度为1,切割点在边的0.263489处。

切割点位置的受到边的构造顺序的影响,以polySurface1.e[3]这条边为例,从边的起点开始,沿着边的终点方向量出整条边的约26%的长度,这个位置就是切割点的位置。

mel语初解之二-多边型建模插图(11)

使用polySplit的一大难点就是判断边的构造顺序,也就是分清边的起点和终点。为了做到这一点,我们需要用到一个mel命令 – polyInfo

选择一条边线(e[3]),在命令行执行”polyInfo -ev;”,可以看到输出结果”// Result: EDGE 3: 3 0 Hard”,其中”EDGE 3:”代表边线(e[3]),3和0分别代表组成这条边的两个点(vtx[3]和vtx[0])的索引号。注意,这两个点的顺序不是按大小排列的,而是按照边线的构造顺序。

mel语初解之二-多边型建模插图(12)

我们把polyInfo按照自己的需要封装起来。主要是用字符处理的方法实现的,注意这里用到了一个前面讲过的工具函数getBaseName()。你会发现这个函数的用途与getVerts()很像,但getVerts()无法得知边线的构造顺序。

// 根据一条边,得到这条边的按构造顺序排列的两个端点。
proc string[] edge2Vertex(string $edge)
{
string $verts[], $buffer[];
string $edgeInfo[] = `polyInfo -ev $edge`;
int $nbVertex = tokenize($edgeInfo[0], $buffer);

string $polyName = getBaseName($edge);
$verts[0] = $polyName + ".vtx[" + $buffer[2] + "]";
$verts[1] = $polyName + ".vtx[" + $buffer[3] + "]";
return $verts;
}
第二步,我们要找到需要切割的两条边。
我们可以根据选择的一条边,和要切割的那个面来判断。
mel语初解之二-多边型建模插图(13)
    选择一条边。Mel历史窗中的代码:  select -r polySurface1.e[6] ;
mel语初解之二-多边型建模插图(14)

Edit Polygons->Selection->Convert Selection to Vertices,转换成顶点。
[注] 这一步mel历史窗中可能看不到变化,按z键undo一下就看到了。

Mel历史窗中的代码: ConvertSelectionToVertices;

mel语初解之二-多边型建模插图(15)

再选择Edit Polygons->Selection->Convert Selection to Edges,转换成边。

Mel历史窗中的代码: ConvertSelectionToEdges;

mel语初解之二-多边型建模插图(16)

去掉开始那条边的选择。

Mel历史窗中的代码: select -tgl polySurface1.e[6] ;

[注] select -d polySurface1.e[6] ;也可。

[注] select -d polySurface1.e[6] ;也可。

现在剩下四条边,可以用getSelEdges()把它们存到一个数组中。
数组1:
{“polySurface1.e[1]”,
“polySurface1.e[3]”,
“polySurface1.e[4]”,
“polySurface1.e[5]”}

mel语初解之二-多边型建模插图(17)

选择要切割的面。Mel历史窗中的代码: select -r polySurface1.f[1] ;

用getEdges()把属于面的四条边存到另一个数组中。
{polySurface1.e[0],
polySurface1.e[1],
polySurface1.e[4],
polySurface1.e[6]}

mel语初解之二-多边型建模插图(18)

用intersectStringArray()可以找到两个数组的共同部分,就是我们将要切割的两条边。
{polySurface1.e[1],
polySurface1.e[4]}

mel语初解之二-多边型建模插图(19)

用getEdges()把属于面的四条边存到另一个数组中。
{polySurface1.e[0],
polySurface1.e[1],
polySurface1.e[4],
polySurface1.e[6]}

mel语初解之二-多边型建模插图(18)

用intersectStringArray()可以找到两个数组的共同部分,就是我们将要切割的两条边。
{polySurface1.e[1],
polySurface1.e[4]}

mel语初解之二-多边型建模插图(19)

把前面Mel历史窗中记录下的代码整理一下,就成了:

// 已知一个面,这个面的一条边,求与(这个面的)这条边相邻的两条边
proc string[] adjacentEdgesInFace(string $face, string $edge)
{
// 获取所有相邻的边线
select -r $edge;
ConvertSelectionToVertices();
ConvertSelectionToEdges();
select -d $edge;
string $edges_vert[] = getSelEdges();

// 获取已知面的所有边线
select -r $face;
string $edges_face[] = getEdges();

// 求两个数组的共同部分
string $edges[] = intersectStringArray($edges_vert, $edges_face);
return $edges;
}

			
 
			
		

第三步,切割一个面。

我们可以先把切割的百分比设置一个固定的数值,设为0.2(20%)。我们可以通过edge2Vertex()来得到要切割的一条边

的起点和终点,如果起点恰好是当初选择的那条边线的一个端点(两条边的公共点),那么这条线的构造顺序是正的,可以直接

使用20%;但如果构造顺序是反的,那就要使用1-20%=80%了。

mel语初解之二-多边型建模插图(22)
这个函数应该这么写:

proc splitByPercent(string $edge1, string $edge2, string $inputEdge)
{
// 预设值,百分比为0.2
float $percent = 0.2;
float $percent1 = $percent; // 0.2
float $percent2 = $percent; // 0.2

// 分别获得三条边所包含的顶点
string $verts1[], $verts2[], $vInput[];
$vInput = edge2Vertex($inputEdge);
$verts1 = edge2Vertex($edge1);
$verts2 = edge2Vertex($edge2);

// 求$edge1与$inputEdge的公共点
string $startVert[] = intersectStringArray($verts1, $vInput);
// 如果公共点不是$edge1的起点
if ($startVert[0] != $verts1[0])
// 百分比变为80%,即1-0.2
$percent1 = 1 - $percent;

// 求$edge2与$inputEdge的公共点
string $startVert[] = intersectStringArray($verts2, $vInput);
if ($startVert[0] != $verts2[0])
$percent2 = 1 - $percent;

// 获得两条边的索引号
string $index1 = getIndex($edge1);
string $index2 = getIndex($edge2);

// 准备命令字符串
string $cmd = "polySplit -ch on -s 1 ";
$cmd += "-ep " + $index1 + " " + $percent1 + " ";
$cmd += "-ep " + $index2 + " " + $percent2 + " ";
$cmd += ";";

// 选择整个多边形物体
string $polyName = getBaseName($edge1);
select -r $polyName;

// 执行命令
evalEcho($cmd);
}

[注] 使用evalEcho执行命令可以把命令字符串在mel历史窗中显示出来。

第四步,切割边线两边的面。

有了前面的准备工作,最后一步就显得比较容易了。

global proc myEdgeChamfer()
{
// 获取选择的一条边
string $edges[] = getSelEdges();
string $inputEdge = $edges[0];

// 获取选择的边相邻的两个面
string $faces[] = getFaces();

// 等比切割第1个面
string $splitEdges[];
$splitEdges = adjacentEdgesInFace($faces[0], $inputEdge);
splitByPercent($splitEdges[0], $splitEdges[1], $inputEdge);

// 等比切割第2个面
$splitEdges = adjacentEdgesInFace($faces[1], $inputEdge);
splitByPercent($splitEdges[0], $splitEdges[1], $inputEdge);
}
附全部源代码。

///////////////////////////////////////////////////////////
// myEdgeChamfer.mel
// myEdgeChamfer v1

// 获取选择的多边形顶点
proc string[] getSelVerts()
{
return `filterExpand -ex true -sm 31`;
}

// 获取选择的多边形边
proc string[] getSelEdges()
{
return `filterExpand -ex true -sm 32`;
}

// 获取选择的多边形面
proc string[] getSelFaces()
{
return `filterExpand -ex true -sm 34`;
}

// 根据点、边、面、UV点的名称得出多边形的名称
// 例如多边形一条边的名称为"pSphere1.e[637]",则这个多边形的
// 名称为"pSphere1"
proc string getBaseName(string $item)
{
string $buffer[];
if ($item != "")
{
tokenize($item, ".", $buffer);
}
return $buffer[0];
}

// 根据点、边、面、UV点的名称得出它们的索引号
// 例如多边形一条边的名称为”pSphere1.e[637]”,则这个多边形的

// 索引号为637
proc int getIndex(string $indexString)
{
string $buffer[];
tokenize($indexString, “[]”, $buffer);
int $index = (int)$buffer[1];
return $index;
}

// 获得两个数组的共同部分
proc string[] intersectStringArray(string $array1[], string $array2[])
{
global string $m_arrayIntersector;
if ($m_arrayIntersector == “”)
$m_arrayIntersector = `stringArrayIntersector`;

stringArrayIntersector -edit -intersect $array1 $m_arrayIntersector;
stringArrayIntersector -edit -intersect $array2 $m_arrayIntersector;
string $result[] = `stringArrayIntersector -query $m_arrayIntersector`;
stringArrayIntersector -edit -reset $m_arrayIntersector;
return $result;
}

///////////////////////////////////////////////////////////
// 第一步,根据一条边,得到这条边的按构造顺序排列的两个端点。
proc string[] edge2Vertex(string $edge)
{
string $verts[], $buffer[];
string $edgeInfo[] = `polyInfo -ev $edge`;
int $nbVertex = tokenize($edgeInfo[0], $buffer);

string $polyName = getBaseName($edge);
$verts[0] = $polyName + “.vtx[” + $buffer[2] + “]”;
$verts[1] = $polyName + “.vtx[” + $buffer[3] + “]”;
return $verts;
}

// 已知一个面,这个面的一条边,求与(这个面的)这条边相邻的两条边
proc string[] adjacentEdgesInFace(string $face, string $edge)
{
// 获取所有相邻的边线
select -r $edge;
ConvertSelectionToVertices();
ConvertSelectionToEdges();
select -d $edge;
string $edges_vert[] = getSelEdges();

// 获取已知面的所有边线
select -r $face;
string $edges_face[] = getEdges();

// 求两个数组的共同部分
string $edges[] = intersectStringArray($edges_vert, $edges_face);
return $edges;
}

// 第三步,等比切割一个面
proc splitByPercent(string $edge1, string $edge2, string $inputEdge)
{
// 预设值,百分比为0.2
float $percent = 0.2;
float $percent1 = $percent; // 0.2
float $percent2 = $percent; // 0.2

// 分别获得三条边所包含的顶点
string $verts1[], $verts2[], $vInput[];
$vInput = edge2Vertex($inputEdge);
$verts1 = edge2Vertex($edge1);
$verts2 = edge2Vertex($edge2);

// 求$edge1与$inputEdge的公共点
string $startVert[] = intersectStringArray($verts1, $vInput);
// 如果公共点不是$edge1的起点
if ($startVert[0] != $verts1[0])
// 百分比变为80%,即1-0.2
$percent1 = 1 – $percent;

// 求$edge2与$inputEdge的公共点
string $startVert[] = intersectStringArray($verts2, $vInput);
if ($startVert[0] != $verts2[0])
$percent2 = 1 – $percent;

// 获得两条边的索引号
string $index1 = getIndex($edge1);
string $index2 = getIndex($edge2);

// 准备命令字符串
string $cmd = “polySplit -ch on -s 1 “;
$cmd += “-ep ” + $index1 + ” ” + $percent1 + ” “;
$cmd += “-ep ” + $index2 + ” ” + $percent2 + ” “;
$cmd += “;”;

// 选择整个多边形物体
string $polyName = getBaseName($edge1);
select -r $polyName;

// 执行命令
evalEcho($cmd);
}

// 第四步,切割选择的一条边线两边的面。
global proc myEdgeChamfer()
{
// 获取选择的一条边
string $edges[] = getSelEdges();
string $inputEdge = $edges[0];

// 获取选择的边相邻的两个面
string $faces[] = getFaces();

// 等比切割第1个面
string $splitEdges[];
$splitEdges = adjacentEdgesInFace($faces[0], $inputEdge);
splitByPercent($splitEdges[0], $splitEdges[1], $inputEdge);

// 等比切割第2个面
$splitEdges = adjacentEdgesInFace($faces[1], $inputEdge);
splitByPercent($splitEdges[0], $splitEdges[1], $inputEdge);
}

mel语初解之二-多边型建模插图(23)myEdgeChamfer_v1.rar (1.46k)

等距切割

mel语初解之二-多边型建模插图(24)

要想知道一条边的长度,如果是Maya5.0或更低版本,可以使用arclen命令,但是到了Maya6.0,arclen命令只支持Nurbs,退化了?

这样我们不得不用到获得两点之间的距离的功能来计算边的长度。现在要提到一点图形学知识,当然是最最基础的,就是”勾股定理”。相信大家都能记得一点”勾股定理”的内容,我就不详细讲了。如果需要详细讲的话,可以提出来,我可以在后面补充。

“勾股定理”的公式是: a2 + b2 = c2

根据这个公式推理出3D空间勾股定理公式:x2 + y2 + z2 = d2

如果求两点之间的距离,公式如图:dist为点P1(x1,y1,z1)和P2(x2,y2,z2)之间的距离

mel语初解之二-多边型建模插图(25)

根据公式,我们来编一个工具函数求两点之间的距离。你也许会感到奇怪,这么常用的功能,Maya中为什么没有内置的命令呢?这一点我也感到奇怪,好在编写这样的功能非常简单。

// 计算两个顶点之间的距离
proc float distance2Verts(string $vert1, string $vert2)
{
// 获取两个顶点的坐标值
float $p1[] = `pointPosition $vert1`;
float $p2[] = `pointPosition $vert2`;

// 计算距离
float $distance;
float $v[3];
$v[0] = $p1[0] – $p2[0];
$v[1] = $p1[1] – $p2[1];
$v[2] = $p1[2] – $p2[2];
$distance = $v[0]*$v[0] + $v[1]*$v[1] + $v[2]*$v[2];
$distance = sqrt($distance);// 开方

return $distance;
}

[注] 获取点的坐标值还有一种方法是:
float $p1[] = `xform -q -ws -t $ver1`;

[注] 获取点的坐标值还有一种方法是:
float $p1[] = `xform -q -ws -t $ver1`;

现在只要把等比切割的函数改一改就差不多了,把splitByPercent()改为splitByDist()。我把没有改动的部分用灰绿色表示,重点看看改动的部分。
[注] dist为distance的缩写。

// 第三步,等距切割一个面
proc splitByDist(string $edge1, string $edge2, string $inputEdge)
{
// 预设值,距离为0.2个单元格
float $dist = 0.2;
float $percent1;
float $percent2;

// 分别获得三条边所包含的顶点
string $verts1[], $verts2[], $vInput[];
$vInput = edge2Vertex($inputEdge);
$verts1 = edge2Vertex($edge1);
$verts2 = edge2Vertex($edge2);


// 计算第一条边的切割处
float $edgeLength; // 求第一条边的长度
$edgeLength = distance2Verts($verts1[0], $verts1[1]);
if ($edgeLength < $dist) // 如果长度小于预设值0.2
$percent1 = 1; // 切割处在线的的末端
else
$percent1 = $dist / $edgeLength; // 计算出百分比

// 计算第二条边的切割处
$edgeLength = distance2Verts($verts2[0], $verts2[1]);
if ($edgeLength < $dist)
$percent2 = 1;
else
$percent2 = $dist / $edgeLength;

// 求$edge1与$inputEdge的公共点
string $startVert[] = intersectStringArray($verts1, $vInput);
// 如果公共点不是$edge1的起点
if ($startVert[0] != $verts1[0])

// 百分比变为1-$percent1
$percent1 = 1 - $percent1;

// 求$edge2与$inputEdge的公共点
string $startVert[] = intersectStringArray($verts2, $vInput);
if ($startVert[0] != $verts2[0])

$percent2 = 1 - $percent2;

// 获得两条边的索引号
string $index1 = getIndex($edge1);
string $index2 = getIndex($edge2);

// 准备命令字符串
string $cmd = "polySplit -ch on -s 1 ";
$cmd += "-ep " + $index1 + " " + $percent1 + " ";
$cmd += "-ep " + $index2 + " " + $percent2 + " ";
$cmd += ";";

// 选择整个多边形物体
string $polyName = getBaseName($edge1);
select -r $polyName;

// 执行命令
evalEcho($cmd);

}

把distance2Verts()函数的定义加进去,再把myEdgeChamfer()函数中的两处splitByPercent()改为splitByDist(),这个程序就完成了。

附全部源代码。

mel语初解之二-多边型建模插图(23)myEdgeChamfer_v2.rar (1.74k)

众所周知,maya的多边形建模能力是不如人意的,因此这时mel会派上很大的用场。相信很多人都用过一些辅助性的建模工具,例如MJPolyTools、BPT、icePolyTools…我在教程中会对这些程序的关键功能的编写方法作出详细说明,希望大家能在掌握这些功能的基础之上编写出自己称心如意的Poly工具。

现在继续mel的教程,如果你对mel还一无所知的话,请看mel语初解之一-基础和界面篇:
http:///photoshop/jiaocheng/106791.html

众所周知,maya的多边形建模能力是不如人意的,因此这时mel会派上很大的用场。相信很多人都用过一些辅助性的建模工具,例如MJPolyTools、BPT、icePolyTools、CPS、drawSplit、rockGen…我在教程中会对这些程序的关键功能的编写方法作出详细说明,希望大家能在掌握这些功能的基础之上编写出自己称心如意的Poly工具。

mel作为脚本语言使用非常方便,在工作中会很容易地把你的一些简单想法付诸实践。

在讲Poly建模之前,需要复习一下以前的知识。

首先要复习一下数组(Array):一群变量放到了一起,这群变量就成了一个数组变量。不过这些变量不是随便放的,每个变量都有一个房间,每个房间都有顺次的门牌号,我们就是根据门牌号来访问任何一个数组成员的。请看这个字符串数组的例子:

选择几个场景中的物体。

mel语初解之二-多边型建模插图

// 获取场景中的每一个物体,分别放入数组$objects的每个房间中
string $objects[] = `ls -sl`;

这时数组的状态如图所示。$objects可以看作是公寓的名称,[]里的红色数字为房间的门牌号,也叫作索引号(index)。数组的索引号总是从0开始的。也就是说$objects[0]为数组的第一个成员,它的值为”pSphere1″;而$objects[1]为数组的第二个成员,他的值为”pCube1″;以此类推。

我们可以从数组中取值,例如:
string $obj = $objects[0];
// 此时变量$obj的值为”pSphere1″

也可以给数组的成员赋值,例如:
$objects[1] = “pBox1”;
// 此时数组$objects的值为{“pSphere1”, “pBox1”, “pCone1”}

mel语初解之二-多边型建模插图(1)

要想遍历数组中的每个成员,可以用for语句,有两种方法。

// 方法一
string $objects[] = `ls -sl`;
for ($i = 0; $i < size($objects); $i++)
{
string $obj = $objects[$i];
// do something …
}

// 方法二
string $objects[] = `ls -sl`;
for ($obj in $objects)
{
// do something …
}
[注] mel的for…in语句和JavaScript有所不同,$obj是字符串,指的是
当前的数组成员,等同于”string $obj = $objects[$i];”

再复习一下函数(Function):

如果你编写比较复杂的程序,就会发现有很多经常用到的语句,这些语句经常以相同的组合出现。这样的语句编写起来有些麻烦,看起来也不太直观。为了提高工作效率,增加可读性,我们可以使用函数把它们封装起来。下面举例说明。

还记得前面讲过的filterExpand获取多边形面的方法吧?

string $faces[] = `filterExpand -ex 1 -sm 34`;

对初学者来说,看到”-sm 34″后,总是很难联想到多边形的面。当然你可以用maya的全局变量$gSelectMeshFaces来替代34,不过这样做有些麻烦。我们编一个新的函数来做与上面代码同样的事情。

proc string[] getSelFaces()
{
return `filterExpand -ex true -sm 34`;
}
// [注] Sel为Selected的缩写

有了这个函数,我们以后再获取多边形的面时,就可以这样写:string $faces[] = `getSelFaces`;

也可以这样写:string $faces[] = getSelFaces();

return为返回的意思,proc后面的字代表返回值的类型,return后面的字(变量或表达式)代表返回值,也就是函数的输出值。

对初学者来说,看到”-sm 34″后,总是很难联想到多边形的面。当然你可以用maya的全局变量$gSelectMeshFaces来替代34,不过这样做有些麻烦。我们编一个新的函数来做与上面代码同样的事情。

proc string[] getSelFaces()
{
return `filterExpand -ex true -sm 34`;
}
// [注] Sel为Selected的缩写

有了这个函数,我们以后再获取多边形的面时,就可以这样写:string $faces[] = `getSelFaces`;

也可以这样写:string $faces[] = getSelFaces();

return为返回的意思,proc后面的字代表返回值的类型,return后面的字(变量或表达式)代表返回值,也就是函数的输出值。

再看一个例子:

proc string[] getPolySel(string $type)
{
if ($type == “vert”)
return `filterExpand -ex true -sm 31`;

if ($type == “edge”)
return `filterExpand -ex true -sm 32`;

if ($type == “face”)
return `filterExpand -ex true -sm 34`;

// 假如输入参数是非预期的,就返回一个空数组
string $null[];
return $null;
}
想要获取多边形的面时,可以这样写: string $faces[] = getSelFaces(“face”);

或: string $faces[] = `getPolySel “face”`;

这回用到了函数的输入参数(string $type),根据输入参数的不同,产成不同的返回值。

mel语初解之二-多边型建模插图(2)

一个函数可以既没有输入参数也没有返回值,也可以只有其一。参数可以是多个,返回值只能是一个。

return语句执行之后,后面的语句将不再执行。例如:
 

proc myProc()
{
// 获取选择的物体
string $objects[] = `ls -sl`;
// 如果什么都没选择,就返回(什么也不做)。
if (!size($objects))
return;

// do something ...
}
proc myProc()
{
// 获取选择的物体
string $objects[] = `ls -sl`;
// 如果什么都没选择,就返回(什么也不做)。
if (!size($objects))
return;

// do something ...
}

或: string $faces[] = `getPolySel “face”`;

这回用到了函数的输入参数(string $type),根据输入参数的不同,产成不同的返回值。

mel语初解之二-多边型建模插图(2)

一个函数可以既没有输入参数也没有返回值,也可以只有其一。参数可以是多个,返回值只能是一个。

return语句执行之后,后面的语句将不再执行。例如:
 

proc myProc()
{
// 获取选择的物体
string $objects[] = `ls -sl`;
// 如果什么都没选择,就返回(什么也不做)。
if (!size($objects))
return;

// do something ...
}
proc myProc()
{
// 获取选择的物体
string $objects[] = `ls -sl`;
// 如果什么都没选择,就返回(什么也不做)。
if (!size($objects))
return;

// do something ...
}

global procproc的区别

proc是局部函数,局部函数只能在编写这个函数的mel文件中使用,不能在其他mel文件中使用,不能作为菜单和按钮命令,不占用内存空间。

global proc是全局函数,没有proc那些局限。使用全局函数应注意,函数名不能与Maya中已有的全局函数或mel命令相同,否则会把原来的覆盖掉,可以通过使用函数名前缀来避免重复命名。关于全局函数的使用,最好了解一些Maya的运行方式。Maya启动时一般只把指定scripts路径中的*.mel文件名(*)载入内存,这样Maya运行时就可以调用这个文件中的同名函数,而当调用这个同名函数时,这个mel文件中的所有全局函数将被载入内存,直到Maya退出。

如果还不明白,那就统统使用global proc好了,没什么大不了的。

下面提供几个多边形建模常用到的函数,因为后面经常用到,所以应该熟练掌握,至少对于每个函数做什么事要很清楚。
// 获取选择的多边形顶点
proc string[] getSelVerts()
{
return `filterExpand -ex true -sm 31`;
}

// 获取选择的多边形边
proc string[] getSelEdges()
{
return `filterExpand -ex true -sm 32`;
}

// 获取选择的多边形面
proc string[] getSelFaces()
{
return `filterExpand -ex true -sm 34`;
}

// 获取选择的多边形UV点
proc string[] getSelUVs()
{
return `filterExpand -ex true -sm 35`;
}

用法范例:

// 获取选择的所有面,存放到数组$faces[]中
string $faces[] = getSelFaces();
    这四个函数是maya内置的,也是菜单命令,经常用到。

// 菜单命令:Edit Polygons->Selection->Convert Selection to Vertices
// 转换当前选择为顶点

ConvertSelectionToVertices();

// 菜单命令:Edit Polygons->Selection->Convert Selection to Edges
// 转换当前选择为边

ConvertSelectionToEdges();

// 菜单命令:Edit Polygons->Selection->Convert Selection to Faces
// 转换当前选择为面

ConvertSelectionToFaces();

// 菜单命令:Edit Polygons->Selection->Convert Selection to UVs
// 转换当前选择为UV点

ConvertSelectionToUVs();

用法范例:

// 获取选择的所有面,存放到数组$faces[]中
string $faces[] = getSelFaces();
    这四个函数是maya内置的,也是菜单命令,经常用到。

// 菜单命令:Edit Polygons->Selection->Convert Selection to Vertices
// 转换当前选择为顶点

ConvertSelectionToVertices();

// 菜单命令:Edit Polygons->Selection->Convert Selection to Edges
// 转换当前选择为边

ConvertSelectionToEdges();

// 菜单命令:Edit Polygons->Selection->Convert Selection to Faces
// 转换当前选择为面

ConvertSelectionToFaces();

// 菜单命令:Edit Polygons->Selection->Convert Selection to UVs
// 转换当前选择为UV点

ConvertSelectionToUVs();

这四个函数在maya的scripts/others目录中,可以直接调用。

// 转换当前选择为顶点,并获取这些顶点的名称
global proc string[] getVerts()
{
select -r `polyListComponentConversion -tv`;
string $result[]=`filterExpand -ex true -sm 31`;
return $result;
}

// 转换当前选择为边,并获取这些点的名称
global proc string[] getEdges()
{
select -r `polyListComponentConversion -te`;
string $result[]=`filterExpand -ex true -sm 32`;
return $result;
}

// 转换当前选择为面,并获取这些面的名称
global proc string[] getFaces()
{
select -r `polyListComponentConversion -tf`;
string $result[]=`filterExpand -ex true -sm 34`;
return $result;
}

// 转换当前选择为UV点,并获取这些UV点的名称
global proc string[] getUVs()
{
string $uvs[];
$uvs=`polyListComponentConversion -tuv`;
if (size($uvs) == 0) return $uvs;
select -r $uvs;
string $result[]=`filterExpand -ex true -sm 35`;
return $result;
}
// 根据点、边、面、UV点的名称得出多边形的名称
// 例如多边形一条边的名称为"pSphere1.e[637]",则这个多边形的
// 名称为"pSphere1"

proc string getBaseName(string $item)
{
string $buffer[];
if ($item != "")
{
tokenize($item, ".", $buffer);
}
return $buffer[0];
}

用法范例:

string $polyName = getBaseName("pSphere1.e[637]");

// 返回值:pSphere1
// 根据点、边、面、UV点的名称得出它们的索引号
// 例如多边形一条边的名称为"pSphere1.e[637]",则这个多边形的
// 索引号为637

proc int getIndex(string $indexString)
{
string $buffer[];
tokenize($indexString, "[]", $buffer);
int $index = (int)$buffer[1];
return $index;
}

用法范例:

int $index = getIndex("pSphere1.e[637]");
// 返回值:637

首先介绍一个有用的函数(intersectStringArray)。这个函数可以找到两个数组的共同部分,比如数组1为{“兔子”, “老虎”, “山羊”, “虫子”},数组2为{“虫子”, “刀子”, “梳子”, “兔子”, “珠子”},你可以获得一个新数组包含它们的共同部分{“兔子”, “虫子”}。

// 获得两个数组的共同部分
proc string[] intersectStringArray(string $array1[], string $array2[])
{
global string $m_arrayIntersector;
if ($m_arrayIntersector == "")
$m_arrayIntersector = `stringArrayIntersector`;

stringArrayIntersector -edit -intersect $array1 $m_arrayIntersector;
stringArrayIntersector -edit -intersect $array2 $m_arrayIntersector;
string $result[] = `stringArrayIntersector -query $m_arrayIntersector`;
stringArrayIntersector -edit -reset $m_arrayIntersector;
return $result;
}
    [注] global string代表一个全局字符串变量,以前讲过全局变量应当尽量避免命名冲突。maya中的全局变量都是以
小写字母"g"开头,为避免冲突,本教程中的全局变量一律使用"m_"作为前缀。
    前面介绍过的函数可以看作是工具函数,这些函数几乎在以后的每个程序中都要用到。如果编写某一功能,还需要编写
一些有针对性的专用函数。现在我们来编一个多边形的导角功能,来看看一个完整的程序是怎样完成的。
mel语初解之二-多边型建模插图(4)
这是一些必须记住的单词,相信所有学过Maya的人都不会感到陌生。

单词 缩写 解释
polygon poly 多边形
vertex v;ver;vert;vtx 多边形顶点
edge e;ed 多边形边线
face f 多边形面
split 切割
index idx 索引
    要编写一个比较复杂的程序,我们首先考虑的是应该怎样把这个程序做最大程度的简化,要把一个庞大的东西拆成一小块
一小块的分别去处理。今天我们需要完成第一小块,就是当你选择一条边时,程序可以在这条边的两侧各切一刀,如图。
mel语初解之二-多边型建模插图(5)

要做到这一点,需要分成四步。

第一步,我们需要做一点准备工作,要了解一下切割命令polySplit和边的构造顺序。

为了更直观的说明程序的原理,我尽量多放一些插图。

选择菜单Polygons->Create Polygon Tool,从左上角开始,画一个正方形。这时看看mel历史窗,可以看到polyCreateFacet命令,这个命令目前还用不到,先不去管他。

mel语初解之二-多边型建模插图(6)

依次选择正方形的四个顶点,看看每个顶点的名称和索引号。

mel语初解之二-多边型建模插图(7)

可以发现索引号是按照创建时的顺序指定的。分别为0,1,2,3。

mel语初解之二-多边型建模插图(8)

再看看每条边的索引号,也是按照创建时的顺序指定的。一条边有两个点,分别为起点和终点,这两个点决定了边的构造顺序。

mel语初解之二-多边型建模插图(9)

使用Edit Polygons->Split Polygon Tool在正方形上切一刀。

mel语初解之二-多边型建模插图(10)

我们看一下polySplit的用法,-ep后面有两个参数,第一个参数(3)是边的索引号,第二个参数(0.263489)是百分比,如果边的长度为1,切割点在边的0.263489处。

切割点位置的受到边的构造顺序的影响,以polySurface1.e[3]这条边为例,从边的起点开始,沿着边的终点方向量出整条边的约26%的长度,这个位置就是切割点的位置。

mel语初解之二-多边型建模插图(11)

使用polySplit的一大难点就是判断边的构造顺序,也就是分清边的起点和终点。为了做到这一点,我们需要用到一个mel命令 – polyInfo

选择一条边线(e[3]),在命令行执行”polyInfo -ev;”,可以看到输出结果”// Result: EDGE 3: 3 0 Hard”,其中”EDGE 3:”代表边线(e[3]),3和0分别代表组成这条边的两个点(vtx[3]和vtx[0])的索引号。注意,这两个点的顺序不是按大小排列的,而是按照边线的构造顺序。

mel语初解之二-多边型建模插图(12)

我们把polyInfo按照自己的需要封装起来。主要是用字符处理的方法实现的,注意这里用到了一个前面讲过的工具函数getBaseName()。你会发现这个函数的用途与getVerts()很像,但getVerts()无法得知边线的构造顺序。

// 根据一条边,得到这条边的按构造顺序排列的两个端点。
proc string[] edge2Vertex(string $edge)
{
string $verts[], $buffer[];
string $edgeInfo[] = `polyInfo -ev $edge`;
int $nbVertex = tokenize($edgeInfo[0], $buffer);

string $polyName = getBaseName($edge);
$verts[0] = $polyName + ".vtx[" + $buffer[2] + "]";
$verts[1] = $polyName + ".vtx[" + $buffer[3] + "]";
return $verts;
}
第二步,我们要找到需要切割的两条边。
我们可以根据选择的一条边,和要切割的那个面来判断。
mel语初解之二-多边型建模插图(13)
    选择一条边。Mel历史窗中的代码:  select -r polySurface1.e[6] ;
mel语初解之二-多边型建模插图(14)

Edit Polygons->Selection->Convert Selection to Vertices,转换成顶点。
[注] 这一步mel历史窗中可能看不到变化,按z键undo一下就看到了。

Mel历史窗中的代码: ConvertSelectionToVertices;

mel语初解之二-多边型建模插图(15)

再选择Edit Polygons->Selection->Convert Selection to Edges,转换成边。

Mel历史窗中的代码: ConvertSelectionToEdges;

mel语初解之二-多边型建模插图(16)

去掉开始那条边的选择。

Mel历史窗中的代码: select -tgl polySurface1.e[6] ;

[注] select -d polySurface1.e[6] ;也可。

[注] select -d polySurface1.e[6] ;也可。

现在剩下四条边,可以用getSelEdges()把它们存到一个数组中。
数组1:
{“polySurface1.e[1]”,
“polySurface1.e[3]”,
“polySurface1.e[4]”,
“polySurface1.e[5]”}

mel语初解之二-多边型建模插图(17)

选择要切割的面。Mel历史窗中的代码: select -r polySurface1.f[1] ;

用getEdges()把属于面的四条边存到另一个数组中。
{polySurface1.e[0],
polySurface1.e[1],
polySurface1.e[4],
polySurface1.e[6]}

mel语初解之二-多边型建模插图(18)

用intersectStringArray()可以找到两个数组的共同部分,就是我们将要切割的两条边。
{polySurface1.e[1],
polySurface1.e[4]}

mel语初解之二-多边型建模插图(19)

用getEdges()把属于面的四条边存到另一个数组中。
{polySurface1.e[0],
polySurface1.e[1],
polySurface1.e[4],
polySurface1.e[6]}

mel语初解之二-多边型建模插图(18)

用intersectStringArray()可以找到两个数组的共同部分,就是我们将要切割的两条边。
{polySurface1.e[1],
polySurface1.e[4]}

mel语初解之二-多边型建模插图(19)

把前面Mel历史窗中记录下的代码整理一下,就成了:

// 已知一个面,这个面的一条边,求与(这个面的)这条边相邻的两条边
proc string[] adjacentEdgesInFace(string $face, string $edge)
{
// 获取所有相邻的边线
select -r $edge;
ConvertSelectionToVertices();
ConvertSelectionToEdges();
select -d $edge;
string $edges_vert[] = getSelEdges();

// 获取已知面的所有边线
select -r $face;
string $edges_face[] = getEdges();

// 求两个数组的共同部分
string $edges[] = intersectStringArray($edges_vert, $edges_face);
return $edges;
}
第三步,切割一个面。
我们可以先把切割的百分比设置一个固定的数值,设为0.2(20%)。我们可以通过edge2Vertex()来得到要切割的一条边
的起点和终点,如果起点恰好是当初选择的那条边线的一个端点(两条边的公共点),那么这条线的构造顺序是正的,可以直接
使用20%;但如果构造顺序是反的,那就要使用1-20%=80%了。
mel语初解之二-多边型建模插图(22)
这个函数应该这么写:

proc splitByPercent(string $edge1, string $edge2, string $inputEdge)
{
// 预设值,百分比为0.2
float $percent = 0.2;
float $percent1 = $percent; // 0.2
float $percent2 = $percent; // 0.2

// 分别获得三条边所包含的顶点
string $verts1[], $verts2[], $vInput[];
$vInput = edge2Vertex($inputEdge);
$verts1 = edge2Vertex($edge1);
$verts2 = edge2Vertex($edge2);

// 求$edge1与$inputEdge的公共点
string $startVert[] = intersectStringArray($verts1, $vInput);
// 如果公共点不是$edge1的起点
if ($startVert[0] != $verts1[0])
// 百分比变为80%,即1-0.2
$percent1 = 1 - $percent;

// 求$edge2与$inputEdge的公共点
string $startVert[] = intersectStringArray($verts2, $vInput);
if ($startVert[0] != $verts2[0])
$percent2 = 1 - $percent;

// 获得两条边的索引号
string $index1 = getIndex($edge1);
string $index2 = getIndex($edge2);

// 准备命令字符串
string $cmd = "polySplit -ch on -s 1 ";
$cmd += "-ep " + $index1 + " " + $percent1 + " ";
$cmd += "-ep " + $index2 + " " + $percent2 + " ";
$cmd += ";";

// 选择整个多边形物体
string $polyName = getBaseName($edge1);
select -r $polyName;

// 执行命令
evalEcho($cmd);
}

[注] 使用evalEcho执行命令可以把命令字符串在mel历史窗中显示出来。

第四步,切割边线两边的面。

有了前面的准备工作,最后一步就显得比较容易了。

global proc myEdgeChamfer()
{
// 获取选择的一条边
string $edges[] = getSelEdges();
string $inputEdge = $edges[0];

// 获取选择的边相邻的两个面
string $faces[] = getFaces();

// 等比切割第1个面
string $splitEdges[];
$splitEdges = adjacentEdgesInFace($faces[0], $inputEdge);
splitByPercent($splitEdges[0], $splitEdges[1], $inputEdge);

// 等比切割第2个面
$splitEdges = adjacentEdgesInFace($faces[1], $inputEdge);
splitByPercent($splitEdges[0], $splitEdges[1], $inputEdge);
}
附全部源代码。

///////////////////////////////////////////////////////////
// myEdgeChamfer.mel
// myEdgeChamfer v1

// 获取选择的多边形顶点
proc string[] getSelVerts()
{
return `filterExpand -ex true -sm 31`;
}

// 获取选择的多边形边
proc string[] getSelEdges()
{
return `filterExpand -ex true -sm 32`;
}

// 获取选择的多边形面
proc string[] getSelFaces()
{
return `filterExpand -ex true -sm 34`;
}

// 根据点、边、面、UV点的名称得出多边形的名称
// 例如多边形一条边的名称为"pSphere1.e[637]",则这个多边形的
// 名称为"pSphere1"
proc string getBaseName(string $item)
{
string $buffer[];
if ($item != "")
{
tokenize($item, ".", $buffer);
}
return $buffer[0];
}

// 根据点、边、面、UV点的名称得出它们的索引号
// 例如多边形一条边的名称为”pSphere1.e[637]”,则这个多边形的

// 索引号为637
proc int getIndex(string $indexString)
{
string $buffer[];
tokenize($indexString, “[]”, $buffer);
int $index = (int)$buffer[1];
return $index;
}

// 获得两个数组的共同部分
proc string[] intersectStringArray(string $array1[], string $array2[])
{
global string $m_arrayIntersector;
if ($m_arrayIntersector == “”)
$m_arrayIntersector = `stringArrayIntersector`;

stringArrayIntersector -edit -intersect $array1 $m_arrayIntersector;
stringArrayIntersector -edit -intersect $array2 $m_arrayIntersector;
string $result[] = `stringArrayIntersector -query $m_arrayIntersector`;
stringArrayIntersector -edit -reset $m_arrayIntersector;
return $result;
}

///////////////////////////////////////////////////////////
// 第一步,根据一条边,得到这条边的按构造顺序排列的两个端点。
proc string[] edge2Vertex(string $edge)
{
string $verts[], $buffer[];
string $edgeInfo[] = `polyInfo -ev $edge`;
int $nbVertex = tokenize($edgeInfo[0], $buffer);

string $polyName = getBaseName($edge);
$verts[0] = $polyName + “.vtx[” + $buffer[2] + “]”;
$verts[1] = $polyName + “.vtx[” + $buffer[3] + “]”;
return $verts;
}

// 已知一个面,这个面的一条边,求与(这个面的)这条边相邻的两条边
proc string[] adjacentEdgesInFace(string $face, string $edge)
{
// 获取所有相邻的边线
select -r $edge;
ConvertSelectionToVertices();
ConvertSelectionToEdges();
select -d $edge;
string $edges_vert[] = getSelEdges();

// 获取已知面的所有边线
select -r $face;
string $edges_face[] = getEdges();

// 求两个数组的共同部分
string $edges[] = intersectStringArray($edges_vert, $edges_face);
return $edges;
}

// 第三步,等比切割一个面
proc splitByPercent(string $edge1, string $edge2, string $inputEdge)
{
// 预设值,百分比为0.2
float $percent = 0.2;
float $percent1 = $percent; // 0.2
float $percent2 = $percent; // 0.2

// 分别获得三条边所包含的顶点
string $verts1[], $verts2[], $vInput[];
$vInput = edge2Vertex($inputEdge);
$verts1 = edge2Vertex($edge1);
$verts2 = edge2Vertex($edge2);

// 求$edge1与$inputEdge的公共点
string $startVert[] = intersectStringArray($verts1, $vInput);
// 如果公共点不是$edge1的起点
if ($startVert[0] != $verts1[0])
// 百分比变为80%,即1-0.2
$percent1 = 1 – $percent;

// 求$edge2与$inputEdge的公共点
string $startVert[] = intersectStringArray($verts2, $vInput);
if ($startVert[0] != $verts2[0])
$percent2 = 1 – $percent;

// 获得两条边的索引号
string $index1 = getIndex($edge1);
string $index2 = getIndex($edge2);

// 准备命令字符串
string $cmd = “polySplit -ch on -s 1 “;
$cmd += “-ep ” + $index1 + ” ” + $percent1 + ” “;
$cmd += “-ep ” + $index2 + ” ” + $percent2 + ” “;
$cmd += “;”;

// 选择整个多边形物体
string $polyName = getBaseName($edge1);
select -r $polyName;

// 执行命令
evalEcho($cmd);
}

// 第四步,切割选择的一条边线两边的面。
global proc myEdgeChamfer()
{
// 获取选择的一条边
string $edges[] = getSelEdges();
string $inputEdge = $edges[0];

// 获取选择的边相邻的两个面
string $faces[] = getFaces();

// 等比切割第1个面
string $splitEdges[];
$splitEdges = adjacentEdgesInFace($faces[0], $inputEdge);
splitByPercent($splitEdges[0], $splitEdges[1], $inputEdge);

// 等比切割第2个面
$splitEdges = adjacentEdgesInFace($faces[1], $inputEdge);
splitByPercent($splitEdges[0], $splitEdges[1], $inputEdge);
}

mel语初解之二-多边型建模插图(23)myEdgeChamfer_v1.rar (1.46k)

等距切割

mel语初解之二-多边型建模插图(24)

要想知道一条边的长度,如果是Maya5.0或更低版本,可以使用arclen命令,但是到了Maya6.0,arclen命令只支持Nurbs,退化了?

这样我们不得不用到获得两点之间的距离的功能来计算边的长度。现在要提到一点图形学知识,当然是最最基础的,就是”勾股定理”。相信大家都能记得一点”勾股定理”的内容,我就不详细讲了。如果需要详细讲的话,可以提出来,我可以在后面补充。

“勾股定理”的公式是: a2 + b2 = c2

根据这个公式推理出3D空间勾股定理公式:x2 + y2 + z2 = d2

如果求两点之间的距离,公式如图:dist为点P1(x1,y1,z1)和P2(x2,y2,z2)之间的距离

mel语初解之二-多边型建模插图(25)

根据公式,我们来编一个工具函数求两点之间的距离。你也许会感到奇怪,这么常用的功能,Maya中为什么没有内置的命令呢?这一点我也感到奇怪,好在编写这样的功能非常简单。

// 计算两个顶点之间的距离
proc float distance2Verts(string $vert1, string $vert2)
{
// 获取两个顶点的坐标值
float $p1[] = `pointPosition $vert1`;
float $p2[] = `pointPosition $vert2`;

// 计算距离
float $distance;
float $v[3];
$v[0] = $p1[0] – $p2[0];
$v[1] = $p1[1] – $p2[1];
$v[2] = $p1[2] – $p2[2];
$distance = $v[0]*$v[0] + $v[1]*$v[1] + $v[2]*$v[2];
$distance = sqrt($distance);// 开方

return $distance;
}

[注] 获取点的坐标值还有一种方法是:
float $p1[] = `xform -q -ws -t $ver1`;

[注] 获取点的坐标值还有一种方法是:
float $p1[] = `xform -q -ws -t $ver1`;

现在只要把等比切割的函数改一改就差不多了,把splitByPercent()改为splitByDist()。我把没有改动的部分用灰绿色表示,重点看看改动的部分。
[注] dist为distance的缩写。

// 第三步,等距切割一个面
proc splitByDist(string $edge1, string $edge2, string $inputEdge)
{
// 预设值,距离为0.2个单元格
float $dist = 0.2;
float $percent1;
float $percent2;

// 分别获得三条边所包含的顶点
string $verts1[], $verts2[], $vInput[];
$vInput = edge2Vertex($inputEdge);
$verts1 = edge2Vertex($edge1);
$verts2 = edge2Vertex($edge2);


// 计算第一条边的切割处
float $edgeLength; // 求第一条边的长度
$edgeLength = distance2Verts($verts1[0], $verts1[1]);
if ($edgeLength < $dist) // 如果长度小于预设值0.2
$percent1 = 1; // 切割处在线的的末端
else
$percent1 = $dist / $edgeLength; // 计算出百分比

// 计算第二条边的切割处
$edgeLength = distance2Verts($verts2[0], $verts2[1]);
if ($edgeLength < $dist)
$percent2 = 1;
else
$percent2 = $dist / $edgeLength;

// 求$edge1与$inputEdge的公共点
string $startVert[] = intersectStringArray($verts1, $vInput);
// 如果公共点不是$edge1的起点
if ($startVert[0] != $verts1[0])

// 百分比变为1-$percent1
$percent1 = 1 - $percent1;

// 求$edge2与$inputEdge的公共点
string $startVert[] = intersectStringArray($verts2, $vInput);
if ($startVert[0] != $verts2[0])

$percent2 = 1 - $percent2;

// 获得两条边的索引号
string $index1 = getIndex($edge1);
string $index2 = getIndex($edge2);

// 准备命令字符串
string $cmd = "polySplit -ch on -s 1 ";
$cmd += "-ep " + $index1 + " " + $percent1 + " ";
$cmd += "-ep " + $index2 + " " + $percent2 + " ";
$cmd += ";";

// 选择整个多边形物体
string $polyName = getBaseName($edge1);
select -r $polyName;

// 执行命令
evalEcho($cmd);

}

把distance2Verts()函数的定义加进去,再把myEdgeChamfer()函数中的两处splitByPercent()改为splitByDist(),这个程序就完成了。

附全部源代码。

mel语初解之二-多边型建模插图(23)myEdgeChamfer_v2.rar (1.74k)

作者: 791650988

为您推荐