The Python Tk-OpenGL Module was written by (in alphabetical order)

The Python Tk-OpenGL widget is based on the Tk-OpenGL (Togl) widget of

History

The work around the OpenGL widget has quite a long history now, so let me (Tom) tell you some of the details. One and a half year ago I produced a first version with the same utils (bgen) the SGI glmodule was generated with. It used lists as input and everything worked well with the glutmodule. I also used it with the TIGER OpenGL widget done by Ekkehard Beier (Ekkehard.Beier@Prakinf.TU-Ilmenau.DE).After a request in the Newsgroup comp.lang.python, David set up a homepage for people interested in the OpenGL-Tk Widget and OpenGL module stuff (He also runs glut/opengl on non-Unix plattforms). After the Matrix SIG was born and Jim contributet his work I transformed the code to allow Matrix input, which seems natural in the context of 3D Graphics. Jim also introduced the namespace convention gl.* instead of the C-Code name gl* used before and some other enhancements. Some of the functions (e.g. glGetDoublev) are quite nasty and the Mesa code helped a lot doing them right.

The reason why the openGL module was not released was the rather complicated installation of the TIGER Tk-Widget, although everything worked nicely with glut. The Matrix module is still not official yet, so we introduced a Compiler switch to allow people using the OpenGL module without the numerical extension, although this will certainly become obsolete very soon.

Fairly recently Mike announced his work, which used the SWIG-generated OpenGL module and a rewritten (In C not C++) TIGER widget. After a day (and a small discussion) he presented a new version of his Opengl-Tk Widget based on Togl. That's what you get here! Mike added a lot of really cool stuff (auto-rotation, picking and the spinning PyOpenGL Logo).

This and the Matrix OpenGL module shold be considered the Official OpenGL module, so please do not rewrite them. Enhancing the work already done seems much better (this is my opinion!). Well, at least have fun with it. It was tested on Linux, SGI and Windows 95/NT except the Tk-OpenGL widget, whis is a pure X11-Widget until now. Would be nice to have a Mac and Windows Port.

Installation

The installation of the OpenGL-Widget for Python/Tk is fairly easy if you are used to build Python and Tk-extensions like Tix. With the new features of Tcl7.5/tk4.1 it's not necessary anymore to incorporate new Tk-widgets directly into the Python interpreter (via the tkappinit.c file). Nevertheless, we suggest to do it the classical way, so you don't have to bother with Tk finding shared objects.

