Home

If you're new to Python
and VPython: Introduction

A VPython tutorial

Pictures of 3D objects

What's new in VPython 6

VPython web site
VPython license
Python web site
Math module (sqrt etc.)
Numpy module (arrays)

 

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 display is in scene, the default window 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.mouse.getclick()

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

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

Starting with VPython 6, an alternative for waiting for a mouse click is to wait for various kinds of mouse or keyboard events:

scene.waitfor('click')     # wait for a click
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('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

As with scene.mouse.getclick(), you can obtain a package of information about the event that caused the end of the wait, with the added information of whether it was a mouse or keyboard event:

from visual import *
box()
scene.waitfor('click')
print('You clicked.')
ev = scene.waitfor('click keydown')
if ev.event == 'click':
    print('You clicked at', ev.pos)
else:
    print('You pressed key '+ev.key)

The first statement, scene.waitfor('click'), makes the display be the focus of keyboard input.

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 The nearest object in the scene which falls under the cursor, or None. At present curve, label, helix, extrusion, and faces cannot be picked. The picked object is scene.mouse.pick.

pickpos The 3D point on the surface of the picked object which falls under the cursor, or None; scene.mouse.pickpos.

camera The read-only current position of the camera as positioned by the user, scene.mouse.camera. For example, mag(scene.mouse.camera-scene.center) is the distance from the center of the scene to the current position of the camera. If you want to set the camera position and direction by program, use scene.forward and scene.center, described in Controlling Windows.

ray A unit vector pointing from camera in the direction of the mouse cursor. The points under the mouse cursor are exactly { camera + t*ray for t>0}.

   The camera and ray attributes together define all of the 3D points under 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 can handle a three-button mouse, with "left", "right", and "middle" buttons. For systems with a two-button mouse, the "middle" button consists of the left and right buttons pressed together. For the Macintosh one-button mouse, the right button is invoked by holding down the Command key (normally used for rotating the camera view), and the middle button is invoked by holding down the Option key (normally used for zooming the camera view).

Design for left-button events if possible

VPython continues to provide the basic mouse event functionality for handling events from right and middle buttons when userspin or userzoom is disabled, out of concern for supporting old programs. However, it has become evident that there are limitations to this approach which could preclude some kinds of mouse handling that people might want to do in the future. For example, you might want to allow userspin with right drags yet also pick up right clicks. For that reason it is conceivable that future developments in this area might break existing programs, and therefore for maximum forward compatibility it is prudent to use only left-button interactions in new programs.

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.

If you use 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 we will discuss it first. Programs that use polling will continue to work, but you cannot mix polling and callback approaches: you must use one or the other in a program.

 

Handling events with callbacks

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

from visual import *
s = sphere(color=color.cyan)

def change():
    if s.color == 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 display 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.

This operation is called a "callback" because with scene.bind you register with VPython 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. The events 'keydown' and 'keyup' are discussed in the keyboard section. 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 click with the mouse or press a key on the keyboard:

scene.bind('click keydown', change)

The example program eventHandlers.py illustrates the callback method for handling many kinds of events.

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, evt.button)

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 the information contained in scene.mouse, plus 'event', which is the name of the event that triggered the callback, such as 'mousedown' or 'click'. The name of the argument need not be 'evt'; use whatever name you like. In addition to evt.event and evt.button, there is further event information in the form of evt.press, evt.click, evt.drag, evt.drop, and evt.release (see details in the section on polling), but this information is more relevant when using polling rather than callbacks to get events.

You can optionally have VPython send the callback function an additional argument. Here is a revised version of the color-change example, in which in the bind operation we specify an additional argument to be sent, in this case a list of objects whose colors should toggle:

from visual import *
s = sphere(pos=(-2,0,0), color=color.cyan)
b = box(pos=(2,0,0), color=color.red)

def change(evt, objects):
    for obj in objects:
        if obj.color == color.cyan:
            obj.color = color.red
        else:
            obj.color = color.cyan

scene.bind('click', change, [s, b])

Right or middle button mouse events

Normally, only the left mouse button will trigger an event, but if you specify scene.userspin = False, so the right button is no longer bound to camera rotation, clicking with the right mouse button will cause a callback. Similarly, if you specify scene.userzoom = False, you can click with the middle button (or left+right buttons).

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

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=(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.)

 

Handling events with polling

The following information on how to handle events using polling is still valid, but you are encouraged to consider using the more powerful callback approach when writing new programs. Remember that you cannot mix the two schemes. You can use either callback or polling in a program, but not both.

