SketchUp頂視圖鎖定
不知道有沒有人用 SketchUp 做平面繪圖工具,如果有,那么他一定有很強的自我約束力,能夠克制按住中鍵拖動來移動畫面的沖動。
SketchUp 是三維草圖軟件,但是這不代表它不能夠勝任二維的草圖工作。如果能夠讓繪圖區(qū)的三維視角固定在頂視圖的角度,那么平面上的二維圖形繪制也就易如反掌了。
那么 SketchUp 中如何設(shè)置固定視角呢?這首先需要先從相機概念開始。
一、相機類 Camera
SketchUp 的具體一個視角由一個相機類(Sketchup:: Camera)表示,由三個向量參數(shù)構(gòu)成。分別是 .eye、?.target?和 .up。其中?.eye?為一個三維點坐標(Geom:: Point3d),表示相機的所在位置,也就是視點的位置;?.target?也是一個三維坐標,表示視線與畫面的交點,也就是心點的位置;?.up?則為一個三維向量(Geom:: Vector3d),表示畫面的上方向。如果將前兩個點坐標相減,就可以得到代表視線方向的向量,即?.direction。
對于一個有效的視角,其?.up 向量和?.direction?向量是相互垂直的;如果是頂視圖,則更為明確,分別是?[0,1,0] 和 [0,0,-1]。當使用環(huán)繞觀察工具拖動屏幕時,視角會根據(jù)鼠標拖動的操作進行相應(yīng)的變化。如果需要鎖定頂視圖視角,就需要在視角發(fā)生變化時,立刻重新將視線調(diào)整為垂直于水平面且方向向下的方向。
除了以上三個坐標參數(shù),還有表示視高的?.height?方法和判斷是否是透視投影的?.perspective??方法等。這些參數(shù)在平行投影的視圖變換中起到相當關(guān)鍵的作用,不過本篇文章不作過多的介紹了。
二、視圖監(jiān)控類?ViewObserver
監(jiān)控視角的變化需要使用到視圖監(jiān)控類(Sketchup:: ViewObserver),監(jiān)控類在之前的文章中有出現(xiàn)過,但是沒有專門介紹。這里簡要介紹一下監(jiān)控類的運行方式。
Ruby API 提供的所有以 “Observer” 為結(jié)尾的類,均屬于監(jiān)控類,用于實現(xiàn)對當前模型特定內(nèi)容的監(jiān)控,并在滿足規(guī)定條件的情況下觸發(fā)預先定義好的事件方法。例如本文中要使用的 ViewObserver 類,其中就定義了一個 onViewChanged 事件,表示在視圖變化時會觸發(fā)這個方法。事件(Event)本質(zhì)上是一類方法(也可以稱為函數(shù)或過程),方法名稱統(tǒng)一以小寫 “on” 開頭(這個開頭并不是強制要求的,但是這種命名規(guī)范就像大部分語言中的縮進一樣,是強約定)。當這個監(jiān)控類生效以后,每次視角更新時就會觸發(fā) onViewChanged 方法。
想象一下,可能存在不同的插件會同時對一類變化進行監(jiān)控,并對相同的觸發(fā)條件有各自不同的操作。而一個 ViewObserver 類的 onViewChanged 方法只能有一個,這就需要引入類繼承來解決這個問題。
例如:插件A需要在視角變化時在控制臺輸出視線的心點的坐標(Sketchup:: Camera# target),而插件B需要輸出視點的坐標(Sketchup:: Camera# eye)。那么這兩個輸出代碼需要分別定義給 ViewObserver 的不同子類中:
def?VO_1.onViewChanged??puts "targ =#{view.camera.target}"enddef?VO_2.onViewChangedputs "eye =#{view.camera.eye}"end
以上代碼并不能執(zhí)行,其中的?VO_1?和?VO_2 分別是兩個 ViewObserver 的子類,這樣才能保證兩個事件可以分別生效。子類需要通過類繼承來定義:
class?VO_1?def?onViewChanged(view)????puts?"targ?=#{view.camera.target}"endendclass?VO_2???def?onViewChanged(view)????puts?"eye ?=#{view.camera.eye}"endend
通過?.superclass 方法可以檢驗?VO_1 和?VO_2 均是?Sketchup:: ViewObserver 的子類:
VO_1.superclass#>> Sketchup::ViewObserverVO_2.superclass#>> Sketchup::ViewObserver
確定是 ViewObserver 類的子類之后,就可以分別創(chuàng)建這兩個子類的實例了:
v1=VO_1.newv2=VO_2.new
兩個監(jiān)控實例創(chuàng)建完成,但是到目前為止,視角變化時控制臺還不會有任何反應(yīng)。這是因為監(jiān)控僅僅是存在而已,并沒有生效。監(jiān)控的生效需要使用 Sketchup:: View 類的?.add_observer 方法(每一個實體類 Entity 的實例都有此方法,可以用于添加相應(yīng)的監(jiān)控)。而通過模型類 Model 的?.active_view 方法可以獲得代表當前視角的對象,并通過?.add_observer?方法將監(jiān)控生效在繪圖區(qū)的當前視角中:
Sketchup.active_model.active_view.add_observer(v1)Sketchup.active_model.active_view.add_observer(v2)
執(zhí)行以上兩行代碼之后,兩個監(jiān)控就成功的生效了,并且會根據(jù)生效的前后順序倒序執(zhí)行:

最后是卸載監(jiān)控,類似于使之生效,使用?.remove_observer 方法即可卸載參數(shù)中指定的監(jiān)控。因此監(jiān)控實例需要留好引用變量,以免在要卸載監(jiān)控時造成尷尬:
Sketchup.active_model.active_view.remove_observer(v1)Sketchup.active_model.active_view.remove_observer(v2)
三、關(guān)于類繼承的兩個補充
(1) 覆蓋事件方法時需要注意參數(shù)定義
上文類繼承的代碼中,兩個?onViewChanged?方法都有一個參數(shù)?view。這個參數(shù)是 ViewObserver 類定義的事件中自帶的,用于傳遞變化后的視圖類(Sketchup:: View)。這是預先規(guī)定好的參數(shù),類繼承覆蓋舊有事件時參數(shù)要保持一致,因為事件的觸發(fā)相當于是在特定的情況下,程序根據(jù)所處狀態(tài)選擇具體的參數(shù)來運行定義好的事件方法。
如果創(chuàng)建的子類此方法沒有?view 參數(shù):
class VO_Err < Sketchup::ViewObserverdef onViewChanged()puts "targ =#{Sketchup.active_model.active_view.camera.target}"endendve=VO_Err.newSketchup.active_model.active_view.add_observer(ve)
監(jiān)控生效之后,更換視角就會發(fā)現(xiàn)控制臺持續(xù)地報告如下錯誤:

這就是因為移動視角時 SketchUp 相當于在調(diào)用?ve.onViewChanged( Sketchup. active_model. active_view)?這樣的指令,由于繼承的子類?VO_Err?的?onViewChanged 方法沒有定義?view 參數(shù),參數(shù)數(shù)量的檢驗就無法通過,也就有了如上的錯誤報告。
(2) 監(jiān)控類本身是抽象類
不同的子類實現(xiàn)不同的監(jiān)控觸發(fā)效果,而它們共同的父類卻不能單獨實例化,也就是說這個父類 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 方法“生效”,但是不會起任何作用。不過這么做同樣不會造成報錯,這大概是因為事件觸發(fā)之前會首先判斷是否定義了這個事件方法。
四、頂視圖鎖定的實現(xiàn)
在使用環(huán)繞觀察工具時,鼠標指針的微小變化就能引起視圖視角的微小改變,而當視線本身垂直于水平面時,視線向量與水平面的交點位置變化不大。因此可以使用這個交點坐標立刻生成一個視距相同的新的俯視視角,從而實現(xiàn)頂視圖視角的鎖定。當然此處并不是真的禁用了環(huán)繞觀察,只是每當視圖不是頂視圖時立刻就近矯正視線而已。
以下是新視線的計算過程:根據(jù)當前(待矯正)視線向量計算出視線與水平面的交點 gp,計算當前視點到?gp 的距離 dist,將新的視點設(shè)置為?gp?正上方距離?dist?處,以?gp?作為新的心點,以綠軸正半軸為畫面正上方。視角矯正之后,再將矯正前的視高和是否為透視投影這些屬性重新賦予新視角。最終將這個過程定義在 onViewChanged?事件中,就可以實時矯正非頂視圖視角了:
#定義一個鎖定頂視圖的監(jiān)控類class LockTopView < Sketchup::ViewObserver??def?onViewChanged(view)cam=view.cameraheight=cam.heightpers=cam.perspective?eye=cam.eyetarget=cam.target????vec=target-eyegp=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=persview.camera.height=height??endend#創(chuàng)建監(jiān)控并使之生效$apiglio_lock_top_view=LockTopView.newSketchup.active_model.active_view.add_observer($apiglio_lock_top_view)#以下為卸載監(jiān)控的代碼Sketchup.active_model.active_view.remove_observer($apiglio_lock_top_view)
這是最終的效果:

本文編號:SU-2021-08
