Added deprecation warning for Node.__getattr__
Expanded flowchart.Node docstrings Added custom node example
This commit is contained in:
parent
4cbc012474
commit
5786a627b5
@ -21,21 +21,24 @@ import pyqtgraph.metaarray as metaarray
|
|||||||
|
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
|
|
||||||
|
## Create main window with grid layout
|
||||||
win = QtGui.QMainWindow()
|
win = QtGui.QMainWindow()
|
||||||
cw = QtGui.QWidget()
|
cw = QtGui.QWidget()
|
||||||
win.setCentralWidget(cw)
|
win.setCentralWidget(cw)
|
||||||
layout = QtGui.QGridLayout()
|
layout = QtGui.QGridLayout()
|
||||||
cw.setLayout(layout)
|
cw.setLayout(layout)
|
||||||
|
|
||||||
|
## Create flowchart, define input/output terminals
|
||||||
fc = Flowchart(terminals={
|
fc = Flowchart(terminals={
|
||||||
'dataIn': {'io': 'in'},
|
'dataIn': {'io': 'in'},
|
||||||
'dataOut': {'io': 'out'}
|
'dataOut': {'io': 'out'}
|
||||||
})
|
})
|
||||||
w = fc.widget()
|
w = fc.widget()
|
||||||
|
|
||||||
|
## Add flowchart control panel to the main window
|
||||||
layout.addWidget(fc.widget(), 0, 0, 2, 1)
|
layout.addWidget(fc.widget(), 0, 0, 2, 1)
|
||||||
|
|
||||||
|
## Add two plot widgets
|
||||||
pw1 = pg.PlotWidget()
|
pw1 = pg.PlotWidget()
|
||||||
pw2 = pg.PlotWidget()
|
pw2 = pg.PlotWidget()
|
||||||
layout.addWidget(pw1, 0, 1)
|
layout.addWidget(pw1, 0, 1)
|
||||||
@ -43,14 +46,17 @@ layout.addWidget(pw2, 1, 1)
|
|||||||
|
|
||||||
win.show()
|
win.show()
|
||||||
|
|
||||||
|
## generate signal data to pass through the flowchart
|
||||||
data = np.random.normal(size=1000)
|
data = np.random.normal(size=1000)
|
||||||
data[200:300] += 1
|
data[200:300] += 1
|
||||||
data += np.sin(np.linspace(0, 100, 1000))
|
data += np.sin(np.linspace(0, 100, 1000))
|
||||||
data = metaarray.MetaArray(data, info=[{'name': 'Time', 'values': np.linspace(0, 1.0, len(data))}, {}])
|
data = metaarray.MetaArray(data, info=[{'name': 'Time', 'values': np.linspace(0, 1.0, len(data))}, {}])
|
||||||
|
|
||||||
|
## Feed data into the input terminal of the flowchart
|
||||||
fc.setInput(dataIn=data)
|
fc.setInput(dataIn=data)
|
||||||
|
|
||||||
|
## populate the flowchart with a basic set of processing nodes.
|
||||||
|
## (usually we let the user do this)
|
||||||
pw1Node = fc.createNode('PlotWidget', pos=(0, -150))
|
pw1Node = fc.createNode('PlotWidget', pos=(0, -150))
|
||||||
pw1Node.setPlot(pw1)
|
pw1Node.setPlot(pw1)
|
||||||
|
|
||||||
@ -59,42 +65,12 @@ pw2Node.setPlot(pw2)
|
|||||||
|
|
||||||
fNode = fc.createNode('GaussianFilter', pos=(0, 0))
|
fNode = fc.createNode('GaussianFilter', pos=(0, 0))
|
||||||
fNode.ctrls['sigma'].setValue(5)
|
fNode.ctrls['sigma'].setValue(5)
|
||||||
fc.connectTerminals(fc.dataIn, fNode.In)
|
fc.connectTerminals(fc['dataIn'], fNode['In'])
|
||||||
fc.connectTerminals(fc.dataIn, pw1Node.In)
|
fc.connectTerminals(fc['dataIn'], pw1Node['In'])
|
||||||
fc.connectTerminals(fNode.Out, pw2Node.In)
|
fc.connectTerminals(fNode['Out'], pw2Node['In'])
|
||||||
fc.connectTerminals(fNode.Out, fc.dataOut)
|
fc.connectTerminals(fNode['Out'], fc['dataOut'])
|
||||||
|
|
||||||
|
|
||||||
#n1 = fc.createNode('Add', pos=(0,-80))
|
|
||||||
#n2 = fc.createNode('Subtract', pos=(140,-10))
|
|
||||||
#n3 = fc.createNode('Abs', pos=(0, 80))
|
|
||||||
#n4 = fc.createNode('Add', pos=(140,100))
|
|
||||||
|
|
||||||
#fc.connectTerminals(fc.dataIn, n1.A)
|
|
||||||
#fc.connectTerminals(fc.dataIn, n1.B)
|
|
||||||
#fc.connectTerminals(fc.dataIn, n2.A)
|
|
||||||
#fc.connectTerminals(n1.Out, n4.A)
|
|
||||||
#fc.connectTerminals(n1.Out, n2.B)
|
|
||||||
#fc.connectTerminals(n2.Out, n3.In)
|
|
||||||
#fc.connectTerminals(n3.Out, n4.B)
|
|
||||||
#fc.connectTerminals(n4.Out, fc.dataOut)
|
|
||||||
|
|
||||||
|
|
||||||
#def process(**kargs):
|
|
||||||
#return fc.process(**kargs)
|
|
||||||
|
|
||||||
|
|
||||||
#print process(dataIn=7)
|
|
||||||
|
|
||||||
#fc.setInput(dataIn=3)
|
|
||||||
|
|
||||||
#s = fc.saveState()
|
|
||||||
#fc.clear()
|
|
||||||
|
|
||||||
#fc.restoreState(s)
|
|
||||||
|
|
||||||
#fc.setInput(dataIn=3)
|
|
||||||
|
|
||||||
|
|
||||||
## Start Qt event loop unless running in interactive mode or using pyside.
|
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
144
examples/FlowchartCustomNode.py
Normal file
144
examples/FlowchartCustomNode.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
This example demonstrates writing a custom Node subclass for use with flowcharts.
|
||||||
|
|
||||||
|
We implement a couple of simple image processing nodes.
|
||||||
|
"""
|
||||||
|
import initExample ## Add path to library (just for examples; you do not need this)
|
||||||
|
|
||||||
|
from pyqtgraph.flowchart import Flowchart, Node
|
||||||
|
import pyqtgraph.flowchart.library as fclib
|
||||||
|
from pyqtgraph.flowchart.library.common import CtrlNode
|
||||||
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
|
import pyqtgraph as pg
|
||||||
|
import numpy as np
|
||||||
|
import scipy.ndimage
|
||||||
|
|
||||||
|
app = QtGui.QApplication([])
|
||||||
|
|
||||||
|
## Create main window with a grid layout inside
|
||||||
|
win = QtGui.QMainWindow()
|
||||||
|
cw = QtGui.QWidget()
|
||||||
|
win.setCentralWidget(cw)
|
||||||
|
layout = QtGui.QGridLayout()
|
||||||
|
cw.setLayout(layout)
|
||||||
|
|
||||||
|
## Create an empty flowchart with a single input and output
|
||||||
|
fc = Flowchart(terminals={
|
||||||
|
'dataIn': {'io': 'in'},
|
||||||
|
'dataOut': {'io': 'out'}
|
||||||
|
})
|
||||||
|
w = fc.widget()
|
||||||
|
|
||||||
|
layout.addWidget(fc.widget(), 0, 0, 2, 1)
|
||||||
|
|
||||||
|
## Create two ImageView widgets to display the raw and processed data with contrast
|
||||||
|
## and color control.
|
||||||
|
v1 = pg.ImageView()
|
||||||
|
v2 = pg.ImageView()
|
||||||
|
layout.addWidget(v1, 0, 1)
|
||||||
|
layout.addWidget(v2, 1, 1)
|
||||||
|
|
||||||
|
win.show()
|
||||||
|
|
||||||
|
## generate random input data
|
||||||
|
data = np.random.normal(size=(100,100))
|
||||||
|
data = 25 * scipy.ndimage.gaussian_filter(data, (5,5))
|
||||||
|
data += np.random.normal(size=(100,100))
|
||||||
|
data[40:60, 40:60] += 15.0
|
||||||
|
data[30:50, 30:50] += 15.0
|
||||||
|
#data += np.sin(np.linspace(0, 100, 1000))
|
||||||
|
#data = metaarray.MetaArray(data, info=[{'name': 'Time', 'values': np.linspace(0, 1.0, len(data))}, {}])
|
||||||
|
|
||||||
|
## Set the raw data as the input value to the flowchart
|
||||||
|
fc.setInput(dataIn=data)
|
||||||
|
|
||||||
|
|
||||||
|
## At this point, we need some custom Node classes since those provided in the library
|
||||||
|
## are not sufficient. Each node will define a set of input/output terminals, a
|
||||||
|
## processing function, and optionally a control widget (to be displayed in the
|
||||||
|
## flowchart control panel)
|
||||||
|
|
||||||
|
class ImageViewNode(Node):
|
||||||
|
"""Node that displays image data in an ImageView widget"""
|
||||||
|
nodeName = 'ImageView'
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
self.view = None
|
||||||
|
## Initialize node with only a single input terminal
|
||||||
|
Node.__init__(self, name, terminals={'data': {'io':'in'}})
|
||||||
|
|
||||||
|
def setView(self, view): ## setView must be called by the program
|
||||||
|
self.view = view
|
||||||
|
|
||||||
|
def process(self, data, display=True):
|
||||||
|
## if process is called with display=False, then the flowchart is being operated
|
||||||
|
## in batch processing mode, so we should skip displaying to improve performance.
|
||||||
|
|
||||||
|
if display and self.view is not None:
|
||||||
|
## the 'data' argument is the value given to the 'data' terminal
|
||||||
|
if data is None:
|
||||||
|
self.view.setImage(np.zeros((1,1))) # give a blank array to clear the view
|
||||||
|
else:
|
||||||
|
self.view.setImage(data)
|
||||||
|
|
||||||
|
## register the class so it will appear in the menu of node types.
|
||||||
|
## It will appear in the 'display' sub-menu.
|
||||||
|
fclib.registerNodeType(ImageViewNode, [('Display',)])
|
||||||
|
|
||||||
|
## We will define an unsharp masking filter node as a subclass of CtrlNode.
|
||||||
|
## CtrlNode is just a convenience class that automatically creates its
|
||||||
|
## control widget based on a simple data structure.
|
||||||
|
class UnsharpMaskNode(CtrlNode):
|
||||||
|
"""Return the input data passed through scipy.ndimage.gaussian_filter."""
|
||||||
|
nodeName = "UnsharpMask"
|
||||||
|
uiTemplate = [
|
||||||
|
('sigma', 'spin', {'value': 1.0, 'step': 1.0, 'range': [0.0, None]}),
|
||||||
|
('strength', 'spin', {'value': 1.0, 'dec': True, 'step': 0.5, 'minStep': 0.01, 'range': [0.0, None]}),
|
||||||
|
]
|
||||||
|
def __init__(self, name):
|
||||||
|
## Define the input / output terminals available on this node
|
||||||
|
terminals = {
|
||||||
|
'dataIn': dict(io='in'), # each terminal needs at least a name and
|
||||||
|
'dataOut': dict(io='out'), # to specify whether it is input or output
|
||||||
|
} # other more advanced options are available
|
||||||
|
# as well..
|
||||||
|
|
||||||
|
CtrlNode.__init__(self, name, terminals=terminals)
|
||||||
|
|
||||||
|
def process(self, dataIn, display=True):
|
||||||
|
# CtrlNode has created self.ctrls, which is a dict containing {ctrlName: widget}
|
||||||
|
sigma = self.ctrls['sigma'].value()
|
||||||
|
strength = self.ctrls['strength'].value()
|
||||||
|
output = dataIn - (strength * scipy.ndimage.gaussian_filter(dataIn, (sigma,sigma)))
|
||||||
|
return {'dataOut': output}
|
||||||
|
|
||||||
|
## register the class so it will appear in the menu of node types.
|
||||||
|
## It will appear in a new 'image' sub-menu.
|
||||||
|
fclib.registerNodeType(UnsharpMaskNode, [('Image',)])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Now we will programmatically add nodes to define the function of the flowchart.
|
||||||
|
## Normally, the user will do this manually or by loading a pre-generated
|
||||||
|
## flowchart file.
|
||||||
|
|
||||||
|
v1Node = fc.createNode('ImageView', pos=(0, -150))
|
||||||
|
v1Node.setView(v1)
|
||||||
|
|
||||||
|
v2Node = fc.createNode('ImageView', pos=(150, -150))
|
||||||
|
v2Node.setView(v2)
|
||||||
|
|
||||||
|
fNode = fc.createNode('UnsharpMask', pos=(0, 0))
|
||||||
|
fc.connectTerminals(fc['dataIn'], fNode['dataIn'])
|
||||||
|
fc.connectTerminals(fc['dataIn'], v1Node['data'])
|
||||||
|
fc.connectTerminals(fNode['dataOut'], v2Node['data'])
|
||||||
|
fc.connectTerminals(fNode['dataOut'], fc['dataOut'])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||||
|
QtGui.QApplication.instance().exec_()
|
@ -13,6 +13,18 @@ def strDict(d):
|
|||||||
return dict([(str(k), v) for k, v in d.items()])
|
return dict([(str(k), v) for k, v in d.items()])
|
||||||
|
|
||||||
class Node(QtCore.QObject):
|
class Node(QtCore.QObject):
|
||||||
|
"""
|
||||||
|
Node represents the basic processing unit of a flowchart.
|
||||||
|
A Node subclass implements at least:
|
||||||
|
|
||||||
|
1) A list of input / ouptut terminals and their properties
|
||||||
|
2) a process() function which takes the names of input terminals as keyword arguments and returns a dict with the names of output terminals as keys.
|
||||||
|
|
||||||
|
A flowchart thus consists of multiple instances of Node subclasses, each of which is connected
|
||||||
|
to other by wires between their terminals. A flowchart is, itself, also a special subclass of Node.
|
||||||
|
This allows Nodes within the flowchart to connect to the input/output nodes of the flowchart itself.
|
||||||
|
|
||||||
|
Optionally, a node class can implement the ctrlWidget() method, which must return a QWidget (usually containing other widgets) that will be displayed in the flowchart control panel. Some nodes implement fairly complex control widgets, but most nodes follow a simple form-like pattern: a list of parameter names and a single value (represented as spin box, check box, etc..) for each parameter. To make this easier, the CtrlNode subclass allows you to instead define a simple data structure that CtrlNode will use to automatically generate the control widget. """
|
||||||
|
|
||||||
sigOutputChanged = QtCore.Signal(object) # self
|
sigOutputChanged = QtCore.Signal(object) # self
|
||||||
sigClosed = QtCore.Signal(object)
|
sigClosed = QtCore.Signal(object)
|
||||||
@ -23,6 +35,31 @@ class Node(QtCore.QObject):
|
|||||||
|
|
||||||
|
|
||||||
def __init__(self, name, terminals=None, allowAddInput=False, allowAddOutput=False, allowRemove=True):
|
def __init__(self, name, terminals=None, allowAddInput=False, allowAddOutput=False, allowRemove=True):
|
||||||
|
"""
|
||||||
|
============== ============================================================
|
||||||
|
Arguments
|
||||||
|
name The name of this specific node instance. It can be any
|
||||||
|
string, but must be unique within a flowchart. Usually,
|
||||||
|
we simply let the flowchart decide on a name when calling
|
||||||
|
Flowchart.addNode(...)
|
||||||
|
terminals Dict-of-dicts specifying the terminals present on this Node.
|
||||||
|
Terminal specifications look like::
|
||||||
|
|
||||||
|
'inputTerminalName': {'io': 'in'}
|
||||||
|
'outputTerminalName': {'io': 'out'}
|
||||||
|
|
||||||
|
There are a number of optional parameters for terminals:
|
||||||
|
multi, pos, renamable, removable, multiable, bypass. See
|
||||||
|
the Terminal class for more information.
|
||||||
|
allowAddInput bool; whether the user is allowed to add inputs by the
|
||||||
|
context menu.
|
||||||
|
allowAddOutput bool; whether the user is allowed to add outputs by the
|
||||||
|
context menu.
|
||||||
|
allowRemove bool; whether the user is allowed to remove this node by the
|
||||||
|
context menu.
|
||||||
|
============== ============================================================
|
||||||
|
|
||||||
|
"""
|
||||||
QtCore.QObject.__init__(self)
|
QtCore.QObject.__init__(self)
|
||||||
self._name = name
|
self._name = name
|
||||||
self._bypass = False
|
self._bypass = False
|
||||||
@ -52,15 +89,25 @@ class Node(QtCore.QObject):
|
|||||||
return name2
|
return name2
|
||||||
|
|
||||||
def addInput(self, name="Input", **args):
|
def addInput(self, name="Input", **args):
|
||||||
|
"""Add a new input terminal to this Node with the given name. Extra
|
||||||
|
keyword arguments are passed to Terminal.__init__.
|
||||||
|
|
||||||
|
This is a convenience function that just calls addTerminal(io='in', ...)"""
|
||||||
#print "Node.addInput called."
|
#print "Node.addInput called."
|
||||||
return self.addTerminal(name, io='in', **args)
|
return self.addTerminal(name, io='in', **args)
|
||||||
|
|
||||||
def addOutput(self, name="Output", **args):
|
def addOutput(self, name="Output", **args):
|
||||||
|
"""Add a new output terminal to this Node with the given name. Extra
|
||||||
|
keyword arguments are passed to Terminal.__init__.
|
||||||
|
|
||||||
|
This is a convenience function that just calls addTerminal(io='out', ...)"""
|
||||||
return self.addTerminal(name, io='out', **args)
|
return self.addTerminal(name, io='out', **args)
|
||||||
|
|
||||||
def removeTerminal(self, term):
|
def removeTerminal(self, term):
|
||||||
## term may be a terminal or its name
|
"""Remove the specified terminal from this Node. May specify either the
|
||||||
|
terminal's name or the terminal itself.
|
||||||
|
|
||||||
|
Causes sigTerminalRemoved to be emitted."""
|
||||||
if isinstance(term, Terminal):
|
if isinstance(term, Terminal):
|
||||||
name = term.name()
|
name = term.name()
|
||||||
else:
|
else:
|
||||||
@ -80,7 +127,9 @@ class Node(QtCore.QObject):
|
|||||||
|
|
||||||
|
|
||||||
def terminalRenamed(self, term, oldName):
|
def terminalRenamed(self, term, oldName):
|
||||||
"""Called after a terminal has been renamed"""
|
"""Called after a terminal has been renamed
|
||||||
|
|
||||||
|
Causes sigTerminalRenamed to be emitted."""
|
||||||
newName = term.name()
|
newName = term.name()
|
||||||
for d in [self.terminals, self._inputs, self._outputs]:
|
for d in [self.terminals, self._inputs, self._outputs]:
|
||||||
if oldName not in d:
|
if oldName not in d:
|
||||||
@ -92,6 +141,10 @@ class Node(QtCore.QObject):
|
|||||||
self.sigTerminalRenamed.emit(term, oldName)
|
self.sigTerminalRenamed.emit(term, oldName)
|
||||||
|
|
||||||
def addTerminal(self, name, **opts):
|
def addTerminal(self, name, **opts):
|
||||||
|
"""Add a new terminal to this Node with the given name. Extra
|
||||||
|
keyword arguments are passed to Terminal.__init__.
|
||||||
|
|
||||||
|
Causes sigTerminalAdded to be emitted."""
|
||||||
name = self.nextTerminalName(name)
|
name = self.nextTerminalName(name)
|
||||||
term = Terminal(self, name, **opts)
|
term = Terminal(self, name, **opts)
|
||||||
self.terminals[name] = term
|
self.terminals[name] = term
|
||||||
@ -105,38 +158,60 @@ class Node(QtCore.QObject):
|
|||||||
|
|
||||||
|
|
||||||
def inputs(self):
|
def inputs(self):
|
||||||
|
"""Return dict of all input terminals.
|
||||||
|
Warning: do not modify."""
|
||||||
return self._inputs
|
return self._inputs
|
||||||
|
|
||||||
def outputs(self):
|
def outputs(self):
|
||||||
|
"""Return dict of all output terminals.
|
||||||
|
Warning: do not modify."""
|
||||||
return self._outputs
|
return self._outputs
|
||||||
|
|
||||||
def process(self, **kargs):
|
def process(self, **kargs):
|
||||||
"""Process data through this node. Each named argument supplies data to the corresponding terminal."""
|
"""Process data through this node. This method is called any time the flowchart
|
||||||
|
wants the node to process data. It will be called with one keyword argument
|
||||||
|
corresponding to each input terminal, and must return a dict mapping the name
|
||||||
|
of each output terminal to its new value.
|
||||||
|
|
||||||
|
This method is also called with a 'display' keyword argument, which indicates
|
||||||
|
whether the node should update its display (if it implements any) while processing
|
||||||
|
this data. This is primarily used to disable expensive display operations
|
||||||
|
during batch processing.
|
||||||
|
"""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def graphicsItem(self):
|
def graphicsItem(self):
|
||||||
"""Return a (the?) graphicsitem for this node"""
|
"""Return the GraphicsItem for this node. Subclasses may re-implement
|
||||||
#print "Node.graphicsItem called."
|
this method to customize their appearance in the flowchart."""
|
||||||
if self._graphicsItem is None:
|
if self._graphicsItem is None:
|
||||||
#print "Creating NodeGraphicsItem..."
|
|
||||||
self._graphicsItem = NodeGraphicsItem(self)
|
self._graphicsItem = NodeGraphicsItem(self)
|
||||||
#print "Node.graphicsItem is returning ", self._graphicsItem
|
|
||||||
return self._graphicsItem
|
return self._graphicsItem
|
||||||
|
|
||||||
|
## this is just bad planning. Causes too many bugs.
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
"""Return the terminal with the given name"""
|
"""Return the terminal with the given name"""
|
||||||
if attr not in self.terminals:
|
if attr not in self.terminals:
|
||||||
raise AttributeError(attr)
|
raise AttributeError(attr)
|
||||||
else:
|
else:
|
||||||
|
import traceback
|
||||||
|
traceback.print_stack()
|
||||||
|
print("Warning: use of node.terminalName is deprecated; use node['terminalName'] instead.")
|
||||||
return self.terminals[attr]
|
return self.terminals[attr]
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
return getattr(self, item)
|
#return getattr(self, item)
|
||||||
|
"""Return the terminal with the given name"""
|
||||||
|
if item not in self.terminals:
|
||||||
|
raise KeyError(item)
|
||||||
|
else:
|
||||||
|
return self.terminals[item]
|
||||||
|
|
||||||
def name(self):
|
def name(self):
|
||||||
|
"""Return the name of this node."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
def rename(self, name):
|
def rename(self, name):
|
||||||
|
"""Rename this node. This will cause sigRenamed to be emitted."""
|
||||||
oldName = self._name
|
oldName = self._name
|
||||||
self._name = name
|
self._name = name
|
||||||
#self.emit(QtCore.SIGNAL('renamed'), self, oldName)
|
#self.emit(QtCore.SIGNAL('renamed'), self, oldName)
|
||||||
@ -154,15 +229,25 @@ class Node(QtCore.QObject):
|
|||||||
return "<Node %s @%x>" % (self.name(), id(self))
|
return "<Node %s @%x>" % (self.name(), id(self))
|
||||||
|
|
||||||
def ctrlWidget(self):
|
def ctrlWidget(self):
|
||||||
|
"""Return this Node's control widget."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def bypass(self, byp):
|
def bypass(self, byp):
|
||||||
|
"""Set whether this node should be bypassed.
|
||||||
|
|
||||||
|
When bypassed, a Node's process() method is never called. In some cases,
|
||||||
|
data is automatically copied directly from specific input nodes to
|
||||||
|
output nodes instead (see the bypass argument to Terminal.__init__).
|
||||||
|
This is usually called when the user disables a node from the flowchart
|
||||||
|
control panel.
|
||||||
|
"""
|
||||||
self._bypass = byp
|
self._bypass = byp
|
||||||
if self.bypassButton is not None:
|
if self.bypassButton is not None:
|
||||||
self.bypassButton.setChecked(byp)
|
self.bypassButton.setChecked(byp)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def isBypassed(self):
|
def isBypassed(self):
|
||||||
|
"""Return True if this Node is currently bypassed."""
|
||||||
return self._bypass
|
return self._bypass
|
||||||
|
|
||||||
def setInput(self, **args):
|
def setInput(self, **args):
|
||||||
@ -179,12 +264,14 @@ class Node(QtCore.QObject):
|
|||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def inputValues(self):
|
def inputValues(self):
|
||||||
|
"""Return a dict of all input values currently assigned to this node."""
|
||||||
vals = {}
|
vals = {}
|
||||||
for n, t in self.inputs().items():
|
for n, t in self.inputs().items():
|
||||||
vals[n] = t.value()
|
vals[n] = t.value()
|
||||||
return vals
|
return vals
|
||||||
|
|
||||||
def outputValues(self):
|
def outputValues(self):
|
||||||
|
"""Return a dict of all output values currently generated by this node."""
|
||||||
vals = {}
|
vals = {}
|
||||||
for n, t in self.outputs().items():
|
for n, t in self.outputs().items():
|
||||||
vals[n] = t.value()
|
vals[n] = t.value()
|
||||||
@ -195,11 +282,15 @@ class Node(QtCore.QObject):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def disconnected(self, localTerm, remoteTerm):
|
def disconnected(self, localTerm, remoteTerm):
|
||||||
"""Called whenever one of this node's terminals is connected elsewhere."""
|
"""Called whenever one of this node's terminals is disconnected from another."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def update(self, signal=True):
|
def update(self, signal=True):
|
||||||
"""Collect all input values, attempt to process new output values, and propagate downstream."""
|
"""Collect all input values, attempt to process new output values, and propagate downstream.
|
||||||
|
Subclasses should call update() whenever thir internal state has changed
|
||||||
|
(such as when the user interacts with the Node's control widget). Update
|
||||||
|
is automatically called when the inputs to the node are changed.
|
||||||
|
"""
|
||||||
vals = self.inputValues()
|
vals = self.inputValues()
|
||||||
#print " inputs:", vals
|
#print " inputs:", vals
|
||||||
try:
|
try:
|
||||||
@ -227,6 +318,9 @@ class Node(QtCore.QObject):
|
|||||||
self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data
|
self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data
|
||||||
|
|
||||||
def processBypassed(self, args):
|
def processBypassed(self, args):
|
||||||
|
"""Called when the flowchart would normally call Node.process, but this node is currently bypassed.
|
||||||
|
The default implementation looks for output terminals with a bypass connection and returns the
|
||||||
|
corresponding values. Most Node subclasses will _not_ need to reimplement this method."""
|
||||||
result = {}
|
result = {}
|
||||||
for term in list(self.outputs().values()):
|
for term in list(self.outputs().values()):
|
||||||
byp = term.bypassValue()
|
byp = term.bypassValue()
|
||||||
@ -266,6 +360,13 @@ class Node(QtCore.QObject):
|
|||||||
self.graphicsItem().setPen(QtGui.QPen(QtGui.QColor(150, 0, 0), 3))
|
self.graphicsItem().setPen(QtGui.QPen(QtGui.QColor(150, 0, 0), 3))
|
||||||
|
|
||||||
def saveState(self):
|
def saveState(self):
|
||||||
|
"""Return a dictionary representing the current state of this node
|
||||||
|
(excluding input / output values). This is used for saving/reloading
|
||||||
|
flowcharts. The default implementation returns this Node's position,
|
||||||
|
bypass state, and information about each of its terminals.
|
||||||
|
|
||||||
|
Subclasses may want to extend this method, adding extra keys to the returned
|
||||||
|
dict."""
|
||||||
pos = self.graphicsItem().pos()
|
pos = self.graphicsItem().pos()
|
||||||
state = {'pos': (pos.x(), pos.y()), 'bypass': self.isBypassed()}
|
state = {'pos': (pos.x(), pos.y()), 'bypass': self.isBypassed()}
|
||||||
termsEditable = self._allowAddInput | self._allowAddOutput
|
termsEditable = self._allowAddInput | self._allowAddOutput
|
||||||
@ -276,6 +377,8 @@ class Node(QtCore.QObject):
|
|||||||
return state
|
return state
|
||||||
|
|
||||||
def restoreState(self, state):
|
def restoreState(self, state):
|
||||||
|
"""Restore the state of this node from a structure previously generated
|
||||||
|
by saveState(). """
|
||||||
pos = state.get('pos', (0,0))
|
pos = state.get('pos', (0,0))
|
||||||
self.graphicsItem().setPos(*pos)
|
self.graphicsItem().setPos(*pos)
|
||||||
self.bypass(state.get('bypass', False))
|
self.bypass(state.get('bypass', False))
|
||||||
|
@ -24,6 +24,8 @@ class Terminal(object):
|
|||||||
renamable (bool) Whether the terminal can be renamed by the user
|
renamable (bool) Whether the terminal can be renamed by the user
|
||||||
removable (bool) Whether the terminal can be removed by the user
|
removable (bool) Whether the terminal can be removed by the user
|
||||||
multiable (bool) Whether the user may toggle the *multi* option for this terminal
|
multiable (bool) Whether the user may toggle the *multi* option for this terminal
|
||||||
|
bypass (str) Name of the terminal from which this terminal's value is derived
|
||||||
|
when the Node is in bypass mode.
|
||||||
============== =================================================================================
|
============== =================================================================================
|
||||||
"""
|
"""
|
||||||
self._io = io
|
self._io = io
|
||||||
|
Loading…
Reference in New Issue
Block a user