2018年7月23日 星期一

VPython進階教學:按鈕

作者:王一哲
日期:2018/7/23




由於我們之前做的動畫,都是在按在 F5 後自動開始執行,如果我們想要在動畫中新增按鈕,讓使用者可以自己控制動畫,應該要怎麼做呢?我們希望按鈕的功能有:

  1. Run:按下時開始執行動畫
  2. Reset:按下時還原成起始狀態
  3. Stop:按下時停止執行程式

我們使用最簡單的動畫〈等速度直線運動〉的程式碼來改寫,成果如下:

有按鈕的等速度直線運動畫面截圖

按下 Run 按鈕後

用滑桿改變速度,使木塊向左移動

按下 Reset 按鈕後



VPython進階教學: 按鈕


"""
 VPython進階教學: 按鈕
 日期: 2018/7/23
 作者: 王一哲
"""
from vpython import *

"""
 1. 參數設定, 設定變數及初始值
"""
size = 0.1        # 木塊邊長
L = 1             # 地板長度
v = 0.03          # 木塊速度
t = 0             # 時間
dt = 0.01         # 時間間隔
re = False        # 重置狀態
running = False   # 物體運動狀態
end = False       # 程式是否結束

"""
 2. 函式設定
"""
# 建立動畫視窗, 建立按鈕時才不會另外開啟一個黑底、無物件的動畫視窗
scene = canvas(title="1D Motion\n\n", width=800, height=400, x=0, y=0,
               center=vec(0, 0.1, 0), background=vec(0, 0.6, 0.6))

# 初始畫面設定
def setup():
    global floor, cube, gd, gd2, xt, vt
    floor = box(pos=vec(0, 0, 0), size=vec(L, 0.1*size, 0.5*L), color=color.blue)
    cube = box(pos=vec(-0.5*L + 0.5*size, 0.55*size, 0), size=vec(size, size, size),
               color=color.red, v=vec(v, 0, 0))
    gd = graph(title="<i>x</i>-<i>t</i> plot", width=600, height=450, x=0, y=400,
               xtitle="<i>t</i> (s)", ytitle="<i>x</i> (m)")
    gd2 = graph(title="<i>v</i>-<i>t</i> plot", width=600, height=450, x=0, y=850,
                xtitle="<i>t</i> (s)", ytitle="<i>v</i> (m/s)")
    xt = gcurve(graph=gd, color=color.red)
    vt = gcurve(graph=gd2, color=color.red)

# 執行按鈕
def run(b1):
    global running
    running = not running
    
b1 = button(text="Run", pos=scene.title_anchor, bind=run)

# 物體運動
def motion(t, dt):
    global running
    while(cube.pos.x <= L*0.5 - size*0.5):
        rate(1000)
        cube.pos += cube.v*dt
        xt.plot(pos=(t, cube.pos.x))
        vt.plot(pos=(t, cube.v.x))
        t += dt
    print("t = ", t)
    running = False

# 重置按鈕
def reset(b2):
    global re
    re = not re
    
b2 = button(text="Reset", pos=scene.title_anchor, bind=reset)

# 重置用, 初始化
def init():
    global re, running
    cube.pos = vec(-L*0.5 + size*0.5, size*0.55, 0)
    cube.v = vec(v, 0, 0)
    t = 0
    xt.delete()
    vt.delete()
    re = False
    running = False

# 停止按鈕
def stop(b3):
    global end
    end = not end
    
b3 = button(text="Stop", pos=scene.title_anchor, bind=stop)

"""
 3. 主程式
"""
setup()
while not end:
    if running:
        print("Run")
        motion(t, dt)
    if re:
        print("Reset")
        init()

print("Stop")



參數設定


除了一些物件的資料之外,另外設定了重置狀態re物體運動狀態running程式是否結束end等3個變數,預設值皆為False。

畫面及函式設定


  1. 先用以下的程式碼新增動畫視窗scene,這是為了在新增按鈕時不會另外開啟一個新的動畫視窗。
    scene = canvas(title="1D Motion\n\n", width=800, height=400, x=0, y=0,
                   center=vec(0, 0.1, 0), background=vec(0, 0.6, 0.6))
  2. 自訂函式setup(),執行此函式時產生木塊、地板、繪圖視窗。
  3. 設定執行按鈕b1,分為2個部分:
    def run(b1):
        global running
        running = not running
        
    b1 = button(text="Run", pos=scene.title_anchor, bind=run)
    
    1. 自訂函式run(b1),函式內定義了全域變數running,當b1被按下時,將running的狀態反過來,若原為False則改為True,若原為True則改為False。
    2. button產生按鈕b1,其中text為按鈕上顯示的文字,pos為按鈕位置,在此設定為scene的標題位置(scene.title_anchot),bind則是用來將這個按鈕物件連結到自訂函式run。
  4. 自訂函式motion(t, dt),執行此函式時,木塊會開始向右移動,直到木塊抵達地板右側邊緣為止,木塊停止後再將running的狀態設為False,避免動畫繼續執行,形成無窮迴圈。
  5. 設定重置按鈕b2,分為3個部分:
    def reset(b2):
        global re
        re = not re
        
    b2 = button(text="Reset", pos=scene.title_anchor, bind=reset)
    
    def init():
        global re, running
        cube.pos = vec(-L*0.5 + size*0.5, size*0.55, 0)
        cube.v = vec(v, 0, 0)
        t = 0
        xt.delete()
        vt.delete()
        re = False
        running = False
    
    1. 自訂函式reset(b2),函式內定義了全域變數re,當b2被按下時,將re的狀態反過來,若原為False則改為True,若原為True則改為False。
    2. button產生按鈕b2。
    3. 自訂函式init(),執行此函式時,木塊會回到初位置,清除x-t圖、v-t圖中的資料。
  6. 設定停止按鈕b3,分為2個部分:
    def stop(b3):
        global end
        end = not end
        
    b3 = button(text="Stop", pos=scene.title_anchor, bind=stop)
    
    1. 自訂函式stop(b3),函式內定義了全域變數end,當b3被按下時,將end的狀態反過來,若原為False則改為True,若原為True則改為False。
    2. button產生按鈕b3。



主程式


由於我們將大部分的內容及效果寫在自訂函式中,所以主程式非常短,只有以下幾行

setup()
while not end:
    if running:
        print("Run")
        motion(t, dt)
    if re:
        print("Reset")
        init()
        
print("Stop")



  1. 先呼叫自訂函式setup(),產生需要的物件。
  2. 進入while迴圈,若end的值為False,則迴圈持續執行;若end的值為True,則結束迴圈並輸出訊息Stop
  3. 如果running的值為True,輸出訊息Run、呼叫自訂函式motion(t, dt)。
  4. 如果re的值為True,輸出訊息Reset、呼叫自訂函式init()。

如果執行此程式並依序按下Run、Reset、Run、Stop,應該會在Python Shell看到以下的訊息:
Run
t =  30.00000000000189
Reset
Run
t =  30.00000000000189
Stop



執行效果


我們可以用滑桿控制木塊的速度使木塊來回移動,當木塊抵達地板邊緣時,木塊會停止移動,但是時間仍會繼續增加,這是和第1版程式最大的差異。
用滑桿控制木塊來回移動的x-t圖

結語


用VPython製作一個有操作界面的動畫,雖然也能做出不錯的效果,但是我們需要花較多的時間寫程式,用來處理物理模型的時間相對上較少,所以我們只簡單地做了這個動畫,之後的課程還是以處理物理模型為主。如果對於VPython的按鈕、拉桿……等元件的用法有興趣,請參考GlowScript網站上的範例自行改寫。



參考資料


  1. VPython官方說明書Widgets: http://www.glowscript.org/docs/VPythonDocs/controls.html
  2. GlowScript網站範例Buttons Sliders Menus: http://www.glowscript.org/#/user/GlowScriptDemos/folder/Examples/program/ButtonsSlidersMenus-VPython





HackMD 版本連結:https://hackmd.io/@yizhewang/H1Uyq-Q4m

沒有留言:

張貼留言