Turtle - make it possible to tilt image shape

I found Turtle module, to be an awesome way to introduce my kid to programming. One thing I’m missing there is ability to tilt turtle, when image is used as a turtle shape. I think that adding an option to apply rotation to the image would open quite a few opportunities - especially when it comes to some more interactive demos.

I looked into this a bit - looks like the problem is that tk library doesn’t have any way to rotate images.

I managed to assemble little PoC/experiment (see below) that allows to register custom shape with some hook methods. That allowed me to achieve this rotation effect, but I’m pretty sure it exposes way too much. Do you think it may make any sense, if I try to make it a bit more restricted and just allow user to do some operations on image based on position/orientation/something_else and replace current one?

Shape class seems to already expose the fact, that TK.PhotoImage is used under the hood, as currently client can instantiate it and directly pass instance of TK.PhotoImage as data argument - I guess this rotating image problem can be handled without exposing more.

Here is my current experiment code: Comparing python:main...kbialowas:tilt-turtle · python/cpython · GitHub

Here is the effect and drawing script that produces it:

import turtle
import math
from PIL import Image, ImageTk

class MyShapeDrawer(turtle.BaseShapeDrawer):
    def __init__(self, screen):
        super().__init__(screen)
        self.originalImg = Image.open("./img.gif")
        self.img = self.originalImg.rotate(0)
        self.tkImg = ImageTk.PhotoImage(self.img)
        self.tkImgOrientation = (0,0)
        self.item = screen._createimage(self.tkImg)

    def draw(self, position, orientation, pen_color, fill_color, transform, pen_size):
        if (self.tkImgOrientation != orientation):
            x,y = orientation
            angle = math.degrees(math.atan2(y,x))
            self.img = self.originalImg.rotate(angle, expand=True)
            self.tkImg = ImageTk.PhotoImage(self.img)
            self.tkImgOrientation = orientation
        self.screen._drawimage(self.item, position, self.tkImg)

    def delete(self):
        self.screen._delete(self.item)

sc = turtle.Screen()
shape = turtle.Shape("shape_drawer", MyShapeDrawer)
sc.addshape("my_shape", shape)

t = turtle.Turtle()
t.shape("my_shape")

t.left(405)
t.stamp()
t.forward(100)
sid = t.stamp()
t.forward(100)
t.left(45)
t.stamp()
t.forward(50)
t.left(45)
t.clearstamp(sid)
turtle.mainloop()

2 Likes

I managed to work it out without any external dependencies - class with transformation seems to be like extra ~100 loc. Here is the code: https://github.com/python/cpython/compare/main…kbialowas:cpython:tilt-turtle-transformation

Entire setup looks like this, no need to create extra delegate classes etc.:

import turtle
import tkinter as tk
...
shape = turtle.Shape("transformable_image", tk.PhotoImage(file="./img.gif"))
sc.addshape("my_shape", shape)

t = turtle.Turtle()
t.shape("my_shape")
...