After testing @moomoomoo309's ellipse code and finding problems (prints in wrong place, width and height don't match arguments, ignores turtle heading so can't print slanted ellipses, heading doesn't track drawing, doesn't leave pen in original state, etc.) I decide to try to write my own.
I chose to use turtle.circle()
as a model with respect to where the ellipse is drawn relative to the existing turtle position and heading, allowing the user to change the steps (i.e. make other irregular polygons), leave the pen state and position where it started, etc. This is what I came up with (I used self
instead of turtle
or pen
as I intended it to be installed as a method):
import turtle
import math
def ellipse(self, x_radius, y_radius, steps=60):
down = self.isdown() # record pen state for restoration later
if not down:
self.pendown()
heading_radians = math.radians(self.heading())
theta_radians = -math.pi / 2
extent_radians = 2 * math.pi
step_radians = extent_radians / steps
extent_radians += theta_radians
x_center, y_start = self.position()
y_center = y_start + y_radius
cos_heading, sin_heading = math.cos(heading_radians), math.sin(heading_radians)
while True:
x, y = x_center + math.cos(theta_radians) * x_radius, y_center + math.sin(theta_radians) * y_radius
# readjust x & y to set the angle of the ellipse based on the original heading of the turtle
x, y = x - x_center, y - y_start
x, y = x * cos_heading - y * sin_heading, x * sin_heading + y * cos_heading
x, y = x + x_center, y + y_start
self.setheading(self.towards(x, y)) # turtle faces direction in which ellipse is drawn
self.goto(x, y)
if theta_radians == extent_radians:
break
theta_radians = min(theta_radians + step_radians, extent_radians) # don't overshoot our starting point
self.setheading(self.towards(x_center, y_start)) # set correct heading for the next thing we draw
if not down: # restore pen state on return
self.penup()
(Optionally) add this method to our turtle per Adding a Method to an Existing Object Instance:
from functools import partial
yertle = turtle.Turtle()
yertle.ellipse = partial(ellipse, yertle)
Demonstration code to show all the new shapes we can draw with turtle.ellipse()
:
if __name__ == "__main__":
from functools import partial
yertle = turtle.Turtle()
yertle.ellipse = partial(ellipse, yertle)
import random
yertle.speed("fastest")
yertle.hideturtle()
yertle.penup()
screen = turtle.Screen()
for _ in range(75):
radius = random.randint(10, 50)
yertle.setheading(random.randint(0, 360))
yertle.setx(random.randint(-screen.window_width()/2 + radius * 2, screen.window_width()/2 - radius * 2))
yertle.sety(random.randint(-screen.window_height()/2 + radius + 2, screen.window_height()/2 - radius * 2))
yertle.color((random.random(), random.random(), random.random()), (random.random(), random.random(), random.random()))
flag = random.choice([True, False, False])
if flag:
yertle.begin_fill()
yertle.ellipse(radius, radius / 0.5 + random.random() * 3, steps=random.choice([3, 4, 5, 6, 7, 8, 60, 60, 60]))
if flag:
yertle.end_fill()
screen.exitonclick()
EXAMPLE OUTPUT

I tried to implement the extent
a la turtle.circle()
but wasn't able to get it to work for arbitrary extents properly (i.e. in such a way that you could invoke turtle.ellipse()
twice with the same extent and have it continue the curve where it left off) so I've left that for another day.
Bringing my answer back to the OP's original problem, we can now do:
import turtle
import math
def ellipse(self, x_radius, y_radius, steps=60):
# ...
def draw_O():
# Draw an O
turtle.penup()
turtle.forward(letter_height/4)
turtle.pendown()
ellipse(turtle, letter_width, letter_height)
turtle.penup()
turtle.forward(space_width + letter_height/4)
turtle.pendown()
letter_width = 10
letter_height = 170
space_width = 5
turtle.onkey(draw_O, "o")
turtle.listen()
turtle.done()
To generate the skinny ellipse-based letter O that the OP desired:
