Flowchart:

* Replaced dynamic imports with static
* Added NodeLibrary allowing multiple customized collections of Node types
This commit is contained in:
Luke Campagnola 2013-12-15 23:50:11 -05:00
parent 59f07a03ee
commit 19be6959f3
4 changed files with 197 additions and 99 deletions

View File

@ -83,9 +83,8 @@ class ImageViewNode(Node):
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
@ -113,12 +112,25 @@ class UnsharpMaskNode(CtrlNode):
strength = self.ctrls['strength'].value()
output = dataIn - (strength * scipy.ndimage.gaussian_filter(dataIn, (sigma,sigma)))
return {'dataOut': output}
## To make our custom node classes available in the flowchart context menu,
## we can either register them with the default node library or make a
## new library.
## 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',)])
## Method 1: Register to global default library:
#fclib.registerNodeType(ImageViewNode, [('Display',)])
#fclib.registerNodeType(UnsharpMaskNode, [('Image',)])
## Method 2: If we want to make our custom node available only to this flowchart,
## then instead of registering the node type globally, we can create a new
## NodeLibrary:
library = fclib.LIBRARY.copy() # start with the default node set
library.addNodeType(ImageViewNode, [('Display',)])
library.addNodeType(UnsharpMaskNode, [('Image',)])
fc.setLibrary(library)
## 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

View File

@ -14,7 +14,7 @@ else:
from .Terminal import Terminal
from numpy import ndarray
from . import library
from .library import LIBRARY
from pyqtgraph.debug import printExc
import pyqtgraph.configfile as configfile
import pyqtgraph.dockarea as dockarea
@ -67,7 +67,8 @@ class Flowchart(Node):
sigChartLoaded = QtCore.Signal()
sigStateChanged = QtCore.Signal()
def __init__(self, terminals=None, name=None, filePath=None):
def __init__(self, terminals=None, name=None, filePath=None, library=None):
self.library = library or LIBRARY
if name is None:
name = "Flowchart"
if terminals is None:
@ -105,6 +106,10 @@ class Flowchart(Node):
for name, opts in terminals.items():
self.addTerminal(name, **opts)
def setLibrary(self, lib):
self.library = lib
self.widget().chartWidget.buildMenu()
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.
@ -194,7 +199,7 @@ class Flowchart(Node):
break
n += 1
node = library.getNodeType(nodeType)(name)
node = self.library.getNodeType(nodeType)(name)
self.addNode(node, name, pos)
return node
@ -846,13 +851,13 @@ class FlowchartWidget(dockarea.DockArea):
self.nodeMenu.triggered.disconnect(self.nodeMenuTriggered)
self.nodeMenu = None
self.subMenus = []
library.loadLibrary(reloadLibs=True)
self.chart.library.reload()
self.buildMenu()
def buildMenu(self, pos=None):
self.nodeMenu = QtGui.QMenu()
self.subMenus = []
for section, nodes in library.getNodeTree().items():
for section, nodes in self.chart.library.getNodeTree().items():
menu = QtGui.QMenu(section)
self.nodeMenu.addMenu(menu)
for name in nodes:

View File

@ -0,0 +1,84 @@
from pyqtgraph.pgcollections import OrderedDict
from Node import Node
def isNodeClass(cls):
try:
if not issubclass(cls, Node):
return False
except:
return False
return hasattr(cls, 'nodeName')
class NodeLibrary:
"""
A library of flowchart Node types. Custom libraries may be built to provide
each flowchart with a specific set of allowed Node types.
"""
def __init__(self):
self.nodeList = OrderedDict()
self.nodeTree = OrderedDict()
def addNodeType(self, nodeClass, paths, override=False):
"""
Register a new node type. If the type's name is already in use,
an exception will be raised (unless override=True).
Arguments:
nodeClass - a subclass of Node (must have typ.nodeName)
paths - list of tuples specifying the location(s) this
type will appear in the library tree.
override - if True, overwrite any class having the same name
"""
if not isNodeClass(nodeClass):
raise Exception("Object %s is not a Node subclass" % str(nodeClass))
name = nodeClass.nodeName
if not override and name in self.nodeList:
raise Exception("Node type name '%s' is already registered." % name)
self.nodeList[name] = nodeClass
for path in paths:
root = self.nodeTree
for n in path:
if n not in root:
root[n] = OrderedDict()
root = root[n]
root[name] = nodeClass
def getNodeType(self, name):
try:
return self.nodeList[name]
except KeyError:
raise Exception("No node type called '%s'" % name)
def getNodeTree(self):
return self.nodeTree
def copy(self):
"""
Return a copy of this library.
"""
lib = NodeLibrary()
lib.nodeList = self.nodeList.copy()
lib.nodeTree = self.treeCopy(self.nodeTree)
return lib
@staticmethod
def treeCopy(tree):
copy = OrderedDict()
for k,v in tree.items():
if isNodeClass(v):
copy[k] = v
else:
copy[k] = NodeLibrary.treeCopy(v)
return copy
def reload(self):
"""
Reload Node classes in this library.
"""
raise NotImplementedError()

View File

