SketchUp组件嵌套扁平化
SketchUp 的组件能隔离线面之间不必要的粘连,但过度地使用组件会使得模型结构变得混乱,路径过深也会使编辑变得麻烦。因此可以按照一定规则对复杂的模型进行扁平化处理,以创造更好的编辑环境。
本篇提供一种将各层次模型中特定组件全部原位转移至模型基层(active_model)的方法,实现同类组件管理上的扁平化。
一、基本思路
扁平化的思路可以解释为,将特定的组件 A 的所有实例从其他组件之中去除,再原位置放置到模型基层中。总体而言过程可以分为3步:第一步是找到包含组件 A 的其他组件定义,并记录组件 A 在其中的位置信息;第二步是在上一步找到的组件定义中删除组件 A 的实例;第三步是在第一步中记录的位置上分别创建新的组件 A 的实例。
例如:有两个组件 B 和 C,其中都包含2个组件 A 的实例,分别为 A1 和 A2。而组件 B 和 C 本身在模型中分别有2和3个实例,记为 B1、 B2 和 C1、 C2、 C3。那么将组件 A 进行扁平化就是如下的效果:
这么做对于模型的储存而言无疑是变大了一些,但带来的好处是,当需要同时调整组件 A 的所有实例的位置时,不需要在多个模型层次之间来回进出。
二、技术难点:查找所有实例路径
上述的操作中,相对比较复杂的过程在于判断组件每一个实例的位置。对于一个确定的组件定义,可以很容易通过 .instances 方法获取其在模型中的所有实例,但是对于明确的一个组件实例 ins 而言,其空间位置却不一定是唯一的。这是因为组件实例并不一定在模型基层内,更有可能在其他组件定义之中,即:
ins.parent.is_a?(Sketchup::ComponentDefinition)
而其所在的组件很有可能存在多个实例,即:
ins.parent.instances.length>1
同一个实例存在多个位置,这与编辑组件内图元时的高亮显示情况是一致的:
因此每一个实例都需要遍历其父模型( .parent )所对应的所有实例,直到每一种可能最终都追溯到模型基层的 Model;同时记录每一层的相对位置,通过相对位置的累乘计算每一种可能的空间位置。
例如一个模型中,基层有两个组件 A 的实例 A1 和 A2,又有3个各包含2个组件 A 的实例 B1、 B2 和 C1。那么通过 .instance 方法将得到8个 A 的实例:
8个实例的位置分别表示如下:
t1=a1.transformation
t2=a2.transformation
tb11=ab1.transformation*b1.transformation
tb12=ab2.transformation*b1.transformation
tb21=ab1.transformation*b2.transformation
tb22=ab2.transformation*b2.transformation
tc11=ac1.transformation*c1.transformation
tc12=ac2.transformation*c1.transformation
由于组件 A 所在的组件( B 和 C)同样有可能在更大的组件之中,因此需要使用类似树形结构来计算所有实例的位置。
以上形如 “Model → B2 → AB1” 的链式结构在 SketchUp 中是专门的一种数据类型,即实例路径类(InstancePath)。其作用为指明具体的空间位置,因而此类有 .transformation 方法,返回此路径对应的空间位置,即链中每一个结构的相对位置之乘积。
具体一个实例的所有位置都可以系列实例路径表示,因此只需要有一个通过实例查找其所有实例路径的方法,就可以获取它所有的空间位置。具体方法如下:
#用于查找ent所在的路径,以Hash为树结构,递归。
def find_path(ent)
res={ent=>[]}
p=ent.parent
res[ent]=ent.parent.instances.map{|inst|find_path(inst)} unless p.is_a?(Sketchup::Model)
return res
end
#用于将Hash的树结构遍历出路径,递归。
def path_traverse(hash,list=nil)
if list.nil? then
list=[]
$hash_paths=[]
end
ins=hash.keys[0]
lst=hash.values[0]
if lst.empty? then
$hash_paths.push(list)
else
lst.each{|inst|
path_traverse(inst,list+[inst])
}
end
return $hash_paths
end
#查找某个图元的所有可能路径
def find_paths(ent)
hash=find_path(ent)
return path_traverse(hash).map{|i|
Sketchup::InstancePath.new(
i.reverse.map{|i|i.keys[0]}
)
}
end
以上代码定义了一个 find_paths 方法,可以由给定的图元返回其所有的实例路径。find_path 和 path_traverse 为查找过程中需要使用到的递归方法,不单独调用。
三、具体实现
具体实现除了之前提到的三个步骤的以外,还需要考虑此功能的可撤销性,否则大量的删除和新增图元的操作很可能超出撤销的记录限制。以下是此功能的实现方式:
#将aDef全部原位置提取到最外层以达到扁平化组件结构的效果
def flat(aDef)
Sketchup.active_model.start_operation("Cge::Defs扁平化")
need_to_placed=[]
need_to_flatten=[]
aDef.instances.each{|ins|
p=ins.parent
if p.is_a?(Sketchup::ComponentDefinition) then
need_to_placed+=find_paths(ins).map{|p|
ins.transformation*p.transformation
}
need_to_flatten.push(p)
end
}
need_to_placed.each{|trans|
Sketchup.active_model.entities.add_instance(aDef,trans)
}
need_to_deleted=[]
need_to_flatten.uniq.each{|defi|
need_to_erased=defi.entities.select{|ent|
ent.respond_to?(:definition)
}
need_to_erased.select!{|ent|ent.definition==aDef}
defi.entities.erase_entities(need_to_erased.uniq)
need_to_deleted.push(defi) if defi.entities.length==0
}
need_to_deleted.each{|defi|
Sketchup.active_model.definitions.remove(defi)
}
Sketchup.active_model.commit_operation();nil
end
以上代码中,定义了几个数组,是此方法的关键。 need_to_placed 表示扁平化过程中要从其他群组或组件内移出的组件的位置, need_to_flatten 表示有需要移出组件的组件定义, need_to_deleted 表示移出组件后组件定义为空的组件定义, need_to_erased 表示具体移出组件过程中每一个组件定义内需要删除的旧的组件实例。整个过程即:先查找要移出组件的位置、再放置新组件、而后删除旧组件、最后删除由此产生的空组件。
最终的效果如下:
![]() | ![]() |
扁平化前 | 扁平化后 |
(完)
从SU-2022-01期开始,本人的 SketchUp Ruby “小功能与灵感” 的文章中涉及的功能会在 GitHub 中相应更新。这样一来,如果日后发现存在bug需要修正或者是想追加相关的功能,都有极大的便利。
本期的代码均位于 Cge.rb 内,此模块用来实现与组件和群组有关的一些功能。查找路径的功能就在 Cge 模块中,而组件扁平化的 flat 功能在 Cge::Defs 模块中。
这个代码仓库同样也包含大部分之前文章的代码,可以访问以下网址查看,也欢迎大家共同提交修改:
https://github.com/Apiglio/SketchupScriptTool
本文编号:SU-2022-02