図形の運動

図1
図1の2つのディスクを動かすPythonスクリプトをリスト1のように作成した。なお、本サイトのスクリプトファイルは圧縮ファイルMovFigSCripts.zipにまとめている。
リスト1 2つのディスクを動かすPythonスクリプト
import tkinter as tk
import numpy as np
root = tk.Tk()
frame = tk.Frame(root)
button = tk.Button(frame, text =
'Close')
button.grid(row = 0, column = 10, padx
= 5, sticky = 'e')
def f_close(event):
    """
        コールバック関数、イベント ハンドラー
    """
    root.destroy()
   
print('Event...\n{}'.format(event))
    print('Good-bye.')
#
#       ボタンクリックのイベントハンドラーを関数f_closeとする
#
button.bind('<Button-1>',
f_close)  
canvas = tk.Canvas(frame, bg =
'#ffffff', width = 500, height = 500)
canvas.grid(row = 1, columnspan = 11,
rowspan = 1)
frame.pack()
ctr_x = 250     #   キャンバス中央のX座標
ctr_y = 250     #   キャンバス中央のY座標
r0 = 120       
#   中央大きいディスクの半径
#       回転角度を表すクラス型
class CAngle:
    def __init__(self):
       
self.angle = 0.0
cangle = CAngle()   #   クラス型CAngleのオブジェクト
#       緑色の大きいディスクを中央に生成
canvas.create_oval(ctr_x - r0, ctr_y -
r0, ctr_x + r0, ctr_y + r0,
                  
fill = '#00ff00', tags = 'circle0')
r1 = 20        
#   小さいディスクの半径
r = r0 + r1     #   小さいディスクの中心からキャンバス中央までの距離
x0 = ctr_x + r *
np.cos(cangle.angle)  #   The x-coordinate of the small disc
y0 = ctr_y + r *
np.sin(cangle.angle)  #   The y-coordinate of the small disc
#       青色の小さいディスクの生成 
canvas.create_oval(x0 - r1, y0 - r1,
x0 + r1, y0 + r1, fill = '#0000ff', tags = 'circle1')
#
#          
ディスクの運動
#
def re_draw(cangle):
    #       回転角度を0.1増やす
    cangle.angle += 0.1
    if cangle.angle > 2 *
np.pi:    #   角度が2πを超えたときは、2πだけ減ずる
       
cangle.angle -= 2 * np.pi
    #       大きいディスクの回転による横方向の収縮度
    scale = np.cos(cangle.angle)
    #       現在のディスクの削除
    canvas.delete('circle0')
    #       回転後の新しいディスクの生成
    canvas.create_oval(ctr_x -
scale * r0, ctr_y - r0,
                     
ctr_x + scale * r0, ctr_y + r0,
                     
fill = '#00ff00', tags = 'circle0')
    #
    #       小さいディスクの現在の位置
    #
    coords = canvas.coords('circle1')
    #   coords = [x1, y1, x2, y2], (x1,
y1)と(x2, y2)がディスクの外接長方形の角の座標を表す
    crrntx = (coords[0] +
coords[2]) / 2    #   小さいディスクの中心のX座標
    crrnty = (coords[1] +
coords[3]) / 2    #   小さいディスクの中心のY座標
    nxtx = ctr_x + r *
np.cos(cangle.angle)       
#   小さいディスクの中心の新しいX座標
    nxty = ctr_y + r *
np.sin(cangle.angle)       
#   小さいディスクの中心の新しいY座標
    #   小さいディスクを新しい位置に動かす
    canvas.move('circle1', nxtx
- crrntx, nxty - crrnty)
    
    root.after(100, re_draw,
cangle)        
#   100ミリ秒後に関数re_draw(cangle)を実行
re_draw(cangle)
root.mainloop()
中央の大きい緑の円盤は、オブジェクトの削除と再生成を行うコード
    #       現在のディスクの削除
    canvas.delete('circle0')
    #       回転後の新しいディスクの生成
    canvas.create_oval(ctr_x -
scale * r0, ctr_y - r0,
                     
ctr_x + scale * r0, ctr_y + r0,
                     
fill = '#00ff00', tags = 'circle0')
によって回転させている。
青の小さいディスクは、Canvas上のオブジェクトの移動を行うコード
    canvas.move('circle1', nxtx
- crrntx, nxty - crrnty)
の実行によって緑の円盤の周りを回転させている。
これらの運動は、次のコード
    root.after(100, re_draw, cangle)
によって関数re_draw(cangle)を100ミリ秒ごとに呼び出すことによって行っている。
フォームに3つのボタンClockwise、Counter、Stopを追加した(図2)。

図2
スクリプトをリスト2に示す。リスト1にボタン追加と少しの変更を加えたものである。スクリプトファイルは圧縮ファイルMovFigSCripts.zipにまとめた。
リスト2 回転を指定するボタンを加えたリスト
import tkinter as tk
import numpy as np
root = tk.Tk()
frame = tk.Frame(root)
button = tk.Button(frame, text =
'Close')
button.grid(row = 0, column = 10, padx
= 5, sticky = 'ew')
buttonC = tk.Button(frame, text =
'Clockwise')
buttonC.grid(row = 0, column = 0, padx
= 2, sticky = 'ew')
buttonA = tk.Button(frame, text =
'Counter')
buttonA.grid(row = 0, column = 1, padx
= 2, sticky = 'ew')
buttonS = tk.Button(frame, text =
'Stop')
buttonS.grid(row = 0, column = 2, padx
= 2, sticky = 'ew')
def f_close(event):
    """
        オブジェクトrootを削除する
        コールバック関数/イベント ハンドラー用関数
        
    """
    root.destroy()      #   rootの削除
   
