Tutorial:

In the following we will assume that you have are running Python1.5.2b1 and that your PYTHONPATH is set such that you can import OpenGL (which implies Tkinter), the Numeric extension and also the viewer module itself. I will also use the word xxx-"object" to refer to an instance of the class xxx.
 

Example 1: Getting started

Let's first import the viewer module from the DejaVu application
>>> from DejaVu import Viewer
All right, so let's instantiate our first viewer:
>>> MyViewer = Viewer()
This command creates a viewer with a first camera (3D window) that was created with double buffering and a z-buffer by default. A viewer can have several cameras allowing the user to look at the scene from several view points.
It is a good idea to use a non trivial name for the viewer (not v since you are very likely to use v to refer to a list of vertices in which case you would loose the handle to your viewer). Of course you can use a simpler alias if you do not want to type in the viewer's whole name:
>>> v1 = MyViewer
After issuing this command you should have the following windows on your screen:
The viewer's GUI, and the first camera called camera 0.

The GUI presents a row of buttons under Transform which allow the user to direct transformations to the current Object, the current Camera, the current Clipping plane, the current Light or the current Texture. Normally  "Object" is selected (I call transformation a rotation, translation or scale operation). This means that the callback functions of the camera's trackball are bound to the functions transforming the current object's position.
Below this set of  buttons we have the object list that currently contains only one object called "root".  root  is highlighted which means it is the current object. This "root" object is protected against deletion and is a place holder for the world transformation. All objects added to this viewer will be children of "root" or one of its children and they all will inherit root's transformation matrix. 3D transformation inheritance makes all children of the current object undergo the same transformations as the current object.  This can be changed programmatically.(see Reference Manual)

Now let's display a bunch of lines. To do this we need 3D coordinates:

>>> coords = [ [0,0,0], [1,1,0], [0,1,0], [1,0,0] ]
an array of indices telling which vertices are to be connected by lines:
(each entry represents a line between two points whose indices in the coords array are given)

>>> indices = [[0,1], [2,3],[0,2],[2,1],[1,3],[3,0]]

Alternatively, the lines could be specified where each list represents a line through the points whose indices are given. The value -1 terminates the line. The first two entries draw the diagonals and the third the box itself:

>>> indices = [[0,1, -1, 0,0], [2,3,-1,0,0], [0,2,1,3,0]]
and an optional array of materials. Here the tuples represent RGB values:
>>> materials = ( (1.,1.,1.,1.), (1.,0.,0.,1.), (0.,1.,0.,1.), (0.,0.,1.,1.) )
We create a geometry object of type IndexedPolylines:
>>> from DejaVu import IndexedPolylines
>>> cross = IndexedPolylines.IndexedPolylines('MyFirstObject')
We add the vertices, lines and materials to that object:
>>> cross.Set(vertices=coords, faces=indices, materials = materials )
and we add the geometry to the viewer:
>>> MyViewer.AddObject(cross)
to see the new object, we redraw the scene:
>>> MyViewer.Redraw()
Now the object listbox should have one more line "~MyFirstObject". The ~ is used to visualize the hierarchy. By default, AddObject makes the new object the child of root.  This can be changed programmatically (see Reference Manual). In the camera you should see a square with four vertices colored white, red, green and blue. You can use the mouse to transform this object:
Mouse default bindings
Middle-button rotation
Right-button XY-translation
Shift + Middle-button scale
Shift + Right-button Z-translation

Because of the perspective, the Z-translation looks like a scaling operation, but it is not. You can tell the difference by doing a Z-translation away from you. You will see the object fade in the fog ...!

Now try to hit the Reset button in the GUI. The object should return to its initial position. What you do here is to reset the current object's transformation matrix.(See Reference Manual for more details.)

If you hit the Normalize button, the square will be scaled to fill out the camera. What happens here is that the bounding box of the scene is computed and then the root object is transformed to fit the bounding box into the camera.

Notice that when you rotate, the square rotates about its lower left corner (the white vertex). Try hitting the Center button and do a rotation. The object now rotates about its center. What happens here is that the center of the scene's bounding box is computed and this point is set as the current object's rotation center. You can set the rotation center of the current object by picking on any vertex in the scene while holding down the Shift key.
 

Shift + Left-button set rotation center to picked vertex
The Reset, Normalize and Center functions are also bound by default to the 'r', 'n' and 'c' keyboard keys (also to the 'R', 'N' and 'C' keys).

