I'm trying to implement a particle filter and I chose python for it because I kinda like python. By now i have written my gui using tkinter and python 3.4.
I use the tkinter.canvas object to display a map (png image loaded with PIL) and then i create dots for each particle like:
dot = canvas.create_oval(x, y, x + 1, y + 1)
When the robot moves I calculate the new position of each particle with the control command of the robot, the particles position and the particles alignment.
To move the particle tkinter.canvas has two methods:
canvas.move() canvas.coords()
But both methods seem to update the gui immediately which is OK when there are about 100 particles but not if there are 200 - 5000 (what I actually should have in the beginning for the global localization). So my problem is the performance of the gui.
So my actual question is: Is there a way in tkinter to stop the canvas from updating the gui, then change the gui and then update the gui again? Or can you recommend me a module that is better than tkinter for my use-case?
Your observation is incorrect. The canvas is not updated immediately. The oval isn't redrawn until the event loop is able to process events. It is quite possible to update thousands of objects before the canvas is redrawn. Though, the canvas isn't a high performance tool so moving thousands of objects at a high frame rate will be difficult.
If you are seeing the object being updated immediately it's likely because somewhere in your code you are either calling update, update_idletasks, or you are otherwise allowing the event loop to run.
The specific answer to your question, then, is to make sure that you don't call update or update_idletasks, or let the event loop process events, until you've changed the coordinates of all of your particles.
Following is a short example. When it runs, notice that all of the particles move at once in one second intervals. This is because all of the calculations are done before allowing the event loop to redraw the items on the canvas.
import Tkinter as tk
import random
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.canvas = tk.Canvas(self, width=500, height=500, background="black")
self.canvas.pack(fill="both", expand=True)
self.particles = []
for i in range(1000):
x = random.randint(1, 499)
y = random.randint(1, 499)
particle = self.canvas.create_oval(x,y,x+4,y+4,
outline="white", fill="white")
self.particles.append(particle)
self.animate()
def animate(self):
for i, particle in enumerate(self.particles):
deltay = (2,4,8)[i%3]
deltax = random.randint(-2,2)
self.canvas.move(particle, deltax, deltay)
self.after(30, self.animate)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Related
I have created an image on a canvas in tkinter that responds to a button event. And, the object is created on position x and position y where that event took place. But the object changes shape constantly.
def leftclick(event):
canvas1=Canvas(play, height=hei, width=wid)
canvas1.grid(row=0, column=0, sticky=W)
canvas1.delete("all")
x=event.x
y=event.y
print(event.x, event.y)
bullet = canvas1.create_oval(x,y, 100,100, fill="red")
xspeed=random.randint(0, 50)
yspeed=random.randint(0,50)
This just draws ovals which are randomly shaped. Why is this happening and how do I fix it?
You should only create your canvas once, but that's not a problem.
The problem is that the tkinter tries to create an oval inside the rectangle. You've specified the 2 points of the rectangle: x,y and 100,100. Just use bullet = canvas1.create_oval(x-50,y-50, x+50,y+50, fill="red") or whatever number you pick instead of 50. Hope that's helpful!
I want to make a circle move and avoid obstacles with collision detection. Here's the code I have.
from Tkinter import *
window = Tk()
canvas = Canvas(window,width=800, height=500, bg='pink')
canvas.pack()
finishline = canvas.create_oval(700, 300, 700+50, 300+50,fill='green')
robot = canvas.create_oval(20,200,20+45,200+45,fill='yellow')#(x1, y1, x2, y2)
ob1 = canvas.create_rectangle(200,400,200+50,200+1,fill='black')
canvas.update()
ob1 = canvas.create_rectangle(500,200,150+400,300+100,fill='blue')
canvas.update()
You could always use the canvas.find_overlapping method (found here: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.find_overlapping-method), and if it ever returns a value other than your own little circle, you can make the circle go in the other direction or something.
It's kind of hard to give a specific answer with such unspecific requirements.
I have an almost-working piece of code (I hope). In the update method of this class, random black points should be drawn at locations bounded by the width and height of the window - the problem is that the points are not drawn. A gtk window containing the background image that is loaded with the cairo ImageSurface.create_from_png(BG_IMG) is displayed and I've also verified that the update function is called (every 17ms with a gobject.timeout_add callback function). I've searched here and elsewhere, but I can't quite see what's wrong with this code..
class Screen(gtk.DrawingArea):
__gsignals__ = {"expose-event": "override"}
def do_expose_event(self, event):
self.cr = self.window.cairo_create()
self.cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
self.cr.clip()
self.draw(*self.window.get_size())
def draw(self, width, height):
x = y = 0
self.bg = c.ImageSurface.create_from_png(BG_IMG)
self.cr.set_source_surface(self.bg, x, y)
self.cr.paint()
def update(self):
x = randint(0, DOCK_W)
y = randint(0, DOCK_H)
self.cr.rectangle(x, y, 1, 1)
self.cr.set_source_rgba(0, 0, 0, 1)
self.cr.fill()
self.cr.paint()
Anybody have some insights into why this is code is failing? Big thanks in advance!
Solved
I was unaware that a new cairo context could be used at each draw operation. That turned out to be the main problem.
Generally speaking, you should not draw directly to the window outside of an expose event. And do not keep the cairo context for later use: create one for each event run.
When you want to draw your points, just do: widget.queue_draw(), and a new expose event will be delivered to you ASAP. But note that in the expose event you will have to paint all the points, not just the new one.
There a useful optimization to your type of code: from the timer do not call queue_draw as it is fairly inefficient. Instead just draw the new point. However that doesn't excuse you to draw all the points in the do_expose_event, as an expose event can happen at any time and you do not want to lose the already painted points.
To do the one-point draw you have to create a new cairo context, but you do not need to save it:
def update(self):
cr = self.window.cairo_create()
x = randint(0, DOCK_W)
y = randint(0, DOCK_H)
self.points.append((x,y)) #for the expose event ;-)
cr.rectangle(x, y, 1, 1)
cr.set_source_rgba(0, 0, 0, 1)
cr.fill()
cr.paint()
Another common optimization, particularly if you have a lot of points is to keep the painted image in a bitmap, so when the expose event happens, you simply blit the bitmap, instead of iterating all along the list of points.
I am drawing a lot of moving particles in a stationary box with Tkinter. My box is always there and does not change as time goes by, whereas the particles need to be updated.
My first intuition is to delete ALL the things (both particles and the box) and then redraw everything.
canvas.delete(ALL)
It indeed works, but the frame updates get extremely slow. This is because my box is of an irregular shape, which implies that I have to draw the box dot by dot. So this delete-everything-and-redraw-everything method is unsatisfactory.
I wish that the box is drawn only once, and only the particles get deleted and redrawn (updated). How should I do this?
Suppose you have a rectangle on canvas:
canvas.create_rectangle(x0, y0, x1, y1)
This would return a handle, so if you keep track of it,
myRectangle = canvas.create_rectangle(x0, y0, x1, y1)
canvas.delete(myRectangle)
This will delete only the myRectangle object.
Another way of doing it is to use tags.
canvas.create_rectangle(x0, y0, x1, y1, tags="myRectangle")
canvas.delete("myRectangle")
What you need to do is assign the drawings to variables, and then delete those. The below script demonstrates this:
from Tkinter import Button, Canvas, Tk
root = Tk()
canvas = Canvas()
canvas.grid()
drawing1 = canvas.create_oval((10,50,20,60), fill="red")
drawing2 = canvas.create_oval((30,70,40,80), fill="blue")
Button(text="Kill 1", command=lambda: canvas.delete(drawing1)).grid()
Button(text="Kill 2", command=lambda: canvas.delete(drawing2)).grid()
root.mainloop()
In addition to ALL, the delete method can also accept a specific drawing.
I am currently writing a fsm editor with tkinter. But, I stuck on connecting two states. I have two questions:
1) How can make the transition arrow growable according to mouse movement?
2) How can I stick the starting point of the arrow on a state and the end point of the arrow on another state?
PS. Do you think the documentation of tkinter is good enough?
Here's an example that shows the concept. In a nutshell, use tags to associate lines with boxes, and simply adjust the coordinates appropriately when the user moves the mouse.
Run the example, then click and drag from within the beige box.
Of course, for production code you need to make a more general solution, but hopefully this shows you how easy it is to create a box with arrows that adjust as you move the box around.
from Tkinter import *
class CanvasDemo(Frame):
def __init__(self, width=200, height=200):
Frame.__init__(self, root)
self.canvas = Canvas(self)
self.canvas.pack(fill="both", expand="1")
self.canvas.create_rectangle(50, 25, 150, 75, fill="bisque", tags="r1")
self.canvas.create_line(0,0, 50, 25, arrow="last", tags="to_r1")
self.canvas.bind("<B1-Motion>", self.move_box)
self.canvas.bind("<ButtonPress-1>", self.start_move)
def move_box(self, event):
deltax = event.x - self.x
deltay = event.y - self.y
self.canvas.move("r1", deltax, deltay)
coords = self.canvas.coords("to_r1")
coords[2] += deltax
coords[3] += deltay
self.canvas.coords("to_r1", *coords)
self.x = event.x
self.y = event.y
def start_move(self, event):
self.x = event.x
self.y = event.y
root = Tk()
canvas = CanvasDemo(root)
canvas.pack()
mainloop()
Tkinter is perfectly fine for this sort of application. In the past I've worked on tools that were boxes connected with arrows that stayed connected as you move the boxes around (which is what I think you are asking about). Don't let people who don't know much about Tkinter sway you -- it's a perfectly fine toolkit and the canvas is very flexible.
The solution to your problem is simple math. You merely need to compute the coordinates of the edges or corners of boxes to know where to anchor your arrows. To make it "grow" as you say, simply make a binding on mouse movements and update the coordinates appropriately.
To make the line growable all you have to do is adjust the coordinates of the line each time the mouse moves. The easiest thing to do is make liberal use of canvas tags. With the tags you can know which arrows connect to which boxes so that when you move the box you adjust the coordinates of any arrows that point to or from it.