maya建模教程:多边型建模篇
再看看每条边的索引号,也是按照创建时的顺序指定的。一条边有两个点,分别为起点和终点,这两个点决定了边的构造顺序。
使用Edit Polygons->Split Polygon Tool在正方形上切一刀。
我们看一下polySplit的用法,-ep后面有两个参数,第一个参数(3)是边的索引号,第二个参数(0.263489)是百分比,如果边的长度为1,切割点在边的0.263489处。
切割点位置的受到边的构造顺序的影响,以polySurface1.e[3]这条边为例,从边的起点开始,沿着边的终点方向量出整条边的约26%的长度,这个位置就是切割点的位置。
使用polySplit的一大难点就是判断边的构造顺序,也就是分清边的起点和终点。为了做到这一点,我们需要用到一个mel命令 - polyInfo。
选择一条边线(e[3]),在命令行执行"polyInfo -ev;",可以看到输出结果"// Result: EDGE 3: 3 0 Hard",其中"EDGE 3:"代表边线(e[3]),3和0分别代表组成这条边的两个点(vtx[3]和vtx[0])的索引号。注意,这两个点的顺序不是按大小排列的,而是按照边线的构造顺序。
我们把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历史窗中的代码:
select -r polySurface1.e[6] ;
Edit Polygons->Selection->Convert Selection to Vertices,转换成顶点。
[注] 这一步mel历史窗中可能看不到变化,按z键undo一下就看到了。
Mel历史窗中的代码:
ConvertSelectionToVertices;
再选择Edit Polygons->Selection->Convert Selection to Edges,转换成边。
Mel历史窗中的代码:
ConvertSelectionToEdges;
去掉开始那条边的选择。
Mel历史窗中的代码:
select -tgl polySurface1.e[6] ;
[注] select -d polySurface1.e[6] ;也可。
现在剩下四条边,可以用getSelEdges()把它们存到一个数组中。
数组1:
{"polySurface1.e[1]",
"polySurface1.e[3]",
"polySurface1.e[4]",
"polySurface1.e[5]"}
选择要切割的面。
Mel历史窗中的代码:
select -r polySurface1.f[1] ;
用getEdges()把属于面的四条边存到另一个数组中。
{polySurface1.e[0],
polySurface1.e[1],
polySurface1.e[4],
polySurface1.e[6]}
用intersectStringArray()可以找到两个数组的共同部分,就是我们将要切割的两条边。
{polySurface1.e[1],
polySurface1.e[4]}
把前面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%了。
这个函数应该这么写:
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();
// 获取已知面的所有边线
sele ct -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);
}
写到大括号里就成了局部变量。
写到大括号外面就是全局变量,不过这时 float $bb = 5 ; 和 global float $bb = 5 ; 还是有一点点差别的,很容易让人忽略。就是如果不写global,包含这句代码的mel文件如果不被source,只是执行了mel文件的同名函数,那么$bb载入内存时将不被赋值,这时$bb的值为 0。所以说最好写上global。
下面我想讲一下关于全局变量和局部变量的区别吧.
如何定义全局变量呢,定义全局变量必须在所有自定义函数的外边,不能定义在{}里面:
global int $a;//定义了全局变量,默认值是0
如果要调用的话:
proc myfn()
{
global int $a;//在调用全局变量的时候必须在自己的函数里面在定义一下
$a=10;
.....
}
//这样就是调用的过程
强调一点的是,要想调用全局变量就必须在自己的函数里面在重新定义一边.不燃的话,你试试看...呵呵
局部变量就是:
int $a;// 不加global 的
但是所有的变量都有生命期的,所谓的生命期就是在一定的范围内有效...
proc myfn()
{
int $a=10;
if($a<20)
{
$a++;
.........
print $a;//reslut 11;
}
print $a;//result 11,而不是10了
.........
}
这样就是生命期,如果你在后面还调用的话,$a就不是10了,就是11了....
我还想讲一下的就是,正如七月冰儿讲的一样,如果想得到几个边的名称的话,
你会发现所有的名称都是按照从小到大的顺序进行排序的来的,这样有好处也有坏处,坏处就是有时后我不想得到是排列之后的名称,我之想得到不排列的名称...
这也是多边形的切割工具一直很麻烦的原因,总不能想3dmax那样随心所欲的进行切割了....
但是办法是有的,目前我没有想好,也许七月冰儿在他以后的版本里会出现这样的功能的....
我也在思索这个问题,其实大家在使用的过程当中,完全可以作出好多的快捷的功能的,就想在调权重值一样,虽然maya提供了cmeditor,但还是很不方便的...
以后接着说,希望大家都能够参与近来呀
假如你有一个mel文件,文件名为myTest.mel,文件内容如下:
float $bb = 5;
global proc myTest()
{
// do nothing
}
启动Maya,使用env命令查看一下当前的全局变量,可以发现此时变量$bb不存在。当执行myTest命令时,这时$bb作为全局变量载入内存,再用env命令查看一下,发现$bb已经存在了。但是执行print $bb;会看到结果为0。
重新启动Maya,启动后执行source myTest.mel;这时再执行print $bb;可以看到输出了正确的结果5,这时$bb已经作为全局变量载入了内存。
这就是我说的如果不写global,包含这句代码的mel文件如果不被source,只是执行了mel文件的同名函数,那么$bb载入内存时将不被赋值,这时$bb的值为 0。
如果你只是(在所有大括号外面)声明变量,在函数中给它赋值,就可以不写global了。比如代码改成这样就没问题了。
// 在Maya6.0中测试通过
float $bb;
global proc myTest()
{
global float $bb;
$bb = 5;
}
如果想在函数外面声明和使用变量,又不想让Maya当作全局变量,可以加大括号,函数的生命在大括号结束时消亡。例如:
{
float $bb;
... ...
}
关于全局变量的用法,可以参考junesnow的说明,不过要注意我做的一点更正。