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.verticesverts.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? thennormals=faces.map{|f|f.normal.to_a}.uniqcase normals.lengthwhen 1 then nilwhen 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#排除边线所连平面不平行的情况endraise 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.verticesverts-=shared#排除公共顶点的点集就是起讫点parent=e1.parentn_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_lineenddef self.find_pair(enmerable_instance,&block)list_1=enmerable_instance.to_alist_2=enmerable_instance.to_ares=[]for i in 0..list_1.length-1 dofor 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])endendreturn resendprivate_class_method :find_pairdef 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_atmp_list.select!{|i|i.is_a?(Sketchup::Edge)}#排除数组中的非边线图元while arr.length>1 dofound_edges=find_pair(tmp_list){|i,j|i.vertices&j.vertices!=[]}break if found_edges.empty?#如果找不到有效配对就退出循环index=0beginnl=simplify_edge(found_edges[index][0],found_edges[index][1],true)#尝试对有效配对的边线进行简化rescueindex+=1return(nil) if index>=found_edges.length#如果简化失败且没有更多配对情况就退出retry#如果有备用配对则重新尝试下一对边线endtmp_list-=found_edges[index]#在数组参数中排除简化并删除的边线tmp_list.unshift(nl)#在数组参数开头追加新的边线#追加在开头而不是尾部是因为简化后的边线大概率能够继续简化,#这样可以使retry的情况少一些,提高一点点运行效率。endSketchup.active_model.commit_operation#提交修改操作endend
本期功能没什么好补充的,就是一个很实用的功能而已,一些细节在代码实现的注释中。
本文编号:SU-2021-11
