def initialize(self): pvhistory = [] lvhistory = [] n = 0 fc = Flowchart(terminals={'in': {'io': 'out'}, 'op': {'io': 'in'}}) nodeList = fc.nodes() fc.removeNode(nodeList['Input']) fc.removeNode(nodeList['Output']) for i in self.mappings["vgs"]: vgNode = Node(i, allowRemove=False, allowAddOutput=False) fc.addNode(vgNode, i, [0, n]) Node.addTerminal(vgNode, 'O', io='out', multi=True) Node.addTerminal(vgNode, 'I', io='in', multi=True) for j in self.mappings["mappings"]: if i == j[1]: if j[2] not in lvhistory: lvNode = Node(j[2], allowRemove=False, allowAddOutput=False) fc.addNode(lvNode, j[2], [200, n]) Node.addTerminal(lvNode, 'I', io='in') try: fc.connectTerminals(vgNode['O'], lvNode['I']) except: pass #cvar2 = Node.addOutput(vgNode) #fc.connectTerminals(vgNode['Out'],cvar2) lvhistory.append(j[2]) else: pass if j[0] not in pvhistory: pvNode = Node(j[0], allowRemove=False, allowAddOutput=False) fc.addNode(pvNode, j[0], [-400, n]) Node.addTerminal(pvNode, 'O', io='out') try: fc.connectTerminals(pvNode['O'], vgNode['I']) except: pass #cvar1 = Node.addInput(vgNode) #fc.connectTerminals(pvNode['Out'], cvar1) pvhistory.append(j[0]) else: pass n = n + 200 #vgNode.ctrls['doubleSpin'].setValue(5) #lvNode = fc.createNode('PlotWidget', 'lv') self.layout = QGridLayout() self.setLayout(self.layout) self.layout.addWidget(fc.widget())
class Flowchart(Node): sigFileLoaded = QtCore.Signal(object) sigFileSaved = QtCore.Signal(object) #sigOutputChanged = QtCore.Signal() ## inherited from Node sigChartLoaded = QtCore.Signal() sigStateChanged = QtCore.Signal() def __init__(self, terminals=None, name=None, filePath=None): pg.setConfigOption('background', 'w') pg.setConfigOption('foreground', 'k') if name is None: name = "Flowchart" if terminals is None: terminals = {} self.filePath = filePath Node.__init__(self, name, allowAddInput=True, allowAddOutput=True) ## create node without terminals; we'll add these later self.inputWasSet = False ## flag allows detection of changes in the absence of input change. self._nodes = {} self.nextZVal = 10 #self.connects = [] #self._chartGraphicsItem = FlowchartGraphicsItem(self) self._widget = None self._scene = None self.processing = False ## flag that prevents recursive node updates self.widget() self.inputNode = Node('Input', allowRemove=False, allowAddOutput=True) self.outputNode = Node('Output', allowRemove=False, allowAddInput=True) self.addNode(self.inputNode, 'Input', [-150, 0]) self.addNode(self.outputNode, 'Output', [300, 0]) self.outputNode.sigOutputChanged.connect(self.outputChanged) self.outputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed) self.inputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed) self.outputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved) self.inputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved) self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded) self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded) self.viewBox.autoRange(padding = 0.04) for name, opts in terminals.items(): self.addTerminal(name, **opts) def setInput(self, **args): """Set the input values of the flowchart. This will automatically propagate the new values throughout the flowchart, (possibly) causing the output to change. """ #print "setInput", args #Node.setInput(self, **args) #print " ....." self.inputWasSet = True self.inputNode.setOutput(**args) def outputChanged(self): ## called when output of internal node has changed vals = self.outputNode.inputValues() self.widget().outputChanged(vals) self.setOutput(**vals) #self.sigOutputChanged.emit(self) def output(self): """Return a dict of the values on the Flowchart's output terminals. """ return self.outputNode.inputValues() def nodes(self): return self._nodes def addTerminal(self, name, **opts): term = Node.addTerminal(self, name, **opts) name = term.name() if opts['io'] == 'in': ## inputs to the flowchart become outputs on the input node opts['io'] = 'out' opts['multi'] = False self.inputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded) try: term2 = self.inputNode.addTerminal(name, **opts) finally: self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded) else: opts['io'] = 'in' #opts['multi'] = False self.outputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded) try: term2 = self.outputNode.addTerminal(name, **opts) finally: self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded) return term def removeTerminal(self, name): #print "remove:", name term = self[name] inTerm = self.internalTerminal(term) Node.removeTerminal(self, name) inTerm.node().removeTerminal(inTerm.name()) def internalTerminalRenamed(self, term, oldName): self[oldName].rename(term.name()) def internalTerminalAdded(self, node, term): if term._io == 'in': io = 'out' else: io = 'in' Node.addTerminal(self, term.name(), io=io, renamable=term.isRenamable(), removable=term.isRemovable(), multiable=term.isMultiable()) def internalTerminalRemoved(self, node, term): try: Node.removeTerminal(self, term.name()) except KeyError: pass def terminalRenamed(self, term, oldName): newName = term.name() #print "flowchart rename", newName, oldName #print self.terminals Node.terminalRenamed(self, self[oldName], oldName) #print self.terminals for n in [self.inputNode, self.outputNode]: if oldName in n.terminals: n[oldName].rename(newName) def createNode(self, nodeType, name=None, pos=None): if name is None: n = 0 while True: name = "%s.%d" % (nodeType, n) if name not in self._nodes: break n += 1 node = library.getNodeType(nodeType)(name) self.addNode(node, name, pos) return node def addNode(self, node, name, pos=None): if pos is None: pos = [0, 0] if type(pos) in [QtCore.QPoint, QtCore.QPointF]: pos = [pos.x(), pos.y()] item = node.graphicsItem() item.setZValue(self.nextZVal*2) self.nextZVal += 1 self.viewBox.addItem(item) item.moveBy(*pos) self._nodes[name] = node self.widget().addNode(node) node.sigClosed.connect(self.nodeClosed) node.sigRenamed.connect(self.nodeRenamed) node.sigOutputChanged.connect(self.nodeOutputChanged) def removeNode(self, node): node.close() def nodeClosed(self, node): del self._nodes[node.name()] self.widget().removeNode(node) try: node.sigClosed.disconnect(self.nodeClosed) except TypeError: pass try: node.sigRenamed.disconnect(self.nodeRenamed) except TypeError: pass try: node.sigOutputChanged.disconnect(self.nodeOutputChanged) except TypeError: pass def nodeRenamed(self, node, oldName): del self._nodes[oldName] self._nodes[node.name()] = node self.widget().nodeRenamed(node, oldName) def arrangeNodes(self): pass def internalTerminal(self, term): """If the terminal belongs to the external Node, return the corresponding internal terminal""" if term.node() is self: if term.isInput(): return self.inputNode[term.name()] else: return self.outputNode[term.name()] else: return term def connectTerminals(self, term1, term2): """Connect two terminals together within this flowchart.""" term1 = self.internalTerminal(term1) term2 = self.internalTerminal(term2) term1.connectTo(term2) def process(self, **args): """ Process data through the flowchart, returning the output. Keyword arguments must be the names of input terminals. The return value is a dict with one key per output terminal. """ data = {} ## Stores terminal:value pairs ## determine order of operations ## order should look like [('p', node1), ('p', node2), ('d', terminal1), ...] ## Each tuple specifies either (p)rocess this node or (d)elete the result from this terminal order = self.processOrder() #print "ORDER:", order ## Record inputs given to process() for n, t in self.inputNode.outputs().items(): if n not in args: raise Exception("Parameter %s required to process this chart." % n) data[t] = args[n] ret = {} ## process all in order for c, arg in order: if c == 'p': ## Process a single node #print "===> process:", arg node = arg if node is self.inputNode: continue ## input node has already been processed. ## get input and output terminals for this node outs = list(node.outputs().values()) ins = list(node.inputs().values()) ## construct input value dictionary args = {} for inp in ins: inputs = inp.inputTerminals() if len(inputs) == 0: continue if inp.isMultiValue(): ## multi-input terminals require a dict of all inputs args[inp.name()] = dict([(i, data[i]) for i in inputs]) else: ## single-inputs terminals only need the single input value available args[inp.name()] = data[inputs[0]] if node is self.outputNode: ret = args ## we now have the return value, but must keep processing in case there are other endpoint nodes in the chart else: try: if node.isBypassed(): result = node.processBypassed(args) else: result = node.process(display=False, **args) except: print("Error processing node %s. Args are: %s" % (str(node), str(args))) raise for out in outs: #print " Output:", out, out.name() #print out.name() try: data[out] = result[out.name()] except: print(out, out.name()) raise elif c == 'd': ## delete a terminal result (no longer needed; may be holding a lot of memory) #print "===> delete", arg if arg in data: del data[arg] return ret def processOrder(self): """Return the order of operations required to process this chart. The order returned should look like [('p', node1), ('p', node2), ('d', terminal1), ...] where each tuple specifies either (p)rocess this node or (d)elete the result from this terminal """ ## first collect list of nodes/terminals and their dependencies deps = {} tdeps = {} ## {terminal: [nodes that depend on terminal]} for name, node in self._nodes.items(): deps[node] = node.dependentNodes() for t in node.outputs().values(): tdeps[t] = t.dependentNodes() #print "DEPS:", deps ## determine correct node-processing order #deps[self] = [] order = toposort(deps) #print "ORDER1:", order ## construct list of operations ops = [('p', n) for n in order] ## determine when it is safe to delete terminal values dels = [] for t, nodes in tdeps.items(): lastInd = 0 lastNode = None for n in nodes: ## determine which node is the last to be processed according to order if n is self: lastInd = None break else: try: ind = order.index(n) except ValueError: continue if lastNode is None or ind > lastInd: lastNode = n lastInd = ind #tdeps[t] = lastNode if lastInd is not None: dels.append((lastInd+1, t)) dels.sort(lambda a,b: cmp(b[0], a[0])) for i, t in dels: ops.insert(i, ('d', t)) return ops def nodeOutputChanged(self, startNode): """Triggered when a node's output values have changed. (NOT called during process()) Propagates new data forward through network.""" ## first collect list of nodes/terminals and their dependencies if self.processing: return self.processing = True try: deps = {} for name, node in self._nodes.items(): deps[node] = [] for t in node.outputs().values(): deps[node].extend(t.dependentNodes()) ## determine order of updates order = toposort(deps, nodes=[startNode]) order.reverse() ## keep track of terminals that have been updated terms = set(startNode.outputs().values()) #print "======= Updating", startNode #print "Order:", order for node in order[1:]: #print "Processing node", node for term in list(node.inputs().values()): #print " checking terminal", term deps = list(term.connections().keys()) update = False for d in deps: if d in terms: #print " ..input", d, "changed" update = True term.inputChanged(d, process=False) if update: #print " processing.." node.update() terms |= set(node.outputs().values()) finally: self.processing = False if self.inputWasSet: self.inputWasSet = False else: self.sigStateChanged.emit() def chartGraphicsItem(self): """Return the graphicsItem which displays the internals of this flowchart. (graphicsItem() still returns the external-view item)""" #return self._chartGraphicsItem return self.viewBox def widget(self): if self._widget is None: self._widget = FlowchartCtrlWidget(self) self.scene = self._widget.scene() self.viewBox = self._widget.viewBox() #self._scene = QtGui.QGraphicsScene() #self._widget.setScene(self._scene) #self.scene.addItem(self.chartGraphicsItem()) #ci = self.chartGraphicsItem() #self.viewBox.addItem(ci) #self.viewBox.autoRange() return self._widget def listConnections(self): conn = set() for n in self._nodes.values(): terms = n.outputs() for n, t in terms.items(): for c in t.connections(): conn.add((t, c)) return conn def saveState(self): state = Node.saveState(self) state['nodes'] = [] state['connects'] = [] #state['terminals'] = self.saveTerminals() for name, node in self._nodes.items(): cls = type(node) if hasattr(cls, 'nodeName'): clsName = cls.nodeName pos = node.graphicsItem().pos() ns = {'class': clsName, 'name': name, 'pos': (pos.x(), pos.y()), 'state': node.saveState()} state['nodes'].append(ns) conn = self.listConnections() for a, b in conn: state['connects'].append((a.node().name(), a.name(), b.node().name(), b.name())) state['inputNode'] = self.inputNode.saveState() state['outputNode'] = self.outputNode.saveState() return state def restoreState(self, state, clear=False): self.blockSignals(True) try: if clear: self.clear() Node.restoreState(self, state) nodes = state['nodes'] nodes.sort(lambda a, b: cmp(a['pos'][0], b['pos'][0])) for n in nodes: if n['name'] in self._nodes: #self._nodes[n['name']].graphicsItem().moveBy(*n['pos']) self._nodes[n['name']].restoreState(n['state']) continue try: node = self.createNode(n['class'], name=n['name']) node.restoreState(n['state']) except: printExc("Error creating node %s: (continuing anyway)" % n['name']) #node.graphicsItem().moveBy(*n['pos']) self.inputNode.restoreState(state.get('inputNode', {})) self.outputNode.restoreState(state.get('outputNode', {})) #self.restoreTerminals(state['terminals']) for n1, t1, n2, t2 in state['connects']: try: self.connectTerminals(self._nodes[n1][t1], self._nodes[n2][t2]) except: print(self._nodes[n1].terminals) print(self._nodes[n2].terminals) printExc("Error connecting terminals %s.%s - %s.%s:" % (n1, t1, n2, t2)) finally: self.blockSignals(False) self.sigChartLoaded.emit() self.outputChanged() self.sigStateChanged.emit() #self.sigOutputChanged.emit() def loadFile(self, fileName=None, startDir=None): if fileName is None: if startDir is None: startDir = self.filePath if startDir is None: startDir = '.' self.fileDialog = pg.FileDialog(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) #self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) self.fileDialog.show() self.fileDialog.fileSelected.connect(self.loadFile) return ## NOTE: was previously using a real widget for the file dialog's parent, but this caused weird mouse event bugs.. #fileName = QtGui.QFileDialog.getOpenFileName(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") fileName = str(fileName) state = configfile.readConfigFile(fileName) self.restoreState(state, clear=True) self.viewBox.autoRange() #self.emit(QtCore.SIGNAL('fileLoaded'), fileName) self.sigFileLoaded.emit(fileName) def saveFile(self, fileName=None, startDir=None, suggestedFileName='flowchart.fc'): if fileName is None: if startDir is None: startDir = self.filePath if startDir is None: startDir = '.' self.fileDialog = pg.FileDialog(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) #self.fileDialog.setDirectory(startDir) self.fileDialog.show() self.fileDialog.fileSelected.connect(self.saveFile) return #fileName = QtGui.QFileDialog.getSaveFileName(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") configfile.writeConfigFile(self.saveState(), fileName) self.sigFileSaved.emit(fileName) def clear(self): for n in list(self._nodes.values()): if n is self.inputNode or n is self.outputNode: continue n.close() ## calls self.nodeClosed(n) by signal #self.clearTerminals() self.widget().clear() def clearTerminals(self): Node.clearTerminals(self) self.inputNode.clearTerminals() self.outputNode.clearTerminals()
class Flowchart(Node): sigFileLoaded = QtCore.Signal(object) sigFileSaved = QtCore.Signal(object) #sigOutputChanged = QtCore.Signal() ## inherited from Node sigChartLoaded = QtCore.Signal() sigStateChanged = QtCore.Signal() def __init__(self, terminals=None, name=None, filePath=None): pg.setConfigOption('background', 'w') pg.setConfigOption('foreground', 'k') if name is None: name = "Flowchart" if terminals is None: terminals = {} self.filePath = filePath Node.__init__( self, name, allowAddInput=True, allowAddOutput=True ) ## create node without terminals; we'll add these later self.inputWasSet = False ## flag allows detection of changes in the absence of input change. self._nodes = {} self.nextZVal = 10 #self.connects = [] #self._chartGraphicsItem = FlowchartGraphicsItem(self) self._widget = None self._scene = None self.processing = False ## flag that prevents recursive node updates self.widget() self.inputNode = Node('Input', allowRemove=False, allowAddOutput=True) self.outputNode = Node('Output', allowRemove=False, allowAddInput=True) self.addNode(self.inputNode, 'Input', [-150, 0]) self.addNode(self.outputNode, 'Output', [300, 0]) self.outputNode.sigOutputChanged.connect(self.outputChanged) self.outputNode.sigTerminalRenamed.connect( self.internalTerminalRenamed) self.inputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed) self.outputNode.sigTerminalRemoved.connect( self.internalTerminalRemoved) self.inputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved) self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded) self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded) self.viewBox.autoRange(padding=0.04) for name, opts in terminals.items(): self.addTerminal(name, **opts) def setInput(self, **args): """Set the input values of the flowchart. This will automatically propagate the new values throughout the flowchart, (possibly) causing the output to change. """ #print "setInput", args #Node.setInput(self, **args) #print " ....." self.inputWasSet = True self.inputNode.setOutput(**args) def outputChanged(self): ## called when output of internal node has changed vals = self.outputNode.inputValues() self.widget().outputChanged(vals) self.setOutput(**vals) #self.sigOutputChanged.emit(self) def output(self): """Return a dict of the values on the Flowchart's output terminals. """ return self.outputNode.inputValues() def nodes(self): return self._nodes def addTerminal(self, name, **opts): term = Node.addTerminal(self, name, **opts) name = term.name() if opts['io'] == 'in': ## inputs to the flowchart become outputs on the input node opts['io'] = 'out' opts['multi'] = False self.inputNode.sigTerminalAdded.disconnect( self.internalTerminalAdded) try: term2 = self.inputNode.addTerminal(name, **opts) finally: self.inputNode.sigTerminalAdded.connect( self.internalTerminalAdded) else: opts['io'] = 'in' #opts['multi'] = False self.outputNode.sigTerminalAdded.disconnect( self.internalTerminalAdded) try: term2 = self.outputNode.addTerminal(name, **opts) finally: self.outputNode.sigTerminalAdded.connect( self.internalTerminalAdded) return term def removeTerminal(self, name): #print "remove:", name term = self[name] inTerm = self.internalTerminal(term) Node.removeTerminal(self, name) inTerm.node().removeTerminal(inTerm.name()) def internalTerminalRenamed(self, term, oldName): self[oldName].rename(term.name()) def internalTerminalAdded(self, node, term): if term._io == 'in': io = 'out' else: io = 'in' Node.addTerminal(self, term.name(), io=io, renamable=term.isRenamable(), removable=term.isRemovable(), multiable=term.isMultiable()) def internalTerminalRemoved(self, node, term): try: Node.removeTerminal(self, term.name()) except KeyError: pass def terminalRenamed(self, term, oldName): newName = term.name() #print "flowchart rename", newName, oldName #print self.terminals Node.terminalRenamed(self, self[oldName], oldName) #print self.terminals for n in [self.inputNode, self.outputNode]: if oldName in n.terminals: n[oldName].rename(newName) def createNode(self, nodeType, name=None, pos=None): if name is None: n = 0 while True: name = "%s.%d" % (nodeType, n) if name not in self._nodes: break n += 1 node = library.getNodeType(nodeType)(name) self.addNode(node, name, pos) return node def addNode(self, node, name, pos=None): if pos is None: pos = [0, 0] if type(pos) in [QtCore.QPoint, QtCore.QPointF]: pos = [pos.x(), pos.y()] item = node.graphicsItem() item.setZValue(self.nextZVal * 2) self.nextZVal += 1 self.viewBox.addItem(item) item.moveBy(*pos) self._nodes[name] = node self.widget().addNode(node) node.sigClosed.connect(self.nodeClosed) node.sigRenamed.connect(self.nodeRenamed) node.sigOutputChanged.connect(self.nodeOutputChanged) def removeNode(self, node): node.close() def nodeClosed(self, node): del self._nodes[node.name()] self.widget().removeNode(node) try: node.sigClosed.disconnect(self.nodeClosed) except TypeError: pass try: node.sigRenamed.disconnect(self.nodeRenamed) except TypeError: pass try: node.sigOutputChanged.disconnect(self.nodeOutputChanged) except TypeError: pass def nodeRenamed(self, node, oldName): del self._nodes[oldName] self._nodes[node.name()] = node self.widget().nodeRenamed(node, oldName) def arrangeNodes(self): pass def internalTerminal(self, term): """If the terminal belongs to the external Node, return the corresponding internal terminal""" if term.node() is self: if term.isInput(): return self.inputNode[term.name()] else: return self.outputNode[term.name()] else: return term def connectTerminals(self, term1, term2): """Connect two terminals together within this flowchart.""" term1 = self.internalTerminal(term1) term2 = self.internalTerminal(term2) term1.connectTo(term2) def process(self, **args): """ Process data through the flowchart, returning the output. Keyword arguments must be the names of input terminals. The return value is a dict with one key per output terminal. """ data = {} ## Stores terminal:value pairs ## determine order of operations ## order should look like [('p', node1), ('p', node2), ('d', terminal1), ...] ## Each tuple specifies either (p)rocess this node or (d)elete the result from this terminal order = self.processOrder() #print "ORDER:", order ## Record inputs given to process() for n, t in self.inputNode.outputs().items(): if n not in args: raise Exception( "Parameter %s required to process this chart." % n) data[t] = args[n] ret = {} ## process all in order for c, arg in order: if c == 'p': ## Process a single node #print "===> process:", arg node = arg if node is self.inputNode: continue ## input node has already been processed. ## get input and output terminals for this node outs = list(node.outputs().values()) ins = list(node.inputs().values()) ## construct input value dictionary args = {} for inp in ins: inputs = inp.inputTerminals() if len(inputs) == 0: continue if inp.isMultiValue( ): ## multi-input terminals require a dict of all inputs args[inp.name()] = dict([(i, data[i]) for i in inputs]) else: ## single-inputs terminals only need the single input value available args[inp.name()] = data[inputs[0]] if node is self.outputNode: ret = args ## we now have the return value, but must keep processing in case there are other endpoint nodes in the chart else: try: if node.isBypassed(): result = node.processBypassed(args) else: result = node.process(display=False, **args) except: print("Error processing node %s. Args are: %s" % (str(node), str(args))) raise for out in outs: #print " Output:", out, out.name() #print out.name() try: data[out] = result[out.name()] except: print(out, out.name()) raise elif c == 'd': ## delete a terminal result (no longer needed; may be holding a lot of memory) #print "===> delete", arg if arg in data: del data[arg] return ret def processOrder(self): """Return the order of operations required to process this chart. The order returned should look like [('p', node1), ('p', node2), ('d', terminal1), ...] where each tuple specifies either (p)rocess this node or (d)elete the result from this terminal """ ## first collect list of nodes/terminals and their dependencies deps = {} tdeps = {} ## {terminal: [nodes that depend on terminal]} for name, node in self._nodes.items(): deps[node] = node.dependentNodes() for t in node.outputs().values(): tdeps[t] = t.dependentNodes() #print "DEPS:", deps ## determine correct node-processing order #deps[self] = [] order = toposort(deps) #print "ORDER1:", order ## construct list of operations ops = [('p', n) for n in order] ## determine when it is safe to delete terminal values dels = [] for t, nodes in tdeps.items(): lastInd = 0 lastNode = None for n in nodes: ## determine which node is the last to be processed according to order if n is self: lastInd = None break else: try: ind = order.index(n) except ValueError: continue if lastNode is None or ind > lastInd: lastNode = n lastInd = ind #tdeps[t] = lastNode if lastInd is not None: dels.append((lastInd + 1, t)) dels.sort(lambda a, b: cmp(b[0], a[0])) for i, t in dels: ops.insert(i, ('d', t)) return ops def nodeOutputChanged(self, startNode): """Triggered when a node's output values have changed. (NOT called during process()) Propagates new data forward through network.""" ## first collect list of nodes/terminals and their dependencies if self.processing: return self.processing = True try: deps = {} for name, node in self._nodes.items(): deps[node] = [] for t in node.outputs().values(): deps[node].extend(t.dependentNodes()) ## determine order of updates order = toposort(deps, nodes=[startNode]) order.reverse() ## keep track of terminals that have been updated terms = set(startNode.outputs().values()) #print "======= Updating", startNode #print "Order:", order for node in order[1:]: #print "Processing node", node for term in list(node.inputs().values()): #print " checking terminal", term deps = list(term.connections().keys()) update = False for d in deps: if d in terms: #print " ..input", d, "changed" update = True term.inputChanged(d, process=False) if update: #print " processing.." node.update() terms |= set(node.outputs().values()) finally: self.processing = False if self.inputWasSet: self.inputWasSet = False else: self.sigStateChanged.emit() def chartGraphicsItem(self): """Return the graphicsItem which displays the internals of this flowchart. (graphicsItem() still returns the external-view item)""" #return self._chartGraphicsItem return self.viewBox def widget(self): if self._widget is None: self._widget = FlowchartCtrlWidget(self) self.scene = self._widget.scene() self.viewBox = self._widget.viewBox() #self._scene = QtGui.QGraphicsScene() #self._widget.setScene(self._scene) #self.scene.addItem(self.chartGraphicsItem()) #ci = self.chartGraphicsItem() #self.viewBox.addItem(ci) #self.viewBox.autoRange() return self._widget def listConnections(self): conn = set() for n in self._nodes.values(): terms = n.outputs() for n, t in terms.items(): for c in t.connections(): conn.add((t, c)) return conn def saveState(self): state = Node.saveState(self) state['nodes'] = [] state['connects'] = [] #state['terminals'] = self.saveTerminals() for name, node in self._nodes.items(): cls = type(node) if hasattr(cls, 'nodeName'): clsName = cls.nodeName pos = node.graphicsItem().pos() ns = { 'class': clsName, 'name': name, 'pos': (pos.x(), pos.y()), 'state': node.saveState() } state['nodes'].append(ns) conn = self.listConnections() for a, b in conn: state['connects'].append( (a.node().name(), a.name(), b.node().name(), b.name())) state['inputNode'] = self.inputNode.saveState() state['outputNode'] = self.outputNode.saveState() return state def restoreState(self, state, clear=False): self.blockSignals(True) try: if clear: self.clear() Node.restoreState(self, state) nodes = state['nodes'] nodes.sort(lambda a, b: cmp(a['pos'][0], b['pos'][0])) for n in nodes: if n['name'] in self._nodes: #self._nodes[n['name']].graphicsItem().moveBy(*n['pos']) self._nodes[n['name']].restoreState(n['state']) continue try: node = self.createNode(n['class'], name=n['name']) node.restoreState(n['state']) except: printExc("Error creating node %s: (continuing anyway)" % n['name']) #node.graphicsItem().moveBy(*n['pos']) self.inputNode.restoreState(state.get('inputNode', {})) self.outputNode.restoreState(state.get('outputNode', {})) #self.restoreTerminals(state['terminals']) for n1, t1, n2, t2 in state['connects']: try: self.connectTerminals(self._nodes[n1][t1], self._nodes[n2][t2]) except: print(self._nodes[n1].terminals) print(self._nodes[n2].terminals) printExc("Error connecting terminals %s.%s - %s.%s:" % (n1, t1, n2, t2)) finally: self.blockSignals(False) self.sigChartLoaded.emit() self.outputChanged() self.sigStateChanged.emit() #self.sigOutputChanged.emit() def loadFile(self, fileName=None, startDir=None): if fileName is None: if startDir is None: startDir = self.filePath if startDir is None: startDir = '.' self.fileDialog = pg.FileDialog(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) #self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) self.fileDialog.show() self.fileDialog.fileSelected.connect(self.loadFile) return ## NOTE: was previously using a real widget for the file dialog's parent, but this caused weird mouse event bugs.. #fileName = QtGui.QFileDialog.getOpenFileName(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") fileName = str(fileName) state = configfile.readConfigFile(fileName) self.restoreState(state, clear=True) self.viewBox.autoRange() #self.emit(QtCore.SIGNAL('fileLoaded'), fileName) self.sigFileLoaded.emit(fileName) def saveFile(self, fileName=None, startDir=None, suggestedFileName='flowchart.fc'): if fileName is None: if startDir is None: startDir = self.filePath if startDir is None: startDir = '.' self.fileDialog = pg.FileDialog(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) #self.fileDialog.setDirectory(startDir) self.fileDialog.show() self.fileDialog.fileSelected.connect(self.saveFile) return #fileName = QtGui.QFileDialog.getSaveFileName(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") configfile.writeConfigFile(self.saveState(), fileName) self.sigFileSaved.emit(fileName) def clear(self): for n in list(self._nodes.values()): if n is self.inputNode or n is self.outputNode: continue n.close() ## calls self.nodeClosed(n) by signal #self.clearTerminals() self.widget().clear() def clearTerminals(self): Node.clearTerminals(self) self.inputNode.clearTerminals() self.outputNode.clearTerminals()