Controlling animation from a button

53 views
Skip to first unread message

Daniel Schroeder

unread,
Dec 30, 2017, 4:01:58 PM12/30/17
to VPython-users
Unhappy with the status of yesterday's experiments (despite all the expert help, for which I'm truly grateful!), today I decided to fool around with GlowScript. Didn't take me long to find a limitation that doesn't seem to be mentioned in the documentation. (There may have been a similar limitation in earlier VPython versions as well.)

I'm simulating projectile motion and I want to use a "Launch" button to launch multiple projectiles, with sliders to set the initial conditions. But never mind the sliders for now. What I've discovered is that when I put an animation loop inside the function called by the button, the code in that function never seems to get executed. Here's a stripped-down example:

scene.center = vector(50,30,0)
field = box(pos=vector(50,0,0), size=vector(120,0.1,40), color=color.green)
ball = sphere(pos=vector(0,0,0), radius=1, color=color.red, make_trail=True)

button(text="Launch!", bind=launch)

def launch(b):
    print("Projectile launched!")
    vx = 25
    vy = 15
    dt = 0.01
    while ball.pos.y >= 0:
        rate(80)
        ball.pos.x += vx * dt
        ball.pos.y += vy * dt
        vy += -9.8 * dt

This code draws the initial scene with the ball at the origin, but clicking the button has no effect (other than changing the button's shading). Even the print statement in the launch function never gets executed. On the other hand, if I remove the line that creates the button and replace it with simply "launch(0)", everything works and the animation runs (just once, because there's no button).

I guess the work-around is to create a single animation loop that runs all the time, but that does nothing until the button is pressed, at which time the variables are initialized and such. I'll try that next, but I'd like to better understand exactly what the limitation is, and whether I'm missing something or if there is a better solution, so I hope someone can explain what's going on here.

Thanks,

Dan

Bruce Sherwood

unread,
Dec 30, 2017, 5:30:20 PM12/30/17
to VPython-users
You've happened on the first try to encounter a limitation of GlowScript, namely that it doesn't work to place a rate or waitfor or sleep statement in a function that is driven by a widget. Happily, I think I now glimpse a way to give an informative error message about this, and I also glimpse a way to document the problem in such a way that users would notice.

Interestingly, had you tried the same program in VPython 7 it would have worked, after dealing with an error message for the button statement: NameError: name 'launch' is not defined. The error comes from the need in Python to define a function before calling it. Move the button statement to the end of the program and it works.

The GlowScript problem is very deep and is related to the fact that JavaScript code cannot be interrupted. For example, an infinite loop in JavaScript (to which Python is compiled using the RapydScript-NG transpiler) locks up the browser. In order for infinite loops to be possible, I have to preprocess your code in a way that includes invoking the Streamline module that rewrites your "synchronous" code into "asynchronous" code, keying off the presence of rate or waitfor or sleep. For highly technical reasons, calling your launch function explicitly can be handled by Streamline, but not calling launch as a function bound to a widget. (Similarly, there's a lot of preprocessing of GlowScript VPython to allow you to use "operator overloading" such as vector+vector being treated differently from scalar+scalar; JavaScript does not natively support operator overloading.)

As you suppose, a natural way to do what you want is this (which works in GlowScript VPython and VPython 7):

from vpython import *
scene.center = vector(50,30,0)
field = box(pos=vector(50,0,0), size=vector(120,0.1,40), color=color.green)
ball = sphere(pos=vector(0,0,0), radius=1, color=color.red, make_trail=True)

run = False
def launch(b):
    global run
    run = True
    scene.append_to_caption("Projectile launched!")

button(text="Launch!", bind=launch)

vx = 25
vy = 15
dt = 0.01

while ball.pos.y >= 0:
    rate(80)
    if run:

Daniel Schroeder

unread,
Dec 30, 2017, 5:59:09 PM12/30/17
to VPython-users
Thanks, Bruce. As you can tell, my Python is rusty. But I have been programming in JavaScript a fair amount lately, so I'm used to not having to pay attention to whether functions are defined before they're called. Meanwhile, I haven't lost my knack for running up against limitations on the first day!

In general, I'm finding your documentation to be excellent in the amount of detail provided--just enough to convey the important information, without wasting the reader's time on superfluous prose.

Bruce Sherwood

unread,
Dec 30, 2017, 11:35:38 PM12/30/17
to VPython-users
I'm delighted that you've found the documentation useful/usable. Thanks. Incidentally, I consider the Examples, accessible from the first page of glowscript.org, to be an important supplement to the Help.

Minor correction to my use of words. I should have said "The error comes from the need in Python to define a function before referencing it", rather than "before calling it". I guess this behavior means that Python is a one-pass compiler.

Bruce

Bruce Sherwood

unread,
Jan 1, 2018, 6:56:25 PM1/1/18
to VPython-users
I figured out and implemented machinery in GlowScript VPython that now gives an error message when one tries to bind a function to an event or a widget, instead of just silently failing.

Bruce
Reply all
Reply to author
Forward
0 new messages