wxOGL

OGL is an interesting graphics package. It has documentation problems.

This page is a result of my (LionKimbro) research on OGL, and the contributions of several, including RobinDunn, and Nikolai.

Status

Development Status

However, I have not heard any word about it disappearing from the wxPython distribution.

"I'm not dead yet!"

More information about OGL's development status can be found in the wxPython demo notes:

The OGL library was originally written in C++ and provided to wxPython via an extension module wrapper as is most of the rest of wxPython. The code has now been ported to Python (with many thanks to Pierre Hjälm!) in order to make it be more easily maintainable and less likely to get rusty because nobody cares about the C++ lib any more.

View the source code at http://svn.wxwidgets.org/viewvc/wx/wxPython/trunk/wx/lib/ogl/

Documentation Status

The standard OGL documentation is old and erronius.

Standard OGL docs consist of:

This wiki entry is an effort to fix the situation.

If you are using OGL, please post your notes and corrections to this wiki page..!

With some time, I'll feel confident enough to post a cookbook entry (wxPython Cookbook). I don't think it's important enough, yet, to warrent a challenge (ObstacleCourse).

Sample Code

Minimal

This is a minimal application demonstrating wxOGL, contributed by LionKimbro.

Lines with "#" at the end are not necessary.

   1 import wx
   2 import wx.lib.ogl as ogl
   3 
   4 class AppFrame(wx.Frame):
   5     def __init__(self):
   6         wx.Frame.__init__( self,
   7                           None, -1, "Demo",
   8                           size=(300,200),
   9                           style=wx.DEFAULT_FRAME_STYLE )
  10         sizer = wx.BoxSizer( wx.VERTICAL )
  11         # put stuff into sizer
  12 
  13         canvas = ogl.ShapeCanvas( self )
  14         sizer.Add( canvas, 1, wx.GROW )
  15 
  16         canvas.SetBackgroundColour( "LIGHT BLUE" ) #
  17 
  18         diagram = ogl.Diagram()
  19         canvas.SetDiagram( diagram )
  20         diagram.SetCanvas( canvas )
  21 
  22         shape = ogl.CircleShape( 20.0 )            #
  23         shape.SetX( 25.0 )                         #
  24         shape.SetY( 25.0 )                         #
  25         canvas.AddShape( shape )                   #
  26         diagram.ShowAll( 1 )                       #
  27 
  28         # apply sizer
  29         self.SetSizer(sizer)
  30         self.SetAutoLayout(1)
  31         self.Show(1)
  32 
  33 
  34 
  35 app = wx.PySimpleApp()
  36 ogl.OGLInitialize()
  37 frame = AppFrame()
  38 app.MainLoop()
  39 app.Destroy()

Mouse Chasing Circle

The following entry was contributed by Nikolai.

Here is a (not so ;-)) simple example of wxOGL, the first one for versions of wxPython before it went into the "new" wx namespace. Scroll down for one that works in wxPy2.6 on Py2.4... --TomPlunket

