Qt is a well-established framework for developing GUI applications in C++, and it has good support for OpenGL. It is relatively cumbersome, though, to set up a project for just a simple, experimental application. Moreover, even though Qt and OpenGL are portable, carrying over your project from, say, your Linux box to a Windows PC is not completely seamless, due to different compiler configurations, etc.
Being a Friend of Python, I was looking for a way to make life simpler in that respect. Can you use Qt and OpenGL in Python? You can!
There are three packages you want to have a look at:
- PyQt4 from Riverbank Computing, a Python binding for Trolltech’s Qt library
- PyOpenGL, for binding Python to GL
- Numpy to represent vectors, vertex array, matrices, and to do math, basically; PyOpenGL supports a few other options as well, check out the PyOpenGL docs.
The Windows binary installer for PyQt4
already contains a copy of the Qt libraries (4.6, currently). so you
don’t have to install Qt separately.
Once all this is set up, writing the application itself becomes fairly easy. Here’s an example to get you started, the famous spinning color cube — complete with application menu and status text:
import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4 import QtOpenGL
from OpenGL import GLU
from OpenGL.GL import *
from numpy import array
class GLWidget(QtOpenGL.QGLWidget):
def __init__(self, parent=None):
self.parent = parent
QtOpenGL.QGLWidget.__init__(self, parent)
self.yRotDeg = 0.0
def initializeGL(self):
self.qglClearColor(QtGui.QColor(0, 0, 150))
self.initGeometry()
glEnable(GL_DEPTH_TEST)
def resizeGL(self, width, height):
if height == 0: height = 1
glViewport(0, 0, width, height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
aspect = width / float(height)
GLU.gluPerspective(45.0, aspect, 1.0, 100.0)
glMatrixMode(GL_MODELVIEW)
def paintGL(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
glTranslate(0.0, 0.0, -50.0)
glScale(20.0, 20.0, 20.0)
glRotate(self.yRotDeg, 0.2, 1.0, 0.3)
glTranslate(-0.5, -0.5, -0.5)
glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_COLOR_ARRAY)
glVertexPointerf(self.cubeVtxArray)
glColorPointerf(self.cubeClrArray)
glDrawElementsui(GL_QUADS, self.cubeIdxArray)
def initGeometry(self):
self.cubeVtxArray = array(
[[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 0.0, 1.0],
[1.0, 1.0, 1.0],
[0.0, 1.0, 1.0]])
self.cubeIdxArray = [
0, 1, 2, 3,
3, 2, 6, 7,
1, 0, 4, 5,
2, 1, 5, 6,
0, 3, 7, 4,
7, 6, 5, 4 ]
self.cubeClrArray = [
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 0.0, 1.0],
[1.0, 1.0, 1.0],
[0.0, 1.0, 1.0 ]]
def spin(self):
self.yRotDeg = (self.yRotDeg + 1) % 360.0
self.parent.statusBar().showMessage('rotation %f' % self.yRotDeg)
self.updateGL()
class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.resize(300, 300)
self.setWindowTitle('GL Cube Test')
self.initActions()
self.initMenus()
glWidget = GLWidget(self)
self.setCentralWidget(glWidget)
timer = QtCore.QTimer(self)
timer.setInterval(20)
QtCore.QObject.connect(timer, QtCore.SIGNAL('timeout()'), glWidget.spin)
timer.start()
def initActions(self):
self.exitAction = QtGui.QAction('Quit', self)
self.exitAction.setShortcut('Ctrl+Q')
self.exitAction.setStatusTip('Exit application')
self.connect(self.exitAction, QtCore.SIGNAL('triggered()'), self.close)
def initMenus(self):
menuBar = self.menuBar()
fileMenu = menuBar.addMenu('&File')
fileMenu.addAction(self.exitAction)
def close(self):
QtGui.qApp.quit()
app = QtGui.QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
Ok, so it’s still 120 lines for a spinning cube, but the overhead is considerably smaller. And you can run this program as is on any platform. To me, it doubles the fun 🙂