Friday, September 28, 2012

Writing Your First PyQt4 OpenGL Program

1 Introduction

In this tutorial, we are going to have a glimpse of OpenGL programming in Python. I will introduce several simple Qt GUI applications and show you basic OpenGL programs.
Python is a programming language that lets you work more quickly and integrate your systems more effectively. PyQt is a Python wrapper for the C++ GUI framework Qt. Qt is a framework that uses the native widgets to draw its user interfaces.
PyOpenGL is a cross-platform open source Python binding to the standard OpenGL API. OpenGL is an application program interface that is used to define 2D and 3D computer graphics.
The article is written under Debian Linux. Packages that are required will be installed using powerful APT tool. If you are not a Linux user, however, don't worry. Since Python is a cross-platform language, Qt is a cross-platform framework, OpenGL is also a cross-platform interface, all the programs in this article also work for you. Just 'translate' the package installations according to your system and have fun.

2 The first PyQt4 hello world program

First, install python of course.
apt-get install python
Then, after installing PyQt, we are able to develop and execute Qt programs in Python.
apt-get install python-qt4
Now let us write the first hello world program.
import sys
from PyQt4.QtGui import *

app = QApplication(sys.argv)

widget = QWidget()
widget.resize(200, 100)
widget.show()

app.exec_()
It will create an empty window as the following.


2.1 Line by line walkthrough of hello world program

import sys
from PyQt4.QtGui import *
Import the modules and classes that we are going to use. Basic Qt GUI widgets are located in the QtGui module. All of those classes use the Q- prefix.
app = QApplication(sys.argv)
Initializes the window system and constructs an application object. There should be exactly one QApplication object in every Qt application. It manages GUI application's control flow and main settings. We create it with the command line arguments argv. The first string in the argument list will become the title of the application.
widget = QWidget()
Construct a Qt widget.
The QWidget class is the base class of all user interface objects. This widget is the atom of the user interface: it receives mouse, keyboard and other events from the window system, and paints a representation of itself on the screen.
We don't provide an argument (for its parent), which means the widget will not be embedded in a parent widget. The new widget will be a window, having a frame and a title bar.
widget.resize(200, 100)
Once the widget instance is created, we can manipulate it. For example, resize it to of width 200 pixels and of height 100 pixels.
widget.show()
Show the widget. If the widget has child widgets, they will also be visible.
app.exec_()
Pass the control to Qt. It enters the main event loop until the application exits. It is necessary to call this function to start event handling. The loop will receive user and system events and dispatch them to the application widgets.

3 Simple notepad

Now take a look at this simple notepad example.
import sys
from PyQt4.QtGui import *

app = QApplication(sys.argv)

textedit = QTextEdit()
textedit.show()

app.exec_()
This will create a notepad-like window.


It is almost the same as the first example, except the QWidget is here an QTextEdit instead. QTextEdit (indirectly) inherits QWidget. It provides a widget that is used to edit and display both plain and rich text.
You may try it out for different widgets, such as the most common widget, QtDialog.

4 Add a Quit button to the notepad application

In a real application, you will normally need more than one widget. We add a QPushButton beneath the text edit, and configure it exiting the notepad application when being pushed.
import sys
from PyQt4.QtGui import *

app = QApplication(sys.argv)

textedit = QTextEdit()
button = QPushButton("&Quit")
button.clicked.connect(app.quit)

layout = QVBoxLayout()
layout.addWidget(textedit)
layout.addWidget(button)

window = QWidget()
window.setLayout(layout)
window.show()

app.exec_()

4.1 Line by line walkthrough for the notepad application with a Quit button

import sys
from PyQt4.QtGui import *

