Python CAD Tutorial 01 - Initial Application



View/Code Download

tut01-interface.png

Figure 1: Point in 3D space

I have decided to write a series of tutorials in the hope we will see some good CAD applications appear on linux. In my experience, the current options are very unstable or difficult to use. By writing this program as a series of tutorials, I hope it will encourage contribution to the project or inspire you to develop your own CAD solution. This will hopefully help anyone who would like to write this style of 3D modelling application. These tutorials will use python, opengl and gtk3 for development. I will not be focusing on performance in this application, but focus more on readability and a clean layout.

Tools used in development of this application are Glade Interface Designer and Geany IDE, developed on ubuntu 13.04. Both opengl and gtk are cross platform so these examples may work on other platforms and distributions, but have not been tested on anything other than ubuntu 13.04. ../../../images/cad/glade-icon.png ../../../images/cad/geany.jpeg

The initial GUI has been designed in Glade and has the following layout: a standard menu and toolbar at the top of the window, then from left to right we have a toolbar like in gimp, in the centre we will display our designs and on the right we will have an object list. At the bottom we will embed an interactive console and a status bar for future expansion.

The glade interface can be found in the code repository above to open up and adjust. If anyone wants to design a nicer GUI, I would be interested to see what you come up with. cad/glade-interface-01.png', title='Glade Interface' [[..../../images/cad/glade-icon.png]]

This initial program will contain just enough code to display the glade interface and display an opengl triangle on the screen to prove everything is setup and working and give us a base to build from.

To get started you will need to install the libraries below from synaptics or by running the command in terminal.

We then create a configure method which grabs the window X id and sets the size of the viewport (in this case the width and height of the drawing area widget). We then create a drawing start and finish method to call before and after we do any drawing to the screen.

The last test method is just a test to make sure every thing is setup and functioning correctly and displays the standard OpenGL coloured triangle in the new window.

sudo apt-get install python-gi python-opengl python-gobject python-xlib

This first class separates the OpenGL and xlib code out from the gtk interface. We could reuse the code in another application. We have to use the c library for some xlib functions because the python implementation is limited - it's only used to grab the current xdisplay for OpenGL rendering.

For some of this code you will need to look at the OpenGL documentations to understand some of the parameters. In the initialise method we setup the OpenGL display parameters we would like to turn on, for example alpha channels for transparency and disable double buffering. We also create a glx context for our OpenGL scene to be drawn to.

class gtkgl:
    """ These method do not seem to exist in python x11 library lets exploit the c methods """
    xlib = cdll.LoadLibrary('libX11.so')
    xlib.XOpenDisplay.argtypes = [c_char_p]
    xlib.XOpenDisplay.restype = POINTER(struct__XDisplay)
    xdisplay = xlib.XOpenDisplay("")
    display = Xlib.display.Display()
    attrs = []

    xwindow_id = None
    width = height = 200

    def __init__(self):
	""" Lets setup are opengl settings and create the context for our window """

	self.add_attribute(GLX.GLX_RGBA, True)
	self.add_attribute(GLX.GLX_RED_SIZE, 1)
	self.add_attribute(GLX.GLX_GREEN_SIZE, 1)
	self.add_attribute(GLX.GLX_BLUE_SIZE, 1)
	self.add_attribute(GLX.GLX_DOUBLEBUFFER, 0)

	xvinfo = GLX.glXChooseVisual(self.xdisplay, self.display.get_default_screen(), self.get_attributes())
	configs = GLX.glXChooseFBConfig(self.xdisplay, 0, None, byref(c_int()))
	self.context = GLX.glXCreateContext(self.xdisplay, xvinfo, None, True)

    def add_attribute(self, setting, value):
	"""a simple method to nicely add opengl parameters"""
	self.attrs.append(setting)
	self.attrs.append(value)

    def get_attributes(self):
	""" return our parameters in the expected structure"""
	attrs = self.attrs + [0, 0]
	return (c_int * len(attrs))(*attrs)

    def configure(self, wid):
	""" setup up a opengl viewport for the current window, grab the xwindow id and store for later usage"""
	self.xwindow_id = GdkX11.X11Window.get_xid(wid)
	if(not GLX.glXMakeCurrent(self.xdisplay, self.xwindow_id, self.context)):
	    print ('configure failed running glXMakeCurrent')
	glViewport(0, 0, self.width, self.height)

    def draw_start(self):
	"""make cairo context current for drawing"""
	if(not GLX.glXMakeCurrent(self.xdisplay, self.xwindow_id, self.context)):
	    print ('draw failed running glXMakeCurrent')

    def draw_finish(self):
	"""swap buffer when we have finished drawing"""
	GLX.glXSwapBuffers(self.xdisplay, self.xwindow_id)

    def test(self):
	"""Test method to draw something so we can make sure opengl is working and we can see something"""
	self.draw_start()

	glClearColor(0.0, 0.0, 0.0, 0.0)
	glClear(GL_COLOR_BUFFER_BIT)
	glBegin(GL_TRIANGLES)
	glIndexi(0)
	glColor3f(1.0, 0.0, 0.0)
	glVertex2i(0, 1)
	glIndexi(0)
	glColor3f(0.0, 1.0, 0.0)
	glVertex2i(-1, -1)
	glIndexi(0)
	glColor3f(0.0, 0.0, 1.0)
	glVertex2i(1, -1)
	glEnd()

	self.draw_finish()

This class will deal with the GUI interaction between GTK and OpenGL; we will capture mouse events, menu events and button events here and use them to adjust the OpenGL application.

The initialisation method will load in the interface from glade and grab the widgets we are interested in, at this stage we are only interested in the window and the drawing area widgets. We want to grab the close and draw events for these widgets and set a few parameters against the drawing widget.

Once we have the drawing area widget we create two methods: configure and draw. The first is called when setting up the drawing area or if we resize it or similar happens we may need to modify the drawing area size. The second method is called whenever the display needs to be updated; we can call this manually or it will get called if another window covers it up to redraw the OpenGL scene.

class gui:
   """cad drawing application."""
   glwrap = gtkgl()

   def __init__(self):
       """ Initialise the GTK interface grab the GTK widgets, connect the events we are interested in receiving"""
       xml = Gtk.Builder()
       xml.add_from_file('interface.glade')
       self.window = xml.get_object('window')
       self.window.connect('delete_event', Gtk.main_quit)
       self.window.connect('destroy', lambda quit: Gtk.main_quit())
       self.window.set_reallocate_redraws(True)

       self.drawing_area = xml.get_object('drawingarea')
       self.drawing_area.set_double_buffered(False)
       self.drawing_area.set_size_request(self.glwrap.width, self.glwrap.height)
       self.drawing_area.connect('configure_event', self.on_configure_event)
       self.drawing_area.connect('draw', self.on_draw)
       self.drawing_area.connect('realize', self.on_draw)

       self.window.show_all()

   def on_configure_event(self, widget, event):
       self.glwrap.configure(widget.get_window())
       return True

   def on_draw(self, *args):
       self.glwrap.test()

   def quit(self, *args):
       Gtk.main_quit()

Comments