Interference of canvas items and problem in setting coordinates - python-3.x

I'm working on an animation of a moving object, while drawing it's path.
I want to draw the pixels in which the center of the object went through... but guess what? python decided to set the NW anchor of the image with the coordinates I send, instead of the center. I infer it has something to do with the pixels I draw simultaneously (creating a one pixel rectangle). so the image appear on the right of the path bellow... I want the center of it to be on the top of the pixels... adding the main of the code:
from tkinter import*
import time
dt = 0.01
clock_place = (500, 10)
def round_two(t, t0):
return round((t-t0)*100)/100
def round_three(t, t0):
return round((t-t0)*1000)/1000
# showing 'real time motion' for a known path (also cyclic), with
# parametric representation
def paint_known_path(x_pos, y_pos, t_0):
window = Tk()
canvas = Canvas(window, height=700, width=1000)
canvas.pack()
canvas.config(background='black')
tennis_ball = PhotoImage(file='tennis ball.png')
t = t_0
x = x_pos(t_0)
y = y_pos(t_0)
particle = canvas.create_image(x, y, image=tennis_ball)
clock = canvas.create_text(clock_place, text=round_two(t, t_0),
fill='white')
while True:
canvas.create_rectangle(x, y, x, y, outline='red')
canvas.itemconfig(clock, text=round_two(t, t_0))
t += dt
x = x_pos(t)
y = y_pos(t)
canvas.moveto(particle, x, y)
window.update()
if x == x_pos(t_0) and y == y_pos(t_0):
if t - t_0 > 100*dt:
break
time.sleep(dt)
canvas.create_text((500, 100), text='orbit duration: ' +
str(round_three(t, t_0)), fill='white')
window.mainloop()

It turns out to be quite a bit require, but here is the main completion components.
The first additional part that you need to add:
# print('the ten ball height', tennis_ball.height(), tennis_ball.width())
# tennis ball dimensions
tb_hght = tennis_ball.height()
tb_wdth = tennis_ball.width()
mid_point_x = x + tennis_ball.height() / 2
mid_point_y = y + tennis_ball.width() / 2
Secondly, also needed to add some functions to for x_pos and y_pos like this (these are just example functions to make the code work):
def x_pos(a):
# any function of t,
return 100
def y_pos(a):
# any function of t,
return 100
Furthermore, you need to call the function at the end like this:
paint_known_path(x_pos,y_pos,0)
Finally, need to add the mid_point_x and mid_point_y to the path that is drawn (as these will be the image centre points).

Related

ipycanvas displaying final stroke_lines thoughout animation