First copy al *.c, *.h files in the src subdirectory to the Modules subdirectory of the Python distribution. The file tkInt.h of the tk4.1 Distribution is included here for convenience to all those who do not have it on their system. (It's not installed by tk4.1!). After that change the line in Setup to something like:

_tkinter _tkinter.c tkappinit.c togl.c -DWITH_APPINIT -lGL -lXext -lXmu -ltk4.1 -ltcl7.5 -lX11
This is for Linux, maybe you do not need all the X-libraries on other systems. Finally you have to add the the new Togl_Init command defined in togl.c to tkappinit.c. Here's how this looks like:
#include 
#include 
#include "togl.h"

int
Tcl_AppInit (interp)
     Tcl_Interp *interp;
{
  Tk_Window main;

  main = Tk_MainWindow(interp);

  if (Tcl_Init (interp) == TCL_ERROR)
    return TCL_ERROR;
  if (Tk_Init (interp) == TCL_ERROR)
    return TCL_ERROR;
  if (Togl_Init (interp) == TCL_ERROR) 
    return TCL_ERROR;
  ...
  return TCL_OK;
}
We assume that you will also use the openglmodule in conjunction with the new widget to do rapid application development with Python and OpenGL. You can use this module eihter statically linked or as a dynamic module. For that purpose add
opengl openglmodule.c glut_shapes.c -lGLU
glu glumodule.c 
to the Setup file and rebuild the Python interpreter as usual with make. If you do not have the numerical extension available (which will be part of the official Python distribution soon), you have to uncomment the line
#define NUMERIC
in openglmodule.c. In this new version we have included standard geometric shapes of the glut library. I you don't like that just uncomment the line
#define GLUT
in openglmodule.c and remove glut_shapes.c in the above example of the Setup file.

Know you are ready to go! Opengl.py provides the Python wrapper code for the new Tcl/Tk command we just incorporated. Make shure it's somewhere in your PYTHONPATH. The same applies to glconst.py and gluconst.py.

First steps

In our first example (see the demos subdirectory) we use already all the elements one has to know. First create a new OpenGL widget and attach the special mehod redraw to it. Then pack the widget and run the Tk-mainloop. When you run the follwing code in first.py
from Opengl import *

def redraw(o):
  gl.ClearColor(0, 0, 1, 0)
  gl.Clear(GL_COLOR_BUFFER_BIT)

o = Opengl(width = 200, height = 200, double = 1)
o.redraw = redraw
o.pack(side = 'top', expand = 1, fill = 'both')
o.mainloop()
you get a blue window on the screen. What you can't see in this simple example is the default behavior of the OpenGL-widget. With the left mousebutton you can translate a scene, with the middle one you can rotate it and the right Mousebutton allows scaling. Of course it's possible to override this key-bindings by your own code, bu't it's certainly usefull for non experts at this point. Let's look at another example which should give you an impression of this default behavior:
from Opengl import *
import sys

def redraw(o):
  gl.ClearColor(1, 0, 1, 0)
  gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
  gl.Color3f(0, 1, 0)
  #draw checkerboard
  N = 4
  gl.Disable(GL_LIGHTING)
  for x in range(-N, N):
    for y in range(-N, N):
      if (x + y) % 2 == 0:
	gl.Color3f(1, 1, 1)
      else:
	gl.Color3f(0, 0, 0)	
      gl.Rectf(x, y, x + 1, y + 1)
  gl.Enable(GL_LIGHTING)

  gl.PushMatrix()
  gl.Translatef(0., 0., 1.)
  gl.SolidSphere(1.0)
  gl.PopMatrix()

def main():
  f = Frame()
  f.pack(side = 'top')
  o = Opengl(width = 200, height = 200, double = 1)
  o.redraw = redraw
  quit = Button(f, text = 'Quit', command = sys.exit)
  quit.pack(side = 'top', side = 'left')
  help = Button(f, text = 'Help', command = o.help)
  help.pack(side = 'top', side = 'left')
  reset = Button(f, text = 'Reset', command = o.reset)
  reset.pack(side = 'top', side = 'left')
  o.pack(side = 'top', expand = 1, fill = 'both')
  o.set_eyepoint(20.)
  o.mainloop()

main()
What we get here is a black and white checkerboard with a small shaded Sphere sitting on it.

Use the mouse buttons to see what happens. The three remaining widgets (quit,help and redraw buttons) should be self-explaining and show the painless integration of usual Tk-widgets with the new OpenGL-widget. Note also the very simple lighting model built into the basic_lighting method:

def basic_lighting(self):
  self.activate()
  light_position = (1, 1, 1, 0);
  gl.Lightf(GL_LIGHT0, GL_POSITION, light_position);
  gl.Enable(GL_LIGHTING);
  gl.Enable(GL_LIGHT0);
  gl.DepthFunc(GL_LESS);
  gl.Enable(GL_DEPTH_TEST);
  gl.MatrixMode(GL_MODELVIEW);
  gl.LoadIdentity()
This method can of course be overriden by derived classes of OpenGL or you can just reset the lights as in the follwing example fog.py:
from Opengl import *

def init():
  mat_ambient    = [0.2, 0.2, 0.2, 1.0]
  mat_diffuse    = [0.8, 0.8, 0.8, 1.0]
  mat_specular   = [1.0, 0.0, 1.0, 1.0]
  mat_shininess  = [50.0]
  light_ambient  = [0.0, 1.0, 0.0, 1.0]
  light_diffuse  = [1.0, 1.0, 1.0, 1.0]
  light_specular = [1.0, 1.0, 1.0, 1.0]
  light_position = [1.0, 1.0, 1.0, 0.0]
  lmodel_ambient = [0.2, 0.2, 0.2, 1.0]
  gl.Materialfv(GL_FRONT, GL_AMBIENT, mat_ambient)
  gl.Materialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse)
  gl.Materialfv(GL_FRONT, GL_SPECULAR, mat_specular)
  gl.Materialfv(GL_FRONT, GL_SHININESS, mat_shininess)
  gl.Lightfv(GL_LIGHT0, GL_AMBIENT, light_ambient)
  gl.Lightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse)
  gl.Lightfv(GL_LIGHT0, GL_SPECULAR, light_specular)
  gl.Lightfv(GL_LIGHT0, GL_POSITION, light_position);   
  gl.LightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient)
  gl.Enable(GL_LIGHTING)
  gl.Enable(GL_LIGHT0)
  gl.DepthFunc(GL_LESS)
  gl.Enable(GL_DEPTH_TEST)

