changeset 44:cbf199e007c2

Added some demo applications
author windel
date Sat, 18 Feb 2012 16:42:23 +0100
parents e47bfef80baf
children 8a52263d67c4
files applications/lab/bouncing_cube.py applications/lab/diagrameditor.py
diffstat 2 files changed, 740 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/applications/lab/bouncing_cube.py	Sat Feb 18 16:42:23 2012 +0100
@@ -0,0 +1,405 @@
+from PyQt4.QtGui import *
+from PyQt4.QtCore import *
+from PyQt4.QtOpenGL import QGLWidget
+from OpenGL.GL import *
+from OpenGL.GLU import gluPerspective
+import sys
+from random import random
+from math import pi, cos, sin, fabs, sqrt
+from numpy import mat, array, ones, zeros, eye
+from numpy.linalg import norm
+import numpy as np
+import time
+import scipy.integrate
+
+"""
+  Test script that lets a dice bounce.
+  Converted from 20-sim equations into python code.
+
+  20-sim website:
+    http://www.20sim.com
+
+"""
+def drawCube(w):
+      glBegin(GL_QUADS)			# Start Drawing The Cube
+      glColor3f(0.0,1.0,0.0)			# Set The Color To Blue
+      glVertex3f( w, w,-w)		# Top Right Of The Quad (Top)
+      glVertex3f(-w, w,-w)		# Top Left Of The Quad (Top)
+      glVertex3f(-w, w, w)		# Bottom Left Of The Quad (Top)
+      glVertex3f( w, w, w)		# Bottom Right Of The Quad (Top)
+
+      glColor3f(1.0,0.5,0.0)			# Set The Color To Orange
+      glVertex3f( w,-w, w)		# Top Right Of The Quad (Bottom)
+      glVertex3f(-w,-w, w)		# Top Left Of The Quad (Bottom)
+      glVertex3f(-w,-w,-w)		# Bottom Left Of The Quad (Bottom)
+      glVertex3f( w,-w,-w)		# Bottom Right Of The Quad (Bottom)
+
+      glColor3f(1.0,0.0,0.0)			# Set The Color To Red
+      glVertex3f( w, w, w)		# Top Right Of The Quad (Front)
+      glVertex3f(-w, w, w)		# Top Left Of The Quad (Front)
+      glVertex3f(-w,-w, w)		# Bottom Left Of The Quad (Front)
+      glVertex3f( w,-w, w)		# Bottom Right Of The Quad (Front)
+
+      glColor3f(1.0,1.0,0.0)			# Set The Color To Yellow
+      glVertex3f( w,-w,-w)		# Bottom Left Of The Quad (Back)
+      glVertex3f(-w,-w,-w)		# Bottom Right Of The Quad (Back)
+      glVertex3f(-w, w,-w)		# Top Right Of The Quad (Back)
+      glVertex3f( w, w,-w)		# Top Left Of The Quad (Back)
+
+      glColor3f(0.0,0.0,1.0)			# Set The Color To Blue
+      glVertex3f(-w, w, w)		# Top Right Of The Quad (Left)
+      glVertex3f(-w, w,-w)		# Top Left Of The Quad (Left)
+      glVertex3f(-w,-w,-w)		# Bottom Left Of The Quad (Left)
+      glVertex3f(-w,-w, w)		# Bottom Right Of The Quad (Left)
+
+      glColor3f(1.0,0.0,1.0)			# Set The Color To Violet
+      glVertex3f( w, w,-w)		# Top Right Of The Quad (Right)
+      glVertex3f( w, w, w)		# Top Left Of The Quad (Right)
+      glVertex3f( w,-w, w)		# Bottom Left Of The Quad (Right)
+      glVertex3f( w,-w,-w)		# Bottom Right Of The Quad (Right)
+      glEnd()				# Done Drawing The Quad
+
+def drawFloor(w, h):
+      glBegin(GL_QUADS)			# Start Drawing The Cube
+
+      glColor3f(1.0,0.5,0.0) # Set The Color To Orange
+      glVertex3f( w,-w,h)# Top Right Of The Quad (Bottom)
+      glVertex3f(-w,-w,h)# Top Left Of The Quad (Bottom)
+      glVertex3f(-w,w,h)# Bottom Left Of The Quad (Bottom)
+      glVertex3f( w,w,h)# Bottom Right Of The Quad (Bottom)
+      glEnd()				# Done Drawing The Quad
+
+def drawAxis():
+  glLineWidth(0.5)
+  glBegin(GL_LINES)
+  glColor3f(1.0, 0.0, 0.0)
+  glVertex3f(0,0,0)
+  glVertex3f(1,0,0)
+  glColor3f(0.0, 1.0, 0.0)
+  glVertex3f(0,0,0)
+  glVertex3f(0,1,0)
+  glColor3f(0.0, 0.0, 1.0)
+  glVertex3f(0,0,0)
+  glVertex3f(0,0,1)
+  glEnd()
+
+
+def cross(A, B):
+   a = A.A1
+   b = B.A1
+   return mat(np.cross(a, b)).T
+
+def skew(X):
+  Y = mat(zeros( (3, 3) ))
+  a,b,c = X.A1
+  Y[0,1] = -c
+  Y[0,2] = b
+  Y[1,0] = c
+  Y[1,2] = -a
+  Y[2,0] = -b
+  Y[2,1] = a
+  return Y
+
+def adjoint(T):
+  W = T[0:3, 0]
+  V = T[3:6, 0]
+  a = mat(zeros( (6,6) ) )
+  a[0:3, 0:3] = skew(W)
+  a[3:6, 0:3] = skew(V)
+  a[3:6, 3:6] = skew(W)
+  return a
+
+def Adjoint(H):
+  R = H[0:3, 0:3]
+  P = H[0:3, 3]
+  a = mat(zeros( (6,6) ) )
+  a[0:3, 0:3] = R
+  a[3:6, 3:6] = R
+  a[3:6, 0:3] = skew(P) * R
+  return a
+
+def quatToR(q):
+  x, y, z, w = q.A1
+  r = mat(eye(3))
+  r[0,0] = 1 - (2*y**2+2*z**2)
+  r[0,1] = 2*x*y+2*z*w
+  r[0,2] = 2*x*z - 2*y*w
+  r[1,0] = 2*x*y-2*z*w
+  r[1,1] = 1 - (2*x**2 + 2*z**2)
+  r[1,2] = 2*y*z + 2*x*w
+  r[2,0] = 2*x*z+2*y*w
+  r[2,1] = 2*y*z - 2*x*w
+  r[2,2] = 1 - (2*x**2+2*y**2)
+  return r
+
+def rotateAbout(axis, angle):
+   ax, ay, az = (axis/norm(axis)).A1
+   qx = ax*sin(angle/2.0)
+   qy = ay*sin(angle/2.0)
+   qz = az*sin(angle/2.0)
+   qw = cos(angle/2.0)
+   q = mat(array([qx,qy,qz,qw])).T
+   return q
+
+def normalizeQuaternion(quat):
+  x,y,z,w = quat.A1
+  magnitude = sqrt(x*x + y*y + z*z + w*w)
+  x = x / magnitude
+  y = y / magnitude
+  z = z / magnitude
+  w = w / magnitude
+  quat[0, 0] = x
+  quat[1, 0] = y
+  quat[2, 0] = z
+  quat[3, 0] = w
+  return quat
+
+def VTo4x4(V):
+    v1, v2, v3 = V.A1
+    return mat(array( \
+      [[0.0, -v3,  v2, -v1], \
+      [ v3,  0.0, -v1, -v2], \
+      [-v2, v1, 0.0, -v3], \
+      [v1, v2, v3, 0.0] ]))
+
+def homogeneous(R,p):
+    H = mat(eye(4))
+    H[0:3, 0:3] = R
+    H[0:3, 3] = p
+    return H
+
+def rateOfChange(states, thetime, parameters):
+    quat = states[0:4,  0] # Orientation (4)
+    pos  = states[4:7,  0] # Position (3)
+    P    = states[7:13, 0] # Momentum (6)
+    massI, gravity = parameters
+    # Rigid body parts:
+    # Forward Kinematic chain:
+    H = homogeneous(quatToR(quat), pos) # Forward kinematics
+
+    AdjX2 = mat(eye(6)) # The connectionpoint in the real world
+    adjAdjX2_1 = adjoint(AdjX2[0:6,0])
+    adjAdjX2_2 = adjoint(AdjX2[0:6,1])
+    adjAdjX2_3 = adjoint(AdjX2[0:6,2])
+    adjAdjX2_4 = adjoint(AdjX2[0:6,3])
+    adjAdjX2_5 = adjoint(AdjX2[0:6,4])
+    adjAdjX2_6 = adjoint(AdjX2[0:6,5])
+    AdjInv2 = Adjoint(H.I)
+    M2 = AdjInv2.T * (massI * AdjInv2) # Transfor mass to base frame
+    MassMatrix = M2
+
+    wrenchGrav2 = mat( zeros((1,6)) )
+    wrenchGrav2[0, 0:3] = -cross(gravity, pos).T
+    wrenchGrav2[0, 3:6] = gravity.T
+
+    Bk = mat( zeros( (6,6) ))
+    Bk[0:3, 0:3] = skew(P[0:3, 0])
+    Bk[3:6, 0:3] = skew(P[3:6, 0])
+    Bk[0:3, 3:6] = skew(P[3:6, 0])
+
+    # TODO: do this a cholesky:
+    v = np.linalg.solve(MassMatrix, P) # Matrix inverse like thingy !
+
+    T2_00 = v # Calculate the relative twist!
+    TM2 = T2_00.T * M2
+    twistExternal = T2_00 # Twist van het blokje
+    TMSum2 = TM2
+
+    PDotBodies = mat( zeros( (6,1)) )
+    PDotBodies[0,0] = TMSum2 * (adjAdjX2_1 * T2_00)
+    PDotBodies[1,0] = TMSum2 * (adjAdjX2_2 * T2_00)
+    PDotBodies[2,0] = TMSum2 * (adjAdjX2_3 * T2_00)
+    PDotBodies[3,0] = TMSum2 * (adjAdjX2_4 * T2_00)
+    PDotBodies[4,0] = TMSum2 * (adjAdjX2_5 * T2_00)
+    PDotBodies[5,0] = TMSum2 * (adjAdjX2_6 * T2_00)
+    
+    PDot = -PDotBodies - Bk * v
+    PDot += wrenchGrav2.T
+    
+    ##### Contact wrench part:
+    HB_W = H # Is H-matrix van het blokje
+    WrenchB = mat(zeros( (1,6) ))
+    for px in [-0.5, 0.5]:
+      for py in [-0.5, 0.5]:
+        for pz in [-0.5, 0.5]:
+          HB1_B = homogeneous(mat(eye(3)), mat([px,py,pz]).T)
+          HB1_W = HB_W * HB1_B
+          HW1_W = homogeneous(mat(eye(3)), HB1_W[0:3,3])
+          HW_W1 = HW1_W.I
+          HB_W1 = HW_W1 * HB_W
+    
+          AdjHB_W1 = Adjoint(HB_W1)
+          TB_W1_W1 = AdjHB_W1 * twistExternal
+          z = HB1_W[2, 3]
+          vx, vy, vz = TB_W1_W1[3:6, 0].A1
+          if z < 0:
+            # Contact forces:
+            Fx = -50.0*vx
+            Fy = -50.0*vy
+            Fz = -z*50000.0 
+          else:
+            Fx = 0.0
+            Fy = 0.0
+            Fz = 0.0
+          # TODO: reflect impulse
+          WrenchW1 = mat([0,0,0,0,0,Fz])
+          # Transform it back:
+          WrenchB += (AdjHB_W1.T * WrenchW1.T).T
+    ##### End of contact wrench
+
+    PDot += (WrenchB * AdjInv2).T
+
+    # Position and orientation rates:
+    QOmega = VTo4x4(v[0:3, 0])
+    quatDot = 0.5 * QOmega * quat
+    vel = v[3:6, 0]
+    posDot = skew(v[0:3]) * pos + vel
+    # The rate vector:
+    rates = mat(zeros( (13,1) ))
+    rates[0:4, 0] = quatDot
+    rates[4:7, 0] = posDot
+    rates[7:13, 0] = PDot
+    return rates
+
+def fWrapper(y, t, parameters):
+   y = mat(y).T
+   dy = rateOfChange(y, t, parameters)
+   return dy.T.A1
+  
+def SCIPY(endtime, dt, state, parameters):
+    times = np.arange(0.0, endtime, dt)
+    y0 = state.T.A1
+    res = scipy.integrate.odeint(fWrapper, y0, times, args=(parameters,))
+    states = []
+    res = res.T
+    r,c =  res.shape
+    for ci in range(0,c):
+      states.append(mat(res[:,ci]).T)
+    return states
+  
+def RK4(endtime, dt, state, parameters):
+    t = 0.0
+    states = []
+    while t < endtime:
+      newstate = mat (zeros( state.shape )) # Create a new object
+
+      #### Runge Kutta integration:
+      k1 = rateOfChange(state,             t, parameters)
+      k2 = rateOfChange(state + 0.5*dt*k1, t, parameters)
+      k3 = rateOfChange(state + 0.5*dt*k1, t, parameters)
+      k4 = rateOfChange(state + dt*k3,     t, parameters)
+      newstate = state + (dt/6.0)*(k1+2*k2+2*k3+k4)
+
+      # Normalize quat:
+      newstate[0:4, 0] = normalizeQuaternion(newstate[0:4, 0])
+      states.append(newstate)
+
+      state = newstate
+
+      t += dt
+      print state[6,0], t, ' ', (t/endtime)*100.0, '%'
+    return states
+  
+
+def simulate(endtime, dt):
+    PInitial = mat( zeros((6,1)) )
+    posInitial = mat(array([-1.2, -1.3, 2.8])).T
+    quatInitial = rotateAbout(mat(array([0.2, 1.0, 0.4])).T, 0.5)
+    # Parameters:
+    gravity = mat( array([0,0,-9.81]) ).T
+    massI = mat(eye(6)) * 1.01 # Mass matrix
+    parameters = (massI, gravity)
+
+    # The state vector!
+    state = mat(zeros( (13,1) ))
+    state[0:4, 0] = quatInitial
+    state[4:7, 0] = posInitial
+    state[7:13, 0] = PInitial
+
+    return SCIPY(endtime, dt, state, parameters)
+
+class W(QGLWidget):
+  time = 0.0
+  index = 0
+  def __init__(self, states, dt, parent=None):
+    super(W, self).__init__(parent)
+    self.firstRun = True
+    self.savePNGS = False
+    self.dt = dt
+    self.resize(500,500)
+    self.states = states
+    self.UP()
+    t = QTimer(self)
+    t.timeout.connect(self.UP)
+    t.start(self.dt*1000.0)
+
+  def UP(self):
+    self.time += self.dt
+    if self.states:
+      state = self.states[self.index]
+      self.Dicequat = state[0:4, 0]
+      self.Dicepos = state[4:7, 0]
+      self.index += 1
+      if self.index == len(self.states):
+        self.index = 0
+        self.firstRun = False
+    # Paint:
+    self.update()
+    if self.firstRun:
+       # Create png images for the movie:
+       if self.savePNGS:
+          pm = self.renderPixmap()
+          pm.save('image'+str(self.index)+'.png')
+
+  def initializeGL(self):
+      glClearColor(0.0, 0.5, 0.0, 1.0)
+      glEnable(GL_DEPTH_TEST)
+      glDepthFunc(GL_LESS)
+      glShadeModel(GL_SMOOTH)
+  def resizeGL(self,w,h):
+      glViewport(0, 0, w, h)
+      glMatrixMode(GL_PROJECTION)
+      glLoadIdentity()
+      gluPerspective(45.0, float(w)/float(h), 0.1, 100.0)
+      glMatrixMode(GL_MODELVIEW)
+  def paintGL(self):
+      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # Clear buffers
+      glLoadIdentity() # Reset The View
+
+      glLoadIdentity()
+      glTranslatef(0.0,-2.0,-10.0) # Move Left And Into The Screen
+      glRotatef(-90.0, 1.0, 0.0, 0.0)
+      drawFloor(2.0, 0.0)
+      drawAxis()
+      self.renderText(1.0, 0.0, 0.0, 'X')
+      self.renderText(0.0, 1.0, 0.0, 'Y')
+      self.renderText(0.0, 0.0, 1.0, 'Z')
+
+      self.renderText(0.0,0.0,1.2,str(self.time))
+
+      x,y,z = self.Dicepos.A1
+      R = quatToR(self.Dicequat)
+
+      glTranslatef(x, y, z)
+      # Trick to rotate the openGL matrix:
+      r = R.A1
+      rotR = (r[0], r[3], r[6], 0.0,
+              r[1], r[4], r[7], 0.0,
+              r[2], r[5], r[8], 0.0,
+              0.0,  0.0,  0.0,  1.0)
+      glMultMatrixd(rotR)
+      
+      drawCube(0.6)
+
+et = 20.0
+dt = 0.04
+print 'starting integration... endtime =', et, ' stepsize =', dt
+t0 = time.time()
+states = simulate(et, dt)
+t1 = time.time()
+print 'That was heavy, it took me ', t1-t0, ' seconds!'
+app = QApplication(sys.argv)
+w = W(states, dt)
+w.show()
+sys.exit(app.exec_())
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/applications/lab/diagrameditor.py	Sat Feb 18 16:42:23 2012 +0100
@@ -0,0 +1,335 @@
+#!/usr/bin/python
+
+from PyQt4 import QtGui, QtCore
+from PyQt4.QtGui import *
+from PyQt4.QtCore import *
+import sys
+
+"""
+  This script implements a basic diagram editor.
+"""
+
+class Connection:
+   """
+    - fromPort
+    - list of line items in between
+    - toPort
+   """
+   def __init__(self, fromPort, toPort):
+      self.fromPort = fromPort
+      self.pos1 = None
+      self.pos2 = None
+      self.p1dir = None
+      self.p2dir = None
+      self.setFromPort(fromPort)
+      self.toPort = toPort
+      # Create arrow item:
+      self.linePieces = []
+   def setFromPort(self, fromPort):
+      self.fromPort = fromPort
+      if self.fromPort:
+         self.pos1 = fromPort.scenePos()
+         self.fromPort.posCallbacks.append(self.setBeginPos)
+   def setToPort(self, toPort):
+      self.toPort = toPort
+      if self.toPort:
+         self.pos2 = toPort.scenePos()
+         self.toPort.posCallbacks.append(self.setEndPos)
+   def setBeginPos(self, pos1):
+      self.pos1 = pos1
+      if self.pos1 and self.pos2:
+         self.updateLineStukken()
+   def setEndPos(self, endpos):
+      self.pos2 = endpos
+      if self.pos1 and self.pos2:
+         self.updateLineStukken()
+   def updateLineStukken(self):
+      """
+         This algorithm determines the optimal routing of all signals.
+         TODO: implement nice automatic line router
+      """
+      # TODO: create pieces of lines.
+
+      # Determine the current amount of linestukken:
+      x1, y1 = self.pos1.x(), self.pos1.y()
+      x2, y2 = self.pos2.x(), self.pos2.y()
+
+      ds = editor.diagramScene
+
+      if y1 == y2 or x1 == x2:
+         pass
+      else:
+         # We require two lijnstukken to make one corner!
+         while len(self.linePieces) < 2:
+            lp = LinePieceItem()
+            ds.addItem(lp)
+            self.linePieces.append(lp)
+         lp1 = self.linePieces[0]
+         lp2 = self.linePieces[1]
+         lp1.setLine(QLineF(x1, y1, x2, y1))
+         lp2.setLine(QLineF(x2, y1, x2, y2))
+
+   def delete(self):
+      editor.diagramScene.removeItem(self.arrow)
+      # Remove position update callbacks:
+
+class ParameterDialog(QDialog):
+   def __init__(self, parent=None):
+      super(ParameterDialog, self).__init__(parent)
+      self.button = QPushButton('Ok', self)
+      l = QVBoxLayout(self)
+      l.addWidget(self.button)
+      self.button.clicked.connect(self.OK)
+   def OK(self):
+      self.close()
+
+class PortItem(QGraphicsEllipseItem):
+   """ Represents a port to a subsystem """
+   def __init__(self, name, parent=None):
+      QGraphicsEllipseItem.__init__(self, QRectF(-6,-6,12.0,12.0), parent)
+      self.setCursor(QCursor(QtCore.Qt.CrossCursor))
+      # Properties:
+      self.setBrush(QBrush(Qt.red))
+      # Name:
+      self.name = name
+      self.posCallbacks = []
+      self.setFlag(self.ItemSendsScenePositionChanges, True)
+   def itemChange(self, change, value):
+      if change == self.ItemScenePositionHasChanged:
+         value = value.toPointF() # Required!
+         for cb in self.posCallbacks:
+            cb(value)
+         return value
+      return super(PortItem, self).itemChange(change, value)
+   def mousePressEvent(self, event):
+      editor.startConnection(self)
+
+# Block part:
+class HandleItem(QGraphicsEllipseItem):
+   """ A handle that can be moved by the mouse """
+   def __init__(self, parent=None):
+      super(HandleItem, self).__init__(QRectF(-4.0,-4.0,8.0,8.0), parent)
+      self.posChangeCallbacks = []
+      self.setBrush(QtGui.QBrush(Qt.white))
+      self.setFlag(self.ItemIsMovable, True)
+      self.setFlag(self.ItemSendsScenePositionChanges, True)
+      self.setCursor(QtGui.QCursor(Qt.SizeFDiagCursor))
+
+   def itemChange(self, change, value):
+      if change == self.ItemPositionChange:
+         value = value.toPointF()
+         x, y = value.x(), value.y()
+         # TODO: make this a signal?
+         # This cannot be a signal because this is not a QObject
+         for cb in self.posChangeCallbacks:
+            res = cb(x, y)
+            if res:
+               x, y = res
+               value = QPointF(x, y)
+         return value
+      # Call superclass method:
+      return super(HandleItem, self).itemChange(change, value)
+
+class BlockItem(QGraphicsRectItem):
+   """ 
+      Represents a block in the diagram
+      Has an x and y and width and height
+      width and height can only be adjusted with a tip in the lower right corner.
+
+      - in and output ports
+      - parameters
+      - description
+   """
+   def __init__(self, name='Untitled', parent=None):
+      super(BlockItem, self).__init__(parent)
+      w = 60.0
+      h = 40.0
+      # Properties of the rectangle:
+      self.setPen(QtGui.QPen(QtCore.Qt.blue, 2))
+      self.setBrush(QtGui.QBrush(QtCore.Qt.lightGray))
+      self.setFlags(self.ItemIsSelectable | self.ItemIsMovable)
+      self.setCursor(QCursor(QtCore.Qt.PointingHandCursor))
+      # Label:
+      self.label = QGraphicsTextItem(name, self)
+      # Create corner for resize:
+      self.sizer = HandleItem(self)
+      self.sizer.setPos(w, h)
+      self.sizer.posChangeCallbacks.append(self.changeSize) # Connect the callback
+      #self.sizer.setVisible(False)
+      self.sizer.setFlag(self.sizer.ItemIsSelectable, True)
+
+      # Inputs and outputs of the block:
+      self.inputs = []
+      self.inputs.append( PortItem('a', self) )
+      self.inputs.append( PortItem('b', self) )
+      self.inputs.append( PortItem('c', self) )
+      self.outputs = []
+      self.outputs.append( PortItem('y', self) )
+      # Update size:
+      self.changeSize(w, h)
+   def editParameters(self):
+      pd = ParameterDialog(self.window())
+      pd.exec_()
+
+   def contextMenuEvent(self, event):
+      menu = QMenu()
+      menu.addAction('Delete')
+      pa = menu.addAction('Parameters')
+      pa.triggered.connect(self.editParameters)
+      menu.exec_(event.screenPos())
+
+   def changeSize(self, w, h):
+      """ Resize block function """
+      # Limit the block size:
+      if h < 20:
+         h = 20
+      if w < 40:
+         w = 40
+      self.setRect(0.0, 0.0, w, h)
+      # center label:
+      rect = self.label.boundingRect()
+      lw, lh = rect.width(), rect.height()
+      lx = (w - lw) / 2
+      ly = (h - lh) / 2
+      self.label.setPos(lx, ly)
+      # Update port positions:
+      if len(self.inputs) == 1:
+         self.inputs[0].setPos(-4, h / 2)
+      elif len(self.inputs) > 1:
+         y = 5
+         dy = (h - 10) / (len(self.inputs) - 1)
+         for inp in self.inputs:
+            inp.setPos(-4, y)
+            y += dy
+      if len(self.outputs) == 1:
+         self.outputs[0].setPos(w+4, h / 2)
+      elif len(self.outputs) > 1:
+         y = 5
+         dy = (h - 10) / (len(self.outputs) + 0)
+         for outp in self.outputs:
+            outp.setPos(w+4, y)
+            y += dy
+      return w, h
+
+class LinePieceItem(QGraphicsLineItem):
+   def __init__(self):
+      super(LinePieceItem, self).__init__(None)
+      self.setPen(QtGui.QPen(QtCore.Qt.red,2))
+      self.setFlag(self.ItemIsSelectable, True)
+   def x(self):
+      pass
+
+class EditorGraphicsView(QGraphicsView):
+   def __init__(self, scene, parent=None):
+      QGraphicsView.__init__(self, scene, parent)
+   def dragEnterEvent(self, event):
+      if event.mimeData().hasFormat('component/name'):
+         event.accept()
+   def dragMoveEvent(self, event):
+      if event.mimeData().hasFormat('component/name'):
+         event.accept()
+   def dropEvent(self, event):
+      if event.mimeData().hasFormat('component/name'):
+         name = str(event.mimeData().data('component/name'))
+         b1 = BlockItem(name)
+         b1.setPos(self.mapToScene(event.pos()))
+         self.scene().addItem(b1)
+
+class LibraryModel(QStandardItemModel):
+   def __init__(self, parent=None):
+      QStandardItemModel.__init__(self, parent)
+   def mimeTypes(self):
+      return ['component/name']
+   def mimeData(self, idxs):
+      mimedata = QMimeData()
+      for idx in idxs:
+         if idx.isValid():
+            txt = self.data(idx, Qt.DisplayRole).toByteArray()
+            mimedata.setData('component/name', txt)
+      return mimedata
+
+class DiagramScene(QGraphicsScene):
+   def __init__(self, parent=None):
+      super(DiagramScene, self).__init__(parent)
+   def mouseMoveEvent(self, mouseEvent):
+      editor.sceneMouseMoveEvent(mouseEvent)
+      super(DiagramScene, self).mouseMoveEvent(mouseEvent)
+   def mouseReleaseEvent(self, mouseEvent):
+      editor.sceneMouseReleaseEvent(mouseEvent)
+      super(DiagramScene, self).mouseReleaseEvent(mouseEvent)
+
+class DiagramEditor(QWidget):
+   def __init__(self, parent=None):
+      QtGui.QWidget.__init__(self, parent)
+      self.setWindowTitle("Diagram editor")
+
+      # Widget layout and child widgets:
+      self.horizontalLayout = QtGui.QHBoxLayout(self)
+      self.libraryBrowserView = QtGui.QListView(self)
+      self.libraryModel = LibraryModel(self)
+      self.libraryModel.setColumnCount(1)
+      # Create an icon with an icon:
+      pixmap = QPixmap(60, 60)
+      pixmap.fill()
+      painter = QPainter(pixmap)
+      painter.fillRect(10, 10, 40, 40, Qt.blue)
+      painter.setBrush(Qt.red)
+      painter.drawEllipse(36, 2, 20, 20)
+      painter.setBrush(Qt.yellow)
+      painter.drawEllipse(20, 20, 20, 20)
+      painter.end()
+
+      self.libItems = []
+      self.libItems.append( QtGui.QStandardItem(QIcon(pixmap), 'Block') )
+      self.libItems.append( QtGui.QStandardItem(QIcon(pixmap), 'Uber Unit') )
+      self.libItems.append( QtGui.QStandardItem(QIcon(pixmap), 'Device') )
+      for i in self.libItems:
+         self.libraryModel.appendRow(i)
+      self.libraryBrowserView.setModel(self.libraryModel)
+      self.libraryBrowserView.setViewMode(self.libraryBrowserView.IconMode)
+      self.libraryBrowserView.setDragDropMode(self.libraryBrowserView.DragOnly)
+
+      self.diagramScene = DiagramScene(self)
+      self.diagramView = EditorGraphicsView(self.diagramScene, self)
+      self.horizontalLayout.addWidget(self.libraryBrowserView)
+      self.horizontalLayout.addWidget(self.diagramView)
+
+      # Populate the diagram scene:
+      b1 = BlockItem('SubSystem1')
+      b1.setPos(50,100)
+      self.diagramScene.addItem(b1)
+      b2 = BlockItem('Unit2')
+      b2.setPos(-250,0)
+      self.diagramScene.addItem(b2)
+
+      self.startedConnection = None
+      fullScreenShortcut = QShortcut(QKeySequence("F11"), self)
+      fullScreenShortcut.activated.connect(self.toggleFullScreen)
+   def toggleFullScreen(self):
+      self.setWindowState(self.windowState() ^ Qt.WindowFullScreen)
+   def startConnection(self, port):
+      self.startedConnection = Connection(port, None)
+   def sceneMouseMoveEvent(self, event):
+      if self.startedConnection:
+         pos = event.scenePos()
+         self.startedConnection.setEndPos(pos)
+   def sceneMouseReleaseEvent(self, event):
+      # Clear the actual connection:
+      if self.startedConnection:
+         pos = event.scenePos()
+         items = self.diagramScene.items(pos)
+         for item in items:
+            if type(item) is PortItem:
+               self.startedConnection.setToPort(item)
+         if self.startedConnection.toPort == None:
+            self.startedConnection.delete()
+         self.startedConnection = None
+
+if __name__ == '__main__':
+   app = QtGui.QApplication(sys.argv)
+   global editor
+   editor = DiagramEditor()
+   editor.show()
+   editor.resize(700, 800)
+   app.exec_()
+