How to draw an ellipse in Python turtle graphics other than stamping?

不问归期 提交于 2020-01-03 03:54:05

问题


I am trying to draw a letter "O" with Python turtle graphics. To cue the drawing of the "O", the function for it is invoked with a key press. Here is what I have so far:

def draw_O():
# Draw an O

penup()
forward(letter_height/4)
pendown()
forward(letter_width/2)
circle(letter_height/4, 90)
forward(letter_height/2)
circle(letter_height/4, 90)
forward(letter_width/2)
circle(letter_height/4, 90)
forward(letter_height/2)
circle(letter_height/4, 90)
forward(letter_width/2)
penup()
forward(space_width + letter_height/4)
pendown()

onkey(draw_O, "o")

The letter_height & letter_width variables can be changed by the user from any value between 10-170 using a dialog box cued by another key press. Right now, the "O" comes out as shown below if letter_height = 170 & letter_width = 10:

However, if you compare this to the "H" (another letter that can be drawn by my program), you can easily see that they are not in proportion whatsoever:

What I want to do is draw an ellipse for the "O" where its vertical radius is equal to letter_height & its horizontal radius is equal to letter_width such that the "O" will get shorter as letter_width increases, and taller as letter_height increases. The problem is, I don't really know how to do that! I heard that you can stamp one, but I really do not want to use the stamp method since its animation does not look as appealing. Also, when I try to map my letter_height and letter_width values to it, it covers the entire screen for some reason!

In conclusion, I would like to know how to draw an ellipse in turtle graphics that can be manipulated like a circle (change radii lengths of the ellipse, change the ellipse's extent, etc). I don't want to use the turtle.stamp() method, so is there any way to draw an ellipse other than stamping one onto the canvas? Any help is much appreciated!


回答1:


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:




回答2:


I'm pretty sure this will work, the 180 in width/180 and height/180 might be off though.

from math import sin,cos,pi
def ellipse(pen, x, y, width, height):
    pen.penup()
    pen.goto(x + width / 2, height)
    pen.pendown()
    penX, penY = pen.pos()
    for i in range(0, 360):
        penX += cos(i*pi/180)*width/180
        penY += sin(i*pi/180)*height/180
        pen.goto(penX, penY)
    pen.penup()


来源:https://stackoverflow.com/questions/34284958/how-to-draw-an-ellipse-in-python-turtle-graphics-other-than-stamping

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!