Referring to previous prototypes this class was setup reasonably fast, the Polygon class however was built from scratch as my prototype for that did it’s own triangulation (slowly I might add) and I figured out that although QPolygon does not accept a list of points it can quite easily be populated with a concave shape with good results.
from PyQt4 import QtGui #import classes from the same package in the same namespace from MouseData import * from Qtutils.LaunchAsStandalone import * from PaintItem import * from Polygon import * ''' GraphicsView is a custom implementation of the QGraphicsView that does not depend on scenes and does not support zooming and panning in such an awkwardly enforced way. In the event of camera usage, the mouse coordinates will also be in a converted state allowing functionality programming in a natural way. ''' class GraphicsView(QtGui.QFrame): def __init__(self, parent=None): #inherit from QFrame for the paint function #and the ability to be used in a window QtGui.QFrame.__init__(self, parent) #track what items are attached to this scene self._paintitems =  ''' Helper function for adding drawable items to the graphicsview, filters out items that do not support drawing @param in_item: item to add on the stage any class with a paint(QPainter) function ''' def addItem(self, in_item): if isinstance(in_item, PaintItem): self._paintitems.append(in_item) ''' Inherited paint event ''' def paintEvent(self, e): #let parent handle drawing the main object QtGui.QFrame.paintEvent(self, e) #create a painter for objects to draw with painter = QtGui.QPainter(self) #draw each paintable item for item in self._paintitems: item.paint(painter)
Then to prototype this class, I use the QtStandalone class and the following main function at the bottom of my GraphicsView file:
#main function to launch as standalone app for unit-tests def main(): w = GraphicsView() w.resize(QtCore.QSize(110,110)) p = Polygon([Vec(0,0), Vec(100,0), Vec(100,100), Vec(66,100), Vec(66,50), Vec(33,50), Vec(33,100), Vec(0,100)]) w.addItem(p) w.show() return w QtStandalone(main)
This won’t work of course, because the Polygon and PaintItem classes are not known. Now I created the PaintItem to be able to do some type checking as well as to implement generalized behaviour as I want my drawn objects to be able to transform (scale, rotate, reposition) later on, and possibly even have a zoom feature, but for not it is pretty much empty:
''' base class for drawable items ''' class PaintItem: def __init__(self): pass
So here’s the polygon class, it stores it’s vertices in a list and rebuilds the internal QPolygon only when necessary to gain performance (I don’t know how slow this program will become at the end of the day so I’m taking some minor precautions).
It is made to be a finalized class so variables are made private with setter functions handling clean assignment to say brush and pen colors as well as points. The addPoint method is to be used extensively while drawing and possibly we need an optimizePoints function to cleanup redundant points after freehand drawing, but more on that later.
from PaintItem import * from PyQt4 import QtCore, QtGui class Polygon(PaintItem): ''' @param in_points: list of Vec2, the points to add as polygon vertices. Default is an empty list. ''' def __init__(self, in_points = ): #polygon vertices, a list of Vec2 self._points = in_points #required for self._polygon to exist self._buildPolygon() self._pen = QtCore.Qt.NoPen; self._brush = QtGui.QBrush( QtGui.QColor(64,64,244,128) ) ''' Set the outline color to draw with @param c: QColor, color to use if c == None there will be no outline ''' def setOutlineQColor(self, c): if c == None: #remove pen self._pen = QtCore.Qt.NoPen elif self._pen == QtCore.Qt.NoPen: #rebuild pen self._pen = QtGui.QPen(c) else: #change color of existing pen self._pen.setColor(c) ''' set the fill color to draw with @param c: QColor, color to use if c == None there will be no fill ''' def setQColor(self, c): if c == None: #remove brush self._brush = QtCore.Qt.NoBrush elif self._brush == QtCore.Qt.NoBrush: #rebuild brush self._brush = QtGui.QBrush(c) else: #change color of existing brush self._brush.setColor(c) ''' Helper function to generate an int-based QColor to set as outline color @param r: int, 0-255 based red value @param g: int, 0-255 based green value @param b: int, 0-255 based blue value ''' def setOutlineColor(self, r, g, b, a=255): self.setOutlineQColor( QtGui.QColor(r,g,b,a) ) ''' Sets the fill color, see setOutlineColor ''' def setColor(self, r, g, b, a): self.setQColor( QtGui.QColor(r,g,b,a) ) ''' Assigns a new Polygon to self._polygon containing the current self._points data Call after changeing self._points ''' def _buildPolygon(self): npt = len(self._points) #create a polygon of the right size self._polygon = QtGui.QPolygon() #initialize argument list starting at point index 0 #WARNING: documentation says this function requires index, nPoints #but that is not the case, the nPoints is handled automatically ptlist =  #add all points' x,y to the arglist for i in range(npt): ptlist.extend([self._points[i], self._points[i]]) #apply the arglist to the yet empty self._polygon apply(self._polygon.putPoints, ptlist) ''' @param in_point: Vec2, point to add to the polygon ''' def addPoint(self, in_point): self._points.append(in_point) #update geometry as the polygon has been modified self._buildPolygon() ''' @param in_points: list of Vec2, points to add to the polygon ''' def addPoints(self, in_points): self._points.extend(in_points) #update geometry as the polygon has been modified self._buildPolygon() ''' Paints the polygon using self._polygon, whether it is out of date or not, and by applying self._brush and self._pen to it ''' def paint(self, painter): #TODO: add color and pen settings painter.setBrush( self._brush ) painter.setPen( self._pen ) #draw the polygon painter.drawPolygon(self._polygon)
Now assuming all files are named after their contained class the GraphicsView should be executable resulting in the following window: