-2

Here is the code, the question is centered around the mainLoop() function at the bottom. I'm wondering if there is a way for this function to be called in a non-blocking manner on an interval based on the 'fps' variable.

I tried something that should technically work, I imported 'threading' and in the mainLoop() function, I set a threading.Timer to call the mainLoop() function again after 1/fps seconds. The only problem with this is that the window closes right after opening, and I know that if I called tkinter's root.mainloop() function, although the window would stay open, it would block and halt the rest of the code.

(I also tried a while True loop and have time.sleep(1/fps) at the end, but this requires everything to be finished before calling the code again (it is blocking))

Is there a better way to do this, maybe using asyncio or something similar? Doing this in JavaScript would just be a matter of 'setInterval(mainLoop, 1000/fps)'.

#! /usr/bin/python
from tkinter import *
from threading import Timer
import math

# setup window + canvas
root = Tk()
root.title("Pendulum Simulation")
root.resizable(0,0)
canvas = Canvas(root, width = 600, height = 600, bg="grey", bd=0)
canvas.pack()
root.update()

# world variables
fps = 60
gravity = -1250

# Pendulum variables
length = 240
radius = 15
pinX = canvas.winfo_width()/2
pinY = canvas.winfo_height()/2
a = 179.9 # angle to vertical
a *= math.pi/180 # conversion to radians, needed for math
aV = 0 # angular velocity



# two wrapper functions to draw shapes
def drawLine(x1,y1,x2,y2):
    id = canvas.create_line(x1,canvas.winfo_height()-y1,x2,canvas.winfo_height()-y2, width = 2)
def drawCircle(x,y,r,c):
    id = canvas.create_oval(x - r,canvas.winfo_height()-y -r,x+r,canvas.winfo_height()-y+r, fill = c, width = 2)



def drawObjects():
    x = pinX + length*math.sin(a)
    y = pinY - length*math.cos(a)
    drawLine(pinX,pinY, pinX + length*math.sin(a), pinY - length*math.cos(a))
    drawCircle(pinX,pinY,2,"black")
    drawCircle(x,y,radius, "crimson")
    
def moveObjects():
    global aV
    global a
    
    aA = gravity/length * math.sin(a)
    aV += aA/fps
    a += aV/fps


def mainLoop():
    canvas.delete(ALL)
    drawObjects()
    root.update()
    moveObjects()
    Timer(round(1/fps,2), mainLoop).start()

mainLoop()
Ayden Cook
  • 53
  • 4
  • 2
    Does this answer your question? [tkinter: how to use after method](https://stackoverflow.com/a/25753719/7414759). Read [Tkinter understanding mainloop](https://stackoverflow.com/a/29158947/7414759) – stovfl Jun 23 '20 at 20:29

1 Answers1

0

Tkinter has its own main loop. You can actually hook your own code into that loop, with the after method of your root window. First, which I believe you haven't done, start the mainloop. In your looping function, you can use the after method, after it is called.

def mainLoop():
     root.after(1000, mainLoop) #Calls mainLoop every 1 second.
     (...)
mainLoop()
root.mainloop()

This is non-blocking and shall execute every second.

Chris Fowl
  • 488
  • 4
  • 16