I am trying to use turtle draw with mouse, I got below demo code works but cursor jump sometimes during mouse move:
#!/usr/bin/env python
import turtle
import sys
width = 600
height = 300
def gothere(event):
turtle.penup()
x = event.x
y = event.y
print "gothere (%d,%d)"%(x,y)
turtle.goto(x,y)
turtle.pendown()
def movearound(event):
x = event.x
y = event.y
print "movearound (%d,%d)"%(x,y)
turtle.goto(x,y)
def release(event):
print "release"
turtle.penup()
def circle(x,y,r):
turtle.pendown()
turtle.goto(x,y)
turtle.circle(r)
turtle.penup()
return
def reset(event):
print "reset"
turtle.clear()
#------------------------------------------------#
sys.setrecursionlimit(90000)
turtle.screensize(canvwidth=width, canvheight=height, bg=None)
turtle.reset()
turtle.speed(0)
turtle.setup(width, height)
canvas = turtle.getcanvas()
canvas.bind("<Button-1>", gothere)
canvas.bind("<B1-Motion>", movearound)
canvas.bind("<ButtonRelease-1>", release)
canvas.bind("<Escape>",reset)
screen = turtle.Screen()
screen.setworldcoordinates(0,height,width,0)
screen.listen()
turtle.mainloop()
#------------------------------------------------#
see below gif for actual behavior:
Not sure if any API call is wrong!
You can find them here https://docs.python.org/3.3/library/turtle.html?highlight=turtle#turtle.shape, or you could use the turtle. register_shape(image_name) function. Hope this helps! The OP's desire "open hand" is not one of the predefined turtle cursor shapes.
penup or pu means pick pen up, so you can move turtle without leaving tracks. pendown or pd means pick pen down, so you can move the turtle and leave tracks. hideturtle or ht means hide the turtle, so you can admire your drawing.
In this section, we will learn about how to get the mouse position in Python turtle. As we know with the help of the mouse we perform most of the functions. Here also on click on the mouse, the cursor which is placed on the screen starts moving. Anywhere the cursor move we get the position and the position is shown on the command prompt.
Turtle is used to draw the shape we can move the turtle with the help of the mouse. The turtle is starting working when the mouse gives the command. In the following code, we will import turtle libraries from turtle import *, import turtle. The turtle () is used to make objects.
Python turtle window is a place where a turtle can draw different shapes and pictures. Here TurtleScreen class defines the window. We can also resize the size of the window by applying a simple method. We can run different turtle commands and also get the running output on the window.
Since the value of the variable isn’t constant, it can change several times during the execution of your program. Now, to open the turtle screen, you initialize a variable for it in the following way: You should see a separate window open up: This window is called the screen. It’s where you can view the output of your code.
I see several issues with your code:
You're mixing the object-oriented interface to turtle with
the functional interface to that module. I recommend one or the
other, but not both. See my import
change to force OOP-only.
You're using low level tkinter mouse and key events instead of turtle's own events. I recommend you try to work at the turtle level (though this introduces a glitch compared to your implementation, see below.)
You're introducing unintended recursion by not turning off events inside your event handlers. Disabling events inside those handlers that take significant time will clean up your graphics.
Here's my rework of your code along the above lines. The one glitch is that unlike your original, the "move turtle here" and "start dragging" will take two clicks, one screen click to move the turtle to the current location and one turtle click to start dragging. This is due to differences in the interface that turtle provides to tkinter events. (Python 3 is a little better in this regard but not for this situation.)
To ease that, I used a larger turtle cursor. I also added heading logic:
from turtle import Turtle, Screen, mainloop
WIDTH = 600
HEIGHT = 300
def gothere(x, y):
screen.onscreenclick(gothere) # disable events inside handler
turtle.penup()
print("gothere (%d,%d)" % (x, y))
turtle.goto(x, y)
turtle.pendown()
screen.onscreenclick(gothere)
def movearound(x, y):
turtle.ondrag(None) # disable events inside handler
turtle.setheading(turtle.towards(x, y))
print("movearound (%d,%d)" % (x, y))
turtle.goto(x, y)
turtle.ondrag(movearound)
def release(x, y):
print("release (%d,%d)" % (x, y))
turtle.penup()
def reset():
print("reset")
turtle.clear()
screen = Screen()
screen.setup(WIDTH, HEIGHT)
# screen.setworldcoordinates(0, HEIGHT, WIDTH, 0) # should work fine either way
turtle = Turtle('turtle')
turtle.speed('fastest')
turtle.ondrag(movearound)
turtle.onrelease(release)
screen.onscreenclick(gothere)
screen.onkey(reset, "Escape")
screen.listen()
mainloop() # normally screen.mainloop() but not in Python 2
But also see this answer where I show how to make tkinter's onmove
event available to turtle.
... the "move here" then "start drag" limitation is very not comfortable for user? How can we improve that?
Combining my above code with my alternate answer that I linked to, we get this solution that is similar to where you started but without the glitches and in a more turtle-like style:
from turtle import Turtle, Screen, mainloop
from functools import partial
WIDTH = 600
HEIGHT = 300
VERBOSE = False
def onscreenmove(self, fun, btn=1, add=None): # method missing from turtle.py
if fun is None:
self.cv.unbind('<Button%s-Motion>' % btn)
else:
def eventfun(event):
fun(self.cv.canvasx(event.x) / self.xscale, -self.cv.canvasy(event.y) / self.yscale)
self.cv.bind('<Button%s-Motion>' % btn, eventfun, add)
def onscreenrelease(self, fun, btn=1, add=None): # method missing from turtle.py
if fun is None:
self.cv.unbind("<Button%s-ButtonRelease>" % btn)
else:
def eventfun(event):
fun(self.cv.canvasx(event.x) / self.xscale, -self.cv.canvasy(event.y) / self.yscale)
self.cv.bind("<Button%s-ButtonRelease>" % btn, eventfun, add)
def gothere(x, y):
if VERBOSE:
print("gothere (%d,%d)" % (x, y))
turtle.penup()
turtle.goto(x, y)
turtle.pendown()
def movearound(x, y):
screen.onscreenmove(None) # disable events inside handler
if VERBOSE:
print("movearound (%d,%d)" % (x, y))
turtle.setheading(turtle.towards(x, y))
turtle.goto(x, y)
screen.onscreenmove(movearound) # reenable events
def release(x, y):
if VERBOSE:
print("release (%d,%d)" % (x, y))
turtle.penup()
def reset():
if VERBOSE:
print("reset")
turtle.clear()
screen = Screen()
screen.setup(WIDTH, HEIGHT)
screen.onscreenrelease = partial(onscreenrelease, screen) # install missing methods
screen.onscreenmove = partial(onscreenmove, screen)
turtle = Turtle('turtle')
turtle.speed('fastest')
screen.onscreenclick(gothere)
screen.onscreenrelease(release)
screen.onscreenmove(movearound)
screen.onkey(reset, "Escape")
screen.listen()
mainloop() # normally screen.mainloop() but not in Python 2
I agree with cdlane that it would be better to avoid Tkinter-level event binding where possible, but I thought it might also be interesting to approach the problem without altering the overall design much. My solution only requires an additional import:
from collections import deque
And a new version of movearound
:
pending = deque()
def movearound(event):
x = event.x
y = event.y
pending.append((x,y))
if len(pending) == 1:
while pending:
x,y = pending[0]
turtle.goto(x,y)
pending.popleft()
As I indicated in my comments, goto
can call movearound
if the window's event queue is backed up, and this can cause a stack overflow or race conditions that make the turtle move in unusual ways. This approach aims to prevent arbitrarily deep recursion, by only letting the topmost instance of movearound
call goto
. if len(pending) == 1:
should only succeed for nonrecursive calls; all recursive calls will see a queue larger than that. The while pending:
loop works through all the built-up events, handling them in the order they arrived.
The result: a turtle that dutifully follows the path of the cursor, albeit at its own turtley pace:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With