app = QApplication(sys.argv)
textedit = QTextEdit()
Import classes and create widgets that we need.
button = QPushButton("&Quit")
Construct a QPushButton. The widget provides a command button.
button.clicked.connect(app.quit)
We need to use Qt's Signals and Slots mechanism to make the application exit when the Quit button is pushed.
A slot is a function that can be invoked at runtime. A signal is a function that when called will invoke slots associated with it. We create the connection between the slot and the signal by calling connect.
quit() is a Qt slot method. It tells the application to exit with return code 0 (success). click is the signal that QPushButton emits when it is pushed.
layout = QVBoxLayout()
This creates a QVBoxLayout.
We can set the bounds (location and size) of widgets directly. For example, in our hello world program, we called resize().
A layout help us manage the bounds of a widget's children. QVBoxLayout, for example, places the children in a vertical row.
layout.addWidget(textedit)
layout.addWidget(button)
These add the textedit and button (children widgets) to the layout.
window = QWidget()
window.setLayout(layout)
Then we construct a QWidget and set the layout manager for it.
window.show()
app.exec_()
Show the window and enter the main event loop.

5 The object oriented programming

Since Python is a very object-oriented language and Qt is also a object-oriented development framework, it makes sense to write our program in an object-oriented way.
from PyQt4.QtGui import *

class WfWidget(QWidget):
    def __init__(self, parent = None):
        super(WfWidget, self).__init__(parent)
        self.createLayout()

    def createLayout(self):
        textedit = QTextEdit()
        button = QPushButton("&Quit")
        button.clicked.connect(self.close)
        layout = QVBoxLayout()
        layout.addWidget(textedit)
        layout.addWidget(button)
        self.setLayout(layout)

if __name__ == '__main__':
    app = QApplication(["Winfred's PyQt"])
    widget = WfWidget()
    widget.show()
    app.exec_()

Articles for your information about object orientation in Python:


5.1 Line by line walkthrough for the object-oriented notepad application

from PyQt4.QtGui import *
Import the classes that we need.
class WfWidget(QWidget):
We define our own class of object named WfWidget and make it inherit properties from QWidget class.
def __init__(self, parent = None):
Methods of the class are defined with def keyword. The _init__ method is called when the object is instantiated.
Whenever a method is called, a reference to the main object is passed as the first argument. It is often called self, and we should follow the convention without special reasons.
The argument parent refers to what the parent class is. Here parent is None because our class will be a top-level window.
super(WfWidget, self).__init__(parent)
Because the parent's _init__ method has been overwritten, Python won't automatically invoke it during instantiating the object. We need the QWidget being initialized thus _init__ has to be called manually. We can call it directly with QWidget(…)._init_(…) , or as the example showed, use the built-in super function to refer to the parent class without naming it explicitly.
self.createLayout()
Then call our defined method createLayout().
def createLayout(self):
    textedit = QTextEdit()
    button = QPushButton("&Quit")
    button.clicked.connect(self.closes)
    layout = QVBoxLayout()
    layout.addWidget(textedit)
    layout.addWidget(button)
    self.setLayout(layout)
The createLayout is generally the same as what we saw. The only difference is that we created a QWidget to be the window in the previous example, and we replace it with self in this example.
if __name__ == '__main__':
_name__ is one of the special built-in variable which evaluate to the name of the current module. It can be used to test whether the code is imported into another module. If our script is being run directly, it will be set to the string "_main__".
app = QApplication(["Winfred's PyQt"])
This time we set the title of the window explicitly.
widget = WfWidget()
Construct our defined object.
widget.show()
app.exec_()
Show the window and enter the main event loop.
Info: There is another good tutorial implementing a simple notepad with PyQt that you might be interested in.

6 The OpenGL program

We also need to install the OpenGL module of PyQt4. It provides widgets and utility classes for OpenGL rendering in a PyQt4 application.
apt-get install python-qt4-gl
So that we are able to write our first PyQt4 OpenGL program.
from OpenGL.GL import *
from PyQt4 import QtGui
from PyQt4.QtOpenGL import *