def redraw(o):
  gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
  gl.PushMatrix()
  gl.Translatef(0, -1, 0)
  gl.Rotatef(250, 1, 0, 0)
  gl.SolidCone(1, 2, 50, 10)
  gl.PopMatrix()

def main():
  o = Opengl(width = 200, height = 200, double = 1)
  o.redraw = redraw
  o.pack(side = 'top', expand = 1, fill = 'both')
  init()
  o.mainloop()

main()
Here are two screenshots of this example:

Up to now we used a procedural approach. Let's use some class Fog in our last example fog.py. We draw five red doughnuts and add two Radiobuttons to switch between different fog models.

from Opengl import *

class Fog:
  def __init__(self):
    self.o = Opengl(width = 250, height = 140, double = 1)
    self.o.redraw = self.redraw
    self.o.pack(side = 'top', expand = 1, fill = 'both')
    self.mode = IntVar(self.o)
    self.mode.set(GL_EXP)
    r1 = Radiobutton(text='GL_LINEAR', anchor=W, variable=self.mode,
                     value=GL_LINEAR, command=self.selectFog)
    r1.pack(side = 'top', expand = 1, fill = 'both')
    r2 = Radiobutton(text='GL_EXP', anchor=W, variable=self.mode,
                     value=GL_EXP, command=self.selectFog)
    r2.pack(side = 'top', expand = 1, fill = 'both')

  def run(self):
    self.init()
    self.o.mainloop()

  def selectFog(self):
    val = self.mode.get()
    if val == GL_LINEAR:
      gl.Fogf(GL_FOG_START, 1.0)
      gl.Fogf(GL_FOG_END, 5.0)
      gl.Fogi(GL_FOG_MODE, val)  
    elif val == GL_EXP:
      gl.Fogi(GL_FOG_MODE, val)
    self.o.tkRedraw()

  def init(self):
    position = [0.0, 3.0, 3.0, 0.0]
    local_view = [0.0]
    gl.Disable(GL_DITHER)
    gl.Enable(GL_DEPTH_TEST)
    gl.DepthFunc(GL_LESS)
    gl.Lightfv(GL_LIGHT0, GL_POSITION, position)
    gl.LightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view)
    gl.FrontFace(GL_CW)
    gl.Enable(GL_LIGHTING)
    gl.Enable(GL_LIGHT0)
    gl.Enable(GL_AUTO_NORMAL)
    gl.Enable(GL_NORMALIZE)
    gl.Enable(GL_FOG)
    fogColor = [0.5, 0.5, 0.5, 1.0]
    gl.Fogi(GL_FOG_MODE, GL_EXP)
    gl.Fogfv(GL_FOG_COLOR, fogColor)
    gl.Fogf(GL_FOG_DENSITY, 0.35)
    gl.Hint(GL_FOG_HINT, GL_DONT_CARE)
    gl.ClearColor(0.5, 0.5, 0.5, 1.0)
  
  def drawTorus(self, x, y, z):
    gl.PushMatrix();
    gl.Translatef(x, y, z);
    gl.Materialfv(GL_FRONT, GL_AMBIENT, [0.1745, 0.01175, 0.01175, 1.0])
    gl.Materialfv(GL_FRONT, GL_DIFFUSE, [0.61424, 0.04136, 0.04136])
    gl.Materialfv(GL_FRONT, GL_SPECULAR, [0.727811, 0.626959, 0.626959])
    gl.Materialf(GL_FRONT, GL_SHININESS, 0.6 * 128.0)
    gl.SolidTorus(0.275, 0.85, 20, 20)
    gl.PopMatrix()

  def redraw(self, o):
    gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    self.drawTorus(-4.0, -0.5, -1.0)
    self.drawTorus(-2.0, -0.5, -2.0)
    self.drawTorus(0.0, -0.5, -3.0)
    self.drawTorus(2.0, -0.5, -4.0)
    self.drawTorus(4.0, -0.5, -5.0)

def main():
  f = Fog()
  f.run()

main()
What we get you can see in the following pictures (all screenshots on a Linux box!).


August 1996 (all images on this page were created with PyOpenGL/Linux2.0)