Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python turtle weird cursor jump

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:

enter image description here

Not sure if any API call is wrong!

like image 941
lucky1928 Avatar asked Apr 30 '18 18:04

lucky1928


People also ask

How do you change the turtle cursor in Python?

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.

What does turtle Penup () do?

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.

How to get the mouse position in Python turtle?

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.

What is the use of turtle () method in Python?

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.

What is a turtle window in Python?

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.

How to open the turtle screen in Python?

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.


Video Answer


2 Answers

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
like image 171
cdlane Avatar answered Sep 29 '22 13:09

cdlane


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:

enter image description here

like image 40
Kevin Avatar answered Sep 29 '22 11:09

Kevin