SketchUp留头留尾的边线简化
SketchUp 模型中,越多的顶点数意味着占用更大的空间资源和更慢的程序处理时间。因此,在保证形态特征的前提下,对边线进行简化非常有意义。例如以下这种情况:
一般在导入其他格式模型后,很可能出现一段边线由多条不平行的线段组成的情况。而从认知的角度,它就是两点之间的连线而已,因此可以对此进行边线的简化。
期望的功能是:在选中这些边线后,运行一段 ruby 代码即可以在不破坏两侧的面图元结构的前提下“截弯取直”,创建新的直线段。效果见以下视频:
一、实现原理
整个功能的原理其实很好理解,选中一系列的边线,从中挑选出首尾相连的部分,然后将这些边线删除,然后从起点和终点创建一条新的边线。不过,具体实现起来还会有一些细节问题。
首先是,为了简化新建边线可能带来的情况,应当对边线采取两两简化的方法。这样可以将简化前后的情况用一个三角形表示,以便考虑各种特殊情况。
特殊情况包括两个边线没有共同端点、两个边线平行(包括重合)和两个边线不同时构成同一组平面。这三种情况都会影响简化效果。所以首先创建一个方法 simplify_edge( e1, e2) 用来执行给定两个边线的简化,使得执行后 e1 和 e2 都被删除,而新生成 n_line。
在这个过程中,只要先创建新的 n_line,再删除 e1 和 e2,那么原先边线所构成的平面就会首先被新边线切分,删除原先的边线也能够正好合并边线两侧的平面。
另外还要单独排除原边线平行的情况,这是因为在这种条件下,新边线没有切分旧平面,删除旧边线时就会删除本应该保留的平面。
在有了 simplify_edge( e1, e2) 方法以后,就需要对同时选中好几个边线的情况进行考虑。本例中通过创建一个 find_pair 方法枚举给定数组中符合块参数的元素对,从而通过每次从选中的边线中选出可以用 simplify_edge 方法简化的边线对。执行完成后将新的边线结果加入数组中并排除简化完成的两个边线,直到数组中只有一个边线或找不到合适的边线对。
二、代码实现
以下是代码的实现方法,所有功能封装在 EdgeCombine 模块中,使用时只需要在选择边线之后在命令行调用以下代码:
EdgeCombine.simplify_edges(Sketchup.active_model.selection.to_a)
完整的代码实现如下:
module EdgeCombine
#删除两个边线之前先创建一条新的边线以保证平面不被删除
def self.simplify_edge(e1,e2,do_not_update_operation=false)
#首先排除集中不适用的情况,
#如果出现这些情况则不执行合并,并且报错:
raise ArgumentError.new("e1 is not a Edge") unless e1.is_a?(Sketchup::Edge)
raise ArgumentError.new("e2 is not a Edge") unless e2.is_a?(Sketchup::Edge)
#判断两个边线参数是否都是Edge类
raise ArgumentError.new("Edges should have the same parent.") unless e1.parent==e2.parent
#排除两个边线不在同一个层次中的情况
verts=e1.vertices+e2.vertices
verts.uniq!
raise ArgumentError.new("Two Edges should share (just) one vertex.") unless verts.length==3
#判断边线是否仅共享一个端点
#判断边线与新边线构成的三角形是否符合要求:
faces=(e1.faces&e2.faces).sort_by(&:persistent_id)
no_face=(e1.faces+e2.faces).uniq.sort_by(&:persistent_id)
raise ArgumentError.new("Two Edges should share exactly the same faces.") unless faces==no_face
#排除边线所连接的平面不相同的情况
unless faces.empty? then
normals=faces.map{|f|f.normal.to_a}.uniq
case normals.length
when 1 then nil
when 2 then raise ArgumentError.new("Two Edges should share parallel faces.") unless Geom::Vector3d.new(normals[0]).parallel?(normals[1])
else raise ArgumentError.new("Two Edges should share at most two faces.")
end
#排除边线所连平面不平行的情况
end
raise ArgumentError.new("Two Edges should not be parallell.") if e1.line[1].parallel?(e2.line[1])
#排除两条边线平行的情况
Sketchup.active_model.start_operation("simplify_edge",true) unless do_not_update_operation
#开始一个可撤销操作
shared=e1.vertices&e2.vertices
verts-=shared
#排除公共顶点的点集就是起讫点
parent=e1.parent
n_line=parent.entities.add_line(verts.map(&:position))
#创建新的边线
parent.entities.add_face(e1,e2,n_line) if n_line.faces.length<2
#保险起见单独创建一次平面
e1.erase!
e2.erase!
#清除简化前的边线
Sketchup.active_model.commit_operation unless do_not_update_operation
#提交可撤销操作
return n_line
end
def self.find_pair(enmerable_instance,&block)
list_1=enmerable_instance.to_a
list_2=enmerable_instance.to_a
res=[]
for i in 0..list_1.length-1 do
for j in i+1..list_2.length-1 do
#两两进行对比,块参数执行结果为true的元素对加入到输出的数组中
res<<[list_1[i],list_2[j]] if block.call(list_1[i],list_2[j])
end
end
return res
end
private_class_method :find_pair
def self.simplify_edges(arr=nil)
arr=Sketchup.active_model.selection.to_a if arr.nil?
#无参数时使用当前选区作为参数
raise ArgumentError.new("parameter is not an Array") unless arr.is_a?(Array)
#排除参数不是数组的情况
Sketchup.active_model.start_operation("simplify_edges",true)
#开始一个可撤销操作
tmp_list=arr.to_a
tmp_list.select!{|i|i.is_a?(Sketchup::Edge)}
#排除数组中的非边线图元
while arr.length>1 do
found_edges=find_pair(tmp_list){|i,j|i.vertices&j.vertices!=[]}
break if found_edges.empty?
#如果找不到有效配对就退出循环
index=0
begin
nl=simplify_edge(found_edges[index][0],found_edges[index][1],true)
#尝试对有效配对的边线进行简化
rescue
index+=1
return(nil) if index>=found_edges.length
#如果简化失败且没有更多配对情况就退出
retry
#如果有备用配对则重新尝试下一对边线
end
tmp_list-=found_edges[index]
#在数组参数中排除简化并删除的边线
tmp_list.unshift(nl)
#在数组参数开头追加新的边线
#追加在开头而不是尾部是因为简化后的边线大概率能够继续简化,
#这样可以使retry的情况少一些,提高一点点运行效率。
end
Sketchup.active_model.commit_operation
#提交修改操作
end
end
本期功能没什么好补充的,就是一个很实用的功能而已,一些细节在代码实现的注释中。
本文编号:SU-2021-11