Home

If you're new to Python
and VPython see more
Help at glowscript.org

Pictures of 3D objects

 

Mouse Interactions

For basic examples of mouse handling, see Click example or Drag example.

The simplest mouse interaction is to wait for the user to click before proceeding in the program. Suppose the 3D canvas is in scene, the default canvas created by VPython. Here is a way to wait for a mouse click, which is defined as the mouse button being pressed and released without moving the mouse (the event occurs when the mouse button is released):

ev = scene.waitfor('click')

You can use the package of information contained in the variable "ev":

sphere(pos=ev.pos, radius=0.1)

The package of information about the event includes information of what kind of event it was:

box()
while True:
    ev = scene.waitfor('mousedown mouseup')
    if ev.event == 'mousedown':
        print('You pressed the mouse button')
    else:
        print('You released the mouse button')
    print(ev.pos) # the position of the mouse

Here are additional options:

scene.waitfor('mousedown') # wait for mouse button press
scene.waitfor('mouseup') # wait for mouse button release
scene.waitfor('mousemove') # wait for mouse to be moved
scene.waitfor('mouseenter') # when move into canvas
scene.waitfor('mouseleave') # when leave canvas
scene.waitfor('mousedown mousemove') # either event
scene.waitfor('keydown') # wait for keyboard key press
scene.waitfor('keyup')   # wait for keyboard key release
scene.waitfor('click keydown') # click or keyboard

A convenient way to wait for a mouse "click" event is to use scene.pause(). You can also show a message to the user on the canvas: scene.pause('Click to proceed').

You can set up a function to be called when a mouse or keyboard event occurs. The following will display the type of event (mouse click or keydown) and, in the case of a keydown event, which key is involved:

def process(ev):
    print(ev.event, ev.which)

scene.bind('click keydown', process)

Important limitation of GlowScript VPython: In situations such as this "process" example, the bound function cannot contain the statements rate, sleep, pause, waitfor, get_library, or read_local_file, statements that require pausing during execution. A related issue is that a function f(...) that is not bound to an event but which contains one of these special statements (rate, etc.) cannot contain a default variable such as f(y=5); in such a case one must pass a dictionary to the function and determine which variables are in that dictionary. These limitations do not apply to VPython 7.

The quantity ev.event will be 'keydown' if a key was pressed or 'mousedown' if the left mouse button was pressed. The quantity ev.which is the numerical key code or mouse button indicator (mouse button is always 1 for now). For example, ev.which is 65 for the 'a' key. The quantity ev.key is the corresponding character string, such as 'a' or 'delete'.

The quantity ev.canvas is the canvas associated with the event. You can bind events on different canvases to the same function and be able to tell in which canvas the event occurred.

Note that scene.mouse.shift is true if the shift key is down at the time of the keyboard event; similarly for scene.mouse.ctrl and scene.mouse.alt.

It is possible to use "anonymous" (unnamed) functions in this situation. For examples, see the mouse drag discussion. However, anonymous functions cannot be used in VPython 7.

The package of information about the event that caused the end of the wait includes the information of whether it was a mouse or keyboard event:

box()
ev = scene.waitfor('mousedown keydown')
if ev.event == 'mousedown':
    print('You pressed the mouse button at', ev.pos)
else:
    print('You pressed the key', ev.key)

The object scene.mouse contains lots of information about the current state of the mouse, which you can interrogate at any time:

pos The current 3D position of the mouse cursor; scene.mouse.pos. VPython always chooses a point in the plane parallel to the screen and passing through scene.center. (See Projecting mouse information onto a given plane for other options.)

pick Execute obj = scene.mouse.pick to obtain the object pointed to by the mouse. If you have a box named B, you can determine whether the picked object is that box by asking if (B == obj). If there is no object pointed to by the mouse, obj is None. Also, obj will be None if the object has the pickable attribute set to False. For example, the curves, spheres, and arrows created by make_trail, attach_trail, and attach_arrow are not pickable, and you may wish to specify that some of your own objects are not pickable. At present label and helix cannot be picked. For curve objects, obj.segment is the number of the picked segment along the curve, starting with 1 (representing the segment from point number 0 to point number 1). You can test for a curve object with if instanceof(obj, curve):. See the GlowScript example MousePicking.

ray A unit vector pointing from the camera in the direction of the mouse cursor.

project() Projects position onto a plane. See Projecting mouse position onto a given plane.

alt = True if the ALT key is down, otherwise False

ctrl = True if the CTRL key is down, otherwise False

shift = True if the SHIFT key is down, otherwise False

Different kinds of mouse

The mouse routines in GlowScript currently handle only the left (or only) mouse button.

Projecting mouse position onto a given plane

Here is a way to get the mouse position relative to a particular plane in space:

temp = scene.mouse.project(
             normal=vec(0,1,0),
             point=vec(0,3,0) )
# None if no intersection with plane:
if temp != None) ball.pos = temp