It should still be easier to understand than the wxOGL.py demo that comes with wxPython. What this beast does is to keep a circle under the mouse-cursor as you move it. Attached values (self.circle.a i.e.) are kept and can be accessed later.

   1 from wxPython.wx import *
   2 from wxPython.ogl import *
   3 
   4 wxOGLInitialize()
   5 
   6 
   7 class MyEvtHandler(wxShapeEvtHandler):
   8     """
   9     Overwrite the default event handler to implement some custom features. 
  10     """
  11     def __init__(self):
  12         wxShapeEvtHandler.__init__(self)
  13 
  14     def OnLeftClick(self, x, y, keys = 0, attachment = 0):
  15         """
  16         The dragging is done here. 
  17         You should probably comment out the EVT_MOTION below, to see it work. 
  18         """
  19         shape = self.GetShape()
  20         print shape.__class__, shape.GetClassName(), shape.a
  21         canvas = shape.GetCanvas()
  22         dc = wxClientDC(canvas)
  23         canvas.PrepareDC(dc)
  24 
  25         if shape.Selected():
  26             shape.Select(False, dc)
  27             canvas.Redraw(dc)
  28         else:
  29             redraw = False
  30             shapeList = canvas.GetDiagram().GetShapeList()
  31             toUnselect = []
  32             for s in shapeList:
  33                 if s.Selected():
  34                     toUnselect.append(s)
  35 
  36             shape.Select(True, dc)
  37 
  38             if toUnselect:
  39                 for s in toUnselect:
  40                     s.Select(False, dc)
  41                 canvas.Redraw(dc)
  42 
  43 
  44 
  45 class OGLCanvas(wxShapeCanvas):
  46     def __init__(self, parent, frame):
  47         wxShapeCanvas.__init__(self, parent)
  48 
  49         self.SetBackgroundColour("LIGHT BLUE")
  50         self.diagram = wxDiagram()
  51         self.SetDiagram(self.diagram)
  52         self.diagram.SetCanvas(self)
  53 
  54         self.circle = wxCircleShape(100)
  55         self.circle.SetCanvas(self)
  56         self.circle.a="Circle identified"
  57         self.diagram.AddShape(self.circle)
  58         self.circle.Show(True)
  59 
  60         self.evthandler = MyEvtHandler()
  61         self.evthandler.SetShape(self.circle)
  62         self.evthandler.SetPreviousHandler(self.circle.GetEventHandler())
  63         self.circle.SetEventHandler(self.evthandler)
  64 
  65         EVT_MOTION(self, self.OnMotion)
  66 
  67     def OnMotion(self, event):
  68         shape = self.evthandler.GetShape()
  69 
  70         bx = shape.GetX()
  71         by = shape.GetY()
  72         bw, bh = shape.GetBoundingBoxMax()
  73         oldrect = wxRect(int(bx-bw/2)-1, int(by-bh/2)-1, int(bw)+2, int(bh)+2)
  74 
  75         canvas = shape.GetCanvas()
  76         dc = wxClientDC(canvas)
  77         canvas.PrepareDC(dc)
  78 
  79         shape.Move(dc, event.GetPosition()[0], event.GetPosition()[1])
  80         canvas.Refresh(False, oldrect)
  81         event.Skip()
  82 
  83 
  84 class OGLFrame(wxFrame):
  85     def __init__(self, *args, **kwds):
  86         wxFrame.__init__(self, *args, **kwds)
  87 
  88         self.SetTitle("OGL TEST")
  89         self.SetBackgroundColour(wxColour(8, 197, 248))
  90         self.canvas = OGLCanvas(self, self)
  91 
  92 class Main(wxApp):
  93     def OnInit(self):
  94         wxInitAllImageHandlers()
  95         frame = OGLFrame(None, -1, "")
  96         self.SetTopWindow(frame)
  97         frame.Show(True)
  98         return True
  99 
 100 if __name__ == "__main__":
 101     app = Main()
 102     app.MainLoop()

