Is it possible to change turtle's pen stroke? - python

I need to draw a bar graph using Python's turtle graphics and I figured it would be easier to simply make the pen a thick square so I could draw the bars like that and not have to worry about making dozens of rectangles and filling them in.
When I set the turtle shape using turtle.shape('square') though, it only changes the appearance of the pen but has no effect on the actual drawing:
Is there a way to make turtle actually draw a rectangular stroke, whether that be through built-in methods or through modifying the turtle file?
I DON'T want rounded edges, like this:

To answer the question asked in the title: No, it is not possible to change the pen stroke directly (see cdlane's answer for a possible way to do it by modifying the hardcoded values from tkinter).
I did find a workaround for the use case presented in the question body, however.
A custom pen shape (in this case, representing the exact shape and size of the bar) can be registered like this:
screen.register_shape("bar", ((width / 2, 0), (-width / 2, 0), (-width / 2, height), (width / 2, height)))`
We can then simply loop through each bar, update the pen shape with the new values, and use turtle.stamp to stamp the completed bars onto the graph, no drawing required.

It looks like changing the shape of the pen stroke itself isn't possible. turtle.shape('square') only changes the shape of the turtle, not the pen stroke. I suggest lowering the pen size, and creating a function to draw a rectangle. You could use this do draw the bars.

I've two solutions to this problem that I've used in various programs.
The first is a variation on your stamp solution. Rather than use screen.register_shape() to register a custom polygon for each line, use a square turtle and for each line turtle.turtlesize() it into the rectangle you want to stamp:
from turtle import Turtle, Screen
STAMP_SIZE = 20 # size of the square turtle shape
WIDTH, LENGTH = 25, 125
yertle = Turtle(shape="square")
yertle.penup()
yertle.turtlesize(WIDTH / STAMP_SIZE, LENGTH / STAMP_SIZE)
yertle.goto(100 + LENGTH//2, 100) # stamps are centered, so adjust X
yertle.stamp()
screen = Screen()
screen.exitonclick()
My other solution, when I need to draw instead of stamp, is to reach into turtle's tkinter underpinning and modify turtle's hardcoded line end shape itself:
from turtle import Turtle, Screen
import tkinter as _
_.ROUND = _.BUTT
WIDTH, LENGTH = 25, 125
yertle = Turtle()
yertle.width(WIDTH)
yertle.penup()
yertle.goto(100, 100)
yertle.pendown()
yertle.forward(LENGTH)
screen = Screen()
screen.exitonclick()

Use multiple stamps like so:
import turtle
turtle.shape("square")
for count in range(x):
turtle.stamp()
turtle.forward(1)

Related

Turtle drawing a hexagon and hexagon grid

current code
#import the turtle modules
import turtle
#Start a work Screen
ws=turtle.Screen()
#Define a Turtle Instance
geekyTurtle=turtle.Turtle()
#executing loop 6 times for 6 sides
for i in range(6):
#Move forward by 90 units
geekyTurtle.forward(90)
#Turn left the turtle by 300 degrees
geekyTurtle.left(300)
My goal is to make a hexagon grid pattern and I am failing to do it properly. My first issue is if you run the code you get a hexagon but the top is flat, I can't get it to get the pointy corners to get on top. Second I tried to make the grid and it failed and I am not sure why I am unable to copy the same hexagon and clone it next to the other. I will or should have a file of the image that I am going for below.
The output I am getting:
The output I am trying to get:
Before going into loop, turn 30 degrees.
geekyTurtle.right(30)
In order to have its clone beside, just put the turtle to the new place and draw the shape again:
for i in range(6):
geekyTurtle.forward(90)
geekyTurtle.left(300)
geekyTurtle.up()
geekyTurtle.goto(90 * 3 ** .5, 0)
geekyTurtle.down()
for i in range(6):
geekyTurtle.forward(90)
geekyTurtle.left(300)
Put it in a loop to have it for more than two times
You can use the idea of .up() and .goto(x, y) and .down() to draw grids.
It seems like this is a problem that recursion could simplify in a fractal-like way. Each side of the initial hexagon is itself a hexagon, and so forth, filling the available space:
from turtle import Screen, Turtle
SIDE = 75 # pixels
def hexagon(side, depth):
if depth > 0:
for _ in range(6):
turtle.forward(side)
turtle.right(60)
hexagon(side, depth - 1)
turtle.left(120)
screen = Screen()
screen.tracer(False) # because I have no patience
turtle = Turtle()
turtle.penup()
turtle.width(2)
turtle.sety(-SIDE) # center hexagons on window
turtle.pendown()
turtle.left(30) # optional, orient hexagons
hexagon(SIDE, depth=6) # depth depends on coverage area
turtle.hideturtle()
screen.tracer(True)
screen.exitonclick()

Why new Turtle shapes have odd orientation (not the one I defined)?

I want to create yet another clone of Pong with Python and Turtle. My goal is to let my pupils (that begin to code in Python) to practise a bit further.
I'd like to create a Turtle whose shape is an horizontal filled rectangle, like a stylized paddle. But when I create a shape I suppose to be convenient, I get a rotated (vertical) paddle instead of the horizontal one I hoped for.
Here is a code that demonstrates this odd behaviour.
from turtle import *
begin_poly()
fd(200)
left(90)
fd(40)
left(90)
fd(200)
left(90)
fd(40)
left(90)
end_poly()
shape = get_poly()
register_shape("drawn", shape)
polyNotOk = ( (0,0), (100, 0), (100, 20), (0, 20) )
register_shape("polyNotOk", polyNotOk)
polyOk = ( (0,0), (0, 100), (20, 100), (20, 0) )
register_shape("polyOk", polyOk)
t1 = Turtle(shape="drawn")
t2 = Turtle(shape="polyNotOk")
t3 = Turtle(shape="polyOk")
t1.color("black")
t2.color("red")
t3.color("blue")
t1.stamp()
t2.stamp()
t3.stamp()
t1.goto(100,200)
t2.goto(100,-50)
t3.goto(100,-150)
t1.forward(100)
t2.forward(100)
t3.forward(100)
mainloop()
So, as you can see if you run the code, the first drawing is OK, with an horizontal shape. But when I stamp the Turtle t1, the shape is vertical.
Same problem with the 2nd shape, defined through polyNotOk (with values for x and y coords which allow to get a horizontal paddle). I need to create a "vertical" poly to get a horizontal paddle.
So I'm able to find a workaround. Yet I'm still not satisfied with this solution, so I'm asking for brilliant explanations ;-) Thanks in advance.
I hope to illuminate this odd behavior, not defend it. First thing to remember about drawn cursors is that where ever (0, 0) falls in your cursor drawing, that's the center point about which your cursor rotates and the pixel in your cursor that lands on any point you goto().
Some insight might be found in the shapesize() method documentation:
shapesize(stretch_wid=None, stretch_len=None, outline=None)
stretch_wid is stretchfactor perpendicular to orientation
stretch_len is stretchfactor in direction of turtles orientation.
That is, if the cursor is in the default (East) orientation, this reverses the sense of X and Y. I believe that's what you're seeing in your drawing. The X plane is perpendicular to orientation (vertical) and the Y plane is in the direction of orientation (horizontal). The opposite of what we normally expect.
This doesn't appear to be the fault of the Shape() class, but buried in the cursor logic. It may be a historical artifact--if we change to mode("logo") and run your code, we get:
More what we might expect, given that "logo" mode's default orientation is North, and more consistent than before.
Regardless, I would make my paddles a different way. Instead of a custom cursor, I'd use turtle's square cursor and reshape it as needed using shapesize():
from turtle import Screen, Turtle
CURSOR_SIZE = 20
screen = Screen()
t0 = Turtle("square")
t0.shapesize(20 / CURSOR_SIZE, 100 / CURSOR_SIZE)
t0.color("green")
screen.exitonclick()
Still rotated logic (not graphic) from what you might expect, but at least the documentation told us that. But, what I really tend to do is make the paddle in the wrong orientation, and use settiltangle(), but not as a workaround as you did, but to make my paddle face in one direction, but move in the other:
from turtle import Screen, Turtle
CURSOR_SIZE = 20
screen = Screen()
t0 = Turtle("triangle")
t0.shapesize(100 / CURSOR_SIZE, 20 / CURSOR_SIZE)
t0.settiltangle(90)
t0.penup()
t0.color("green")
t0.speed("slowest") # for demonstration purposes
t0.forward(300)
t0.backward(600)
t0.forward(300)
screen.exitonclick()
Notice that I can use forward(10) and backward(10) to move my paddle and not have to do awful things like t0.setx(t0.xcor() + 10). Works great for Space Invader type games where the player faces upwards but moves sideways.

How to put the figure inside a circle using turtle?

I got a simple figure using turtle. But the problem is I dunno how to put that figure inside circle.
Code:
import turtle
painter = turtle.Turtle()
painter.pencolor("blue")
for i in range(50):
painter.forward(100)
painter.left(123*2)
painter.circle(70)
turtle.done()
A bit of trigonometry in my head and I figured the angle. Not sure if I got the radius correct though. Ideally figure out the coordinates of the center instead, but a quick and dirty solution is:
import turtle
painter = turtle.Turtle()
painter.pencolor("blue")
for i in range(50):
painter.forward(100)
painter.left(123*2)
painter.right(123)
painter.right(90)
painter.penup()
painter.forward(10)
painter.left(90)
painter.pendown()
painter.circle(70)
turtle.done()
You will need to move the turtle to the correct starting position. NOTE that's not the circle's center! It starts drawing the circle from its rightmost position - i.e., if you want a circle with radius 70 around (0,0), then move to (70,0), e.g.:
painter.penup()
painter.goto(70,0)
painter.pendown()
painter.circle(70)
FYI: I can't immediately figure out where the center of your drawing is, but I suspect it is NOT at (0,0). In all cases, you should place the turtle to the right of your shape's center, offset by the circle's radius, to make the circle go around it.
Another approach would be to average the positions of your arbitrary image and then use that average as the center of the surrounding circle:
from turtle import Screen, Turtle, Vec2D
CIRCLE_RADIUS = 70
POLYGON_LENGTH = 100
POINTS = 50
screen = Screen()
painter = Turtle()
painter.speed('fastest')
painter.pencolor("blue")
total = Vec2D(0, 0)
for _ in range(POINTS):
painter.forward(POLYGON_LENGTH)
total += painter.position()
painter.left(246)
x, y = total * (1.0 / POINTS) # Vec2D can multiply by scalar but not divide
painter.penup()
painter.goto(x, y - CIRCLE_RADIUS)
painter.setheading(0)
painter.pendown()
painter.circle(CIRCLE_RADIUS)
screen.exitonclick()

Python drawing images and dynamic shape size

I have a couple of questions. Firstly, I am wondering how to I get shape sizes for shapes I call to be dynamic, and adjust based on my movement of the window that they are in. Is there a simple command for this? Secondly, I am wondering if instead of using something like Turtle to draw images, how do I get an image to just appear once I run drawing code, as opposed to watching it be drawn?
from turtle import *
import math
radius = 100
t = turtle.Turtle()
radius = 100
colormode(255)
t.speed(1)
t.color(0,255,0)
fillcolor(200, 125, 200)
t.begin_fill()
t.circle(radius)
t.end_fill()
exitonclick()
One way you can go about this is by designing a turtle cursor and stamping it. Cursors are drawn all at once and have more graphics operations at their disposal, like resizing, shear, etc. Here's a simple example using turtle's built in circle shape but you can just as easily design your own and register it as a cursor:
RADIUS = 100
CURSOR_SIZE = 20
screen = Screen()
screen.colormode(255)
turtle = Turtle("circle", visible=False)
turtle.speed('fastest')
turtle.penup()
turtle.pencolor(0, 255, 0)
turtle.fillcolor(200, 125, 200)
turtle.shapesize(RADIUS / CURSOR_SIZE, outline=5)
turtle.stamp()
turtle.goto(250, 250)
turtle.shapesize(2 * RADIUS / CURSOR_SIZE, outline=10)
turtle.stamp()
screen.exitonclick()

Python Turtle unit of measurement

When we instantiate a turtle object, we can draw a circle. I wonder about the radius parameter of the circle() method.
import turtle
myTurtle = turtle.Turtle()
myTurtle.circle(50)
What is the unit of measurement of this parameter?
Does the radius equal to 50 pixels or 50 inches?
the documentation for turtle.setup indicates that size parameters, if expressed as integers, are pixels, if expressed as floats, are fractions of the screen.
As you can see from the first few lines from the documentation, the forward method uses the unit pixel and since there is no other unit used in the documentation, you can conclude the all methods use pixel.
Edit: After looking at the source code form turtle, I'm on 100% sure that it is using pixel as unit, since it is adding the distance to the position directly.
It depends on whether we're measuring the image on the screen, or a printed PostScript image obtained through the underlying tkinter canvas.
My Dell display has a pixel pitch of 0.282mm so I expect to see 90 dots per inch. If turtle draws a circle with a radius of 45 pixels, what I measure on my screen is a circle with a 1" diameter.
However, if I print that image, and turn off any printing scaling, I won't get a 1" printed circle, but something larger. To achieve a properly printed circle, turtle needs to draw with a radius of 36, as the underlying measure for PostScript conversion seems to be based on points (# 72 per inch):
from turtle import Turtle, Screen
screen = Screen()
screen.setup(400, 600)
tortoise = Turtle()
tortoise.circle(36)
canvas = screen.getcanvas()
canvas.postscript(file="circle.ps")
screen.exitonclick()
Although this circle measures around 3/4" on my screen, the PostScript output on the printer is 1". (Make sure to turn off any automatic scaling features and print # 100%)
The actual size of the image on your monitor depends on its pixel pitch. Although tkinter can work in fixed size points for both display and print, Python turtle only works in variable size pixels for display. Once your determine your monitor's pixel pitch, you can use your own scaling factor, or:
turtle.setworldcoordinates(llx, lly, urx, ury)
to configure your own scaling:
from turtle import Turtle, Screen
PIXEL_PITCH = 0.282 # mm
MM_PER_INCH = 25.4
DOTS_PER_INCH = int(1 / (PIXEL_PITCH / MM_PER_INCH))
screen = Screen()
screen.setup(4 * DOTS_PER_INCH, 5 * DOTS_PER_INCH)
screen.setworldcoordinates(-2, -2.5, 2, 2.5) # convert to inches
tortoise = Turtle()
tortoise.circle(0.5) # 1/2" radius, one inch diameter on my screen
screen.exitonclick()
Which should be reasonably accurate on the screen but not print correctly without extra work to switch units during PostScript conversion or use the correct scaling factor in your print dialog.
Usage of the circle() function:
circle(radius, extent=None, steps=None)
See more here: https://docs.python.org/2/library/turtle.html#turtle.circle
So yes, it is the radius as pixels, the first parameter of the function.

Categories