Up to now the current object was always the "root". Therefore, all transformations were applied to "root" and the square moved because it is a child of root and thus inherits its transformations.

You can change the current object either by selecting its name in the object list or by picking its graphical representation with the mouse in the camera. Picking in the camera not on an object makes the root the current object. (Also, an object can be made the current object programmatically which is described in the Reference Manual.)
 

Left-button selection picking
Using the left mouse button you can select an object (i.e. make it the current object). Try picking on the square. If picking was successful, the Object browser in the GUI should now highlight the object "~cross". Any rotation, translation or scale operation will now apply to this object and its children but won't affect its siblings.

The Properties Panel appears below the Reset, Norm. and Center buttons.  This part of the GUI allows the user to interactively change the properties of the current object, camera, any Clipping planes and any of the lights present.  Selecting Object, Camera, Clip or Light button causes the appropriate submenu to be displayed at the bottom of the GUI.

By default, the Object submenu is displayed. The Object submenu has 3 checkbuttons: Show Object lets you display or hide the current object,( it has no effect if the current object is root) and the Edit Object Material button lets you change the color, value and surface properties of the current object.

Set the current object to "~MyFirstObject" and toggle the Show Object checkbutton.  The box should appear and disappear in the Camera 0.

Click on the Edit Object Material checkbutton.  Controls are displayed which allow you to change the color, value and other properties of the materials used to display the current object, "~MyFirstObject."   (Example Two below shows you more about changing the materials of the current object.)
Click on this button again to close this portion of the GUI.  (It is not possible to change the properties of the root's materials)

Next is the Delete Object button.  If you press this button, the current object will be deleted and removed from the viewer.  Try it.  The box should disappear from Camera 0. To restore it, you must again add it to your viewer.

>>> MyViewer.AddObject(cross)

to see the new object, we redraw the scene:
>>> MyViewer.Redraw()

The LineWidth and PointWidth Sliders allow you to change these properties of the current object if it is possible.
Make "~MyFirstObject" the current object and try dragging the small blue triangle above the slider. You should see the line widths change in Camera 0.

The last four buttons allow you to make choices about how the Front and Back views of the current object are displayed,  what kind of shading is used and what kind of culling is done on it.  These are illustrated with the third example below.

Up to now we have transformed geometries, but you can also transform the camera position or the clipping planes.
Let's first name a clipping plane:

>>> cl = MyViewer.clipP[0]
This a handle to the first clipping plane. By default this plane has equation x=0. The number of clipping planes available depends on your hardware. The OpenGL specifications speak about 6 or more such planes. When a viewer gets instantiated, it finds out how many such planes your OpenGL implementation provides and creates an array of clipping planes for the viewer (MyViewer.clipP).

Now we can add this clipping plane to our object, meaning this object will be clipped by the plane . (HINT: you may want to increase the size of "cross" with Shift-Middle Mouse Button before starting this section)

>>> cross.AddClipPlane(cl)
To display the plane,  select the Clip button in the Properties Panel.  This will show the Clip Properties submenu.  Click on row 1 button under the disp. (display)  heading.  Notice that as you translate the square, it gets clipped when it moves into the left half of the camera.  If you toggle the button in row 1 under the side heading, this will change.  You can add other clipping planes using this submenu.  First, be sure that ~MyFirstObject is the current object.  Then clipping planes are made to slice it by turning on the buttons in the first column.  The side buttons toggle which part of the clipped object is displayed.

When you bind clipping planes to an object, you can specify whether or not it should clip this object's children using the inh buttons.

We can now transform the clipping plane either by picking on it (left mouse button) or by selection (Transform Clip) in the GUI.
As you rotate the plane you will see that is is actually a square with diagonals. Translations are always done perpendicular to the clipping plane. Scale only changes the geometry representing the clipping plane which is infinite .  Of course, the plane can be made invisible by toggling the disp button. The reset button in the GUI will reset the clipping plane's transformation if the Clip button under Transform is on.

Note that when you move the "root" object or the "~MyFirstObject" object, the clipping plane doesn't move. this is because it doesn't inherit transformations. If you want to look at how it clips your geometry from somewhere else you'll have to transform the camera's position. You can do this either by double clicking (left mouse button) on the camera's background or by selecting "Transform Camera" in the GUI.
 
 

Double Left-button transform camera
 Now when you rotate the relative position of the plane, the object doesn't change. You look at it from somewhere else.

Finally, we can add a second camera:

>>> c2 = MyViewer.AddCamera()
As you move the mouse from one camera to the other, notice that the cameras maintain their own transformation mode. The first camera should be in Transform Camera mode while the second one appears in the default Transform object mode.
Try some transformations in each window.

Also notice that camera 0 has a red border, meaning that it is the current camera. If you pick (left mouse button) in camera 1, it will become the current camera.
 

Example Two:  Changing materials

Start this example by making a collection of points, v:
>>>  v = ( (0.,0.,0.),  (1.,0.,0.),
      (0.,1.,0.),  (1.,1.,0.),
      (0.,2.,0.),  (1.,2.,0.),
      (0.,3.,0.),  (1.,3.,0.),
      (0.,4.,0.),  (1.,4.,0.),
      (0.,5.,0.),  (1.,5.,0.),
      (0.,6.,0.),  (1.,6.,0.))
 
 

and defining some colors:
>>> RED =   (1., 0., 0.)
>>> GREEN = (0., 1., 0.)
>>> BLUE =  (0., 0., 1.)

and collections of colors:
>>> col = ( RED, RED, RED, GREEN, GREEN, GREEN, BLUE, BLUE, BLUE, RED, GREEN, BLUE, RED, GREEN )

>>> col2 = ( RED, RED, RED, RED, RED, RED, RED, GREEN, GREEN, GREEN, GREEN, GREEN, GREEN, GREEN)

Define a list to specify the faces of the lines we make later:
>>> ind = (range(14),)

Start up a viewer :

>>> from DejaVu import Viewer
>>> vi = Viewer()

and make a line:

>>> from DejaVu.IndexedPolylines import IndexedPolylines
>>> p = IndexedPolylines('testColor', vertices = v, faces = ind, materials = col)

and add it to the viewer:
>>> vi.AddObject(p)

to see the new object, we redraw the scene:
>>> vi.Redraw()

and make another line:
>>> p2 = IndexedPolylines('testColor2', vertices = v, faces = ind,materials = col2)

and add it to the viewer:

>>> vi.AddObject(p2)

to see the new object, we redraw the scene:
>>> vi.Redraw()

With these two objects in the viewer, try changing the current object and transforming it.

Add another line:
>>> norm = ((1.0, 0., 0.0 ),) * 14
>>> pn = IndexedPolylines('testMaterial', vertices = v, faces = ind,
                      materials = col, vnormals = norm)
>>> vi.AddObject(pn)

to see the new object, we redraw the scene:
>>> vi.Redraw()

Add another line:
>>> pn2col = IndexedPolylines('testMaterial2', vertices = v, faces = ind,
                          materials = col2,
                          vnormals = norm)
>>> vi.AddObject(pn2col)

to see the new object, we redraw the scene:
>>> vi.Redraw()

Finally, try making some rows of spheres colored differently:
>>> from DejaVu.Spheres import Spheres
>>> s1 = Spheres('test', centers = v, radii = (0.4,), materials = col)
>>> vi.AddObject(s1)

to see the new object, we redraw the scene:
>>> vi.Redraw()

>>> s2 = Spheres('test', centers = v, radii = (0.4,), materials = col2)
>>> vi.AddObject(s2)

to see the new object, we redraw the scene:
>>> vi.Redraw()
 
 

Example Three:  a simple surface and its normals

This example starts by importing the function which we will use to read in a precalculated msms surface from the files 'surface.vertices' and 'surface.triangles':

>>> from tutorialFunctions import readSurface

The function readSurface returns v, a list of vertices and t a list of triangles
>>>  v,t = readSurface ('surface')

We make a numeric array out of the vertices so we can easily separate vertices and normals

>>> import Numeric
>>> vn = Numeric.array(v)

We get a viewer:

>>> from DejaVu import Viewer
>>> vi = Viewer()

We add the surface to the viewer
>>> from DejaVu.IndexedPolygons import IndexedPolygons
>>> srf = IndexedPolygons('myFirstSurface', vertices = vn[:,:3], faces = t)
>>> vi.AddObject(srf)

to see the new object, we redraw the scene:
>>> vi.Redraw()

The surface composed of triangles should appear in camera 0.   The buttons at the bottom of the Viewer GUI let you change this display.  First make "~myFirstSurface" the current object.  Then, click on the Front button.  A drop-down menu appears with 4 radiobuttons: inherit, point, line or fill.  Try changing the surface to Fill.  While it is shown in this representation, experiment with the Shading menu.  The Culling menu allows you to select which side of the surface is hidden (culled).  Try rotating the object while the front is culled.  Notice that if Front_and_Back is culled the object disappears!
To see the effects of selecting "INHERIT" for the Front representation, make "root" the current object and change its representation from line to fill or to point.
Notice what the surface looks like if Culling is set to None: If the Front and Back representations are both set to Line you see all the triangles in both the front and back.  If you now change the Back representation to Fill, you will see the Front triangles on top of the Back shaded areas. Experiment with changing culling.

To make this example a little more interesting, we will add lines to represent the normals to each vertex , spheres to mark the center of each face and lines representing the normal to the center of each face:

To add the normals to each vertex (each point in pts which is a copy of vn):
>>> from DejaVu.Polylines import Polylines
>>> pts = vn.__copy__()
>>> vn[:,3:6] = vn[:,:3]+vn[:,3:6]
>>> pts = Numeric.reshape( vn, (-1,2,3) )
>>> p = Polylines('normals', vertices = pts)
>>> vi.AddObject(p)
to see the new object, we redraw the scene:
>>> vi.Redraw()

To add the face centers (by computing the center of gravity of each triangle):
>>> from DejaVu.Spheres import Spheres
>>> pts = Numeric.take(vn[:,:3], t)
>>> cg = Numeric.sum(pts, 1)/3.0
>>> s = Spheres('faceCenters', centers=cg, radii=0.1 )
>>> vi.AddObject(s)
to see the new object, we redraw the scene:
>>> vi.Redraw()

To add the normals to the triangles placed on the face centers we just computed:
This is done by calculating the normals to the triangles:
>>> from OpenGL import GL
>>> vc = vn[:,:3].__copy__()
>>> nf = GL.glTriangleNormals(vc, t, 'PER_FACE' )

Then by drawing lines from the center of the triangles to these points:
>>> pts = Numeric.concatenate( (cg, cg+nf), 1 )
>>> pts  = Numeric.reshape(pts, (-1,2,3))
>>> pf = Polylines('faceNormals', vertices = pts)
>>> vi.AddObject(pf)
to see the new object, we redraw the scene:
>>> vi.Redraw()

 'normals' and 'faceNormals' have been show in the viewer as a collection of lines. 'faceCenters' has been added to the viewer and appears as a collection of spheres marking the centers of the triangles making up the surface. (NB:You may need to do a "Reset" if you transformed the surface in the viewer before these additions.) As discussed above, transformations are directed to the current object so that it is possible to transform 'normals',  'faceNormals', or 'faceCenters' independently of  'myFirstSurface.'  If 'root' is the current object,  everything in the viewer is transformed together.
 

Example Four:  Callbacks and the event Manager

This example illustrates the event Manager property of the camera.
 

First get a handle to the event manager:

>>> ehm=MyViewer.cameras[0].eventManager

The eventManager is a property of an individual camera.  You can get a complete listing off all the callback functions bound to keys strokes existing in the event Manager at any time:

>>> ehm.ListBindings()

or the list of all callbacks bound to any specific key stroke:

>>> ehm.ListBindings('<Button-1>')
 

Predefined callback functions can be added to specific key strokes in the camera:

>>> def mycallback1(event):
...     print 'mycallback1 Event at %d %d' % (event.x, event.y)

>>> def mycallback2(event):
...     print 'mycallback2 Event at %d %d' % (event.x, event.y)

>>> ehm.AddCallback("<F1>", mycallback1)

Note that the callback function must have 'event' as its parameter and the key stroke must occur with the cursor over the camera.  AddCallback adds this callback to whatever else may have been previously bound to this keystroke. Another function, SetCallback, replaces the previously bound funtions with the new one.  It returns a list of the previous callbacks which can be stored and restored.

>>> funcList = ehm.SetCallback("<F1>", mycallback2)
 

Now, funcList is a list:

>>> funcList
[<function mycallback1 at 10345b68>]

and mycallback2 is bound to F1.  mycallback1 could be restored as follows:

>>>  ehm.SetCallback("<F1>", funcList)

Callback functions can be removed:

>>>  ehm.RemoveCallback("<F1>", funcList)