For the "new" wx namespace, this bit works in wx 2.6 on Py 2.4. Mind, I still don't understand it entirely, but it works as expected. Yeah, I use tabs. Sue me. ;)

   1 import wx
   2 import wx.lib.ogl as ogl
   3 
   4 CLICK_TO_DRAG = True
   5 
   6 class MyEvtHandler(ogl.ShapeEvtHandler):
   7         """
   8         Overwrite the default event handler to implement some custom features. 
   9         """
  10         def __init__(self):
  11                 ogl.ShapeEvtHandler.__init__(self)
  12 
  13         def OnLeftClick(self, x, y, keys = 0, attachment = 0):
  14                 """
  15                 The dragging is done here. 
  16                 """
  17                 shape = self.GetShape()
  18                 print shape.__class__, shape.GetClassName(), shape.a
  19                 canvas = shape.GetCanvas()
  20                 dc = wx.ClientDC(canvas)
  21                 canvas.PrepareDC(dc)
  22                 
  23                 if shape.Selected():
  24                         shape.Select(False, dc)
  25                         canvas.Redraw(dc)
  26                 else:
  27                         redraw = False
  28                         shapeList = canvas.GetDiagram().GetShapeList()
  29                         toUnselect = []
  30                         for s in shapeList:
  31                                 if s.Selected():
  32                                         toUnselect.append(s)
  33 
  34                         shape.Select(True, dc)
  35                         
  36                         if toUnselect:
  37                                 for s in toUnselect:
  38                                         s.Select(False, dc)
  39                                 canvas.Redraw(dc)
  40 
  41 
  42 
  43 class OGLCanvas(ogl.ShapeCanvas):
  44         def __init__(self, parent, frame):
  45                 ogl.ShapeCanvas.__init__(self, parent)
  46                 
  47                 self.SetBackgroundColour("LIGHT BLUE")
  48                 self.diagram = ogl.Diagram()
  49                 self.SetDiagram(self.diagram)
  50                 self.diagram.SetCanvas(self)
  51                 
  52                 self.circle = ogl.CircleShape(100)
  53                 self.circle.SetCanvas(self)
  54                 self.circle.a="Circle identified"
  55                 self.diagram.AddShape(self.circle)
  56                 self.circle.Show(True)
  57                 
  58                 if CLICK_TO_DRAG:
  59                         self.evthandler = MyEvtHandler()
  60                         self.evthandler.SetShape(self.circle)
  61                         self.evthandler.SetPreviousHandler(self.circle.GetEventHandler())
  62                         self.circle.SetEventHandler(self.evthandler)
  63                 else:
  64                         self.Bind(wx.EVT_MOTION, self.OnMotion, self)
  65 
  66         def OnMotion(self, event):
  67                 shape = self.circle
  68                 
  69                 bx = shape.GetX()
  70                 by = shape.GetY()
  71                 bw, bh = shape.GetBoundingBoxMax()
  72                 oldrect = wx.Rect(int(bx-bw/2)-1, int(by-bh/2)-1, int(bw)+2, int(bh)+2)
  73                 
  74                 canvas = shape.GetCanvas()
  75                 dc = wx.ClientDC(canvas)
  76                 canvas.PrepareDC(dc)
  77                 
  78                 shape.Move(dc, event.GetPosition()[0], event.GetPosition()[1])
  79                 canvas.Refresh(False, oldrect)
  80                 event.Skip()
  81 
  82 
  83 class OGLFrame(wx.Frame):
  84         def __init__(self, *args, **kwds):
  85                 wx.Frame.__init__(self, *args, **kwds)
  86                 
  87                 self.SetTitle("OGL TEST")
  88                 self.SetBackgroundColour(wx.Colour(8, 197, 248))
  89                 self.canvas = OGLCanvas(self, self)
  90 
  91 if __name__ == "__main__":
  92         app = wx.PySimpleApp(False)
  93         wx.InitAllImageHandlers()
  94         ogl.OGLInitialize()
  95         frame = OGLFrame(None, -1, "")
  96         app.SetTopWindow(frame)
  97         frame.Show(True)
  98         app.MainLoop()

Documentation Errors

wxShapeCanvas' Parent

The documentation says that wxShapeCanvas is derived from wxCanvas.

In fact, it is derived from wxScrolledWindow.

RobinDunn refers us to the CVS source.

Alternatives

wxArt2D (Nov 2, 2011)

wxShapeFramework (Nov 2, 2011)

FloatCanvas (Nov 2, 2011)

See also Pyxel, which works with WxPython or PyGame (link updated Jan 7, 2011).


KevinAltis has informed me of two sort-of alternatives to wxOGL:

  1. JohnAnderson's SimpleCanvas.py. (used to be part of Chandler)

  2. Chris Barker's FloatCanvas (now included with wxPython)


You might to check out Piddle, too. It's like an anygui for canvases. Download Piddle and then run piddleWxDcDemo.py.

Piddle doesn't have persistent drawing objects like simplecanvas or floatcanvas or connected objects like wxOGL, but if you made something that integrated something like simplecanvas or floatcanvas with Piddle it would make it easier to draw diagrams to PDFs or PIL images or other contexts besides the application window.

SimpleCanvas seems better designed to build a wxOGL alternative. It separates model-view-controller and has built in support for drag and drop and scrolling, although it is complex. You subclass wxSimpleDrawableObject for objects in the canvas (like draw_object in FloatCanvas) and override the Draw method and OnMouseEvent. The only example I can see of this though is ColumnarItem.py, which draws a rounded rectangle to show an event in the Chandler calendar parcel.

Other example code that is informative is the wxGrid_MegaExample.py included in the wxPython demo. It shows how you can create custom renderers for cells in a grid (MegaImageRenderer and MegaFontRenderer).

And one other example helpful to me is the source for the throbber control seen in the wxPython demo (/wxPython/lib/throbber.py). Because I'd like to have some components in an OGL-type canvas be animated.

