Board logo

标题: [原创]飞浪脚本零起点入门系列(十二)多边形编辑 Editable Poly [打印本页]

作者: feilang    时间: 2010-11-16 18:15     标题: [原创]飞浪脚本零起点入门系列(十二)多边形编辑 Editable Poly

声明:本教程为CG++原创,请尊重作者劳动,转载请注明,谢谢:)
上一节:飞浪脚本零起点入门系列(十一)样条线编辑Editable Spline

飞浪脚本零起点入门系列(十二)多边形编辑 Editable Poly
本节关键词:Editable_Poly Method, EditablePoly,bitarray

在此系列教程中,本人一直试图把自己学习maxscript的经验奉献给读者,当然我的方法肯定不是最好的,但是只要这些经验能帮助更多喜欢maxscript的人,本人的目的就达到了。前面的所有章节中,不是所有的地方100%正确,比如第八节,我建议大家在变量名前面加前缀,后来我在其他书上看到说这个命名方法早就过时了。本人并不是计算机科班出身,对于程序的理解描述有些地并不专业,甚至还会出纰漏,如果你发现有些地方有问题请不要惊讶,还请不吝指教。
maxscript的语法就像英语的语法,内置函数就像单词,而编写maxscript就是把内置函数按照语法写成句子,再按照一定的逻辑排版出来,出来的就是一个完整的代码。这些内置函数全部在maxscript reference里面,它就是一部牛津词典。查词典大家都会,不认识的可以用金山词霸,而造句需要有想法。在maxscript里面,这些想法并没有程序专业讲的数据结构那么高深,简单的组织一下就可以完成高效的工作。本节涉及到的主要函数在下面两个网页:
Editable_Poly Methods
http://www.cgplusplus.com/online-reference/maxscript-reference/source/editable_poly_methods.htm
Interface: EditablePoly
http://www.cgplusplus.com/online-reference/maxscript-reference/source/interface_editablepoly.htm
下面跟我一起来走近Editable_Poly吧!
创建一个茶壶保持选中,运行以下代码:
  1. polyOp.getNumVerts $
复制代码
咦?出错啦,得到如下结果:
-- Runtime error: EPoly operation on non-Editable Poly: Teapot
它说EPoly的操作运行在非Editable Poly的物体上导致出错,所以本节所讲的操作全部要在Editable Poly下运行啊。
那我们就把它转成EPoly:
  1. convertTo $ Editable_Poly
复制代码

再运行第一个代码polyOp.getNumVerts $
得到结果530,这个是此茶壶的点的总数量。看上面那句代码,polyOp是一个已经编写好的结构,里面包括了许许多多的EPoly操作函数,运行一下polyOp得到如下结果:
#Struct:polyop(
  getEDataChannelSupport:<fn>,
  getMapFace:<fn>,
  makeEdgesPlanar:<fn>,
  capHolesByFace:<fn>,
  makeVertsPlanar:<fn>,
  setSlicePlane:<fn>,
  getEdgeVerts:<fn>,
  getDeadVerts:<fn>,
  getNumVerts:<fn>,
  ......
调用这些函数只需要用点语法即可。函数的说明在本文开头介绍的网页里面。polyOp.getNumEdges $是获取边的总数,polyOp.getNumFaces $是获取面的总数。下面我用场景助手里面的随机选择点、线、面、元素来说说EPoly的基本操作。
选择茶壶的一些点,运行代码:
  1. polyOp.getVertSelection $
复制代码
得到类似于下面的结果:
#{61..65, 129..132, 193..196, 246..247, 517..518}
这些是什么东东?getVertSelection的作用是获取选中的点,返回的就是这些点的信息了,那这结果有什么意义呢?其实我们得到的结果是一个bitarray,它是另一种数据常量,前面没有细讲。这里顺带讲一下。bitarray字面意思是位数组,我们知道数组#(1,2,3)用的是中括号,而位数组用的是大括号:#{1,2,3};数组里面的元素可以是任何数据,而位数组里面只能是正数。bitarray里面的数字只是一个开关,
表示在此bitarray里面,此位置是true还是false。怎么理解呢?bitarray对子元素的访问跟array一样,如有以下代码:
  1. bitarr=#{1,3,5,6,7}
  2. for i in 1 to 7 do print bitarr[i]
复制代码
得到的结果是true,false,true,false...,上面的#{1,3,5,6,7}表示1,3,5,6,7这四个位置是开启的,所以为true,用在EPoly可以表示选中了第1,3,5,6,7个点。而其他的数字在上面的bitarray里面都是false在EPoly里面可以表示这些点没有选中。可以用两个小点表示两个及以上的连续数字,如上面的bitarr可以写成#{1,3,5..7}。现在再回头看看上面得到的点的信息:#{61..65, 129..132, 193..196, 246..247, 517..518},这个bitarray在61到65的位置是true,所以这些点是选中的,后面类推。下面是对bitarray一个直观的解释:
  1. bitarr=#{1,3,5..7} --假设有一排灯,分别以1,2,3,...编号,这个数组就表示只打开了1,3,5,6,7号灯
  2. bitarr.count --表示此bitarr中的最大的数字(打开的灯中最大的编号),在这里是7,注意这里跟array不同
  3. bitarr.numberset --表示此bitarr中所有为true的元素的数量(所有打开的灯的数量),这里才是array里面的count
  4. bitarr[1]=false --把1号位的灯关掉
  5. bitarr[12]=true --把12号位的灯打开
  6. bitarr.count --此时打开的灯中最大的编号变成了12
  7. bitarr as array --把bitarr转变成了array,自己观察一下结果
  8. #(1,2,3,55,57,98,99,100) as bitarray --当然array也可以转变成bitarray,但是array里面的元素必须为正数
复制代码
回到EPoly中来,怎么样选中自己设定的点呢?执行如下代码:
  1. polyOp.setVertSelection $ #{1..100} --选中EPoly的第1到100个点,切换到点层级就能看到效果
复制代码

(转二楼)
作者: feilang    时间: 2010-11-16 18:19

现在我们知道了点的总数量用polyOp.getNumVerts,选择点用polyOp.setVertSelection,那什么,来个随机选择点呗,来啦:
  1. vertTotal=polyOp.getNumVerts $ --点总数
  2. percent=0.3 --选择30%
  3. selVert=#{} --要选择的点的ID,这里是空的
  4. while selVert.numberset < vertTotal*percent do
  5. ( --当点数小于总数的30%时,不断的随机出数字,将selVert中对应的点设置为true
  6. selVert[random 1 vertTotal]=true
  7. )
  8. polyOp.setVertSelection $ selVert --选择点
  9. subobjectlevel = 1 --设置当前选择物体的子层级为1,EPoly即为点层级
  10. update $ --刷新一下物体,马上看到效果
复制代码
边的总量用polyOp.getNumEdges,选择边用polyOp.setEdgeSelection,子层级为2,把上面的代码替换一下就可以随机选择边了,同理面是polyOp.getNumFaces,选择用polyOp.setFaceSelection ,子层级为4,替换代码即可选择面,这些函数都从reference中来,没有什么高深的。下面来看看EPoly中的element。打开macro recoder,在element层级选择壶嘴,可以看到如下代码:
$.EditablePoly.SetSelection #Face #{321..384}
这是选择了ID号从312到384的面,如果用polyOp表示就是:
  1. polyOp.setFaceSelection $ #{321..384}
复制代码
壶嘴是一个元素element,但是显示的为什么还是选择面呢?其实,element就是一些ID号连续的面的集合,element本身是没有ID的,我们有办法用脚本选择指定的element吗?答案当然是有啦。在本文的开头介绍的第二个网页里面,有一个超级用的的函数SelectElement(),它可以根据当然选择的面,选择与这些面相关的element。执行如下代码:
  1. polyOp.setFaceSelection $ #{1} --选择ID号为1的面
  2. $.EditablePoly.SelectElement() --选择与选择的面为同一element的面,是不是有点绕口?
复制代码
这样就选择了包括ID号为1的element。那其他的element呢?既然element自身没有ID,那么我们就用face的ID号来标记,每个element只需要取出一个face ID出来就行了。理解一下下面的函数:
  1. fn getIDsOfElement obj =
  2. ( --此函数用来获取每个element中的第一个face ID
  3. select obj
  4. faceNum=polyOp.getNumFaces $
  5. ids=#{1} --储存face id
  6. tempID=1
  7. while tempID <= faceNum do
  8. ( polyOp.setFaceSelection $ #{tempID}
  9.   $.EditablePoly.SelectElement()
  10.   selFace=polyOp.getFaceSelection $
  11.   ids[tempID]=true
  12.   tempID+=selFace.numberset
  13. )
  14. ids as array --把结果转换成数组,便于使用
  15. )
复制代码
有了上面这个函数,我们就可以对element像对待face一样的操作了,对茶壶执行此函数:
  1. getIDsOfElement $
复制代码
得到结果#(1, 257, 321, 385)
此结果里面的数字是face ID,每个ID代表着一个element,那么对element随机操作也变得简单了:
  1. eleID=getIDsOfElement $ --获取与element相关的face ID
  2. percent=0.5 --选择50%的element
  3. selID=#() --需要选择的face ID
  4. while selID.count < eleID.count*percent do
  5. (
  6. tempID=eleID[random 1 eleID.count] --随机抽取一个ID
  7. if finditem selID tempID == 0 then append selID tempID
  8. )
  9. polyOp.setFaceSelection $ (selID as bitarray) --选择这些面,记得要转换成bitarray
  10. $.EditablePoly.SelectElement()
复制代码
上面讲了点、线、面、元素,EPoly的第三个层级没说,就是Border,它表示的是只有一个面用过的边,孤独的存在着,我们也不能把它遗忘啊,下面的代码直接选择这些边:
  1. subobjectlevel=3
  2. myEdges=polyOp.getOpenEdges $
  3. polyOp.setEdgeSelection $ myEdges
复制代码
到这里,相信大家对EPoly在maxscript中的操作已经有了一个认识了吧,本节所用到的函数只是EPoly操作中的几个而已,开头提到的两个网页的内容也不是全部的EPoly操作,帮助里面Frequently Asked Questions也有关于EPoly的例子,还是那句,函数都在帮助文档里面,你需要做的就是找出自己需要的函数然后按一定逻辑编写在一起,CG++里面的高手在逐渐增多,希望大家自由交流,快乐编程:)

补充:下面代码是分离出所有的Epoly,在老外的代码基础上更改过来的,polyop.getElementsUsingFace函数以前没发现。原贴地址:http://forums.cgsociety.org/showthread.php?f=98&t=785636 原来代码在15楼。更改后的代码:
  1. fn separateByFaceElements obj:selection[1] = if iskindof obj Editable_Poly do
  2. (
  3. local elements = #()
  4. local faces = obj.faces as bitarray
  5. while (f = (faces as array)[1]) != undefined do
  6. (
  7. ff = polyop.getElementsUsingFace obj #{f}
  8. append elements ff
  9. faces -= ff
  10. )
  11. local nodes = #()
  12. for k=1 to elements.count do
  13. (
  14. maxops.CloneNodes obj newNodes:&nn
  15. newname=uniqueName obj.name
  16. polyOp.detachFaces nn[1] elements[k] delete:on asNode:on name:newname
  17. centerPivot (execute ("$'"+newname+"'"))
  18. -- update nn[1]
  19. delete nn[1]
  20. )
  21. delete selection[1]
  22. )
复制代码
选择物体后运行:separateByFaceElements()
作者: 小雪    时间: 2010-11-18 12:58

支持,顶......
作者: feng523    时间: 2010-11-18 20:45

抢座……好好学习!
作者: feng523    时间: 2010-11-19 17:10

为什么我把我随机收集的数组转换成位数组后数量不对?少了几个……怎么回事?
  1. the_ary =for a = 1 to 64 collect random 1 (polyop.getnumverts $)
  2. the_ary.count
  3. bit_ary2 =the_ary as bitarray
  4. bit_ary2.numberset
  5. polyop.setvertselection $ bit_ary2
复制代码

作者: liliangxiong    时间: 2010-11-19 17:38

支持斑竹 支持脚本
作者: feilang    时间: 2010-11-19 19:12

为什么我把我随机收集的数组转换成位数组后数量不对?少了几个……怎么回事?
feng523 发表于 2010-11-19 17:10



    第一句就有问题:
  1. the_ary =for a = 1 to 64 collect random 1 (polyop.getnumverts $)
  2. the_ary.count
复制代码

第一句中random 1 (polyop.getnumverts $)的随机数有可能有重复,所以第二句不一定都是你要的结果.
作者: feng523    时间: 2010-11-20 10:06

惭愧啊,昨天刚走出大楼就想出来了……呵呵,还是版主分析能力强
还有一个问题想请教一下版主,就是法线问题,怎么判断法线是不是统一的?怎么统一?情况是在多边形里有零星的法线翻转的面,不是被镜像过后的对象
有什么好办法吗?
作者: feilang    时间: 2010-11-29 11:14

我没有办法~~
作者: feng523    时间: 2010-11-29 12:12

……我心都凉了,竟然没有办法,很伤心。
作者: SITT    时间: 2010-11-29 14:49

如果模型相互之间是连接的,直接用normal修改器就可以,如果不是,没什么好办法
作者: feng523    时间: 2010-11-29 17:11

彻底崩溃ing……
作者: yzycn    时间: 2010-12-1 11:26

飞浪又出了这么多新教程,回来学习一下,这下工作不忙的时候又有事情做了
作者: 新乞丐王子    时间: 2010-12-13 18:00

飞浪你好,我最近在尝试自己写模型导出脚本,但是看了你的文章之后有一个疑问!
我目前在用的许多导出插件,功能都不错,不过格式都和我程序中的多少有些出入都需要再次转换!
所以我想自己写一套脚本!
但是在你的文章中介绍方法都是对现有模型的操作!
可是我并不想对现有模型做任何修改,比如一个我建了一个BOX,也许给它赋了材质,也许还给它绑了骨骼!
但是我并不需要先塌陷它才能取得点面等信息,像Panda的插件一样直接导出信息!
而且因为我的程序中对各物体的名称以及层级关系要求比较严格,所以克隆一套塌,陷取数据,再删除的办法也不太好!
那还有什么办法嘛?
作者: 新乞丐王子    时间: 2010-12-14 10:55

经过昨天的摸索,发现可以不塌陷也能取到顶点等信息了!
的确想写好脚本就要满Reference到处找,无限的搜索...
作者: qq56    时间: 2011-1-16 23:46

看过一些关于MAXSCRIPT的教程,王华的书我买了,好多看不懂,就是直接翻译帮组文件。

有本老虎工作室的书看过,懂了,写的还好,就是量太少。

希望浪哥可以继续写下去,如果您方便的话。浪哥的教程我很喜欢。

谢谢
作者: niaochao    时间: 2011-1-19 15:08

太牛了,学习中!!!!
作者: 麻菜    时间: 2011-3-10 15:46

浪哥威武啊!等你的书
作者: c28728714    时间: 2011-9-27 19:17

支持飞浪的教程简单易懂!我喜欢!
作者: linkvebo    时间: 2013-6-4 17:18

比如第八节,我建议大家在变量名前面加前缀,后来我在其他书上看到说这个命名方法早就过时了


不知现在流行的命名方法是怎么样了??刚开始学脚本,所以问问,,,这里有挖坟一说吗..
作者: feilang    时间: 2014-7-8 16:29

不知现在流行的命名方法是怎么样了??刚开始学脚本,所以问问,,,这里有挖坟一说吗..
linkvebo 发表于 2013-6-4 17:18



   自己习惯就行,可以google一下变量命名法,挑一个你自己喜欢的就行。
作者: szdot    时间: 2016-1-31 14:20

@feilang
能不能得到 一条Edge的长度呢




欢迎光临 CG++ (http://www.cgplusplus.com/bbs/) Powered by Discuz! 7.2