@ -1,103 +1,100 @@
# -*- coding: utf-8 -*-
from pyqtgraph.pgcollections import OrderedDict
from pyqtgraph import importModules
#from pyqtgraph import importModules
import os, types
from pyqtgraph.debug import printExc
from ..Node import Node
#from ..Node import Node
from ..NodeLibrary import NodeLibrary, isNodeClass
import pyqtgraph.reload as reload
NODE_LIST = OrderedDict() ## maps name:class for all registered Node subclasses
NODE_TREE = OrderedDict() ## categorized tree of Node subclasses
# Build default library
LIBRARY = NodeLibrary()
def getNodeType(name):
try:
return NODE_LIST[name]
except KeyError:
raise Exception("No node type called '%s'" % name)
# For backward compatibility, expose the default library's properties here:
NODE_LIST = LIBRARY.nodeList
NODE_TREE = LIBRARY.nodeTree
registerNodeType = LIBRARY.addNodeType
getNodeTree = LIBRARY.getNodeTree
getNodeType = LIBRARY.getNodeType
def getNodeTree():
return NODE_TREE
def registerNodeType(cls, paths, override=False):
"""
Register a new node type. If the type's name is already in use,
an exception will be raised (unless override=True).
# Add all nodes to the default library
for modName in ['Data', 'Display', 'Filters', 'Operators']:
mod = __import__(modName, globals(), locals(), [], -1)
nodes = [getattr(mod, name) for name in dir(mod) if isNodeClass(getattr(mod, name))]
for node in nodes:
LIBRARY.addNodeType(node, [(modName,)])
Arguments:
cls - a subclass of Node (must have typ.nodeName)
paths - list of tuples specifying the location(s) this
type will appear in the library tree.
override - if True, overwrite any class having the same name
"""
if not isNodeClass(cls):
raise Exception("Object %s is not a Node subclass" % str(cls))
#NODE_LIST = OrderedDict() ## maps name:class for all registered Node subclasses
#NODE_TREE = OrderedDict() ## categorized tree of Node subclasses
#def getNodeType(name):
#try:
#return NODE_LIST[name]
#except KeyError:
#raise Exception("No node type called '%s'" % name)
#def getNodeTree():
#return NODE_TREE
#def registerNodeType(cls, paths, override=False):
#"""
#Register a new node type. If the type's name is already in use,
#an exception will be raised (unless override=True).
name = cls.nodeName
if not override and name in NODE_LIST:
raise Exception("Node type name '%s' is already registered." % name)
#Arguments:
#cls - a subclass of Node (must have typ.nodeName)
#paths - list of tuples specifying the location(s) this
#type will appear in the library tree.
#override - if True, overwrite any class having the same name
#"""
#if not isNodeClass(cls):
#raise Exception("Object %s is not a Node subclass" % str(cls))
NODE_LIST[name] = cls
for path in paths:
root = NODE_TREE
for n in path:
if n not in root:
root[n] = OrderedDict()
root = root[n]
root[name] = cls
def isNodeClass(cls):
try:
if not issubclass(cls, Node):
return False
except:
return False
return hasattr(cls, 'nodeName')
def loadLibrary(reloadLibs=False, libPath=None):
"""Import all Node subclasses found within files in the library module."""
global NODE_LIST, NODE_TREE
#if libPath is None:
#libPath = os.path.dirname(os.path.abspath(__file__))
#name = cls.nodeName
#if not override and name in NODE_LIST:
#raise Exception("Node type name '%s' is already registered." % name)
if reloadLibs:
reload.reloadAll(libPath)
#NODE_LIST[name] = cls
#for path in paths:
#root = NODE_TREE
#for n in path:
#if n not in root:
#root[n] = OrderedDict()
#root = root[n]
#root[name] = cls
#def isNodeClass(cls):
#try:
#if not issubclass(cls, Node):
#return False
#except:
#return False
#return hasattr(cls, 'nodeName')
#def loadLibrary(reloadLibs=False, libPath=None):
#"""Import all Node subclasses found within files in the library module."""
#global NODE_LIST, NODE_TREE
#if reloadLibs:
#reload.reloadAll(libPath)
mods = importModules('', globals(), locals())
#for f in frozenSupport.listdir(libPath):
#pathName, ext = os.path.splitext(f)
#if ext not in ('.py', '.pyc') or '__init__' in pathName or '__pycache__' in pathName:
#continue
#try:
##print "importing from", f
#mod = __import__(pathName, globals(), locals())
#except:
#printExc("Error loading flowchart library %s:" % pathName)
#continue
#mods = importModules('', globals(), locals())
for name, mod in mods.items():
nodes = []
for n in dir(mod):
o = getattr(mod, n)
if isNodeClass(o):
#print " ", str(o)
registerNodeType(o, [(name,)], override=reloadLibs)
#nodes.append((o.nodeName, o))
#if len(nodes) > 0:
#NODE_TREE[name] = OrderedDict(nodes)
#NODE_LIST.extend(nodes)
#NODE_LIST = OrderedDict(NODE_LIST)
#for name, mod in mods.items():
#nodes = []
#for n in dir(mod):
#o = getattr(mod, n)
#if isNodeClass(o):
#registerNodeType(o, [(name,)], override=reloadLibs)
def reloadLibrary():
loadLibrary(reloadLibs=True)
#def reloadLibrary():
#loadLibrary(reloadLibs=True)
loadLibrary()
#NODE_LIST = []
#for o in locals().values():
#if type(o) is type(AddNode) and issubclass(o, Node) and o is not Node and hasattr(o, 'nodeName'):
#NODE_LIST.append((o.nodeName, o))
#NODE_LIST.sort(lambda a,b: cmp(a[0], b[0]))
#NODE_LIST = OrderedDict(NODE_LIST)
#loadLibrary()