class WfWidget(QGLWidget):
    def __init__(self, parent = None):
        super(WfWidget, self).__init__(parent)

    def paintGL(self):
        glColor3f(0.0, 0.0, 1.0)
        glRectf(-5, -5, 5, 5)
        glColor3f(1.0, 0.0, 0.0)
        glBegin(GL_LINES)
        glVertex3f(0, 0, 0)
        glVertex3f(20, 20, 0)
        glEnd()

    def resizeGL(self, w, h):
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glOrtho(-50, 50, -50, 50, -50.0, 50.0)
        glViewport(0, 0, w, h)

    def initializeGL(self):
        glClearColor(0.0, 0.0, 0.0, 1.0)
        glClear(GL_COLOR_BUFFER_BIT)

if __name__ == '__main__':
    app = QtGui.QApplication(["Winfred's PyQt OpenGL"])
    widget = WfWidget()
    widget.show()
    app.exec_()

6.1 Line by line walkthrough of the PyQt4 OpenGL program.

from OpenGL.GL import *
from PyQt4 import QtGui
from PyQt4.QtOpenGL import *
Also import OpenGL classes.
class WfWidget(QGLWidget):
    def __init__(self, parent = None):
        super(WfWidget, self).__init__(parent)
This time we define our WfWidget class that inherits QGLWidget class. It is a widget for rendering OpenGL graphics. QGLWidget actually inherits QWidget, thus what we knew works the same.
In order to perform typical OpenGL tasks, we have to reimplement three methods:
  • paintGL - Renders the OpenGL scene.
  • resizeGL - Sets up the OpenGL viewport, projection, etc.
  • initializeGL - Sets up the OpenGL rendering context, defines display lists, etc.
def paintGL(self):
This method is called whenever the widget needs to be updated (painted).
glColor3f(0.0, 0.0, 1.0)
glRectf(-5, -5, 5, 5)
The glColor3f specifies new red, green, and blue values explicitly (it also set current alpha to full intensity implicitly). The glRectf draws a rectangle specified by two consecutive pairs of (x, y) coordinates. In the example, we want to draw a blue rectangle from (-5, -5) to (5, 5) in z-plane.
glColor3f(1.0, 0.0, 0.0)
glBegin(GL_LINES)
glVertex3f(0, 0, 0)
glVertex3f(20, 20, 0)
glEnd()
glBegin(GLLINES) tells OpenGL we are going to draw a line, and glEnd tells it we are done. glVertex series functions are used within glBegin / glEnd pair to specify point, line, and polygon vertices. In the example, we draw a red line from (0, 0, 0) to (20, 20, 0).
def resizeGL(self, w, h):
This method is called whenever the widget has been resized. resizeGL is also called when the it is shown for the first time because all newly created widgets get a resize event automatically.
glMatrixMode(GL_PROJECTION)
glMatrixMode specifies which matrix stack is the target for subsequent matrix operations. GLPROJECTION applies the matrix operations to the projection. We will see a projection of the drawings.
glLoadIdentity()
glLoadIdentity replaces the current matrix with the identity matrix. It essentially resets the matrix back to its default state. glLoadIdentity is called after the matrix mode change so we are starting fresh.
glOrtho(-50, 50, -50, 50, -50.0, 50.0)
glOrtho describes a transformation that produces a parallel projection. To be easy, it specifies the range to clip the objects that we drawed.
glViewport(0, 0, w, h)
The glViewport function specifies the affine transformation of x and y from normalized device coordinates to window coordinates. It describes how the drawed objects are going to be viewed. We use parameters from resizeGL function in order to resize the objects to the size of the window.
def initializeGL(self):
This method is called once before the first call to paintGL() or resizeGL(), and then once whenever the widget has been assigned a new QGLContext.
glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT)
glClearColor specifies the color (RGB and alpha) that will be used by glClear to clear the color buffers. glClear clears buffers to preset values. These functions help to clear the window to black.
if __name__ == '__main__':
    app = QtGui.QApplication(["Winfred's PyQt OpenGL"])
    widget = WfWidget()
    widget.show()
    app.exec_()
The main section.

Date: 2012-09-27 Fri
Author: Winfred Lu
Org version 7.8.11 with Emacs version 23

No comments: