結局wxPython

glutではちと機能不足になってきたのでXとWindows両方で動くpython GUIを選定していたのだが、
TkinterOpenGLを使うのに失敗した(ToglをWindowsにインストールするのが難航しそうだった)のと、
GentooでQtのコンパイルが一向に終わらないため使ったことのあるwxPythonに決めた。
wxPythonではwx.glcanvas.GLCanvasを使う。
glutDisplayFuncとかglutReshapeFuncとかはwxPythonの流儀でBindとかを使う。
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBG)で何もしない関数を登録しておかないとOpenGLWxWidgetsで2重にクリアするためかちらつくのに注意。

# -*- coding: utf-8 -*-
import wx
import wx.glcanvas
from OpenGL.GLUT import *
from OpenGL.GLU import *
from OpenGL.GL import *

class Camera(object):
  """視点
  """
  __slots__=['pos', 'target', 'up']
  def __init__(self, pos, target, up, aspect=1):
    self.pos=pos
    self.target=target
    self.up=up

class Projection(object):
  """投影情報
  """
  __slots__=['fovy', 'near', 'far', 'aspect']
  def __init__(self, fovy, near, far):
    self.fovy=fovy
    self.near=near
    self.far=far
    self.aspect=1

class MyGLCanvas(wx.glcanvas.GLCanvas):
  def __init__(self, *args, **keys):
    attribList = (wx.glcanvas.WX_GL_RGBA, # RGBA
                  wx.glcanvas.WX_GL_DOUBLEBUFFER, # Double Buffered
                  wx.glcanvas.WX_GL_DEPTH_SIZE, 32) # 32 bit
    super(MyGLCanvas, self).__init__(attribList=attribList, *args, **keys)
    # コールバック
    self.Bind(wx.EVT_PAINT, self.OnPaint)
    self.Bind(wx.EVT_SIZE, self.OnResize)
    self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBG)
    # camera
    self.camera=Camera((0, 0, 10), (0, 0, 0), (0, 1, 0))
    # 投影
    self.projection=Projection(30.0, 0.1, 20)
    # 初期化フラグ
    self.initialized=0

  def InitGL(self):
    """初期化。GLCanvasの実体が作成された後でコールすべし?
    """
    # 光源
    glEnable(GL_LIGHTING)
    glEnable(GL_LIGHT0)
    # 隠面消去を有効に
    glEnable(GL_DEPTH_TEST)
    # 法線の自動正規化
    glEnable(GL_NORMALIZE)
    # 背景色
    glClearColor(0.09375, 0.2578125, 0.3515625, 1.0);
    # デップスバッファ
    glClearDepth(1.0)
    # 初期化フラグ
    self.initialized = 1

  def Draw(self):
    """
    OpenGLの描画
    """
    # クリア
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    # 行列の初期化
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()
    # 視点
    gluLookAt(
        self.camera.pos[0], self.camera.pos[1], self.camera.pos[2],
        self.camera.target[0], self.camera.target[1], self.camera.target[2], 
        self.camera.up[0], self.camera.up[1], self.camera.up[2]
        )
    # ティーポット
    glutSolidTeapot( 2 )
    # 描画
    self.SwapBuffers()

  def OnPaint(self, event):
    """画面更新
    """
    dc = wx.PaintDC(self)
    self.SetCurrent()
    if not self.initialized:
      self.InitGL()
    self.Draw()
    event.Skip()
    return

  def OnResize(self, event):
    """ Windowのサイズ変更
    """
    size = self.size = self.GetClientSize()

    if self.GetContext():
        self.SetCurrent()
        glViewport(0, 0, self.size.width, self.size.height)
    if self.size.height!=0:
      self.projection.aspect=float(self.size.width)/float(self.size.height)

    # 透視投影
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(
        self.projection.fovy, 
        self.projection.aspect, 
        self.projection.near,
        self.projection.far)

    self.Refresh()
    event.Skip()

  def OnEraseBG(self, event):
    """Process the erase background event.
    """
    pass # Do nothing, to avoid flashing on MSWin


class MyFrame(wx.Frame):
  def __init__(self, parent):
    super(MyFrame, self).__init__(parent)
    panel=MyGLCanvas(self)    

    sizer=wx.BoxSizer(wx.HORIZONTAL)
    self.SetSizer(sizer)
    sizer.Add(panel, 1, wx.EXPAND)


if __name__ == '__main__': 
  app = wx.PySimpleApp()
  frame = MyFrame(None)
  frame.Show()
  app.MainLoop()