print('Event...\n{}'.format(event))
    print('Good-bye.')
#
#       ボタンbuttonのマウス左ボタンクリックイベント'<Button-1>'を
#       関数f_closeに結合
#
button.bind('<Button-1>',
f_close)
global step        
#   回転角度の1回あたりの変化量
                   
#   +:時計回り、 −:反時計回り
step = 0.1
def f_clock(event):
    global step
    step = 0.1      #   時計回り
#   ボタンbuttonCのクリックイベントで関数f_clockを呼び出す
buttonC.bind('<Button-1>',
f_clock)
def f_counter(event):
    global step
    step = -0.1     #   反時計回り
#   ボタンbuttonAのクリックで関数f_counterを呼び出す
buttonA.bind('<Button-1>',
f_counter)
def f_stop(event):
    global step
    step = 0.0      #   変化量を0.0にして、回転を止める
#   ボタンbuttonSのクリックで関数f_stopを呼び出す
buttonS.bind('<Button-1>',
f_stop)
#       キャンバスの生成
canvas = tk.Canvas(frame, bg =
'#ffffff', width = 500, height = 500)
canvas.grid(row = 1, columnspan = 11,
rowspan = 1)
frame.pack()
ctr_x = 250     #   キャンバス中央のX座標
ctr_y = 250     #   キャンバス中央のY座標
r0 = 120       
#   中央の大きいディスクの半径
#   回転角を表すクラス型
class CAngle:
    def __init__(self):
       
self.angle = 0.0
cangle = CAngle()       #   クラス型CAngleのオブジェクト
#       中央の大きい緑のディスクの生成
canvas.create_oval(ctr_x - r0, ctr_y -
r0, ctr_x + r0, ctr_y + r0,
                  
fill = '#00ff00', tags = 'circle0')
r1 = 20        
#   小さいディスクの半径
r = r0 + r1     #   小さいディスクの中心とキャンバスの中心との距離
x0 = ctr_x + r *
np.cos(cangle.angle)  #   小さいディスクの中心のX座標
y0 = ctr_y + r *
np.sin(cangle.angle)  #   小さいディスクの中心のY座標
#       青色の小さいディスクの生成
canvas.create_oval(x0 - r1, y0 - r1,
x0 + r1, y0 + r1, fill = '#0000ff', tags = 'circle1')
#
#       ディスクの運動
#
def re_draw(cangle):
    global step
    #
    #       角度の更新
 
  #
    cangle.angle += step
    if cangle.angle > 2 *
np.pi:    #   角度が2πを超えないようにする
       
cangle.angle -= 2 * np.pi
    if cangle.angle < -2 *
np.pi:
       
cangle.angle += 2 * np.pi  
#   角度がー2πより小さくならないようにする
    #   回転角に応じた横方向の収縮度を求める
    scale = np.cos(cangle.angle)
    #   現在のディスクの削除
    canvas.delete('circle0')
    #   新しい角度に対応したディスクの生成
    canvas.create_oval(ctr_x -
scale * r0, ctr_y - r0,
                     
ctr_x + scale * r0, ctr_y + r0,
                     
fill = '#00ff00', tags = 'circle0')
    #
    #   小さいディスクの現在の位置
    #
    coords =
canvas.coords('circle1')
    #   coords = [x1, y1, x2, y2]: (x1, y1)と(x2, y2)がディスクの外接長方形の角の座標を表す
    crrntx = (coords[0] +
coords[2]) / 2    #   ディスクの中心のX座標
    crrnty = (coords[1] +
coords[3]) / 2    #   ディスクの中心のY座標
    nxtx = ctr_x + r *
np.cos(cangle.angle)       
#   回転後の新しい中心のX座標
    nxty = ctr_y + r *
np.sin(cangle.angle)       
#   回転後の新しい中心のY座標
    #   ディスクを新しい中心へ移動
    canvas.move('circle1', nxtx
- crrntx, nxty - crrnty)
    
    root.after(100, re_draw,
cangle)        
#   100ミリ秒後に関数re_draw(cangle)を実行
re_draw(cangle)
root.mainloop()
回転指定ボタンとそのクリックに対応する関数の設定は以下のように行われている。
ボタンの追加スクリプト
buttonC = tk.Button(frame, text =
'Clockwise')
buttonC.grid(row = 0, column = 0, padx
= 2, sticky = 'ew')
buttonA = tk.Button(frame, text =
'Counter')
buttonA.grid(row = 0, column = 1, padx
= 2, sticky = 'ew')
buttonS = tk.Button(frame, text =
'Stop')
buttonS.grid(row = 0, column = 2, padx
= 2, sticky = 'ew')
回転指定のための関数、およびボタンとの結合
global step        
#   回転角度の1回あたりの変化量
                   
#   +:時計回り、 −:反時計回り
step = 0.1
def f_clock(event):
    global step
    step = 0.1      #   時計回り
#   ボタンbuttonCのクリックイベントで関数f_clockを呼び出す
buttonC.bind('<Button-1>',
f_clock)
def f_counter(event):
    global step
    step = -0.1     #   反時計回り
#   ボタンbuttonAのクリックで関数f_counterを呼び出す
buttonA.bind('<Button-1>',
f_counter)
def f_stop(event):
    global step
    step = 0.0      #   変化量を0.0にして、回転を止める
#   ボタンbuttonSのクリックで関数f_stopを呼び出す
buttonS.bind('<Button-1>',
f_stop)
回転の変化量を表すグローバル変数stepが導入されていること以外は、リスト1のスクリプトと原則同じである。