This projects the mouse cursor onto a plane that is perpendicular to the specified normal. If the second parameter is not specified, the plane passes through the origin. It returns a 3D position, or None if the projection of the mouse misses the plane (scene.mouse.ray is parallel to the plane).

In the example shown above, the user of your program will be able to use the mouse to place balls in a plane parallel to the xy plane, a height of 3 above the xy plane, no matter how the user has rotated the point of view.

You can instead specify a perpendicular distance from the origin to the plane that is perpendicular to the specified normal. The example above is equivalent to

temp=scene.mouse.project(
              normal=vec(0,1,0), d=3 )

Polling and callback

There are two different ways to get a mouse event, "polling" and "callback". In polling, you continually check scene.mouse.events to see whether any events are waiting to be processed, and you use scene.mouse.getevent() to get the next event to process. Prior to VPython 6, this was the only way you could handle mouse or keyboard events, but this scheme is not available in GlowScript

In the callback method, you specify a function to be executed when a specific type of event occurs, and the function is sent the event information when the specified type of event occurs. For many purposes this is a better way to handle mouse and keyboard events, and it works in both classic VPython and in GlowScript.

 

Callbacks

Here is a simple example of how to use callbacks to process click events:

s = sphere(color=color.cyan)

def change():
    if s.color.equals(color.cyan):
        s.color = color.red
    else:
        s.color = color.cyan

scene.bind('click', change)

We define a "function" named "change". Then we "bind" this function to click events occurring in the canvas named "scene". Whenever VPython detects that a click event has occurred, VPython calls the bound function, which in this case toggles the sphere's color between cyan and red. (In classic VPython one could say "if s.color == color.cyan", but in GlowScript it is necesary to use the "equals" function to compare two vectors.

This operation is called a "callback" because with scene.bind you register that you want to be called back any time there is a click event. Here are the built-in events that you can specify in a bind operation:

Mouse:    click, mousedown, mousemove, mouseup
Keyboard: keydown, keyup
Other:    redraw, draw_complete

The event 'mousedown' or 'mouseup' occurs when you press or release the left button on the mouse, and the 'mousemove' event occurs whenever the mouse moves, whether or not a button is depressed. A 'redraw' event occurs just before the 3D scene is redrawn on the screen, and a 'draw_complete' event occurs just after the redrawing (these event have rather technical uses such as timing how often redrawings occur, or how much time they take).

You can bind more than one event to a function. The following will cause the callback function to be executed whether you press or release the mouse button:

scene.bind('mouseup mousedown', change)

Another use of callbacks is to drive a function periodically. See the example program Bounce-Callbacks-VPython.

Details of the event

You can get detailed information about the event by writing the callback function like this (note the variable 'evt' in parentheses):

def info(evt):
    print(evt.event, evt.pos)

Here we specify an argument in the definition of the callback function ('evt' in this case). When the function is called due to a specified event happening, VPython sends the function information about the event. The name of the argument need not be 'evt'; use whatever name you like. In addition to evt.event, evt.pos, and evt.pick, there is further event information in the form of evt.press and evt.release which are 'left' for press or release events.

The quantity evt.event will be 'keydown' if a key was pressed. The quantity evt.which is the numerical key code or mouse button indicator (mouse button is always 1 for now). For example, evt.which is 65 for the 'a' key. Note that scene.mouse.shift is true if the shift key is down at the time of the keyboard event; similarly for scene.mouse.ctrl and scene.mouse.alt.

In classic VPython you can optionally have VPython send the callback function an additional argument, but this is not currently possible in GlowScript.

Right or middle button mouse events

There is currently no way in GlowScript to handle right button or middle button events.

Unbinding

Suppose you executed scene.bind('mousedown mousemove', Drag), but now you no longer want to send mousemove events to that function. Do this:

scene.unbind('mousemove', Drag)

You can also leave a function bound but start and stop having events sent to it:

D = scene.bind('mousemove', Drag)
...
D.stop() # temporarily stop events going to Drag
...
D.start() # start sending events to Drag again

You can check whether the callback is in start or stop mode with D.enabled, which is True if the callback has been started and False if it has been stopped.

 

Custom events: triggers -- THIS CURRENTLY DOES NOT WORK IN VPython

It is possible to create your own event type, and trigger a callback function to do something. Consider the following example, where the event type is 'color_the_ball':

def clickFunc():
    s = sphere(pos=scene.mouse.pos, radius=0.1)
    scene.trigger('color_the_ball', s)

def ballFunc(newball):
    newball.color=color.cyan

scene.bind('click', clickFunc)
scene.bind('color_the_ball', ballFunc)

box(pos=vector(1,0,0))

We bind click events to the function clickFunc, and we bind our own special event type 'color_the_ball' to the function ballFunc. The function clickFunc is executed when the user clicks the mouse. This function creates a small sphere at the location of the mouse click, then triggers an event 'color_the_ball', with the effect of passing to the function ballFunc the sphere object. Finally ballFunc applies a color to the sphere. (Obviously one could color the sphere in clickFunc; the example is just for illustration of the basic concept.)