So I was playing with animating some Bezier curves - just part of learning how to use ipycanvas (0,10,2) -- The animation I produced is really hurting my head. What I expected to see was a set of straight lines between 4 Bezier control points "bouncing" around the canvas with the Bezier curve moving along with them.
I did get the moving Bezier curve -- BUT the control points stayed static. Even stranger they were static in the final position and the curve came to meet them.
Now sometimes Python's structures and references can get a little tricky and so you can sometimes get confusing results if you are not really thinking it through -- and this totally could be what's going on - but I am at a loss.
So to make sure I was not confused I printed the control points (pts) at the beginning and then displayed them to the canvas. This confirmed my suspicion. Through quantum tunneling or some other magic time travel the line canvas.stroke_lines(pts) reaches into the future and grabs the pts array as it will exist in the future and keeps the control points in their final state.
Every other use of pts uses the current temporal state.
So what I need to know is A) The laws of physics are safe and I am just too dumb to understand my own code. B) There is some odd bug in ipycanvas that I should report. C) How to monetize this time-traveling code -- like, could we use it to somehow factor large numbers?
from ipycanvas import Canvas, hold_canvas
import numpy as np
def rgb_to_hex(rgb):
if len(rgb) == 3:
return '#%02x%02x%02x' % rgb
elif len(rgb) == 4:
return '#%02x%02x%02x%02x' % rgb
def Bezier4(t, pts):
p = t**np.arange(0, 4,1)
M=np.matrix([[0,0,0,1],[0,0,3,-3],[0,3,-6,3],[1,-3,3,-1]])
return np.asarray((p*M*pts))
canvas = Canvas(width=800, height=800)
display(canvas) # display the canvas in the output cell..
pts = np.random.randint(50, 750, size=[4, 2]) #choose random starting point
print(pts) #print so we can compare with ending state
d = np.random.uniform(-4,4,size=[4,2]) #some random velocity vectors
c = rgb_to_hex(tuple(np.random.randint(75, 255,size=3))) #some random color
canvas.font = '16px serif' #font for displaying the changing pts array
with hold_canvas(canvas):
for ani in range(300):
#logic to bounce the points about...
for n in range(0,len(pts)):
pts[n]=pts[n] + d[n]
if pts[n][0] >= 800 or pts[n][0] <= 0 :
d[n][0] = - d[n][0]
if pts[n][1] >= 800 or pts[n][1] <= 0 :
d[n][1] = - d[n][1]
#calculate the points needed to display a bezier curve
B = [(Bezier4(i, pts)).ravel() for i in np.linspace(0,1,15)]
#begin display output....
canvas.clear()
#first draw bezier curve...
canvas.stroke_style = c
canvas.stroke_lines(B)
#Now draw control points
canvas.stroke_style = rgb_to_hex((255,255,128, 50))
canvas.stroke_lines(pts)
#print the control points to the canvas so we can see them move
canvas.stroke_style = rgb_to_hex((255,255,128, 150))
canvas.stroke_text(str(pts), 10, 32)
canvas.sleep(20)
In all seriousness, I have tried to think through what can be happening and I am coming up blank. Since ipycanvas is talking to the browser/javascript maybe all of the data for the frames are rendered first and the array used to hold the pts data for the stroke_lines ends up with the final values... Whereas the B array is recreated in each loop... It's a guess.
There are two ways to get the code to behave as expected and avoid the unsightly time-traveling code. The first way is to switch the location of the line with hold_canvas(canvas): to inside the loop. This however renders the canvas.sleep(20) line rather useless.
canvas = Canvas(width=800, height=800)
display(canvas)
pts = np.random.randint(50, 750, size=[4, 2])
print(pts)
d = np.random.uniform(-8,8,size=[4,2])
c = rgb_to_hex(tuple(np.random.randint(75, 255,size=3)))
canvas.font = '16px serif'
#with hold_canvas(canvas):
for ani in range(300):
with hold_canvas(canvas):
for n in range(0,len(pts)):
if pts[n][0] > 800 or pts[n][0] < 0 :
d[n][0] = -d[n][0]
if pts[n][1] > 800 or pts[n][1] < 50 :
d[n][1] = -d[n][1]
pts[n]=pts[n] + d[n]
B = [(Bezier4(i, pts)).ravel() for i in np.linspace(0,1,25)]
canvas.clear()
canvas.stroke_style = c
canvas.stroke_lines(B)
canvas.stroke_style = rgb_to_hex((255,255,128, 50))
#pts2 = np.copy(pts)
canvas.stroke_lines(pts)
canvas.fill_style = rgb_to_hex((255,255,255, 150))
canvas.fill_circles(pts.T[0], pts.T[1],np.array([4]*4))
canvas.stroke_style = rgb_to_hex((255,255,128, 150))
canvas.fill_text(str(pts), 10, 32)
sleep(20/1000)
#canvas.sleep(20)
In this version, the control lines are updated as expected. This version is a little more "real time" and thus the sleep(20/1000) is needed to
The other way to do it would be just to ensure that a copy of pts is made and passed to canvas.stroke_lines:
canvas = Canvas(width=800, height=800)
display(canvas)
pts = np.random.randint(50, 750, size=[4, 2])
print(pts)
d = np.random.uniform(-8,8,size=[4,2])
c = rgb_to_hex(tuple(np.random.randint(75, 255,size=3)))
canvas.font = '16px serif'
with hold_canvas(canvas):
for ani in range(300):
#with hold_canvas(canvas):
for n in range(0,len(pts)):
if pts[n][0] > 800 or pts[n][0] < 0:
d[n][0] = -d[n][0]
if pts[n][1] > 800 or pts[n][1] < 50:
d[n][1] = -d[n][1]
pts[n]=pts[n] + d[n]
B = [(Bezier4(i, pts)).ravel() for i in np.linspace(0,1,35)]
canvas.clear()
canvas.stroke_style = c
canvas.stroke_lines(B)
canvas.stroke_style = rgb_to_hex((255,255,128, 50))
pts2 = np.copy(pts)
canvas.stroke_lines(pts2)
canvas.fill_style = rgb_to_hex((255,255,255, 150))
canvas.fill_circles(pts.T[0], pts.T[1],np.array([4]*4))
canvas.stroke_style = rgb_to_hex((255,255,128, 150))
canvas.fill_text(str(pts), 10, 32)
#sleep(20/1000)
canvas.sleep(20)
I could not actually find the data passed between the python and the browser but it seems pretty logical that what is happening is that python is finishing its work (and ani loop) before sending the widget instructions on what to draw, and the pts values sent are the final ones.
(yes I know there is a bug in the bouncing logic)

