Modeling simultaneous moving bodies in canvas - python

I'm strugling with canvas.move in a simulation of a celestial system. How can I simultaneously move multiple objects in a defined Space instance? I guess I have to do something with the identity of the body objects. But I cannot find it out. Perhaps I should use repeating draw en delete methods in stead of canvas.move? See a simplified version of the coden below. Does some body has a suggestion? Many thanks
import tkinter as tk
class Space(tk.Frame):
def __init__(self, master, size, bg=None):
super().__init__(master)
frame = tk.Frame(master, border = 5)
frame.pack()
self.width, self.height = size[0], size[1]
self.canvas = tk.Canvas(frame,
width = self.width,
height = self.height,
borderwidth= 0,
highlightthickness= 0,
bg=bg)
self.canvas.pack()
def place_body(self, body):
x1, y1 = body.loc[0], body.loc[1]
x2, y2 = x1+body.size, y1+body.size
self.canvas.create_oval(x1,y1,x2,y2, fill=body.color)
def distance_step(self):
pass
def move_body(self, body):
# in stead of distance_step:
dx, dy = body.speed[0], body.speed[1]
self.canvas.move(body, dx, dy)
self.canvas.after(1, lambda: self.move_body(body))
class CelestialBody:
def __init__(self, name, size, mass, loc, speed, color="white"):
self.name = name
self.size = size
self.mass = mass
self.loc = loc
self.speed = speed
self.color = color
def __repr__(self):
return f"{self.name}"
class App:
def __init__(self):
x, y = 1000, 800
size = (x,y)
space = Space(root,size, bg = 'black')
sun1_size = 30
sun1_mass = 10
sun1_loc = (700, 450)
sun1_speed = (-200,0)
sun2_size = 30
sun2_mass = 10
sun2_loc = (300, 350)
sun2_speed = (200,0)
sun1 = CelestialBody("sun1", sun1_size, sun1_mass, sun1_loc, sun1_speed, color = "yellow")
sun2 = CelestialBody("sun2", sun2_size, sun2_mass, sun2_loc, sun2_speed, color ="yellow")
space.place_body(sun1)
space.place_body(sun2)
space.move_body(sun1)
space.move_body(sun2)
print(sun1, sun2)
root.mainloop()
root = tk.Tk()
root.title('UNIVERSE')
app = App()```

You need to keep track of the tag returned from canvas.create_oval(). See .tk_tag below. I had to slow down your speeds because the object immediately left the screen. Also note: instead of dx, dy = body.speed[0], body.speed[1], you can just do dx, dy = body.speed.
import tkinter as tk
class Space(tk.Frame):
def __init__(self, master, size, bg=None):
super().__init__(master)
frame = tk.Frame(master, border=5)
frame.pack()
self.width, self.height = size
self.canvas = tk.Canvas(frame,
width=self.width,
height=self.height,
borderwidth=0,
highlightthickness=0,
bg=bg)
self.canvas.pack()
def place_body(self, body):
x1, y1 = body.loc
x2, y2 = x1 + body.size, y1 + body.size
body.tk_tag = self.canvas.create_oval(x1, y1, x2, y2, fill=body.color)
def distance_step(self):
pass
def move_body(self, body):
# in stead of distance_step:
dx, dy = body.speed
dx, dy = dx/100, dy/100
self.canvas.move(body.tk_tag, dx, dy)
self.canvas.after(1, lambda: self.move_body(body))
class CelestialBody:
def __init__(self, name, size, mass, loc, speed, color="white"):
self.name = name
self.size = size
self.mass = mass
self.loc = loc
self.speed = speed
self.color = color
self.tk_tag = None
def __repr__(self):
return f"{self.name}"
class App:
def __init__(self):
x, y = 1000, 800
size = (x, y)
space = Space(root, size, bg='black')
sun1_size = 30
sun1_mass = 10
sun1_loc = (700, 450)
sun1_speed = (-200, 0)
sun2_size = 30
sun2_mass = 10
sun2_loc = (300, 350)
sun2_speed = (200, 0)
sun1 = CelestialBody("sun1", sun1_size, sun1_mass, sun1_loc, sun1_speed, color="yellow")
sun2 = CelestialBody("sun2", sun2_size, sun2_mass, sun2_loc, sun2_speed, color="yellow")
space.place_body(sun1)
space.place_body(sun2)
space.move_body(sun1)
space.move_body(sun2)
print(sun1, sun2)
root.mainloop()
root = tk.Tk()
root.title('UNIVERSE')
app = App()

Related

How to put a button image borderless/tansparent

I'm trying to put my button borderless so we can we see the progressbar on the background
I tried all the forum but coundt put the border of the button round or make something like the second picture
this is the part of the code for the button :
bg3 = Image.open("blanc.png")
resized_bg03 = bg3.resize((20, 20),Image.ANTIALIAS)
new_bg03 = ImageTk.PhotoImage(resized_bg03)
s = Button(Fenetre,image=new_bg03,borderwidth=0,highlightthickness=0)
s.place(x=600, y=10,height = 20 , width = 20)
I would like to have something more like this with a circle and no border :
can someone help me :)
Try this:
import tkinter as tk
# Kindly plagiarised from: https://stackoverflow.com/a/17985217/11106801
def _create_circle(self, x, y, r, **kwargs):
return self.create_oval(x-r, y-r, x+r, y+r, **kwargs)
tk.Canvas.create_circle = _create_circle
# Please suggest a better name for the class
class ProgressBar(tk.Canvas):
def __init__(self, height=20, radius=6, width=400, circle_colour="black",
colour="red", bar_width=6, bd=0, highlightthickness=0,
anti_bar_colour="white", **kwargs):
super().__init__(height=height, width=width, bd=bd,
highlightthickness=highlightthickness, **kwargs)
self.radius = radius
self.height = height
self.width = width
self.rectangle = None
self.circle_colour = circle_colour
self.colour = colour
self.bar_width = bar_width
self.circle = None
self.button_1_down = False
self.create_anti_bar(anti_bar_colour)
self.progress = 0
super().bind("<Enter>", self.show_circle)
super().bind("<Leave>", self.hide_circle)
super().bind("<Button-1>", self.mouse_click)
super().bind("<ButtonRelease-1>", self.mouse_release)
super().bind("<B1-Motion>", self.mouse_motion)
def create_anti_bar(self, colour):
start_y = (self.height - self.bar_width)/2
end_y = self.height - start_y
start_x = self.radius
end_x = self.width - self.radius
# Change it to >= if you don't what the bar to appear when the
# progress is at 0
if start_x > end_x:
return None
super().create_rectangle(start_x, start_y, end_x, end_y,fill=colour,
outline=colour)
def mouse_click(self, event):
self.button_1_down = True
self.progress = (event.x - self.radius)/(self.width - 2*self.radius)
def mouse_release(self, event=None):
self.button_1_down = False
def mouse_motion(self, event):
if self.button_1_down:
self.mouse_click(event)
def hide_circle(self, event=None):
if self.circle is not None:
super().delete(self.circle)
self.circle = None
def show_circle(self, event=None):
# Try removing the circle if we can
self.hide_circle()
x = (self.width - 2*self.radius)*self._progress + self.radius
self.circle = super().create_circle(x, self.height//2, self.radius,
fill=self.circle_colour,
outline=self.circle_colour)
def update_bar(self):
# Try removing the progress bar
if self.rectangle is not None:
super().delete(self.rectangle)
start_y = (self.height - self.bar_width)/2
end_y = self.height - start_y
start_x = self.radius
end_x = (self.width - 2*self.radius)*self._progress + self.radius
# Change it to >= if you don't what the bar to appear when the
# progress is at 0
if start_x > end_x:
return None
self.rectangle = super().create_rectangle(start_x, start_y, end_x,
end_y, fill=self.colour,
outline=self.colour)
#property
def progress(self):
return self._progress
#progress.setter
def progress(self, new_value):
# Check if the new_value is in the correct range
if new_value < 0:
new_value = 0
elif new_value > 1:
new_value = 1
# Update self._progress
self._progress = new_value
# Update the progress bar
self.update_bar()
# If the circle was shown update it
if self.circle is not None:
self.show_circle()
if __name__ == "__main__":
root = tk.Tk()
pbar = ProgressBar()
pbar.pack()
pbar.progress = 0
# keep incrementing the progress until the end then stop
def increment_progress():
pbar.progress += 0.001
if pbar.progress >= 1:
return None
pbar.after(10, increment_progress)
increment_progress()
root.mainloop()
Tell me if you don't get what any of the methods do. It is too much code to properly annotate

tkinter: object with different movement patterns

I'm working on a simulation in which some cubes of the same class are moving randomly. My aim is to give them another moving pattern, when they fulfill some characteristics (for example their object number).
My Problem:
If they fulfill the characteristics, how can I "switch off" the first moving pattern and activate the next?
Here a strongly simplified example of the simulation, and how it doesn't work:
from tkinter import *
from random import *
class Cubes:
def __init__(self, master, canvas, number, x1, y1, color):
self.master = master
self.canvas = canvas
self.number = number
self.x1 = x1
self.y1 = y1
self.x2 = x1 + 15
self.y2 = y1 + 15
self.color = color
self.rectangle = canvas.create_rectangle(x1, y1, self.x2, self.y2, fill=color)
def movement(self):
self.x = randint(-10, 10)
self.y = randint(-10, 10)
canvas.move(self.rectangle, self.x, self.y)
if self.number == 2:
def movementII(self):
canvas.move(self.rectangle, 0, 0)
self.canvas.after(100, self.movementII)
self.canvas.after(100, self.movement)
if __name__ == "__main__":
master = Tk()
canvas = Canvas(master, width=900, height=600)
canvas.pack()
master.title("Simulation")
cube = Cubes(master, canvas, 2, randint(50, 800), randint(25, 500), "black")
cube.movement()
mainloop()
how can I "switch off" the first moving pattern and activate the next?
When you call after, it returns a unique identifier. You can save that identifier and then later pass it to after_cancel to cancel the job if it hasn't already run.
I'm not entirely clear what you want, but if you want to turn off the old movement when you switch to the new, it would look something like this:
class Cubes:
def __init__(self, master, canvas, number, x1, y1, color):
...
self.after_id = None
...
def cancel(self):
if self.after_id is not None:
self.after_cancel(self.after_id)
self.after_id = None
def movement(self):
self.x = randint(-10, 10)
self.y = randint(-10, 10)
canvas.move(self.rectangle, self.x, self.y)
if self.number == 2:
def movementII(self):
canvas.move(self.rectangle, 0, 0)
self.cancel()
self.after_id = self.canvas.after(100, self.movementII)
self.after_id = self.canvas.after(100, self.movement)
A better way might be to have a single method that you call with after, and it simply calls the appropriate method.
For example, something like this:
def move(self):
if self.number == 1:
self.movement_1()
elif self.number == 2:
self.movement_2()
self.canvas.after(100, self.move)
def movement_1(self):
self.x = randint(-10, 10)
self.y = randint(-10, 10)
canvas.move(self.rectangle, self.x, self.y)
def movement_2(self):
canvas.move(self.rectangle, 0, 0)
Then, to switch the movement method, just change self.number and it will automatically be called at the appropriate time.

Python Creating a tick in a class

So I'm spawning circles at random in Python and I decided I wanted to make each circle it's own object. So I created a class for them.
I wanted each circle to have their own tick function so that each one can do their own thing. (change colors, size, de-spawn etc.) However I'm having a lot of issues getting the tick method to work.
Here is my code:
from random import *
from tkinter import *
from time import *
size = 2000
window = Tk()
count = 0
class Shape(object):
def __init__(self,name, canv, size, col, x0, y0,d,outline):
self.name = name
self.canv = canv
self.size = size
self.col = col
self.x0 = x0
self.y0 = y0
self.d = d
self.outline = outline
self.age=0
def death(self):
pass
def spawn(self):
self.canv.create_oval(self.x0, self.y0, self.x0 + self.d, self.y0 + self.d, outline=self.outline, fill = self.col)
def tick(self):
self.age = self.age +time.time()
while True:
tick()
# var canv = A new Canvas Object (The Window Object to be put in, The Canvice width, The canvice Height)
canv = Canvas(window, width=size, height=size)
canv.pack()
d = 0
while True:
col = choice(['#EAEA00'])
x0 = randint(0, size)
y0 = randint(0, size)
#d = randint(0, size/5)
d = (d + 0.001)
count = count+1
outline = 'white'
shapes[count] = Shape("shape" + str(count), canv, size, col, x0, y0, d, outline)
shapes[count].spawn()
#canv.create_oval(x0, y0, x0 + d, y0 + d, outline='white', fill = col)
window.update()
So basically I get two different errors from this part of the code:
while True:
tick()
Ether it's a TypeError: tick() missing one required positional argument: 'self' or if I put self into the parameters I get self is undefined.
So what am I doing wrong?

Redraw circle - python

I have this bit of code here:
from tkinter import *
class player():
def __init__(self, radius, xcoordinate = 0, ycoordinate = 0):
self.xcoordinate = xcoordinate
self.ycoordinate = ycoordinate
self.radius = radius
def moveRight(self, event):
self.xcoordinate += 25
self.draw()
print("Right key pressed")
print("x: " + str(self.xcoordinate))
def moveLeft(self, event):
self.ycoordinate += 25
self.draw()
print("Left key pressed")
print("y: " + str(self.ycoordinate))
def draw(self):
world = client()
world.title("World")
world.bind('<Right>', self.moveRight)
world.bind('<Left>', self.moveLeft)
canvas = Canvas(world, width=200, height=200, borderwidth=0,highlightthickness=0, bg="black")
canvas.grid()
canvas.draw_player(self.xcoordinate, self.ycoordinate, self.radius, fill="blue", width=4)
world.mainloop()
class client(Tk):
def __init__(self):
super().__init__()
def draw_player(self, x, y, r, **kwargs):
return self.create_oval(x-r, y-r, x+r, y+r, **kwargs)
Canvas.draw_player = draw_player
p1 = player(50)
p1.draw()
The problem is that whenever I press the right or left arrow keys, it calls the draw() method. The draw() method constructs a new client object and etc.
So you end up opening multiple windows each with a circle with different x and y coordinates. How do I make this such that when I call draw() it only edits the x and y coordinates and redraws the circle on the same window?
Please no suggestions to use pygame, my IDE gets errors when I try to import the module.
The code can be simplified by passing the move parameters to a single function.
from tkinter import *
from functools import partial
class player():
def __init__(self, master, radius, xcoordinate=100, ycoordinate=100):
self.master=master
self.xcoordinate = xcoordinate
self.ycoordinate = ycoordinate
self.radius = radius
self.master.title("World")
self.master.bind('<Right>', partial(self.move_oval, 25, 0))
self.master.bind('<Left>', partial(self.move_oval, -25, 0))
self.master.bind('<Up>', partial(self.move_oval, 0, -25))
self.master.bind('<Down>', partial(self.move_oval, 0, 25))
self.draw() ## called once
def move_oval(self, x, y, event):
self.canvas.move(self.oval_id, x, y)
print("key pressed", x, y)
def draw(self):
self.canvas = Canvas(self.master, width=200, height=200,
borderwidth=0,highlightthickness=0, bg="black")
self.canvas.grid()
self.oval_id=self.canvas.create_oval(self.xcoordinate-self.radius,
self.ycoordinate-self.radius,
self.xcoordinate+self.radius,
self.ycoordinate+self.radius,
fill="red")
master=Tk()
p1 = player(master, 50)
master.mainloop()
I assume that
redraws the circle on the same window
means moving the circle, and does not mean drawing a second circle in the same space. Use the move() function for a canvas object to do this http://effbot.org/tkinterbook/canvas.htm . Note that you have to save a reference to the object to be moved. Also your moveLeft() function doesn't.
class Player():
def __init__(self, master, radius, xcoordinate=100, ycoordinate=100):
self.master=master
self.xcoordinate = xcoordinate
self.ycoordinate = ycoordinate
self.radius = radius
self.master.title("World")
self.master.bind('<Right>', self.moveRight)
self.master.bind('<Left>', self.moveLeft)
self.draw() ## called once
def moveRight(self, event):
self.canvas.move(self.oval_id, 25, 0)
print("Right key pressed")
print("x: " + str(self.xcoordinate))
def moveLeft(self, event):
self.canvas.move(self.oval_id, 0, 25)
print("Left key pressed")
print("y: " + str(self.ycoordinate))
def draw(self):
self.canvas = Canvas(self.master, width=200, height=200,
borderwidth=0,highlightthickness=0, bg="black")
self.canvas.grid()
self.oval_id=self.canvas.create_oval(self.xcoordinate-self.radius,
self.ycoordinate-self.radius,
self.xcoordinate+self.radius,
self.ycoordinate+self.radius,
fill="red")
master=Tk()
p1 = Player(master, 50)
master.mainloop()

The item configure method didn't work in Tkinter

I tried to use the Tkinter library for my small project in python. I create a 500 by 500 square with 10000 small square in it.
And I want each small square turns black when user click on it. Can someone please tell me why, I would really appreciate it. Here is the graphics code:
from Tkinter import *
from button import *
class AppFrame(Frame):
def __init__(self):
self.root = Tk()
self.root.geometry = ("1000x1000")
self.f = Frame(self.root, relief = 'sunken', width = 600, height = 600)
self.w = Canvas(self.f,width = 505, height =505)
##get the x, y value whenever the user make a mouse click
self.w.bind("<Button-1>", self.xy)
self.bolist = []
for k in range(1,101):
for i in range(1, 101):
button = Buttons(self.w, i * 5, k * 5, i * 5 + 5, k * 5 + 5)
self.bolist.append(button)
self.f.grid(column =0, columnspan = 4)
self.w.grid(column = 0)
self.root.mainloop()
def xy (self, event):
self.x, self.y = event.x, event.y
print (self.x, self.y)
##check each button if it's clicked
for hb in self.bolist:
if hb.clicked(self.x, self.y):
print ("hurry")
hb.activate()
And
##button.py
from Tkinter import *
class Buttons:
def __init__(self,canvas,bx,by,tx,ty):
self.canvas = canvas
self.rec = canvas.create_rectangle((bx,by,tx,ty),fill = "lightgray",
activefill= 'black', outline = 'lightgray')
self.xmin = bx
self.xmax = tx
self.ymin = by
self.ymax = ty
##print (bx, by, tx, ty)
def clicked(self, px, py):
return (self.active and self.xmin <= px <= self.xmax and
self.ymin <= py <= self.ymax)
def activate(self):
self.canvas.itemconfigure(slef.rec, fill = 'black')
self.active = True
The problem is that you don't initialize the active attribute, so it doesn't exist until the cell becomes active. To fix that, add self.active = False inside the __init__ method of Buttons.
You also have a typo in this line (notice you use slef rather than self):
self.canvas.itemconfigure(slef.rec, fill = 'black')
Instead of a global binding on the canvas, it would be more efficient to set a binding on each individual rectangle. You can then use the binding to pass the instance of the Buttons class to the callback. This way you don't have to iterate over several thousand widgets looking for the one that was clicked on.
To do this, use the tag_bind method of the canvas. You can make it so that your main program passes in a reference to a function to call when the rectangle is clicked, then the binding can call that method and pass it a reference to itself.
For example:
class Buttons:
def __init__(self,canvas,bx,by,tx,ty, callback):
...
self.rec = canvas.create_rectangle(...)
self.canvas.tag_bind(self.rec, "<1>",
lambda event: callback(self))
...
class AppFrame(Frame):
def __init__(...):
...
button = Buttons(..., self.callback)
...
def callback(self, b):
b.activate()
Here, I looked at your code, debugged it, and made some adjustments. It works now.
Just keep both the scripts in one folder and run your AppFrame script (the second one in this answer)
##button.py
from Tkinter import *
class Buttons:
def __init__(self,canvas,bx,by,tx,ty):
self.canvas = canvas
self.rec = canvas.create_rectangle((bx,by,tx,ty),fill = "lightgray", activefill= 'black', outline = 'lightgray')
self.xmin = bx
self.xmax = tx
self.ymin = by
self.ymax = ty
##print (bx, by, tx, ty)
def clicked(self, px, py):
return (self.xmin <= px <= self.xmax and
self.ymin <= py <= self.ymax)
def activate(self):
self.canvas.itemconfigure(self.rec, fill = 'black')
AND
from Tkinter import *
from button import *
class AppFrame(Frame):
def __init__(self):
self.root = Tk()
self.root.geometry = ("1000x1000")
self.f = Frame(self.root, relief = 'sunken', width = 600, height = 600)
self.w = Canvas(self.f,width = 505, height =505)
##get the x, y value whenever the user make a mouse click
self.w.bind("<Button-1>", self.xy)
self.bolist = []
for k in range(1,101):
for i in range(1, 101):
button = Buttons(self.w, i * 5, k * 5, i * 5 + 5, k * 5 + 5)
self.bolist.append(button)
self.f.grid(column =0, columnspan = 4)
self.w.grid(column = 0)
self.root.mainloop()
def xy (self, event):
self.x, self.y = event.x, event.y
print (self.x, self.y)
##check each button if it's clicked
for hb in self.bolist:
if hb.clicked(self.x, self.y):
print ("hurry")
hb.activate()
newApp = AppFrame()

Categories