def __init__(self, viewport, viewportMngr, vpType, prototypeMngr, parent=None): """ Initialization of the CompositionView class Parameters: viewportMngr - the manager of the viewports where the composition view can reside in prototypeMngr - the manager of the prototypes is used to obtain the results of the solver """ QtWidgets.QDialog.__init__(self, parent) self.prototypeManager = prototypeMngr self.viewport = viewport self.viewportManager = viewportMngr self.settings = Settings() self.setWindowFlags(QtCore.Qt.Window) self.timer = QtCore.QObject() # QtCore.qsrand(QtCore.QTime(0,0,0).secsTo(QtCore.QTime.currentTime())) self.tree = Tree(None) self.infoOverlay = CVInfoOverlay(self) self.connections = [] self.ui = Ui_compositionView() self.ui.setupUi(self) self.ui.graphicsView.setupViewport( QtOpenGL.QGLWidget( QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers | QtOpenGL.QGL.DoubleBuffer))) # self.ui.graphicsView.setViewport(QtGui.QWidget()) self.ui.graphicsView.setRenderHints( QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform) self.collapsed = False self.currentTool = None self.viewportType = vpType self.first = False self.nodeId = 0 self.overConstrainedColor = QtGui.QColor(0, 0, 255) self.underConstrainedColor = QtGui.QColor(255, 0, 0) self.wellConstrainedColor = QtGui.QColor(0, 255, 0) self.unsolvedColor = QtGui.QColor(125, 124, 255) self.setScene() self.createTriggers()
def __init__(self, viewport, viewportMngr, vpType, prototypeMngr, parent=None): """ Initialization of the CompositionView class Parameters: viewportMngr - the manager of the viewports where the composition view can reside in prototypeMngr - the manager of the prototypes is used to obtain the results of the solver """ QtGui.QDialog.__init__(self, parent) self.prototypeManager = prototypeMngr self.viewport = viewport self.viewportManager = viewportMngr self.settings = Settings() self.setWindowFlags(QtCore.Qt.Window) self.timer = QtCore.QObject() """map GeometricDecomposition to CVCluster""" self.map = {} self.ui = Ui_compositionView() self.ui.setupUi(self) self.ui.graphicsView.setupViewport(QtOpenGL.QGLWidget(QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers|QtOpenGL.QGL.DoubleBuffer))) self.ui.graphicsView.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform) self.currentTool = None self.viewportType = vpType self.orientation = TreeOrientation.BOTTOM self.overConstrainedColor = QtGui.QColor(0,0,255) self.underConstrainedColor = QtGui.QColor(255,0,0) self.wellConstrainedColor = QtGui.QColor(0,255,0) self.unsolvedColor = QtGui.QColor(125,124,255) self.createScene() self.createTriggers()
def __init__(self, viewport, viewportMngr, vpType, prototypeMngr, parent=None): """ Initialization of the CompositionView class Parameters: viewportMngr - the manager of the viewports where the composition view can reside in prototypeMngr - the manager of the prototypes is used to obtain the results of the solver """ QtGui.QDialog.__init__(self, parent) self.prototypeManager = prototypeMngr self.viewport = viewport self.viewportManager = viewportMngr self.settings = Settings() self.setWindowFlags(QtCore.Qt.Window) self.timer = QtCore.QObject() """map GeometricDecomposition to CVCluster""" self.map = {} self.ui = Ui_compositionView() self.ui.setupUi(self) self.ui.graphicsView.setupViewport( QtOpenGL.QGLWidget( QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers | QtOpenGL.QGL.DoubleBuffer))) self.ui.graphicsView.setRenderHints( QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform) self.currentTool = None self.viewportType = vpType self.orientation = TreeOrientation.BOTTOM self.overConstrainedColor = QtGui.QColor(0, 0, 255) self.underConstrainedColor = QtGui.QColor(255, 0, 0) self.wellConstrainedColor = QtGui.QColor(0, 255, 0) self.unsolvedColor = QtGui.QColor(125, 124, 255) self.createScene() self.createTriggers()
def __init__(self, viewport, viewportMngr, vpType, prototypeMngr, parent=None): """ Initialization of the CompositionView class Parameters: viewportMngr - the manager of the viewports where the composition view can reside in prototypeMngr - the manager of the prototypes is used to obtain the results of the solver """ QtGui.QDialog.__init__(self, parent) self.prototypeManager = prototypeMngr self.viewport = viewport self.viewportManager = viewportMngr self.settings = Settings() self.setWindowFlags(QtCore.Qt.Window) self.timer = QtCore.QObject() #QtCore.qsrand(QtCore.QTime(0,0,0).secsTo(QtCore.QTime.currentTime())) self.tree = Tree(None) self.infoOverlay = CVInfoOverlay(self) self.connections = [] self.ui = Ui_compositionView() self.ui.setupUi(self) self.ui.graphicsView.setupViewport(QtOpenGL.QGLWidget(QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers|QtOpenGL.QGL.DoubleBuffer))) #self.ui.graphicsView.setViewport(QtGui.QWidget()) self.ui.graphicsView.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform) self.collapsed = False self.currentTool = None self.viewportType = vpType self.first = False self.nodeId = 0 self.overConstrainedColor = QtGui.QColor(0,0,255) self.underConstrainedColor = QtGui.QColor(255,0,0) self.wellConstrainedColor = QtGui.QColor(0,255,0) self.unsolvedColor = QtGui.QColor(125,124,255) self.setScene() self.createTriggers()
class DecompositionView(QtGui.QDialog): """ A view where the decomposition of the system of constraints is visualised as a directed acyclic graph""" def __init__(self, viewport, viewportMngr, vpType, prototypeMngr, parent=None): """ Initialization of the CompositionView class Parameters: viewportMngr - the manager of the viewports where the composition view can reside in prototypeMngr - the manager of the prototypes is used to obtain the results of the solver """ QtGui.QDialog.__init__(self, parent) self.prototypeManager = prototypeMngr self.viewport = viewport self.viewportManager = viewportMngr self.settings = Settings() self.setWindowFlags(QtCore.Qt.Window) self.timer = QtCore.QObject() """map GeometricDecomposition to CVCluster""" self.map = {} self.ui = Ui_compositionView() self.ui.setupUi(self) self.ui.graphicsView.setupViewport(QtOpenGL.QGLWidget(QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers|QtOpenGL.QGL.DoubleBuffer))) self.ui.graphicsView.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform) self.currentTool = None self.viewportType = vpType self.orientation = TreeOrientation.BOTTOM self.overConstrainedColor = QtGui.QColor(0,0,255) self.underConstrainedColor = QtGui.QColor(255,0,0) self.wellConstrainedColor = QtGui.QColor(0,255,0) self.unsolvedColor = QtGui.QColor(125,124,255) self.createScene() self.createTriggers() def createTriggers(self): """ Create the triggers for the components in the graphical window """ QtCore.QObject.connect(self.ui.zoomInButton,QtCore.SIGNAL("clicked()"),self.zoomIn) QtCore.QObject.connect(self.ui.zoomOutButton,QtCore.SIGNAL("clicked()"),self.zoomOut) QtCore.QObject.connect(self.ui.fitButton, QtCore.SIGNAL("clicked()"), self.fit) #QtCore.QObject.connect(self.ui.collapseButton, QtCore.SIGNAL("clicked()"), self.collapse) QtCore.QObject.connect(self.ui.graphicsScene, QtCore.SIGNAL("changed(const QList<QRectF> & )"), self.updateSceneRect) QtCore.QObject.connect(self.ui.verticalSlider,QtCore.SIGNAL("valueChanged(int)"),self.setupMatrix) #QtCore.QObject.connect(self.settings.dvData,QtCore.SIGNAL("treeOrientationChanged()"), self.updateTreeOrientation) def getViewportType(self): return self.viewportType def updateGL(self): self.update() def createDecomposition(self): """ Create a new decomposition. If an older one exists it will be removed. """ self.clearScene() self.createScene() def clearScene(self): self.map = {} if self.ui.graphicsScene != None: for item in self.ui.graphicsView.items(): item.hide() if item.parentItem() == None: self.ui.graphicsScene.removeItem(item) def createScene(self): """ Updating the view with new data and nodes for the visualisation of the tree """ if self.prototypeManager.result != None: # get all clusters from result new = [self.prototypeManager.result] clusters = set() while len(new) > 0: c = new.pop() clusters.add(c) for child in c.subs: if child not in clusters: new.append(child) # create N layers for clusters with 1-N variables N = len(self.prototypeManager.result.variables) layers = [] for n in range(0,N+1): layers.append([]) # add clusters to layers for c in clusters: n = len(c.variables) layers[n].append(c) # sort clusters in layers # start from layer N (largest clusters) # clusters are initially ordered according to the order in which sub-clusters appear in the previous (n+1) layer for n in reversed(range(1,N)): print "ordering layers",n # find subiable pseudo-ordering in previous layers subordervalue = {} clusterindex = 0 for cluster in layers[n+1]: clusterindex = clusterindex+1 for sub in cluster.subs: # order by first appearence in cluster from left to right if sub not in subordervalue: subordervalue[sub] = clusterindex # determine pseudo-order clusters in this layers: sum subordervalues per cluster clusterordervalue = {} for cluster in layers[n]: clusterordervalue[cluster] = 0 for sub in cluster.subs: if sub in subordervalue: clusterordervalue[cluster] += subordervalue[sub] # sort clusters in layers layers[n].sort(lambda x,y:clusterordervalue[x]<clusterordervalue[y]) # map GeometricDecompositions to CVClusters for n in range(0,N+1): layer = layers[n] for k in range(0,len(layer)): c = layer[k] y = n * 50.0 x = (k - len(layer)/2.0) * 50.0 * n cvcluster = CVCluster(self, c, x,y) self.ui.graphicsScene.addItem(cvcluster) self.map[c] = cvcluster self.map[cvcluster] = c # add CVConnections for c in clusters: for child in c.subs: self.ui.graphicsScene.addItem(CVConnection(self, self.map[c], self.map[child])) # iteratively improve graph layout self.optimiseGraphLayout() def optimiseGraphLayout(self): print "optimising graph layout..." # force due to overlapping overlaps force_cluster = 0.2 # force due to connection length force_connection = 0.025 # force due to clusters overlapping connections force_cluster_connection = 0.05 # create a graph of clusters and connections graph = geosolver.graph.Graph() if self.ui.graphicsScene != None: for item in self.ui.graphicsView.items(): if isinstance(item, CVCluster): graph.add_vertex(item) item.force = numpy.array([0.0,0.0]) elif isinstance(item, CVConnection): graph.add_edge(item.nodeFrom, item.nodeTo, item) l = list(graph.vertices()) # iteratively improve layout for i in range(100): # clear forces for c in l: c.force = numpy.array([random.random()*0, random.random()*0]) # determine forces due to overlapping cluster boxes n = len(l) for i in range(n): for j in range(i+1,n): c1 = l[i] c2 = l[j] box1 = c1.boundingRect().translated(c1.position) box1.setWidth(2*box1.width()) box1.setHeight(2*box1.height()) box2 = c2.boundingRect().translated(c2.position) box2.setWidth(2*box2.width()) box2.setHeight(2*box2.height()) #print "box 1", box1 #print "box 2", box2 if box1.intersects(box2): #print "intersects" force = box1.intersected(box2).width() + box1.intersected(box2).height() centerdiff = box2.center()-box1.center() direction = numpy.array([centerdiff.x(),centerdiff.y()]) norm = numpy.linalg.norm(direction) if norm != 0: direction = direction / numpy.linalg.norm(direction) else: direction = numpy.array([0,0]) #direction[1] = 0.0 c1.force += -force*direction * force_cluster; c2.force += force*direction * force_cluster; #print "force 1", c1.force #print "force 2", c2.force # determine forces due to connections for e in graph.edges(): c1 = e[0] c2 = e[1] box1 = c1.boundingRect().translated(c1.position) box2 = c2.boundingRect().translated(c2.position) # force 1: pull together on x centerdiff = box2.center()-box1.center() direction = numpy.array([centerdiff.x(),0]) norm = numpy.linalg.norm(direction) if norm != 0: direction = direction / numpy.linalg.norm(direction) else: direction = numpy.array([0,0]) goal = 0 force = (norm - goal) * force_connection; c1.force += +force*direction; c2.force += -force*direction; # force 2: keep y at distance and in layer order direction = numpy.array([0, centerdiff.y()]) norm = numpy.linalg.norm(direction) if norm != 0: direction = direction / numpy.linalg.norm(direction) else: direction = numpy.array([0,0]) goal = box1.height() + box2.height() force = (norm - goal) * force_connection; c1.force += +force*direction; c2.force += -force*direction; #print "force ", force # determine forces due to clusters overlapping connections n = len(l) for c in graph.vertices(): for e in graph.edges(): box1 = c.boundingRect().translated(c.position) box1.setWidth(0.5*box1.width()) box1.setHeight(0.5*box1.height()) con = graph.get(e[0],e[1]) box2 = con.boundingRect() box2.setWidth(0.5*box2.width()) box2.setHeight(0.5*box2.height()) #print "box 1", box1 #print "box 2", box2 if box1.intersects(box2): #print "intersects" force = box1.intersected(box2).width() + box1.intersected(box2).height() centerdiff = box2.center()-box1.center() direction = numpy.array([centerdiff.x(),centerdiff.y()]) norm = numpy.linalg.norm(direction) if norm != 0: direction = direction / numpy.linalg.norm(direction) else: direction = numpy.array([0,0]) c.force += -force*direction * force_cluster_connection; e[0].force += force*direction * force_cluster_connection; e[1].force += force*direction * force_cluster_connection; #print "force 1", c1.force #print "force 2", c2.force # apply forces for c in l: move = QtCore.QPointF(c.force[0],c.force[1]) c.position += move c.translate(move.x(), move.y()) # uppate connectors for e in graph.edges(): connector = graph.get(e[0],e[1]) connector.determinePath() # done iterating print "done" def updateViewports(self): self.viewportManager.updateViewports() def updateSceneRect(self, rectList=None): self.ui.graphicsScene.setSceneRect(self.ui.graphicsScene.itemsBoundingRect()) def zoomIn(self): """ Zoom in the graphics view, by updating the vertical slider """ self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() + 1) def zoomOut(self): """ Zoom out the graphics view, by updating the vertical slider """ self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() - 1) def fit(self): """ Fits the tree exactly in the graphics view """ self.ui.graphicsView.fitInView(0.0, 0.0, self.ui.graphicsScene.width(), self.ui.graphicsScene.height(), QtCore.Qt.KeepAspectRatio) """ Update the slider """ value = (math.log(self.ui.graphicsView.matrix().m11(),2)*50) + 250.0 self.ui.verticalSlider.setValue(value) def setupMatrix(self, value): """ Zoom in/out the graphics view, depending on the value of the slider Parameters value - value of the updated slider """ scale = math.pow(2.0, (self.ui.verticalSlider.value()-250.0)/50.0) matrix = QtGui.QMatrix() matrix.scale(scale,scale) self.ui.graphicsView.setMatrix(matrix)
class CompositionView(QtGui.QDialog): """ A view where the decomposition of the system of constraints is visualised as a tree """ def __init__(self, viewport, viewportMngr, vpType, prototypeMngr, parent=None): """ Initialization of the CompositionView class Parameters: viewportMngr - the manager of the viewports where the composition view can reside in prototypeMngr - the manager of the prototypes is used to obtain the results of the solver """ QtGui.QDialog.__init__(self, parent) self.prototypeManager = prototypeMngr self.viewport = viewport self.viewportManager = viewportMngr self.settings = Settings() self.setWindowFlags(QtCore.Qt.Window) self.timer = QtCore.QObject() #QtCore.qsrand(QtCore.QTime(0,0,0).secsTo(QtCore.QTime.currentTime())) self.tree = Tree(None) self.infoOverlay = CVInfoOverlay(self) self.connections = [] self.ui = Ui_compositionView() self.ui.setupUi(self) self.ui.graphicsView.setupViewport(QtOpenGL.QGLWidget(QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers|QtOpenGL.QGL.DoubleBuffer))) #self.ui.graphicsView.setViewport(QtGui.QWidget()) self.ui.graphicsView.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform) self.collapsed = False self.currentTool = None self.viewportType = vpType self.first = False self.nodeId = 0 self.overConstrainedColor = QtGui.QColor(0,0,255) self.underConstrainedColor = QtGui.QColor(255,0,0) self.wellConstrainedColor = QtGui.QColor(0,255,0) self.unsolvedColor = QtGui.QColor(125,124,255) self.setScene() self.createTriggers() def createTriggers(self): """ Create the triggers for the components in the graphical window """ QtCore.QObject.connect(self.ui.zoomInButton,QtCore.SIGNAL("clicked()"),self.zoomIn) QtCore.QObject.connect(self.ui.zoomOutButton,QtCore.SIGNAL("clicked()"),self.zoomOut) QtCore.QObject.connect(self.ui.fitButton, QtCore.SIGNAL("clicked()"), self.fit) QtCore.QObject.connect(self.ui.collapseButton, QtCore.SIGNAL("clicked()"), self.collapse) QtCore.QObject.connect(self.ui.graphicsScene, QtCore.SIGNAL("changed(const QList<QRectF> & )"), self.updateSceneRect) QtCore.QObject.connect(self.ui.verticalSlider,QtCore.SIGNAL("valueChanged(int)"),self.setupMatrix) QtCore.QObject.connect(self.settings.dvData,QtCore.SIGNAL("treeOrientationChanged()"), self.updateTreeOrientation) def setScene(self): """ The scene where the tree is visualised in, will be created and set """ self.initView() def getViewportType(self): return self.viewportType def updateGL(self): self.update() def createDecomposition(self): """ Create a new decomposition. If an older one exists it will be removed. """ if self.ui.graphicsScene != None: for item in self.ui.graphicsView.items(): item.hide() if item.parentItem() == None: self.ui.graphicsScene.removeItem(item) if self.tree.root != None: self.tree.clear(self.tree.root) del self.connections[:] del self.settings.dvData.fixedClusterIds[:] self.initView() def initView(self): """ Updating the view with new data and nodes for the visualisation of the tree """ if self.prototypeManager.result != None: self.nodeId = 0 self.tree.root = self.populateTree(self.prototypeManager.result.subs, None, self.prototypeManager.result) self.drawTree(self.ui.graphicsScene, self.tree.root, self.tree.root.children) self.drawConnections(self.ui.graphicsScene) self.determineCollapse(self.tree.root) self.showConnections() self.tree.root.showChildren() self.updateTree() self.addInfoOverlay() self.initFixStates() def updateViewports(self): self.viewportManager.updateViewports() def updateTree(self): """ Update the tree, where the node positions and connections between the nodes are updated """ self.tree.clear(self.tree.root) self.tree.updateTree() self.updateNodePositions(self.tree.root) self.showConnections() def populateTree(self, nodes, rootNode, currentNode, id=0): """ Recursive function to populate a tree, from the results of the solver to finally display it in the Decomposition View. The population is depth first. Parameters: nodes - the childnodes rootNode - root node of the (partial) tree currentNode - the current node of the result obtained from the constraints solver """ if len(currentNode.variables) == 1: self.createLeafPoint(rootNode, currentNode.variables[0], self.nodeId) else: newNode = CVCluster(self, rootNode, self.nodeId) newNode.flag = currentNode.flag newNode.variables = currentNode.variables needCollapse = False """ Add children to the rootNode to create the full tree """ if rootNode != None: #self.setCollapse(newNode) rootNode.children += [newNode] """ Create a connection between the nodes if the current node has a rootnode""" newConnection = CVConnection(self, rootNode, newNode) self.connections += [newConnection] """ get the leaf nodes """ if len(nodes) == 0: for variable in newNode.variables: self.createLeafPoint(newNode, variable, self.nodeId) # Rick 20091116 - skip this for debug # for node in nodes: # self.nodeId += 1 # self.populateTree(node.subs, newNode, node, self.nodeId) """ To return the whole tree, a check will be performed for the rootnode """ if rootNode == None: return newNode def initFixStates(self): """ Initialize the fix states from another view if available """ for fixedId in self.settings.dvData.fixedClusterIds: self.updateState(fixedId, True, self.tree.root) def stateChange(self, id, fixed): """ Change the state of the cluster which might be fixed and report it to the other decomposition views. Paramaters: id - unique id of the cluster fixed - should the clusters be fixed or not """ self.viewportManager.updateDecompositionFixed(id, fixed) if fixed: self.settings.dvData.fixedClusterIds += [id] elif not fixed: self.settings.dvData.fixedClusterIds = filter(lambda x:x!=id, self.settings.dvData.fixedClusterIds) def updateState(self, id, fixed, rootNode): """ Update the fixed cluster, with the visuals. Parameters: id - unique id of the cluster fixed - should the cluster be fixed or not rootNode - recursive funcion to walk the tree """ if rootNode.identifier == id: if fixed and rootNode.isVisible(): rootNode.fixGraphic.show() rootNode.clusterActive = True elif fixed and not rootNode.isVisible(): rootNode.fixGraphic.hide() rootNode.clusterActive = True else: rootNode.fixGraphic.hide() rootNode.clusterActive = False self.prototypeManager.removeClusterObjects([rootNode.permCluster], True, True) rootNode.permCluster = None return True for node in rootNode.children: found = self.updateState(id, fixed, node) if found: break return False def createLeafPoint(self, node, variable, id): cvPoint = CVPoint(self, node, id) cvPoint.setWidthAndHeight(20, 20) cvPoint.prtRef = self.prototypeManager.getObjectByKey(variable) cvPoint.isCollapsed = False cvPoint.canCollapse = False #cvPoint.setInfoOverlay() node.children += [cvPoint] newConnection = CVConnection(self, node, cvPoint) self.connections += [newConnection] def setCollapse(self, node): node.isCollapsed = False if not isinstance(node, CVPoint): if not node.collapseFromResult(): node.updateCluster() def determineCollapse(self, node): self.setCollapse(node) for child in node.children: self.determineCollapse(child) def addInfoOverlay(self): self.ui.graphicsScene.addItem(self.infoOverlay) self.infoOverlay.hide() def drawTree(self, scene, root, childNodes): """ The different nodes are added to the scene and will automatically be drawn Parameters: scene - the scene where the rootnode has to be drawn in root - the rootnode of the (sub-) tree childNode - the children of this root node """ root.setPos(root.position) scene.addItem(root) #print "#nodes: ", len(childNodes), " position: ",root.position.x(), " " , root.position.y() for node in childNodes: self.drawTree(scene, node, node.children) def drawConnections(self, scene): """ The connections between the nodes are added to the scene and will automatically be drawn Parameters: scene - the scene where the connections has to be drawn in """ for connection in self.connections: #connection.setPos() scene.addItem(connection) def showConnections(self): """ Show/hide the connections between the nodes, this depends if the node is collapsed """ for connection in self.connections: connection.setPos(connection.nodeTo.position) if connection.nodeFrom.isCollapsed or (connection.nodeTo.isVisible() == False): connection.hide() else: connection.show() def updateConnections(self): for connection in self.connections: connection.update() def nrVisibleConnections(self): number = 0 #for connection in self.connections: #print "x, y: " , connection.x(), connection.y() #print "nr of visible connections: " , number def updateNodePositions(self, node): """ Map the position of the nodes in the tree on the graphical view Parameters: node - a node in the tree for which the position is set """ node.setPos(node.position) for childNode in node.children: self.updateNodePositions(childNode) def updateSceneRect(self, rectList=None): self.ui.graphicsScene.setSceneRect(self.ui.graphicsScene.itemsBoundingRect()) def updateTreeOrientation(self): self.tree.orientation = self.settings.dvData.treeAlignment def zoomIn(self): """ Zoom in the graphics view, by updating the vertical slider """ self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() + 1) def zoomOut(self): """ Zoom out the graphics view, by updating the vertical slider """ self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() - 1) def fit(self): """ Fits the tree exactly in the graphics view """ self.ui.graphicsView.fitInView(0.0, 0.0, self.ui.graphicsScene.width(), self.ui.graphicsScene.height(), QtCore.Qt.KeepAspectRatio) """ Update the slider """ value = (math.log(self.ui.graphicsView.matrix().m11(),2)*50) + 250.0 self.ui.verticalSlider.setValue(value) def collapseAll(self, node): if node.canCollapse: node.collapse() for childNode in node.children: self.collapseAll(childNode) def expandAll(self, node): node.expand() for childNode in node.children: self.expandAll(childNode) def collapse(self): if self.collapsed: self.collapsed = False self.collapseAll(self.tree.root) else: self.collapsed = True self.expandAll(self.tree.root) self.updateTree() self.update() def setupMatrix(self, value): """ Zoom in/out the graphics view, depending on the value of the slider Parameters value - value of the updated slider """ scale = math.pow(2.0, (self.ui.verticalSlider.value()-250.0)/50.0) matrix = QtGui.QMatrix() matrix.scale(scale,scale) self.ui.graphicsView.setMatrix(matrix)
class CompositionView(QtGui.QDialog): """ A view where the decomposition of the system of constraints is visualised as a tree """ def __init__(self, viewport, viewportMngr, vpType, prototypeMngr, parent=None): """ Initialization of the CompositionView class Parameters: viewportMngr - the manager of the viewports where the composition view can reside in prototypeMngr - the manager of the prototypes is used to obtain the results of the solver """ QtGui.QDialog.__init__(self, parent) self.prototypeManager = prototypeMngr self.viewport = viewport self.viewportManager = viewportMngr self.settings = Settings() self.setWindowFlags(QtCore.Qt.Window) self.timer = QtCore.QObject() #QtCore.qsrand(QtCore.QTime(0,0,0).secsTo(QtCore.QTime.currentTime())) self.tree = Tree(None) self.infoOverlay = CVInfoOverlay(self) self.connections = [] self.ui = Ui_compositionView() self.ui.setupUi(self) self.ui.graphicsView.setupViewport( QtOpenGL.QGLWidget( QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers | QtOpenGL.QGL.DoubleBuffer))) #self.ui.graphicsView.setViewport(QtGui.QWidget()) self.ui.graphicsView.setRenderHints( QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform) self.collapsed = False self.currentTool = None self.viewportType = vpType self.first = False self.nodeId = 0 self.overConstrainedColor = QtGui.QColor(0, 0, 255) self.underConstrainedColor = QtGui.QColor(255, 0, 0) self.wellConstrainedColor = QtGui.QColor(0, 255, 0) self.unsolvedColor = QtGui.QColor(125, 124, 255) self.setScene() self.createTriggers() def createTriggers(self): """ Create the triggers for the components in the graphical window """ QtCore.QObject.connect(self.ui.zoomInButton, QtCore.SIGNAL("clicked()"), self.zoomIn) QtCore.QObject.connect(self.ui.zoomOutButton, QtCore.SIGNAL("clicked()"), self.zoomOut) QtCore.QObject.connect(self.ui.fitButton, QtCore.SIGNAL("clicked()"), self.fit) QtCore.QObject.connect(self.ui.collapseButton, QtCore.SIGNAL("clicked()"), self.collapse) QtCore.QObject.connect( self.ui.graphicsScene, QtCore.SIGNAL("changed(const QList<QRectF> & )"), self.updateSceneRect) QtCore.QObject.connect(self.ui.verticalSlider, QtCore.SIGNAL("valueChanged(int)"), self.setupMatrix) QtCore.QObject.connect(self.settings.dvData, QtCore.SIGNAL("treeOrientationChanged()"), self.updateTreeOrientation) def setScene(self): """ The scene where the tree is visualised in, will be created and set """ self.initView() def getViewportType(self): return self.viewportType def updateGL(self): self.update() def createDecomposition(self): """ Create a new decomposition. If an older one exists it will be removed. """ if self.ui.graphicsScene != None: for item in self.ui.graphicsView.items(): item.hide() if item.parentItem() == None: self.ui.graphicsScene.removeItem(item) if self.tree.root != None: self.tree.clear(self.tree.root) del self.connections[:] del self.settings.dvData.fixedClusterIds[:] self.initView() def initView(self): """ Updating the view with new data and nodes for the visualisation of the tree """ if self.prototypeManager.result != None: self.nodeId = 0 self.tree.root = self.populateTree( self.prototypeManager.result.subs, None, self.prototypeManager.result) # return # Rick 20090522 debug self.drawTree(self.ui.graphicsScene, self.tree.root, self.tree.root.children) self.drawConnections(self.ui.graphicsScene) self.determineCollapse(self.tree.root) self.showConnections() self.tree.root.showChildren() self.updateTree() self.addInfoOverlay() self.initFixStates() def updateViewports(self): self.viewportManager.updateViewports() def updateTree(self): """ Update the tree, where the node positions and connections between the nodes are updated """ self.tree.clear(self.tree.root) self.tree.updateTree() self.updateNodePositions(self.tree.root) self.showConnections() def populateTree(self, nodes, rootNode, currentNode, id=0): """ Recursive function to populate a tree, from the results of the solver to finally display it in the Decomposition View. The population is depth first. Parameters: nodes - the childnodes rootNode - root node of the (partial) tree currentNode - the current node of the result obtained from the constraints solver """ if len(currentNode.variables) == 1: self.createLeafPoint(rootNode, currentNode.variables[0], self.nodeId) else: newNode = CVCluster(self, rootNode, self.nodeId) newNode.flag = currentNode.flag newNode.variables = currentNode.variables needCollapse = False """ Add children to the rootNode to create the full tree """ if rootNode != None: #self.setCollapse(newNode) rootNode.children += [newNode] """ Create a connection between the nodes if the current node has a rootnode""" newConnection = CVConnection(self, rootNode, newNode) self.connections += [newConnection] """ get the leaf nodes """ if len(nodes) == 0: for variable in newNode.variables: self.createLeafPoint(newNode, variable, self.nodeId) for node in nodes: self.nodeId += 1 self.populateTree(node.subs, newNode, node, self.nodeId) """ To return the whole tree, a check will be performed for the rootnode """ if rootNode == None: return newNode def initFixStates(self): """ Initialize the fix states from another view if available """ for fixedId in self.settings.dvData.fixedClusterIds: self.updateState(fixedId, True, self.tree.root) def stateChange(self, id, fixed): """ Change the state of the cluster which might be fixed and report it to the other decomposition views. Paramaters: id - unique id of the cluster fixed - should the clusters be fixed or not """ self.viewportManager.updateDecompositionFixed(id, fixed) if fixed: self.settings.dvData.fixedClusterIds += [id] elif not fixed: self.settings.dvData.fixedClusterIds = filter( lambda x: x != id, self.settings.dvData.fixedClusterIds) def updateState(self, id, fixed, rootNode): """ Update the fixed cluster, with the visuals. Parameters: id - unique id of the cluster fixed - should the cluster be fixed or not rootNode - recursive funcion to walk the tree """ if rootNode.identifier == id: if fixed and rootNode.isVisible(): rootNode.fixGraphic.show() rootNode.clusterActive = True elif fixed and not rootNode.isVisible(): rootNode.fixGraphic.hide() rootNode.clusterActive = True else: rootNode.fixGraphic.hide() rootNode.clusterActive = False self.prototypeManager.removeClusterObjects( [rootNode.permCluster], True, True) rootNode.permCluster = None return True for node in rootNode.children: found = self.updateState(id, fixed, node) if found: break return False def createLeafPoint(self, node, variable, id): cvPoint = CVPoint(self, node, id) cvPoint.setWidthAndHeight(20, 20) cvPoint.prtRef = self.prototypeManager.getObjectByKey(variable) cvPoint.isCollapsed = False cvPoint.canCollapse = False #cvPoint.setInfoOverlay() node.children += [cvPoint] newConnection = CVConnection(self, node, cvPoint) self.connections += [newConnection] def setCollapse(self, node): node.isCollapsed = False if not isinstance(node, CVPoint): if not node.collapseFromResult(): node.updateCluster() def determineCollapse(self, node): self.setCollapse(node) for child in node.children: self.determineCollapse(child) def addInfoOverlay(self): self.ui.graphicsScene.addItem(self.infoOverlay) self.infoOverlay.hide() def drawTree(self, scene, root, childNodes): """ The different nodes are added to the scene and will automatically be drawn Parameters: scene - the scene where the rootnode has to be drawn in root - the rootnode of the (sub-) tree childNode - the children of this root node """ root.setPos(root.position) scene.addItem(root) #print "#nodes: ", len(childNodes), " position: ",root.position.x(), " " , root.position.y() for node in childNodes: self.drawTree(scene, node, node.children) def drawConnections(self, scene): """ The connections between the nodes are added to the scene and will automatically be drawn Parameters: scene - the scene where the connections has to be drawn in """ for connection in self.connections: #connection.setPos() scene.addItem(connection) def showConnections(self): """ Show/hide the connections between the nodes, this depends if the node is collapsed """ for connection in self.connections: connection.setPos(connection.nodeTo.position) if connection.nodeFrom.isCollapsed or ( connection.nodeTo.isVisible() == False): connection.hide() else: connection.show() def updateConnections(self): for connection in self.connections: connection.update() def nrVisibleConnections(self): number = 0 #for connection in self.connections: #print "x, y: " , connection.x(), connection.y() #print "nr of visible connections: " , number def updateNodePositions(self, node): """ Map the position of the nodes in the tree on the graphical view Parameters: node - a node in the tree for which the position is set """ node.setPos(node.position) for childNode in node.children: self.updateNodePositions(childNode) def updateSceneRect(self, rectList=None): self.ui.graphicsScene.setSceneRect( self.ui.graphicsScene.itemsBoundingRect()) def updateTreeOrientation(self): self.tree.orientation = self.settings.dvData.treeAlignment def zoomIn(self): """ Zoom in the graphics view, by updating the vertical slider """ self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() + 1) def zoomOut(self): """ Zoom out the graphics view, by updating the vertical slider """ self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() - 1) def fit(self): """ Fits the tree exactly in the graphics view """ self.ui.graphicsView.fitInView(0.0, 0.0, self.ui.graphicsScene.width(), self.ui.graphicsScene.height(), QtCore.Qt.KeepAspectRatio) """ Update the slider """ value = (math.log(self.ui.graphicsView.matrix().m11(), 2) * 50) + 250.0 self.ui.verticalSlider.setValue(value) def collapseAll(self, node): if node.canCollapse: node.collapse() for childNode in node.children: self.collapseAll(childNode) def expandAll(self, node): node.expand() for childNode in node.children: self.expandAll(childNode) def collapse(self): if self.collapsed: self.collapsed = False self.collapseAll(self.tree.root) else: self.collapsed = True self.expandAll(self.tree.root) self.updateTree() self.update() def setupMatrix(self, value): """ Zoom in/out the graphics view, depending on the value of the slider Parameters value - value of the updated slider """ scale = math.pow(2.0, (self.ui.verticalSlider.value() - 250.0) / 50.0) matrix = QtGui.QMatrix() matrix.scale(scale, scale) self.ui.graphicsView.setMatrix(matrix)
class DecompositionView(QtGui.QDialog): """ A view where the decomposition of the system of constraints is visualised as a directed acyclic graph""" def __init__(self, viewport, viewportMngr, vpType, prototypeMngr, parent=None): """ Initialization of the CompositionView class Parameters: viewportMngr - the manager of the viewports where the composition view can reside in prototypeMngr - the manager of the prototypes is used to obtain the results of the solver """ QtGui.QDialog.__init__(self, parent) self.prototypeManager = prototypeMngr self.viewport = viewport self.viewportManager = viewportMngr self.settings = Settings() self.setWindowFlags(QtCore.Qt.Window) self.timer = QtCore.QObject() """map GeometricDecomposition to CVCluster""" self.map = {} self.ui = Ui_compositionView() self.ui.setupUi(self) self.ui.graphicsView.setupViewport( QtOpenGL.QGLWidget( QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers | QtOpenGL.QGL.DoubleBuffer))) self.ui.graphicsView.setRenderHints( QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform) self.currentTool = None self.viewportType = vpType self.orientation = TreeOrientation.BOTTOM self.overConstrainedColor = QtGui.QColor(0, 0, 255) self.underConstrainedColor = QtGui.QColor(255, 0, 0) self.wellConstrainedColor = QtGui.QColor(0, 255, 0) self.unsolvedColor = QtGui.QColor(125, 124, 255) self.createScene() self.createTriggers() def createTriggers(self): """ Create the triggers for the components in the graphical window """ QtCore.QObject.connect(self.ui.zoomInButton, QtCore.SIGNAL("clicked()"), self.zoomIn) QtCore.QObject.connect(self.ui.zoomOutButton, QtCore.SIGNAL("clicked()"), self.zoomOut) QtCore.QObject.connect(self.ui.fitButton, QtCore.SIGNAL("clicked()"), self.fit) #QtCore.QObject.connect(self.ui.collapseButton, QtCore.SIGNAL("clicked()"), self.collapse) QtCore.QObject.connect( self.ui.graphicsScene, QtCore.SIGNAL("changed(const QList<QRectF> & )"), self.updateSceneRect) QtCore.QObject.connect(self.ui.verticalSlider, QtCore.SIGNAL("valueChanged(int)"), self.setupMatrix) #QtCore.QObject.connect(self.settings.dvData,QtCore.SIGNAL("treeOrientationChanged()"), self.updateTreeOrientation) def getViewportType(self): return self.viewportType def updateGL(self): self.update() def createDecomposition(self): """ Create a new decomposition. If an older one exists it will be removed. """ self.clearScene() self.createScene() def clearScene(self): self.map = {} if self.ui.graphicsScene != None: for item in self.ui.graphicsView.items(): item.hide() if item.parentItem() == None: self.ui.graphicsScene.removeItem(item) def createScene(self): """ Updating the view with new data and nodes for the visualisation of the tree """ if self.prototypeManager.result != None: # get all clusters from result new = [self.prototypeManager.result] clusters = set() while len(new) > 0: c = new.pop() clusters.add(c) for child in c.subs: if child not in clusters: new.append(child) # create N layers for clusters with 1-N variables N = len(self.prototypeManager.result.variables) layers = [] for n in range(0, N + 1): layers.append([]) # add clusters to layers for c in clusters: n = len(c.variables) layers[n].append(c) # sort clusters in layers # start from layer N (largest clusters) # clusters are initially ordered according to the order in which sub-clusters appear in the previous (n+1) layer for n in reversed(range(1, N)): print "ordering layers", n # find subiable pseudo-ordering in previous layers subordervalue = {} clusterindex = 0 for cluster in layers[n + 1]: clusterindex = clusterindex + 1 for sub in cluster.subs: # order by first appearence in cluster from left to right if sub not in subordervalue: subordervalue[sub] = clusterindex # determine pseudo-order clusters in this layers: sum subordervalues per cluster clusterordervalue = {} for cluster in layers[n]: clusterordervalue[cluster] = 0 for sub in cluster.subs: if sub in subordervalue: clusterordervalue[cluster] += subordervalue[sub] # sort clusters in layers layers[n].sort( lambda x, y: clusterordervalue[x] < clusterordervalue[y]) # map GeometricDecompositions to CVClusters for n in range(0, N + 1): layer = layers[n] for k in range(0, len(layer)): c = layer[k] y = n * 50.0 x = (k - len(layer) / 2.0) * 50.0 * n cvcluster = CVCluster(self, c, x, y) self.ui.graphicsScene.addItem(cvcluster) self.map[c] = cvcluster self.map[cvcluster] = c # add CVConnections for c in clusters: for child in c.subs: self.ui.graphicsScene.addItem( CVConnection(self, self.map[c], self.map[child])) # iteratively improve graph layout self.optimiseGraphLayout() def optimiseGraphLayout(self): print "optimising graph layout..." # force due to overlapping overlaps force_cluster = 0.2 # force due to connection length force_connection = 0.025 # force due to clusters overlapping connections force_cluster_connection = 0.05 # create a graph of clusters and connections graph = geosolver.graph.Graph() if self.ui.graphicsScene != None: for item in self.ui.graphicsView.items(): if isinstance(item, CVCluster): graph.add_vertex(item) item.force = numpy.array([0.0, 0.0]) elif isinstance(item, CVConnection): graph.add_edge(item.nodeFrom, item.nodeTo, item) l = list(graph.vertices()) # iteratively improve layout for i in range(100): # clear forces for c in l: c.force = numpy.array( [random.random() * 0, random.random() * 0]) # determine forces due to overlapping cluster boxes n = len(l) for i in range(n): for j in range(i + 1, n): c1 = l[i] c2 = l[j] box1 = c1.boundingRect().translated(c1.position) box1.setWidth(2 * box1.width()) box1.setHeight(2 * box1.height()) box2 = c2.boundingRect().translated(c2.position) box2.setWidth(2 * box2.width()) box2.setHeight(2 * box2.height()) #print "box 1", box1 #print "box 2", box2 if box1.intersects(box2): #print "intersects" force = box1.intersected( box2).width() + box1.intersected(box2).height() centerdiff = box2.center() - box1.center() direction = numpy.array( [centerdiff.x(), centerdiff.y()]) norm = numpy.linalg.norm(direction) if norm != 0: direction = direction / numpy.linalg.norm( direction) else: direction = numpy.array([0, 0]) #direction[1] = 0.0 c1.force += -force * direction * force_cluster c2.force += force * direction * force_cluster #print "force 1", c1.force #print "force 2", c2.force # determine forces due to connections for e in graph.edges(): c1 = e[0] c2 = e[1] box1 = c1.boundingRect().translated(c1.position) box2 = c2.boundingRect().translated(c2.position) # force 1: pull together on x centerdiff = box2.center() - box1.center() direction = numpy.array([centerdiff.x(), 0]) norm = numpy.linalg.norm(direction) if norm != 0: direction = direction / numpy.linalg.norm(direction) else: direction = numpy.array([0, 0]) goal = 0 force = (norm - goal) * force_connection c1.force += +force * direction c2.force += -force * direction # force 2: keep y at distance and in layer order direction = numpy.array([0, centerdiff.y()]) norm = numpy.linalg.norm(direction) if norm != 0: direction = direction / numpy.linalg.norm(direction) else: direction = numpy.array([0, 0]) goal = box1.height() + box2.height() force = (norm - goal) * force_connection c1.force += +force * direction c2.force += -force * direction #print "force ", force # determine forces due to clusters overlapping connections n = len(l) for c in graph.vertices(): for e in graph.edges(): box1 = c.boundingRect().translated(c.position) box1.setWidth(0.5 * box1.width()) box1.setHeight(0.5 * box1.height()) con = graph.get(e[0], e[1]) box2 = con.boundingRect() box2.setWidth(0.5 * box2.width()) box2.setHeight(0.5 * box2.height()) #print "box 1", box1 #print "box 2", box2 if box1.intersects(box2): #print "intersects" force = box1.intersected( box2).width() + box1.intersected(box2).height() centerdiff = box2.center() - box1.center() direction = numpy.array( [centerdiff.x(), centerdiff.y()]) norm = numpy.linalg.norm(direction) if norm != 0: direction = direction / numpy.linalg.norm( direction) else: direction = numpy.array([0, 0]) c.force += -force * direction * force_cluster_connection e[0].force += force * direction * force_cluster_connection e[1].force += force * direction * force_cluster_connection #print "force 1", c1.force #print "force 2", c2.force # apply forces for c in l: move = QtCore.QPointF(c.force[0], c.force[1]) c.position += move c.translate(move.x(), move.y()) # uppate connectors for e in graph.edges(): connector = graph.get(e[0], e[1]) connector.determinePath() # done iterating print "done" def updateViewports(self): self.viewportManager.updateViewports() def updateSceneRect(self, rectList=None): self.ui.graphicsScene.setSceneRect( self.ui.graphicsScene.itemsBoundingRect()) def zoomIn(self): """ Zoom in the graphics view, by updating the vertical slider """ self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() + 1) def zoomOut(self): """ Zoom out the graphics view, by updating the vertical slider """ self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() - 1) def fit(self): """ Fits the tree exactly in the graphics view """ self.ui.graphicsView.fitInView(0.0, 0.0, self.ui.graphicsScene.width(), self.ui.graphicsScene.height(), QtCore.Qt.KeepAspectRatio) """ Update the slider """ value = (math.log(self.ui.graphicsView.matrix().m11(), 2) * 50) + 250.0 self.ui.verticalSlider.setValue(value) def setupMatrix(self, value): """ Zoom in/out the graphics view, depending on the value of the slider Parameters value - value of the updated slider """ scale = math.pow(2.0, (self.ui.verticalSlider.value() - 250.0) / 50.0) matrix = QtGui.QMatrix() matrix.scale(scale, scale) self.ui.graphicsView.setMatrix(matrix)