【Python】turtle.shapetransform徹底解説!拡大・傾斜・回転の基本と応用
turtle.shapetransform()
は、Python の turtle
モジュールにおける、カメ(タートルグラフィックスで描画を行う「ペン」の役割を果たすオブジェクト)の形状を変形させるためのメソッドです。
通常の turtle.shape()
メソッドは、カメの形状を "arrow"、"turtle"、"circle"、"square"、"triangle"、"classic" など、あらかじめ定義された形に変更します。しかし、shapetransform()
を使うと、これらの基本的な形状をさらに拡大・縮小、傾斜(シア)、回転させることができます。
shapetransform()
の使い方
shapetransform()
メソッドは、以下の4つの引数を取ります。
turtle.shapetransform(t11=None, t12=None, t21=None, t22=None)
これらの引数は、2D変換行列の要素に対応しています。具体的には、カメの形状の各点を (x, y)
として、新しい座標 (x', y')
を以下の式で計算します。
(x′y′​)=(t11t21​t12t22​)(xy​)
t22
: y軸方向の拡大・縮小と、x軸方向の傾斜に影響します。t21
: x軸方向の傾斜と、y軸方向の拡大・縮小に影響します。t12
: y軸方向の拡大・縮小と、x軸方向の傾斜に影響します。t11
: x軸方向の拡大・縮小と、y軸方向の傾斜に影響します。
重要な注意点
- 行列式 t11​×t22​−t12​×t21はゼロであってはなりません。もしゼロになるとエラーが発生します。これは、形状が一点に潰れてしまうような変換を防ぐためです。
- これらの引数(t11​,t12​,t21​,t22​)はすべてオプションです。指定しない場合は、その要素は現在の値が維持されます。
具体的な例
-
回転
回転は少し複雑ですが、三角関数を使って表現できます。例えば、angle
だけ回転させる場合、以下のようになります。turtle.shapetransform(math.cos(angle), -math.sin(angle), math.sin(angle), math.cos(angle))
-
傾斜(シア)
turtle.shapetransform(1, 0.5, 0, 1)
: x方向に傾斜させます。turtle.shapetransform(1, 0, 0.5, 1)
: y方向に傾斜させます。
-
turtle.shapetransform(2, 0, 0, 2)
: カメの形状を2倍に拡大します。turtle.shapetransform(0.5, 0, 0, 0.5)
: カメの形状を半分の大きさに縮小します。turtle.shapetransform(2, 0, 0, 1)
: カメの形状をx方向に2倍、y方向はそのままにします。
shapetransform()
は、より高度なグラフィックス表現を可能にします。例えば、以下のような場合に役立ちます。
- 物理シミュレーション
オブジェクトの回転や伸縮を表現したい場合。 - アニメーション効果
カメの形状を動的に変形させることで、より魅力的なアニメーションを作成したい場合。 - カスタム形状の作成
基本的な形状を基に、より複雑なカスタム形状を作成したい場合。
termcap: missing xterm-256color エラー
これは shapetransform()
に直接関連するエラーではありませんが、turtle
モジュールをターミナル(特にSSH経由など)で実行しようとしたときによく発生する問題です。
原因
turtle
モジュールはグラフィカルな表示を必要としますが、ターミナル環境がグラフィカルな出力を適切にサポートしていない場合に発生します。xterm-256color
の設定が見つからないというメッセージは、色のサポートに関連していますが、根本的にはGUIの表示環境が整っていないことを示唆しています。
トラブルシューティング
- Jupyter Notebook/Lab
- Jupyter NotebookやJupyterLabで実行すると、通常はグラフィカルな出力が可能です。
- SSH接続の場合
- SSHで接続している場合は、X転送(X11 forwarding)を有効にして接続する必要があります。
これにより、サーバー側のGUIアプリケーションの表示をローカルのXサーバーに転送できます。ただし、ローカル環境にもXサーバーがインストールされている必要があります(WindowsではWSL2 + VcXsrv、macOSではXQuartzなど)。ssh -X user@your_server_ip
- SSHで接続している場合は、X転送(X11 forwarding)を有効にして接続する必要があります。
- GUI環境で実行する
- Pythonスクリプトを、GUI(Graphical User Interface)環境が利用可能なデスクトップ(Windows、macOS、Linuxのデスクトップ環境など)で直接実行してください。
- VS Codeなどの統合開発環境(IDE)を使用している場合は、その環境がグラフィカル出力をサポートしていることを確認してください。
_tkinter.TclError: matrix is singular (または ValueError: determinant of matrix is zero)
これは shapetransform()
で最もよく遭遇するエラーの一つです。
原因
引数として渡された変換行列の行列式がゼロになる場合に発生します。行列式がゼロの変換行列は、形状を一点または直線に潰してしまうような変換を表します。このような変換は可逆的ではないため、turtle
はこれを許可しません。
数学的に言うと、t11​×t22​−t12​×t21​=0 となる場合にこのエラーが発生します。
例
turtle.shapetransform(0, 1, 0, 2)
: 0×2−1×0=0turtle.shapetransform(1, 0, 2, 0)
: 1×0−0×2=0turtle.shapetransform(0, 0, 0, 0)
: 全てゼロ
トラブルシューティング
- 意図しないゼロ値
- 計算結果が意図せずゼロになっていないか、浮動小数点数の丸め誤差などに注意してください。ただし、Pythonの浮動小数点数は通常、正確なゼロとは異なります。
- 拡大・縮小でゼロを指定しない
- 特に拡大・縮小を意図している場合(例:
t12
とt21
がゼロ)、t11
またはt22
のいずれかがゼロになると、その軸方向のスケールがゼロになり、形状が潰れてしまいます。例えば、turtle.shapetransform(1, 0, 0, 0)
はエラーになります。
- 特に拡大・縮小を意図している場合(例:
- 行列式の確認
t11
,t12
,t21
,t22
の値を確認し、t11​×t22​−t12​×t21がゼロにならないように調整します。
形状が消える、または非常に小さくなる
エラーは発生しないものの、カメの形状が見えなくなることがあります。
原因
- 画面外への移動
shapetransform()
自体はカメの位置を変更しませんが、turtle.setx()
,turtle.sety()
,turtle.goto()
などでカメを画面外に移動させている可能性があります。 - 極端な縮小
t11
やt22
に非常に小さい値(ゼロではないが、ゼロに近い値)を指定した場合、形状が肉眼では見えないほど小さくなることがあります。
トラブルシューティング
- カメの位置の確認
turtle.pos()
またはturtle.position()
でカメの現在位置を確認し、画面内にいるか確認します。turtle.screensize()
で画面のサイズを確認し、形状がその範囲外に出ていないか確認します。turtle.tracer(0)
でアニメーションをオフにしている場合は、turtle.update()
を呼び出して画面を更新しているか確認します。
- t11, t22 の値の確認
- 縮小率が適切か確認してください。小さすぎないようにします。
形状の歪みが意図しないものになる
特定の変換を期待しているのに、形状が予想と異なる歪み方をする。
原因
- 連続した変換
複数のshapetransform()
を連続して適用する場合、前の変換の結果に次の変換が適用されるため、複合的な影響を理解する必要があります。 - 回転と傾斜の混同
例えば、回転と傾斜(シア)は異なる種類の変換であり、それぞれの行列要素が異なる影響を与えます。 - 変換行列の理解不足
t11
,t12
,t21
,t22
がそれぞれ何に影響するかを正確に理解していない場合に発生します。
トラブルシューティング
- 視覚的なデバッグ
- 小さな値から始めて、各引数を少しずつ変更しながら、形状の変化を観察してください。
turtle.write()
を使って、各引数の値を画面に表示しながらデバッグするのも有効です。
- 単純な変換から試す
- まずは拡大・縮小(
t12=0
,t21=0
の場合)から試してみて、次に傾斜、回転と、徐々に複雑な変換に挑戦します。
- まずは拡大・縮小(
- 2D変換行列の学習
- 線形代数の基本的な2D変換行列(拡大縮小、回転、傾斜など)について学ぶと、各要素の意味が明確になります。
原因
- カスタム形状に適用できない
screen.addshape()
で追加したカスタム形状(GIFなど)にはshapetransform()
は適用できません。 - カメの形状がデフォルトではない
turtle.shape("classic")
やturtle.shape("arrow")
などの標準的な形状に設定されていない場合、shapetransform()
は適用されません。shapetransform()
は、内部的に定義されたポリゴン形状("turtle", "arrow" など)に対して機能します。
- 標準形状の利用
turtle.shape("turtle")
やturtle.shape("arrow")
など、turtle
モジュールが提供する標準の形状を使用していることを確認してください。turtle.shape()
を呼び出す前にshapetransform()
を呼び出している場合、turtle.shape()
がその変換を上書きしてしまう可能性があります。turtle.shape()
を呼び出した後にshapetransform()
を呼び出すようにしてください。
準備
まず、turtle
モジュールを使用するための基本的な準備です。
import turtle
import math # 回転のためにmathモジュールを使います
# 画面とカメのセットアップ
screen = turtle.Screen()
screen.setup(width=600, height=600)
screen.title("Turtle shapetransform Examples")
t = turtle.Turtle()
t.shape("turtle") # カメの形状を「turtle」に設定
t.color("blue")
t.penup() # 線を描かないようにペンを上げる
t.speed(0) # アニメーションを最速にする
# アニメーションをオフにして、最後に一気に描画することでスムーズにする(オプション)
# screen.tracer(0)
拡大・縮小 (Scaling)
最も単純な変換で、カメの形状を大きくしたり小さくしたりします。
t11
と t22
はそれぞれX軸方向とY軸方向の拡大率に相当します。t12
と t21
はゼロに設定します。
# 元のカメの形状(基準)
t.goto(-200, 100)
t.stamp() # 現在の形状をスタンプで残す(元の大きさ)
# 2倍に拡大
t.goto(-100, 100)
t.shapetransform(2, 0, 0, 2) # X, Y方向ともに2倍に拡大
t.stamp()
# X方向のみ2倍、Y方向はそのまま
t.goto(0, 100)
t.shapetransform(2, 0, 0, 1)
t.stamp()
# Y方向のみ2倍、X方向はそのまま
t.goto(100, 100)
t.shapetransform(1, 0, 0, 2)
t.stamp()
# 半分に縮小
t.goto(200, 100)
t.shapetransform(0.5, 0, 0, 0.5)
t.stamp()
# 元の形状に戻す(単位行列)
t.shapetransform(1, 0, 0, 1)
傾斜 (Shearing / Skewing)
カメの形状を傾かせます。
t12
はX軸方向にY座標が影響する傾斜、t21
はY軸方向にX座標が影響する傾斜です。
# 基準のカメ
t.goto(-200, -100)
t.shapetransform(1, 0, 0, 1) # リセット
t.stamp()
# X軸方向に傾斜 (t12 を変更)
# 正の値で右上に傾く
t.goto(-100, -100)
t.shapetransform(1, 0.5, 0, 1) # t12を0.5に設定
t.stamp()
# Y軸方向に傾斜 (t21 を変更)
# 正の値で右下に傾く
t.goto(0, -100)
t.shapetransform(1, 0, 0.5, 1) # t21を0.5に設定
t.stamp()
# 両方向に傾斜
t.goto(100, -100)
t.shapetransform(1, 0.3, 0.3, 1)
t.stamp()
# 元の形状に戻す
t.shapetransform(1, 0, 0, 1)
回転 (Rotation)
カメの形状を回転させます。回転は三角関数(sin
と cos
)を使って変換行列を構成します。
(cosθsinθ​−sinθcosθ​)
$\theta$
はラジアン単位の角度です。
# 基準のカメ
t.goto(-200, -200)
t.shapetransform(1, 0, 0, 1) # リセット
t.stamp()
# 45度回転
t.goto(-100, -200)
angle_deg = 45
angle_rad = math.radians(angle_deg) # 度をラジアンに変換
t.shapetransform(math.cos(angle_rad), -math.sin(angle_rad),
math.sin(angle_rad), math.cos(angle_rad))
t.stamp()
# 90度回転
t.goto(0, -200)
angle_deg = 90
angle_rad = math.radians(angle_deg)
t.shapetransform(math.cos(angle_rad), -math.sin(angle_rad),
math.sin(angle_rad), math.cos(angle_rad))
t.stamp()
# -30度回転
t.goto(100, -200)
angle_deg = -30
angle_rad = math.radians(angle_deg)
t.shapetransform(math.cos(angle_rad), -math.sin(angle_rad),
math.sin(angle_rad), math.cos(angle_rad))
t.stamp()
# 元の形状に戻す
t.shapetransform(1, 0, 0, 1)
複合変換 (Combined Transformations)
拡大縮小、傾斜、回転を組み合わせることも可能です。行列の各要素に影響を組み合わせた値を渡します。
# 拡大と回転の組み合わせ(例:斜めに伸びるカメ)
t.goto(-100, 0)
# X方向2倍、Y方向1.5倍に拡大しつつ、30度回転
angle_deg = 30
angle_rad = math.radians(angle_deg)
scale_x = 2
scale_y = 1.5
# 拡大行列 S = [[scale_x, 0], [0, scale_y]]
# 回転行列 R = [[cos(a), -sin(a)], [sin(a), cos(a)]]
# 複合変換行列 M = R * S
# M = [[scale_x*cos(a), -scale_y*sin(a)], [scale_x*sin(a), scale_y*cos(a)]]
t11 = scale_x * math.cos(angle_rad)
t12 = -scale_y * math.sin(angle_rad)
t21 = scale_x * math.sin(angle_rad)
t22 = scale_y * math.cos(angle_rad)
t.shapetransform(t11, t12, t21, t22)
t.stamp()
# 元の形状に戻す
t.shapetransform(1, 0, 0, 1)
アニメーションでの利用
shapetransform()
をループ内で使用することで、カメの形状を動的に変化させるアニメーションを作成できます。
t.clearstamps() # これまでのスタンプをクリア
t.goto(0, 0)
t.color("red")
# screen.tracer(0) を有効にすると、アニメーションがスムーズになります
# ループの最後で screen.update() を呼び出すのを忘れないでください。
for i in range(36):
angle_deg = i * 10 # 0度から350度まで10度ずつ
angle_rad = math.radians(angle_deg)
# 回転と、わずかな拡大縮小を組み合わせる
scale = 1 + math.sin(angle_rad * 2) * 0.2 # 脈打つような拡大縮小
t11 = scale * math.cos(angle_rad)
t12 = -scale * math.sin(angle_rad)
t21 = scale * math.sin(angle_rad)
t22 = scale * math.cos(angle_rad)
t.shapetransform(t11, t12, t21, t22)
t.stamp()
# screen.update() # tracer(0) の場合、各フレームで更新
# アニメーションをオフにした場合は最後に更新
# screen.update()
終了処理
最後に、画面がすぐに閉じないようにします。
t.hideturtle() # カメを非表示にする
screen.exitonclick() # クリックで画面を閉じる
- 位置は変わらない
shapetransform()
はカメの形状を変形させるだけで、カメの画面上の位置は変更しません。位置を変えるには、t.goto()
,t.forward()
,t.setheading()
などを使用します。 - デフォルト形状への適用
shapetransform()
は、turtle.shape()
で設定された組み込みの形状("turtle", "arrow" など)に適用されます。screen.addshape()
で追加したカスタムの画像ファイル(GIFなど)には適用されません。 - 行列式の非ゼロ
前述の通り、t11​×t22​−t12​×t21がゼロになるとエラーになります。これは、形状が一点や直線に潰れてしまうのを防ぐためです。 - 変換行列の理解
shapetransform()
の引数は2D変換行列の要素です。これらの引数が形状にどのように影響するかを理解するためには、線形代数の基本的な知識があると非常に役立ちます。
turtle.shapetransform()
はカメの既存の形状を変形させるための強力なメソッドですが、Pythonの turtle
モジュールには、カメの形状をカスタマイズしたり、描画オブジェクトを制御したりするための他の方法もいくつか存在します。これらの方法は、目的によっては shapetransform()
の代替として機能します。
turtle.shape() と標準形状の利用
最も基本的な代替方法であり、多くの場合これで十分です。
説明
turtle.shape(name)
メソッドは、カメの形状をあらかじめ定義されたいくつかの形状("arrow"
, "turtle"
, "circle"
, "square"
, "triangle"
, "classic"
)の中から選択します。shapetransform()
のように複雑な変形はできませんが、簡単な形状変更には最適です。
利点
turtle
モジュールに組み込まれているため、追加のコードやファイルは不要。- 非常に簡単で直感的。
欠点
- 拡大縮小、傾斜、回転などの複雑な変形はできない。
- 形状の選択肢が限られている。
コード例
import turtle
screen = turtle.Screen()
t = turtle.Turtle()
t.shape("arrow") # 矢印の形状
t.forward(50)
t.shape("turtle") # カメの形状
t.right(90)
t.forward(50)
t.shape("circle") # 円の形状
t.left(90)
t.forward(50)
screen.exitonclick()
screen.addshape() とカスタム形状の利用
独自の画像ファイル(GIF形式)や、複数の頂点で定義されたポリゴン形状をカメの形状として登録できます。
GIF画像の使用
説明
外部のGIF画像をカメの形状として使用する方法です。shapetransform()
とは異なり、ピクセルデータに基づいて形状を表示するため、拡大縮小や回転は可能ですが、傾斜は画像自体を変形させることにはなりません。また、shapetransform()
のような数学的な変換は適用できません。
利点
- 写真や複雑なグラフィックをカメにできる。
- 完全に自由なデザインの形状を使用できる。
欠点
- 画像の回転は可能だが、ピクセル単位での変形は別途画像処理が必要。
shapetransform()
のような行列変換は適用できない。- GIFファイルを用意する必要がある。
コード例
import turtle
screen = turtle.Screen()
screen.setup(width=400, height=400)
# 準備: カレントディレクトリに "my_image.gif" というGIF画像を置く
# (例として、小さなシンプルなアイコン画像などを準備してください)
try:
screen.addshape("my_image.gif")
t = turtle.Turtle()
t.shape("my_image.gif") # カスタム形状を適用
t.penup()
t.goto(-100, 0)
t.stamp()
t.setheading(90) # 向きを変える(画像は回転する)
t.goto(0, 0)
t.stamp()
t.shapesize(2, 2, 2) # サイズ変更(画像が拡大縮小される)
t.goto(100, 0)
t.stamp()
except turtle.TurtleGraphicsError:
print("my_image.gif が見つからないか、有効なGIF画像ではありません。")
print("スクリプトと同じディレクトリにGIF画像を置いてください。")
screen.exitonclick()
ポリゴン形状の定義
説明
カメの形状を、点の座標のリスト(ポリゴン)で定義する方法です。これにより、shapetransform()
を使うよりも直接的に、任意の多角形の形状を作成できます。
利点
- 複雑な図形を作成できる。
- GIF画像を用意する必要がない。
- プログラム内で形状を完全に制御できる。
欠点
shapetransform()
のような行列演算による変形ではないため、同じような柔軟性はない。- 頂点座標を自分で計算する必要があるため、少し手間がかかる場合がある。
コード例
import turtle
screen = turtle.Screen()
screen.setup(width=400, height=400)
# ハートの形状を定義 (点の座標リスト)
heart_shape = ((0, 0), (10, 20), (20, 0), (10, -20)) # 単純な例
screen.addshape("my_heart", heart_shape)
t = turtle.Turtle()
t.shape("my_heart") # 定義したポリゴン形状を適用
t.color("red")
t.penup()
t.goto(-50, 0)
t.stamp()
# 拡大縮小は shapesize() で可能
t.shapesize(2, 2)
t.goto(50, 0)
t.stamp()
screen.exitonclick()
描画コマンドを直接使う
説明
カメの形状を変形させるのではなく、turtle
の描画コマンド(forward()
, right()
, left()
, circle()
, begin_fill()
, end_fill()
など)を組み合わせて、目的の形状をその場で描画する方法です。これにより、非常に柔軟なグラフィックを作成できます。
利点
- 完全な自由度がある。
- 描画プロセス自体をアニメーションとして見せることができる。
shapetransform()
が適用できないような複雑な図形(曲線を含むものなど)も描画できる。
欠点
- 描画オブジェクトを「選択して移動」するような直感的な操作ではない。
- 描画が複雑になると、コード量が増える。
- 描画した形状はカメ自体ではないため、カメの動きに合わせて描画し直す必要がある(つまり、形状が「移動する」のではなく、「再描画される」)。
import turtle
screen = turtle.Screen()
screen.setup(width=400, height=400)
t = turtle.Turtle()
t.speed(0)
t.penup()
def draw_custom_star(turtle_obj, size, x, y):
turtle_obj.goto(x, y)
turtle_obj.pendown()
turtle_obj.begin_fill()
for _ in range(5):
turtle_obj.forward(size)
turtle_obj.right(144)
turtle_obj.end_fill()
turtle_obj.penup()
# 異なるサイズの星を描画
draw_custom_star(t, 50, -100, 0)
draw_custom_star(t, 80, 50, 0)
screen.exitonclick()
- 形状をゼロから描画し、高度なアニメーションや複雑なグラフィック表現を行いたい場合
直接描画コマンドを使用する。 - 既存の標準形状を数学的に拡大・縮小、傾斜、回転させたい場合
turtle.shapetransform()
(この記事で説明されている主たる目的) - 独自の複雑な形状を一度だけ表示したい、またはGIF画像を使いたい場合
screen.addshape()
(GIFまたはポリゴン) - 簡単な形状変更で十分な場合
turtle.shape()