#!/usr/bin/env python ''' Shapes.py This program is based upon the shapes.c demo by Naofumi. This is not a straight conversion from C to Python. The original idea of displaying different shapes have been retained but here a range of widgets are used to demonstrate the use of OpenGL in conjunction with a variety of Gtk+ widgets. You can use sliders to rotate the object and change colours for the foreground and background using the standard Gtk+ colour selection dialog. Alif Wahid, March, 2003 <awah005@users.sourceforge.net> ''' import sys import pygtk pygtk.require('2.0') from gtk.gtkgl.apputils import * from OpenGL.GLE import * # Implement the GLScene interface # to have a shape rendered. class Shapes(GLScene, GLSceneButton, GLSceneButtonMotion): def __init__(self): GLScene.__init__(self, gtk.gdkgl.MODE_RGB | gtk.gdkgl.MODE_DEPTH | gtk.gdkgl.MODE_DOUBLE) self.light_ambient = [0.0, 1.0, 0.0, 1.0] self.light_diffuse = [1.0, 1.0, 1.0, 1.0] self.light_specular = [1.0, 1.0, 1.0, 1.0] self.light_position = [1.0, 1.0, 1.5, 0.0] self.mat_ambient = [0.2, 0.2, 0.2, 1.0] self.mat_diffuse = [0.8, 0.8, 0.8, 1.0] self.mat_specular = [1.0, 0.0, 1.0, 1.0] self.mat_shininess = 50.0 self.depth = 105.0 self.rotx = 0 self.roty = 0 self.rotz = 0 self.beginx = 0 self.beginy = 0 # Empirically derived value for the background # to make it the same colour as the background # of all the widgets. This way the shapes will # appear as though they have been drawn on top # of the current window. It's specific to the # default Gtk+-2.2 theme only though. You can # also assign the current colour of any part of # the window from the colourselection dialog # by using the eye dropper. That's how I derived # at these values for guiBg. self.guiBg = [0.8627, 0.8549, 0.8353] self.colourFg = [1.0, 0.0, 0.0] self.colourBg = self.guiBg self.is_solid = False self.__drawShape = { 'Helicoid' : self.__draw_helicoid, 'Teapot' : self.__draw_teapot, 'Torus' : self.__draw_torus, 'Sphere' : self.__draw_sphere, 'Cube' : self.__draw_cube, 'Cone' : self.__draw_cone, 'Tetrahedron' : self.__draw_tetrahedron, 'Octahedron' : self.__draw_octahedron, 'Dodecahedron' : self.__draw_dodecahedron, 'Icosahedron' : self.__draw_icosahedron } self.currentShape = 'Sphere' self.availableShapes = self.__drawShape.keys() # Private methods that are used in the expose # method in a transparent manner to provide the # underlying rendering for a specific shape. def __draw_icosahedron(self): glPushMatrix() glScalef(12.0, 12.0, 12.0) gtk.gdkgl.draw_icosahedron(self.is_solid) glPopMatrix() def __draw_dodecahedron(self): glPushMatrix() glScalef(10.0, 10.0, 10.0) gtk.gdkgl.draw_dodecahedron(self.is_solid) glPopMatrix() def __draw_octahedron(self): glPushMatrix() glScalef(12.0, 12.0, 12.0) gtk.gdkgl.draw_octahedron(self.is_solid) glPopMatrix() def __draw_tetrahedron(self): glPushMatrix() glScalef(12.0, 12.0, 12.0) gtk.gdkgl.draw_tetrahedron(self.is_solid) glPopMatrix() def __draw_cone(self): gtk.gdkgl.draw_cone(self.is_solid, 6.0, 12.0, 30, 30) def __draw_cube(self): gtk.gdkgl.draw_cube(self.is_solid, 12) def __draw_helicoid(self): gleSetJoinStyle(TUBE_NORM_EDGE | TUBE_JN_ANGLE | TUBE_JN_CAP) gleHelicoid(1.0, 5.0, 1.0, -15.0, 6.0, None, None, 0.0, 1800.0) def __draw_teapot(self): gtk.gdkgl.draw_teapot(self.is_solid, 11.0) def __draw_torus(self): gtk.gdkgl.draw_torus(self.is_solid, 3.0, 12.0, 30, 30) def __draw_sphere(self): gtk.gdkgl.draw_sphere(self.is_solid, 12.0, 30, 30); # GLSceneInterface implementation. def init(self): glClearDepth(1.0) glClearColor(self.colourBg[0], self.colourBg[1], self.colourBg[2], 0.0) glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE) # The material properties are constant at this # stage, but if they need to be user selectable # then it'll be better to move these 4 calls # to the 'expose' method. That way everytime # the scene is rendered any change in materials # will be automatically detected without the # need for calling 'realize'. glMaterial(GL_FRONT, GL_AMBIENT, self.mat_ambient) glMaterial(GL_FRONT, GL_DIFFUSE, self.mat_diffuse) glMaterial(GL_FRONT, GL_SPECULAR, self.mat_specular) glMaterial(GL_FRONT, GL_SHININESS, self.mat_shininess) glLight(GL_LIGHT0, GL_AMBIENT, self.light_ambient) glLight(GL_LIGHT0, GL_DIFFUSE, self.light_diffuse) glLight(GL_LIGHT0, GL_SPECULAR, self.light_specular) glLight(GL_LIGHT0, GL_POSITION, self.light_position) glLightModel(GL_LIGHT_MODEL_AMBIENT, self.light_ambient) glShadeModel(GL_SMOOTH) glDepthFunc(GL_LESS) glFrontFace(GL_CW) glEnable(GL_AUTO_NORMAL) glEnable(GL_NORMALIZE) glEnable(GL_LIGHTING) glEnable(GL_LIGHT0) glEnable(GL_DEPTH_TEST) glEnable(GL_COLOR_MATERIAL) def display(self, width, height): # Set the background colour first as the user has # the option of changing it, so we need to take that # into account during every expose event. glClearColor(self.colourBg[0], self.colourBg[1], self.colourBg[2], 0.0) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glMatrixMode(GL_MODELVIEW) glLoadIdentity() glTranslatef(0.0, 0.0, -self.depth) glRotate(self.rotx, 1, 0, 0) glRotate(self.roty, 0, 1, 0) glRotate(self.rotz, 0, 0, 1) # Set the foreground colour as the user has # the option of changing it, so we need to take that # into account during every expose event. glColor(self.colourFg) self.__drawShape[self.currentShape]() def reshape(self, width, height): glViewport(0, 0, width, height) glMatrixMode(GL_PROJECTION) glLoadIdentity() # Calculate left/right and top/bottom clipping planes based on # the smallest square viewport. a = 13.0/min(width, height) clipping_planes = (a*width, a*height) # Setup the projection glFrustum(-clipping_planes[0], clipping_planes[0], -clipping_planes[1], clipping_planes[1], 50.0, 150.0) def button_press(self, width, height, event): self.beginx = event.x self.beginy = event.y def button_release(self, width, height, event): pass def button_motion(self, width, height, event): if event.state == gtk.gdk.BUTTON1_MASK: self.rotx = self.rotx + ((event.y-self.beginy)/width)*360.0 self.roty = self.roty + ((event.x-self.beginx)/height)*360.0 elif event.state == gtk.gdk.BUTTON2_MASK: self.depth = self.depth - ((event.y-self.beginy)/(height))*50.0; if self.depth > 130.0: self.depth = 130.0; elif self.depth < 80.0: self.depth = 80.0; self.beginx = event.x self.beginy = event.y self.queue_draw() # A window to show the Shapes scene # in a GLArea widget along with two # sliders for rotating the shape rendered # in the scene. The shape can also be # rotated using mouse button drag motion. class ShapesWindow(gtk.Window): def __init__(self): gtk.Window.__init__(self) # Set self attfibutes. self.set_title('Shapes') self.set_position(gtk.WIN_POS_CENTER_ALWAYS) self.connect('destroy', lambda quit: gtk.main_quit()) if sys.platform != 'win32': self.set_resize_mode(gtk.RESIZE_IMMEDIATE) self.set_reallocate_redraws(True) # Create the table that will hold everything. self.table = gtk.Table(3, 3) self.table.set_border_width(5) self.table.set_col_spacings(5) self.table.set_row_spacings(5) self.table.show() self.add(self.table) # The Shapes scene and the # GLArea widget to # display it. self.shape = Shapes() self.glarea = GLArea(self.shape) self.glarea.set_size_request(300,300) self.glarea.show() self.table.attach(self.glarea, 1, 2, 0, 1) # 3 Frames showing rotation sliders self.zframe = gtk.Frame('Z-Axis') self.zframe.show() self.zfbox = gtk.VBox() self.zfbox.set_border_width(10) self.zfbox.show() self.zadj = gtk.Adjustment(0.0, -360.0, 360.0, 5.0, 20.0, 0.0) self.zadj.connect('value_changed', self.zchanged) self.zscale = gtk.VScale(self.zadj) self.zscale.set_value_pos(gtk.POS_LEFT) self.zscale.show() self.zfbox.add(self.zscale) self.zframe.add(self.zfbox) self.table.attach(self.zframe, 0, 1, 0, 1, xoptions=gtk.FILL, yoptions=gtk.FILL) self.xframe = gtk.Frame('X-Axis') self.xframe.show() self.xfbox = gtk.VBox() self.xfbox.set_border_width(10) self.xfbox.show() self.xadj = gtk.Adjustment(0.0, -360.0, 360.0, 5.0, 20.0, 0.0) self.xadj.connect('value_changed', self.xchanged) self.xscale = gtk.VScale(self.xadj) self.xscale.set_value_pos(gtk.POS_RIGHT) self.xscale.show() self.xfbox.add(self.xscale) self.xframe.add(self.xfbox) self.table.attach(self.xframe, 2, 3, 0, 1, xoptions=gtk.FILL, yoptions=gtk.FILL) self.yframe = gtk.Frame('Y-Axis') self.yframe.show() self.yfbox = gtk.VBox() self.yfbox.set_border_width(10) self.yfbox.show() self.yadj = gtk.Adjustment(0.0, -360.0, 360.0, 5.0, 20.0, 0.0) self.yadj.connect('value_changed', self.ychanged) self.yscale = gtk.HScale(self.yadj) self.yscale.set_value_pos(gtk.POS_TOP) self.yscale.show() self.yfbox.add(self.yscale) self.yframe.add(self.yfbox) self.table.attach(self.yframe, 1, 2, 1, 2, xoptions=gtk.FILL, yoptions=gtk.FILL) # A box to hold some control interface stuff. self.cbox = gtk.HBox(True, spacing=10) self.cbox.show() self.table.attach(self.cbox, 1, 2, 2, 3, xoptions=gtk.FILL, yoptions=gtk.FILL) # A frame showing some colour changing buttons. self.colourFrame = gtk.Frame('Change Colour') self.colourFrame.show() self.cbox.pack_start(self.colourFrame) self.fbox1 = gtk.VBox() self.fbox1.set_border_width(10) self.fbox1.show() self.colourFrame.add(self.fbox1) self.colourButtonFg = gtk.Button('Foreground') self.colourButtonFg.connect('clicked', self.changeColourFg) self.colourButtonFg.show() self.fbox1.pack_start(self.colourButtonFg, expand=True, padding=5) self.colourButtonBg = gtk.Button('Background') self.colourButtonBg.connect('clicked', self.changeColourBg) self.colourButtonBg.show() self.fbox1.pack_start(self.colourButtonBg, expand=True, padding=5) # A frame holding menu and checkbutton for # changing the current shape attributes. self.shapeFrame = gtk.Frame('Shape Attrubutes') self.shapeFrame.show() self.cbox.pack_start(self.shapeFrame) self.fbox2 = gtk.VBox() self.fbox2.set_border_width(10) self.fbox2.show() self.shapeFrame.add(self.fbox2) # This is the option menu that lets the # user change the shape. self.shapeOptions = gtk.combo_box_new_text() for shape in self.shape.availableShapes: self.shapeOptions.append_text(shape) self.shapeOptions.connect('changed', self.shapeChanged) self.shapeOptions.set_active(0) self.shapeOptions.show() self.fbox2.pack_start(self.shapeOptions, expand=True, padding=5) self.solidButton = gtk.CheckButton('Solid Shape') self.solidButton.connect('toggled', self.shapeSolidityToggled) self.solidButton.show() self.fbox2.pack_start(self.solidButton, expand=True, padding=5) def shapeChanged(self, option): self.shape.currentShape = self.shape.availableShapes[self.shapeOptions.get_active()] self.glarea.queue_draw() def shapeSolidityToggled(self, button): self.shape.is_solid = not self.shape.is_solid self.glarea.queue_draw() def changeColourBg(self, button): dialog = gtk.ColorSelectionDialog("Changing colour of Background") dialog.set_transient_for(self) colorsel = dialog.colorsel colorsel.set_has_palette(True) response = dialog.run() if response == gtk.RESPONSE_OK: colour = colorsel.get_current_color() self.shape.colourBg = [colour.red/65535.0, colour.green/65535.0, colour.blue/65535.0] self.glarea.queue_draw() dialog.destroy() def changeColourFg(self, button): dialog = gtk.ColorSelectionDialog("Choose colour of Object") dialog.set_transient_for(self) colorsel = dialog.colorsel colorsel.set_has_palette(True) response = dialog.run() if response == gtk.RESPONSE_OK: colour = colorsel.get_current_color() self.shape.colourFg = [colour.red/65535.0, colour.green/65535.0, colour.blue/65535.0] self.glarea.queue_draw() dialog.destroy() def zchanged(self, zadj): self.shape.rotz = zadj.value self.glarea.queue_draw() def xchanged(self, zadj): self.shape.rotx = zadj.value self.glarea.queue_draw() def ychanged(self, yadj): self.shape.roty = yadj.value self.glarea.queue_draw() def run(self): self.show() gtk.main() if __name__ == '__main__': app = ShapesWindow() app.run()