How to get canvas current size with guiZero

I am aiming for make GUI that changes depending on the canvas size. I need to be able to actively check the window size so i know when to display extras.
Using
Python 3.8.2
GuiZero
You can use tkinter event <Configure> on the canvas:
def on_resize(event):
print(app.height)
...
app.tk.bind('<Configure>', on_resize)
I was finally able to make something but it does throw and error after the app quits.
w=None
while True:
x=app.tk.winfo_height()
if x!=w:
print(x)
w=app.tk.winfo_height()
app.update()
I came across this problem when creating a digital etch-a-sketch, using a Raspberry Pi and two potentiometers as the horizontal and vertical controls. How to get the current size of the canvas? Annoyingly when you set height and width to "fill" and then try to interrogate these values all you get is "fill", which is no use if you're trying to determine the upper bounds of the available canvas. I dug down into the object hierarchy and discovered that .tk_winfo_height() and .tk.winfo_width() return integer values. For this purpose I've removed the code that reacts to the twiddling of the potentiometers and put a row of buttons at the bottom of the screen to control the vertical and horizontal movement.
from guizero import App, Box, Drawing, PushButton
x = 0
y = 0
def clear_screen():
drawing.clear()
def move_left():
global x, y
if x > 0 :
drawing.line(x, y, x - 1, y)
x = x - 1
def move_right():
global x, y
if x < drawing.tk.winfo_width() :
drawing.line(x, y, x + 1, y)
x = x + 1
def move_up():
global x, y
if y > 0 :
drawing.line(x, y, x, y - 1)
y = y - 1
def move_down():
global x, y
if y < drawing.tk.winfo_height() :
drawing.line(x, y, x, y + 1)
y = y + 1
app = App()
drawing = Drawing(app, height="fill", width="fill")
drawing.bg="white"
bbox = Box(app, align="bottom")
lbtn = PushButton(bbox, align="left", command=move_left, text="Left")
ubtn = PushButton(bbox, align="left", command=move_up, text="Up")
cbtn = PushButton(bbox, align="left", command=clear_screen, text="Clear")
rbtn = PushButton(bbox, align="left", command=move_right, text="Right")
dbtn = PushButton(bbox, align="left", command=move_down, text="Down")
app.display()

Tkinter create shrinking circle on each mouse click, how to make it work with multiple clicks?

I am creating a simple program which draws a shrinking circle of random color on every clicked location by each mouse click. Each click creates a circle of diameter 50 which starts shrinking till 0 immediately. Each click is supposed to create new shrinking circle.
However, my program stops shrinking of first circle after I click and create another circle. It completely shrinks only the last created circle. All others remain still.
I believe the problem lies in function itself. It calls the same function which is not finished. How to make it run multiple times (on each click separately)? Or do I have it wrong with local and global variables?
Here is my code so far:
import tkinter
import random
c = tkinter.Canvas(width = 400, height = 300)
c.pack()
def klik(event):
global x, y, farba, circ, r
r = 50 #circle diameter
x, y = event.x, event.y #clicked position
color = '#{:06x}'.format(random.randrange(256 ** 3)) #random color picker
circ = c.create_oval(x - r, y - r, x + r, y + r, fill=color) #print circle
print(x, y, farba) #check clicked coordinates, not important
if r < 50: #reset size after each circle
r = 50
shrink()
def shrink():
global circ, x, y, r
print(r) #check if countdown runs correctly
if r > 0:
r -= 1 #diameter shrinking
c.coords(circ, x-r, y-r, x+r, y+r) #changing circle size
c.after(100, shrink) #timer, size 1pt smaller until size is 0
c.bind('<Button-1>', klik)
tkinter.mainloop()
If you move everything into a class then each circle will be its own instance and will not interfere with each other.
Take a look at the below modified version of your code. It is probably not perfect but should be a good foundation for you to work with.
import tkinter
import random
c = tkinter.Canvas(width = 400, height = 300)
c.pack()
class create_circles():
def __init__(self, event):
self.r = 50
self.x, self.y = event.x, event.y
self.color = '#{:06x}'.format(random.randrange(256 ** 3))
self.circ = c.create_oval(self.x - self.r, self.y - self.r, self.x + self.r, self.y + self.r, fill=self.color)
self.shrink()
def shrink(self):
if self.r > 0:
self.r -= 1
c.coords(self.circ, self.x-self.r, self.y-self.r, self.x+self.r, self.y+self.r)
c.after(100, self.shrink)
c.bind('<Button-1>', create_circles)
tkinter.mainloop()
There is another way you can do this outside of a class.
You can use a nested function and avoid global. Your issues in your question is actually being caused because everything relies on global variables.
Try this below code for a non-class option.
import tkinter
import random
c = tkinter.Canvas(width = 400, height = 300)
c.pack()
def klik(event):
r = 50
x, y = event.x, event.y
color = '#{:06x}'.format(random.randrange(256 ** 3))
circ = c.create_oval(x - r, y - r, x + r, y + r, fill=color)
def shrink(r, x, y, color, circ):
if r > 0:
r -= 1
c.coords(circ, x-r, y-r, x+r, y+r)
c.after(100, shrink, r, x, y, color, circ)
shrink(r, x, y, color, circ)
c.bind('<Button-1>', klik)
tkinter.mainloop()
As noted, you do not need classes to solve this nor nested functions. The key, as #LioraHaydont was hinting at, is you need to use local, rather than global variables:
import tkinter as tk
from random import randrange
def klik(event):
r = 50 # circle radius
x, y = event.x, event.y # clicked position
color = '#{:06x}'.format(randrange(256 ** 3)) # random color picker
c = canvas.create_oval(x - r, y - r, x + r, y + r, fill=color) # print circle
canvas.after(100, shrink, c, x, y, r)
def shrink(c, x, y, r):
if r > 0:
r -= 1 # radius shrinking
canvas.coords(c, x - r, y - r, x + r, y + r) # changing circle size
canvas.after(100, shrink, c, x, y, r) # timer, size 1pt smaller until size is 0
canvas = tk.Canvas(width=400, height=300)
canvas.pack()
canvas.bind('<Button-1>', klik)
tk.mainloop()

TkInter python - creating points on a canvas to obtain a Sierpinsky triangle

I want to make a program which plots a Sierpinsky triangle (of any modulo). In order to do it I've used TkInter. The program generates the fractal by moving a point randomly, always keeping it in the sides. After repeating the process many times, the fractal appears.
However, there's a problem. I don't know how to plot points on a canvas in TkInter. The rest of the program is OK, but I had to "cheat" in order to plot the points by drawing small lines instead of points. It works more or less, but it doesn't have as much resolution as it could have.
Is there a function to plot points on a canvas, or another tool to do it (using Python)? Ideas for improving the rest of the program are also welcome.
Thanks. Here's what I have:
from tkinter import *
import random
import math
def plotpoint(x, y):
global canvas
point = canvas.create_line(x-1, y-1, x+1, y+1, fill = "#000000")
x = 0 #Initial coordinates
y = 0
#x and y will always be in the interval [0, 1]
mod = int(input("What is the modulo of the Sierpinsky triangle that you want to generate? "))
points = int(input("How many points do you want the triangle to have? "))
tkengine = Tk() #Window in which the triangle will be generated
window = Frame(tkengine)
window.pack()
canvas = Canvas(window, height = 700, width = 808, bg = "#FFFFFF") #The dimensions of the canvas make the triangle look equilateral
canvas.pack()
for t in range(points):
#Procedure for placing the points
while True:
#First, randomly choose one of the mod(mod+1)/2 triangles of the first step. a and b are two vectors which point to the chosen triangle. a goes one triangle to the right and b one up-right. The algorithm gives the same probability to every triangle, although it's not efficient.
a = random.randint(0,mod-1)
b = random.randint(0,mod-1)
if a + b < mod:
break
#The previous point is dilated towards the origin of coordinates so that the big triangle of step 0 becomes the small one at the bottom-left of step one (divide by modulus). Then the vectors are added in order to move the point to the same place in another triangle.
x = x / mod + a / mod + b / 2 / mod
y = y / mod + b / mod
#Coordinates [0,1] converted to pixels, for plotting in the canvas.
X = math.floor(x * 808)
Y = math.floor((1-y) * 700)
plotpoint(X, Y)
tkengine.mainloop()
If you are wanting to plot pixels, a canvas is probably the wrong choice. You can create a PhotoImage and modify individual pixels. It's a little slow if you plot each individual pixel, but you can get dramatic speedups if you only call the put method once for each row of the image.
Here's a complete example:
from tkinter import *
import random
import math
def plotpoint(x, y):
global the_image
the_image.put(('#000000',), to=(x,y))
x = 0
y = 0
mod = 3
points = 100000
tkengine = Tk() #Window in which the triangle will be generated
window = Frame(tkengine)
window.pack()
the_image = PhotoImage(width=809, height=700)
label = Label(window, image=the_image, borderwidth=2, relief="raised")
label.pack(fill="both", expand=True)
for t in range(points):
while True:
a = random.randint(0,mod-1)
b = random.randint(0,mod-1)
if a + b < mod:
break
x = x / mod + a / mod + b / 2 / mod
y = y / mod + b / mod
X = math.floor(x * 808)
Y = math.floor((1-y) * 700)
plotpoint(X, Y)
tkengine.mainloop()
You can use canvas.create_oval with the same coordinates for the two corners of the bounding box:
from tkinter import *
import random
import math
def plotpoint(x, y):
global canvas
# point = canvas.create_line(x-1, y-1, x+1, y+1, fill = "#000000")
point = canvas.create_oval(x, y, x, y, fill="#000000", outline="#000000")
x = 0 #Initial coordinates
y = 0
#x and y will always be in the interval [0, 1]
mod = int(input("What is the modulo of the Sierpinsky triangle that you want to generate? "))
points = int(input("How many points do you want the triangle to have? "))
tkengine = Tk() #Window in which the triangle will be generated
window = Frame(tkengine)
window.pack()
canvas = Canvas(window, height = 700, width = 808, bg = "#FFFFFF") #The dimensions of the canvas make the triangle look equilateral
canvas.pack()
for t in range(points):
#Procedure for placing the points
while True:
#First, randomly choose one of the mod(mod+1)/2 triangles of the first step. a and b are two vectors which point to the chosen triangle. a goes one triangle to the right and b one up-right. The algorithm gives the same probability to every triangle, although it's not efficient.
a = random.randint(0,mod-1)
b = random.randint(0,mod-1)
if a + b < mod:
break
#The previous point is dilated towards the origin of coordinates so that the big triangle of step 0 becomes the small one at the bottom-left of step one (divide by modulus). Then the vectors are added in order to move the point to the same place in another triangle.
x = x / mod + a / mod + b / 2 / mod
y = y / mod + b / mod
#Coordinates [0,1] converted to pixels, for plotting in the canvas.
X = math.floor(x * 808)
Y = math.floor((1-y) * 700)
plotpoint(X, Y)
tkengine.mainloop()
with a depth of 3 and 100,000 points, this gives:
Finally found a solution: if a 1x1 point is to be placed in pixel (x,y), a command which does it exactly is:
point = canvas.create_line(x, y, x+1, y+1, fill = "colour")
The oval is a good idea for 2x2 points.
Something remarkable about the original program is that it uses a lot of RAM if every point is treated as a separate object.

How to get a rectangle to move along X axis using checkMouse()?

I'm trying to get a rectangle to move along the X axis but checkMouse() isn't working. What needs to be done to make it work?
from graphics import*
import time
from random import randrange
wd=GraphWin("Catch A Ball",500,500)
wd.setBackground("lightblue")
p1=220 #size of rectangle
p2=250
for i in range(1):
spt1=Point(p1,480)
spt2=Point(p2,500)
rct=Rectangle(spt1,spt2)
rct.setOutline("black")
rct.setFill("black")
rct.draw(wd)
p=wd.checkMouse()
c=rct.getCenter()
dx=p.getX() - c.getX()
dy=p.getY() - c.getY()
rct.move(dx,0)
You're missing a loop and I recommend working with getMouse() initially until you've a need to switch to checkMouse():
from graphics import *
window = GraphWin("Catch A Ball", 500, 500)
window.setBackground("lightblue")
point1 = Point(220, 480) # size of rectangle
point2 = Point(250, 500)
rectangle = Rectangle(point1, point2)
rectangle.setFill("black")
rectangle.draw(window)
while True:
point = window.getMouse()
if point is not None: # so we can switch to checkMouse() if desired
center = rectangle.getCenter()
dx = point.getX() - center.getX()
dy = point.getY() - center.getY()
rectangle.move(dx, 0)
You need to add some sort of action/event to break out of the while True: infinite loop.

Resources