But here's basic skeleton code that is similar to what FloatCanvas and SimpleCanvas do. Don't use this code though, use something like SimpleCanvas or FloatCanvas. This is just to help make them more understandable.

   1 class DrawableObject:
   2     def __init__(self, parentcanvas, pos, size):
   3        self.canvas = parentcanvas
   4        self.x = pos[0]
   5        self.y = pos[1]
   6        self.width = size[0]
   7        self.height = size[1]
   8        self.bounds = wxRect (self.x,self.y,self.width,self.height)
   9        #self.forecolor, bgcolor, border, font
  10 
  11     def Draw(self,dc):
  12        pass
  13     def OnMouseDown(self,x,y):
  14        return false
  15 
  16 class Rectangle(DrawableObject):
  17    def __init__(self, parentcanvas,pos, size):
  18        DrawableObject.__init__(self,parentcanvas,pos,size)
  19 
  20    def Draw(self, dc): #maybe try Piddle here instead of using dc
  21        #draw a rectangle, something like:
  22        dc.DrawRectangle(self.x,self.y,self.width,self.height)
  23 
  24    def OnMouseDown(self, x,y):
  25        #handle mouse event
  26        return true #if handled 
  27 
  28 
  29 class ObjectCanvas(wxWindow):
  30    def __init__(self):
  31         wxWindow.__init__( self, parent, id, wxDefaultPosition, size)
  32         self.DrawableObjectList = []
  33         EVT_PAINT (self, self.OnPaint)
  34         EVT_MOUSE_EVENTS (self, self.OnMouseEvent)
  35 
  36    def OnPaint (self, event):
  37         paintDC = wxPaintDC (self)
  38         self.PrepareDC (paintDC)
  39         self.Draw(dc) #actually they use DoubleBufferedDrawing
  40 
  41    def Draw(self, dc):
  42         for Object in self.DrawableObjectList:
  43             Object.Draw(dc) #here you could use Piddle instead of using wxPython's paintDC,memoryDC
  44 
  45    def OnMouseEvent (self, event):
  46         #you'd handle drag and drop here too
  47         x, y = event.GetX(),event.GetY()
  48         if event.LeftDown(): #or just pass any mouse event directly to the drawableobject like simplecanvas
  49             for obj in self.DrawableObjectList:
  50                 if obj.bounds.Inside ((x, y)):
  51                     if obj.OnMouseDown(x - obj.bounds.GetX(), y - obj.bounds.GetY()):
  52                         return true
  53 
  54    def AddObject(self, drawable):
  55         self.DrawableObjectList.insert(0,drawable)
  56 
  57    def RemoveObject(self, drawable):
  58         self.DrawableObjectList.remove(drawable)

We just need something like a DraggableToolBar class too, to make it easier for users to drag new objects into the OGL-like canvas.

Other Notes

There is no relationship between wxOGL and wxArt2d. (http://wxart2d.sourceforge.net/)

WineRackDesigner Using wx.OGL to design a wine rack layout, or alternatively FloatCanvas

Comments

I had to change the code from LionKimbro slightly to get this to work with wxPython 2.5 for Python 2.3.

Additionally, I had to modify the wx.lib.ogl.diagram.ShowAll() function to pass the second parameter to Show(). My new version looks like this:

    def ShowAll(self, show):
        """Call Show for each shape in the diagram."""
        for shape in self._shapeList:
            shape.Show(show)

instead of this:

    def ShowAll(self, show):
        """Call Show for each shape in the diagram."""
        for shape in self._shapeList:
            shape.Show()

Here's the example from LionKimbro, modified and working on my computer™

   1 from wxPython.wx  import *
   2 from wx.lib.ogl import *
   3 
   4 class AppFrame(wxFrame):
   5     def __init__(self):
   6         wxFrame.__init__( self,
   7                           None, -1, "Demo",
   8                           size=(300,200),
   9                           style=wxDEFAULT_FRAME_STYLE )
  10         sizer = wxBoxSizer( wxVERTICAL )
  11         # put stuff into sizer
  12 
  13         canvas = ShapeCanvas( self )
  14         sizer.Add( canvas, 1, wxGROW )
  15 
  16         canvas.SetBackgroundColour( "LIGHT BLUE" ) #
  17 
  18         diagram = Diagram()
  19         canvas.SetDiagram( diagram )
  20         diagram.SetCanvas( canvas )
  21 
  22         shape = CircleShape( 20.0 )              #
  23         shape.SetX( 25.0 )                         #
  24         shape.SetY( 25.0 )                         #
  25         canvas.AddShape( shape )                   #
  26         diagram.ShowAll( 1 )                       #
  27 
  28         # apply sizer
  29         self.SetSizer(sizer)
  30         self.SetAutoLayout(1)
  31         self.Show(1)
  32 
  33 
  34 
  35 
  36 app = wxPySimpleApp()
  37 OGLInitialize()
  38 frame = AppFrame()
  39 app.MainLoop()

wxOGL (last edited 2011-11-02 18:08:28 by D-69-91-157-28)