【SU Ruby教程】图元定义(3):Loop类和EdgeUse类
上一篇教程中介绍了 SketchUp 中2种最基本的图元,以及构成它们的端点(Vertex)结构。并且区分了端点与它们的区别。本篇将进一步介绍两个类似于端点的结构,分别是 Loop 类和 EdgeUse 类。这两类结构均与平面图元密切相关,因此本篇还需要从平面类尚未介绍的内容展开。
图元定义(3):Loop 类与 EdgeUse 类
【本期目录】 | |
(1)再谈平面类 ①端点的顺序 ②正面与反面 ③内环与外环 | (3)EdgeUse 类 ①EdgeUse 与平面类 ②调用 EdgeUse ③EdgeUse 能做什么 |
(2)Loop 类 ①Loop 与平面类 ②调用 Loop ③Loop 能做什么 |
(1)再谈平面类
①端点的顺序
首先,关于 Entities 类的 .add_face 方法还需要进行一些补充。上一篇教程 [SU-R11] 中已经介绍过它的用法,但是并没有对它的参数情况深究。这里需要对参数的顺序进行一些额外的探讨。
使用 .add_face 方法需要给定至少3个不重复的点坐标或者是3段以上首尾相连的边线图元。在仅给定三个处于不同高度的点坐标时,新生成的平面会根据点坐标的顺序方向确定平面的正面与反面——根据右手螺旋定则确定正面。也就是说,从正面(白面)看这个三角形平面,端点在参数中的顺序是逆时针排列的。
因此使用 .add_face 方法生成的绝大多数平面都默认为这种端点顺序,也就是未翻转的平面。如果这种平面没有与其他平面共享边线,可以用这样的代码来验证:
face.edges.none?{|e|e.reversed_in?(f)}
#返回 true 即为未翻转的平面
这里的 .reversed_in? 方法(Edge 实例的方法)就是用来判断一条边线与一个平面之间的方向关系。如果沿边线前进方向并将平面旋转至左侧,其正面向上则返回 false,否则返回 true。
但是之前的描述中使用了诸如“处于不同高度”和“绝大多数”这类表达,是因为 .add_face 方法生成的平面在正反面这一特点上存在一个特例,即当其生成的平面在 z=0 平面上时,其正面(白面)始终朝向 z 轴负半轴:
这么做的好处在于创建一个基平面可以很确切地使用负数进行推拉而不用额外判断新创建平面的正反面,更不用刻意地去记忆参数的顺序。
另外,关于给 .add_face 方法的参数不完美这种情况,本人也做了一些尝试,例如以下两段代码:
ents.add_face(arr[1],arr[2],arr[4],
arr[0],arr[3],arr[4])
ents.add_face(arr[1],arr[4],arr[0],
arr[2],arr[3],arr[7],
arr[6],arr[5])
分别可以得到以下结果,红色表示生成的平面:
可以发现 .add_face 方法会从最后一个参数开始寻找第一个闭环的情况,而不会扭曲地创建平面。当然这里仅是极端情况的尝试,不要试图去利用这种特性。
②正面与反面
由于平面有正反面之分,因此也会有与之有关的修改。例如上文中提到的 Edge 类的实例方法 .reversed_in? 方法。此方法用于检验一个边线图元与之连接的平面图元的方向关系。
上文中由于是孤立的平面图元,不涉及共用边线,而创建平面时使用了坐标点参数,这意味着边线也是按照参数顺序创建的,因此所有边线与这个平面的方向关系都符合右手螺旋定则,因此其返回值均为 false。如果是多个平面共用边线,那么就无法保证所有边线的返回值均相同了:
对于一个平面,可以通过右键菜单操作翻转平面,对应的就是 Face 类的 .reverse! 方法——从后缀就可以看出它是一个可以修改模型数据的方法。因为翻转操作只修改平面属性,不影响边线属性,因此翻转后的结果是其所有边线的 .reversed_in? 方法将返回与翻转前相反的值。
平面正反面也会影响平面类的 .plane 和 .normal 方法,使它们的返回值也与翻转前完全相反。换言之就是翻转改变平面的法向量方向。
③内环与外环
在 SketchUp 中,绝不会缺少这样一类平面,它们不仅有外边缘,平面内也有孔洞:
这类平面并不能直接创建,需要在已有的平面内创建新的平面,以达到挖洞的效果:
如上述例子中,首先创建了一个大的三角形 f1,之后在其面域内创建小三角形 f2。这时再选中 f1 就会发现,它已经成为一个带有孔洞的平面。图中的 Sel << [f1] 指令表示向选区中追加 f1 表示的图元,收录在本人的选区代码辅助工具 Sel 之中,代码与使用方法见[SU-2021-05]。
当这类平面图元需要进行边线的操作时就会有一个比较棘手的问题,那就是如何确定内边缘与外边缘。如果使用 Face 类的 .edges 方法,就会一次性返回内外边缘的所有边线,在不知道外边缘边线段数量时,区分它们将会十分困难。因此内环与外环的概念就非常有必要了。
这就是 Face 类的 .loops 和 .outer_loop 方法的作用。前者 .loops 返回一个数组,这个数组中至少包含一个 Sketchup:: Loop 实例,至少包含的那一个即代表外边缘,它总在数组的第0个;它同样可以通过 .outer_loop 方法访问。因此 .outer_loop 方法等同于 .loops[0] 这样的表述。通过这两个方法返回的 Loop 类对象就可以允许我们访问各个环上的边线和端点。
(2)Loop 类
①Loop 与平面类
关于 Loop 类,官方文档的解释非常的简略,不过在有一本英文教程的附录中有对它的进一步说明:
Loop objects are generated from Faces, and they're usually used in topological applications where direction and orientation are important. The Primary purpose of a Loop is to return the set of ordered EdgeUses that make up the boundary of the Face.
"Automatic SketchUp - Creating 3-D Models in Ruby" Matthew Scarpino
Loop 对象附属于平面图元,每一个 Face 至少有一个 Loop。其通常用于与空间拓扑相关的工作之中,可以按特定顺序返回一组 EdgeUse。
EdgeUse 可以理解成是具体一条边线与具体某个相连平面的关系,而 Loop 则是同一个平面图元中具体一个环上的所有这种关系的有序集合。
②调用 Loop
Loop 对象和 端点类 (Vertex) 一样,并不能由用户创建,因此都是通过其他图元进行访问。除了使用 Face 图元的 .outer_loop 和 .loops 方法,还可以通过 Vertex 实例的 .loops 方法访问具体一个端点所参与的环状结构。此处不举例演示。
③Loop 能做什么
上文表示层次的图片中左侧列出的 .vertices、 .edges 和 .edgeuses 方法分别按照顺序返回这个环状结构的端点、边线和 EdgeUse 列表。此处的顺序与前文中“端点的顺序”保持一致。 .face 方法返回此环状结构所属的平面图元。
另外还有两个以问号为结尾的方法用于判断 Loop 的两个属性: .outer? 方法返回 Loop 是否是平面的外边缘, .convex? 方法返回 Loop 是否是凸环。
(2)EdgeUse 类
①EdgeUse 与平面类
至于这个类,甚至找不到一个合适的中文译名,英文教程附录中是如下描述这个类的:
An EdgeUse represents an ordered Edge in a Loop generated from a Face. In addition to being accessible in order, EdgeUses differ from Edges in that you can obtain the normal vector for the start and end points of EdgeUse.
"Automatic SketchUp - Creating 3-D Models in Ruby" Matthew Scarpino
与 Loop 相同,这个类也是附属于平面图元,其特色在于可以访问边线两个端点处的平面法向量。(不过说实话,任何一个边线端点处的法向量和平面法向量不就是同一个方向吗?)
EdgeUse 类或许可以叫做“线-面关系类”,它通过 Loop 类来访问,每一个实例都可以查找到与其自身相连的另外两个 EdgeUse 实例。
②调用 EdgeUse
调用 EdgeUse 可以通过 Loop 类的 .edgeuses 方法,此方法按照定义顺序返回一个环的所有 EdgeUse 实例。此外 EdgeUse 类也提供了三个方法来调用与当前 EdgeUse 有特定关系的其他 EdgeUse。
.previous 方法返回与当前 EdgeUse 相连的上一个 EdgeUse, .next 方法则返回下一个。 .partners 方法返回与当前 EdgeUse 共用同一个边线图元的其他所有 EdgeUse,返回结果为一个数组。
③EdgeUse 能做什么
类似于 Loop 类, EdgeUse 也同样可以访问与之相连的图元或是拓扑结构,这其中包括 .edge、 .face、 .loop、 .next、 .previous 和 .partners 等一众方法,各自含义如上图所示。但这其中除了 .partners 方法以外,大多数的功能都与 Face 类和 Loop 类的方法有重合。这里就用此方法做一个展示。
#令模型中至少有一个平面图元
f=Sketchup.active_model.entities.grep(Sketchup::Face)[0]
#当需要判断一个平面是否完全孤立
#(所有边线均不构成其他平面)
#可以使用如下两种表达:
f.edges.all?{|e|e.faces.length==1}
f.loops[0].edgeuses.all?{|e|e.partners.length==0}
EdgeUse 类还有 .end_vertex_normal 和 .start_vertex_normal 方法,用于返回其两个端点处的法线,因为同属于一个平面,所以其返回值必然相等,也必然等于平面的法向量:
目前我能作出的猜测是,它们的区别类似于容器类中 .count 和 .length 的区别,是实现方法不同。因为使用 .normal 方法会比另两个方法快一些,有可能是因为平面图元会专门储存它的法向量,而此方法只需要调用这个值,而另外两个方法则直接通过面线关系重新计算得到此结果。
EdgeUse 还有一个判断函数,即 .reversed? 方法。它用来判断 EdgeUse 对应的边线是否和自身是相同方向。也就是判断 self.edge.start 是否是自身与 self.previous 共有的端点,如果是则返回 false。
最后,像 Loop 和 EdgeUse 类,存在大量与 Face 类功能重复的方法,例如 .vertices 和 .edges 方法。
以上例子中, f 为一个不带孔洞的平面。能够很明显的发现使用 Loop 类提供的容器类来访问这些图元会有微小的速度优势。
文中在介绍端点顺序时,使用代码随机创建三角平面,并且按顺序标注平面的端点,以下为实现代码:
def genRandTriangle(bounds)
unless bounds.is_a? Geom::BoundingBox then
raise ArgumentError.new("expected Geom::BoundingBox")
end
pts=[[],[],[]]
for i in 0..2 do
pts[i]<<bounds.min.x + rand()*bounds.width
pts[i]<<bounds.min.y + rand()*bounds.height
pts[i]<<bounds.min.z + rand()*bounds.depth
end
Sketchup.active_model.entities.add_face(*pts)
end
def faceLabeling(face)
face.vertices.each_with_index{|e,i|
Sketchup.active_model.entities.add_text("V"+i.to_s,e.position)
}
end
(完)
本文编号:SU-R12