SketchUp顶视图锁定
共 4414字,需浏览 9分钟
·
2021-10-16 02:30
不知道有没有人用 SketchUp 做平面绘图工具,如果有,那么他一定有很强的自我约束力,能够克制按住中键拖动来移动画面的冲动。
SketchUp 是三维草图软件,但是这不代表它不能够胜任二维的草图工作。如果能够让绘图区的三维视角固定在顶视图的角度,那么平面上的二维图形绘制也就易如反掌了。
那么 SketchUp 中如何设置固定视角呢?这首先需要先从相机概念开始。
一、相机类 Camera
SketchUp 的具体一个视角由一个相机类(Sketchup:: Camera)表示,由三个向量参数构成。分别是 .eye、 .target 和 .up。其中 .eye 为一个三维点坐标(Geom:: Point3d),表示相机的所在位置,也就是视点的位置; .target 也是一个三维坐标,表示视线与画面的交点,也就是心点的位置; .up 则为一个三维向量(Geom:: Vector3d),表示画面的上方向。如果将前两个点坐标相减,就可以得到代表视线方向的向量,即 .direction。
对于一个有效的视角,其 .up 向量和 .direction 向量是相互垂直的;如果是顶视图,则更为明确,分别是 [0,1,0] 和 [0,0,-1]。当使用环绕观察工具拖动屏幕时,视角会根据鼠标拖动的操作进行相应的变化。如果需要锁定顶视图视角,就需要在视角发生变化时,立刻重新将视线调整为垂直于水平面且方向向下的方向。
除了以上三个坐标参数,还有表示视高的 .height 方法和判断是否是透视投影的 .perspective? 方法等。这些参数在平行投影的视图变换中起到相当关键的作用,不过本篇文章不作过多的介绍了。
二、视图监控类 ViewObserver
监控视角的变化需要使用到视图监控类(Sketchup:: ViewObserver),监控类在之前的文章中有出现过,但是没有专门介绍。这里简要介绍一下监控类的运行方式。
Ruby API 提供的所有以 “Observer” 为结尾的类,均属于监控类,用于实现对当前模型特定内容的监控,并在满足规定条件的情况下触发预先定义好的事件方法。例如本文中要使用的 ViewObserver 类,其中就定义了一个 onViewChanged 事件,表示在视图变化时会触发这个方法。事件(Event)本质上是一类方法(也可以称为函数或过程),方法名称统一以小写 “on” 开头(这个开头并不是强制要求的,但是这种命名规范就像大部分语言中的缩进一样,是强约定)。当这个监控类生效以后,每次视角更新时就会触发 onViewChanged 方法。
想象一下,可能存在不同的插件会同时对一类变化进行监控,并对相同的触发条件有各自不同的操作。而一个 ViewObserver 类的 onViewChanged 方法只能有一个,这就需要引入类继承来解决这个问题。
例如:插件A需要在视角变化时在控制台输出视线的心点的坐标(Sketchup:: Camera# target),而插件B需要输出视点的坐标(Sketchup:: Camera# eye)。那么这两个输出代码需要分别定义给 ViewObserver 的不同子类中:
def VO_1.onViewChanged
puts "targ =#{view.camera.target}"
end
def VO_2.onViewChanged
puts "eye =#{view.camera.eye}"
end
以上代码并不能执行,其中的 VO_1 和 VO_2 分别是两个 ViewObserver 的子类,这样才能保证两个事件可以分别生效。子类需要通过类继承来定义:
class VO_1 < Sketchup::ViewObserver
def onViewChanged(view)
puts "targ =#{view.camera.target}"
end
end
class VO_2 < Sketchup::ViewObserver
def onViewChanged(view)
puts "eye =#{view.camera.eye}"
end
end
通过 .superclass 方法可以检验 VO_1 和 VO_2 均是 Sketchup:: ViewObserver 的子类:
VO_1.superclass
#>> Sketchup::ViewObserver
VO_2.superclass
#>> Sketchup::ViewObserver
确定是 ViewObserver 类的子类之后,就可以分别创建这两个子类的实例了:
v1=VO_1.new
v2=VO_2.new
两个监控实例创建完成,但是到目前为止,视角变化时控制台还不会有任何反应。这是因为监控仅仅是存在而已,并没有生效。监控的生效需要使用 Sketchup:: View 类的 .add_observer 方法(每一个实体类 Entity 的实例都有此方法,可以用于添加相应的监控)。而通过模型类 Model 的 .active_view 方法可以获得代表当前视角的对象,并通过 .add_observer 方法将监控生效在绘图区的当前视角中:
Sketchup.active_model.active_view.add_observer(v1)
Sketchup.active_model.active_view.add_observer(v2)
执行以上两行代码之后,两个监控就成功的生效了,并且会根据生效的前后顺序倒序执行:
最后是卸载监控,类似于使之生效,使用 .remove_observer 方法即可卸载参数中指定的监控。因此监控实例需要留好引用变量,以免在要卸载监控时造成尴尬:
Sketchup.active_model.active_view.remove_observer(v1)
Sketchup.active_model.active_view.remove_observer(v2)
三、关于类继承的两个补充
(1) 覆盖事件方法时需要注意参数定义
上文类继承的代码中,两个 onViewChanged 方法都有一个参数 view。这个参数是 ViewObserver 类定义的事件中自带的,用于传递变化后的视图类(Sketchup:: View)。这是预先规定好的参数,类继承覆盖旧有事件时参数要保持一致,因为事件的触发相当于是在特定的情况下,程序根据所处状态选择具体的参数来运行定义好的事件方法。
如果创建的子类此方法没有 view 参数:
class VO_Err < Sketchup::ViewObserver
def onViewChanged()
puts "targ =#{Sketchup.active_model.active_view.camera.target}"
end
end
ve=VO_Err.new
Sketchup.active_model.active_view.add_observer(ve)
监控生效之后,更换视角就会发现控制台持续地报告如下错误:
这就是因为移动视角时 SketchUp 相当于在调用 ve.onViewChanged( Sketchup. active_model. active_view) 这样的指令,由于继承的子类 VO_Err 的 onViewChanged 方法没有定义 view 参数,参数数量的检验就无法通过,也就有了如上的错误报告。
(2) 监控类本身是抽象类
不同的子类实现不同的监控触发效果,而它们共同的父类却不能单独实例化,也就是说这个父类 ViewObserver 是个抽象类。它和它的子类都能够满足 is_a? Sketchup::ViewObserver 的条件,但是它本身并没有定义 onViewChanged 方法:
v_abs=Sketchup::ViewObserver.new
[v1,v2,ve,v_abs].map{|i|i.is_a?(Sketchup::ViewObserver)}
#>> [true, true, true, true]
[v1,v2,ve,v_abs].map{|i|i.respond_to?(:onViewChanged)}
#>> [true, true, true, false]
对于 ViewObserver 以及它没有 onViewChanged 方法的子类,它们的实例一样可以通过 .add_observer 方法“生效”,但是不会起任何作用。不过这么做同样不会造成报错,这大概是因为事件触发之前会首先判断是否定义了这个事件方法。
四、顶视图锁定的实现
在使用环绕观察工具时,鼠标指针的微小变化就能引起视图视角的微小改变,而当视线本身垂直于水平面时,视线向量与水平面的交点位置变化不大。因此可以使用这个交点坐标立刻生成一个视距相同的新的俯视视角,从而实现顶视图视角的锁定。当然此处并不是真的禁用了环绕观察,只是每当视图不是顶视图时立刻就近矫正视线而已。
以下是新视线的计算过程:根据当前(待矫正)视线向量计算出视线与水平面的交点 gp,计算当前视点到 gp 的距离 dist,将新的视点设置为 gp 正上方距离 dist 处,以 gp 作为新的心点,以绿轴正半轴为画面正上方。视角矫正之后,再将矫正前的视高和是否为透视投影这些属性重新赋予新视角。最终将这个过程定义在 onViewChanged 事件中,就可以实时矫正非顶视图视角了:
#定义一个锁定顶视图的监控类
class LockTopView < Sketchup::ViewObserver
def onViewChanged(view)
cam=view.camera
height=cam.height
pers=cam.perspective?
eye=cam.eye
target=cam.target
vec=target-eye
gp=Geom.intersect_line_plane([eye,vec],[0,0,1,0])
dist=gp.distance(eye)
up=[0,1,0]
eye=[gp[0],gp[1],dist]
view.camera=Sketchup::Camera.new(eye,gp,up)
view.camera.perspective=pers
view.camera.height=height
end
end
#创建监控并使之生效
$apiglio_lock_top_view=LockTopView.new
Sketchup.active_model.active_view.add_observer($apiglio_lock_top_view)
#以下为卸载监控的代码
Sketchup.active_model.active_view.remove_observer($apiglio_lock_top_view)
这是最终的效果:
本文编号:SU-2021-08