SketchUp文本标注的层级显示
在电子地图中文本标注有其显示层级的区别,因此重要的标注能够在更小的比例尺下显示,这样控制标注可以在保持图面简洁的同时尽量保留关键信息。如果一个 SketchUp 模型拥有过多的文本标注,能不能使用类似的显示方式予以筛选呢?答案是肯定的,效果如下:
文中是实现思路与方法,其中最后一部分是完整的代码呈现和使用方法。
〇、基本思路
对于平面地图来说,文本标注的显示与否取决于地图的比例尺,即地图显示层级;而对于 SketchUp 中的三维场景,可以用视点位置与显示位置之间的距离来替代。因此判断文本标注显示与否,就需要读取当前视图的相机位置。如果希望每次变换视角都可以更新标注图元的显隐状态,就需要使用视图监控 ViewObserver,即通过向当前视图添加监控,在 onViewChanged 方法中对标注图元进行显隐性的判断。
另外,不同重要性的标注有不同的显隐需求,因此需要给每一个需要控制显隐性的标注图元一个专门的自定义属性(Attribute)。同时为了更快速地更新所有文本标注的显隐性,需要创建一个标注图元的列表,以便快速地枚举需要判断是否显示的图元。
一、标注层级的设置
具有引线定位的文本标注图元都能够通过 .point 方法获取其所附着的点位,通过计算其与当前视图视点( model .active_view .camera .eye )的距离可以近似地用于表示类似平面地图中比例尺(层级)的概念。因此文本标注图元的分层级显示可以表述成以下两条规则:
①标注距离视点越远,则越有可能被隐藏;
②标注图元越重要,其刚开始被隐藏的视点距离越大。
基于这两条规则,可以用视点距离表示标注图元的重要性,将其作为一个参数保存在图元中:
# text 为一个文本标注图元
# value 为表示重要性的距离值
value=100.m
# 设置重要性:
text.set_attribute("APIGLIO","LabelRank",value)
# 读取重要性:
value=text.get_attribute("APIGLIO","LabelRank")
二、控制标注的显影
在确定了标注图元的重要性后,根据当前的视图视角便可以计算具体位置上特定重要程度的标注图元是否显示。
# text 为一个文本标注图元
view=Sketchup.active_model.active_view
max_dist=text.get_attribute("APIGLIO","LabelRank")
distance=view.camera.eye.distance(text.point)
text.visible=true if distance<=max_dist and text.hidden?
text.hidden=true if distance>max_dist and text.visible?
以上代码中读取文本标注的视点距离阈值 max_dist,并和实际与视点距离 distance 进行比较,若不大于阈值且图元不可见,则使其可见;若大于阈值且实际可见,则将其隐藏。
需要注意的是,这里为了演示的简便,忽略了文本标注所在的模型层次,即文本标注可能在模型中,也可能在群组或者组件之中。后一种情况不能简单地通过 .point 方法获取其位置,实现方法相对复杂。
三、监控视图的改变
由于希望文本标注的显隐能够自动地同步于视图的变化,因此需要增加一个实时的视图监控,在每一次视角改变时重新计算。这需要通过创建视图监控实现,在每一次视图变换后,计算和更新文本标注的显隐情况。
class NewViewObserver < Sketchup::ViewObserver
def onViewChanged(view)
# 此处更新标注显隐性
end
end
视图变换时需要一次性更新所有文本标注的显隐性,这需要遍历所有文本标注,因此需要首先获得文本标注的图元列表:
model=Sketchup.active_model
text_list=model.entities.grep(Sketchup::Text)
当然,在图元较多时,每次变换视图都计算一次标注图元列表是非常不明智的,这会造成严重的卡顿。因此最好的处理方式是在开始监控时创建一次标注图元列表,之后仅在模型发生改变时增减列表中的图元。而这种处理方式需要 ModelObserver 和 EntitiesObserver 的辅助,才能完整实现。
四、监控标注的增减
标注的增减监控的大致逻辑如下:通过图元容器监控 EntitiesObserver 中的 .onElementAdded 和 .onElementRemoved 方法,监控当前图元容器的图元增减情况,在将新增的标注图元加入列表的同时删除列表中已删除的图元。同时通过模型监控 ModelObserver 中的 .onActivePathChanged 方法监控编辑路径的修改,在新的图元容器中进行图元增减的监控。
这部分实现方法可直接参考最终的代码,其包括两个部分:一是监控类的定义部分,二是对监控的开关控制部分。
五、代码实现
整个功能通过 LabelRanker 模块实现,完整代码在此部分最后,同时也可以在以下网址中获取:
Apiglio/SketchupScriptTool
https://github.com/Apiglio/SketchupScriptTool/blob/main/Cam.rb
将整段代码复制到控制台中按回车键执行(或者通过 load 方法调用),之后便可以通过以下代码启用层级显示功能:
LabelRanker.start
如果是从GitHub中下载的代码,由于 LabelRanker 模块定义在 Cam 模块中,需要使用如下代码启用:
Cam::LabelRanker.start
之后选择一个文本标注,令其在远于视点一公里的位置下隐藏则需要如下代码:
# 仅选择一个文本标注图元
text=Sketchup.active_model.selection[0]
LabelRanker.set_rank(text,1000.m)
#或
Cam::LabelRanker.set_rank(text,1000.m)
当然,如果有需要也可以通过自定义的 HtmlDialog 类创建专门的显示层级参数的编辑器,本篇文章暂不做演示,后续或在 GitHub 中更新。
关闭层次显示功能则与启用代码类似:
LabelRanker.stop
#或
Cam::LabelRanker.stop
完整的代码公布如下:
#用来做分级标注显示,等级较低的标注在距视点较远距离时会隐藏
module LabelRanker
@ents_lnk=0
@ents_obs=0
@view_lnk=0
@view_obs=0
$apiglio_Cam_LabelRanker_list=[]
@mod_obs=nil
class EntsObserver < Sketchup::EntitiesObserver
def onElementAdded(entities, entity)
$apiglio_Cam_LabelRanker_list|=[entity] if entity.is_a?(Sketchup::Text) or entity.is_a?(Sketchup::Dimension)
end
def onElementRemoved(entities, entity_id)
$apiglio_Cam_LabelRanker_list.reject!{|i|i.deleted?}
end
end
class ViewObserver < Sketchup::ViewObserver
def text_world_position(text)
tmp=text
res=text.point
mod=Sketchup.active_model
while tmp.parent!=mod
inst=tmp.parent.instances
return nil unless inst.length==1 # 排除标注在组件(或有多个实例的群组)中的情况
tmp=inst[0]
res=tmp.transformation.inverse*res
end
return res
end
private :text_world_position
def onViewChanged(view)
#puts "onViewChanged: #{view}"
$apiglio_Cam_LabelRanker_list.each{|text|
if text.is_a?(Sketchup::Text) then
next if text_world_position(text).nil?
max_dist=Cam::LabelRanker.get_rank(text)
next if max_dist.nil?
distance=view.camera.eye.distance(text.point)
text.visible=true if distance<=max_dist and text.hidden?
text.hidden=true if distance>max_dist and text.visible?
elsif text.is_a?(Sketchup::Dimension) then
#
else
#
end
}
end
end
class ModObserver < Sketchup::ModelObserver
def onActivePathChanged(model)
LabelRanker.update_obs
end
end
def self.test
return [@ents_lnk,@ents_obs,@view_lnk,@view_obs,@mod_obs,$apiglio_Cam_LabelRanker_list]
end
def self.update_obs
@ents_lnk.remove_observer(@ents_obs) if @ents_lnk!=0
@view_lnk.remove_observer(@view_obs) if @view_lnk!=0
if Sketchup.active_model.active_path.nil? then
@ents_lnk=Sketchup.active_model
else
@ents_lnk=Sketchup.active_model.active_path.last
end
@ents_obs=EntsObserver.new
@ents_lnk.entities.add_observer(@ents_obs)
@view_lnk=Sketchup.active_model.active_view
@view_obs=ViewObserver.new
@view_lnk.add_observer(@view_obs)
end
def self.all_text_into_list(model_or_defs)
model_or_defs.entities.grep(Sketchup::Text).each{|e|$apiglio_Cam_LabelRanker_list|=[e]}
model_or_defs.entities.grep(Sketchup::Group).each{|g|all_text_into_list(g.definition)}
end
def self.all_dimension_into_list(model_or_defs)
model_or_defs.entities.grep(Sketchup::Dimension).each{|e|$apiglio_Cam_LabelRanker_list|=[e]}
model_or_defs.entities.grep(Sketchup::Group).each{|g|all_dimension_into_list(g.definition)}
end
private_class_method :all_text_into_list
private_class_method :all_dimension_into_list
def self.start
Sketchup.active_model.remove_observer(@mod_obs) unless @mod_obs.nil?
@mod_obs=ModObserver.new
Sketchup.active_model.add_observer(@mod_obs)
update_obs()
all_text_into_list(Sketchup.active_model)
all_dimension_into_list(Sketchup.active_model)
Sketchup.active_model.active_view.invalidate
end
def self.stop
@ents_lnk.remove_observer(@ents_obs)
@view_lnk.remove_observer(@view_obs)
$apiglio_Cam_LabelRanker_list.each{|l|l.visible=true}
$apiglio_Cam_LabelRanker_list.clear
GC.start
end
def self.set_rank(text,value)
text.set_attribute("APIGLIO","LabelRank",value)
end
def self.get_rank(text)
text.get_attribute("APIGLIO","LabelRank")
end
end
(完)
从本期开始,本人的 SketchUp Ruby “小功能与灵感” 的文章中涉及的功能会在 GitHub 中相应更新。这样一来,如果日后发现存在bug需要修正或者是想追加相关的功能,都有极大的便利。
这个代码仓库同样也包含大部分之前文章的代码,可以访问以下网址查看,也欢迎大家共同提交修改:
https://github.com/Apiglio/SketchupScriptTool
本文编号:SU-2022-01