The simplest polling mouse interaction is to wait for a mouse click:

scene.mouse.getclick() Wait for a mouse click. If you say m = scene.mouse.getclick(), the variable m gives information about the event. For example, m.pos is the location of the mouse at the time of the click event.

It is a useful debugging technique to insert scene.mouse.getclick() into your program at a point where you would like to stop temporarily to examine the scene. Then just click to proceed.

In the Drag example you will see how to use event-handling functions to process mouse events continuously.

events The number of events (press, click, drag, or drop) which have been queued; e.g. scene.mouse.events.
scene.mouse.events = 0 may be used to discard all input. No value other than zero can be assigned.

getevent() Obtains the earliest mouse event and removes it from the input queue. If no events are waiting in the queue (that is, if scene.mouse.events is zero), getevent() waits until the user enters a mouse event (press, click, drag, or drop). getevent() returns an object with attributes similar to a mouse object: pos, button, pick, pickpos, camera, ray, project(), alt, ctrl, and shift. These attributes correspond to the state of the mouse when the event took place. For example, after executing mm = scene.mouse.getevent() you can look at the various properties of this event, such as mm.pos, mm.pick, mm.drag (see below), etc.

The getevent() function provides additional information, in addition to the usual information such as pos or pick:

press = 'left' for a press event, or 'right' or 'middle', or None. That is, if you execute mm = scene.mouse.getevent(), mm.press will be 'left', 'right', 'middle', or None. A press event occurs when a mouse button is depressed.

click = 'left' for a click event, or 'right' or 'middle', or None. A click event occurs when all mouse buttons are released with no movement of the mouse. (This is also a release event.) Note that a click event happens when the mouse button is released. See Click example.

drag = 'left' for a drag event, or 'right' or 'middle', or None; in this case pos and other attributes correspond to the state of the mouse at the time of the original press event, so as not to lose initial position information. A drag event occurs when the mouse is moved slightly after a press event, with mouse buttons still down. This can be used to signal the beginning of dragging an object. See Drag example.

drop = 'left' for a drop event, or 'right' or 'middle', or None. A drop event occurs when the mouse buttons are released after a drag event. (This is also a release event.)

release = 'left' following click and drop events, indicating which button was released, or 'right' or 'middle', or None. A release event occurs when the mouse buttons are released after a click or drag event.

button = 'left', 'right', or 'middle'.

If you are interested in every type of event (press, click, drag, and drop), you must use events and getevent(). If you are only interested in left click events (left button down and up without significant mouse movement), you can use clicked and getclick():

clicked The number of left clicks which have been queued; e.g. scene.mouse.clicked.
This does not include a count of nonclick events (press, drag, or drop).

getclick() Obtains the earliest mouse left click event (pressing the left button and releasing it in nearly the same position) and removes it from the input queue, discarding any earlier press, drag, or drop events. If no clicks are waiting in the queue (that is, if scene.mouse.clicked is zero), getclick() waits until the user clicks. Otherwise getclick() is just like getevent().

It is a useful debugging technique to insert scene.mouse.getclick() into your program at a point where you would like to stop temporarily to examine the scene. Then just click to proceed.

Between a drag event (start of dragging) and a drop event (end of dragging), there are no mouse events but you can examine the continuously updated position of the mouse indicated by scene.mouse.pos.

Normally, dragging with right or middle button represents spin or zoom, and is handled automatically by VPython, so you can check for left-button drag or drop events simply by checking whether drag or drop is true (in Python, a nonempty string such as 'left' is true, None is false). Unless you disable user zoom (scene.userzoom = False), press, click, drag, drop, and release with the middle button are invisible to your program. Unless you disable user spin (scene.userspin = False), press, click, drag, drop, and release with the right button are invisible to your program.

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=(0,1,0), point=(0,3,0))
if temp: # temp is None if no intersection with plane
    ball.pos = temp

This projects the mouse cursor onto a plane that is perpendicular to the specified normal. If point 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.

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 d from the origin to the plane that is perpendicular to the specified normal. The example above is equivalent to

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

Pausing for mouse or keyboard input

Often you want to pause for either mouse or keyboard input. You can copy the following function into your program, and then insert pause() wherever you want to pause.

def pause():
    while True:
        rate(30)
        if scene.mouse.events:
            m = scene.mouse.getevent()
            if m.click == 'left': return
        elif scene.kb.keys:
            k = scene.kb.getkey()
            return

As of VPython 6, an alternative to this function is simply to write scene.waitfor('click keydown').