From 19be6959f3c14362f4f899b7bab7efa58584fe54 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Sun, 15 Dec 2013 23:50:11 -0500 Subject: [PATCH] Flowchart: * Replaced dynamic imports with static * Added NodeLibrary allowing multiple customized collections of Node types --- examples/FlowchartCustomNode.py | 28 ++-- pyqtgraph/flowchart/Flowchart.py | 15 ++- pyqtgraph/flowchart/NodeLibrary.py | 84 ++++++++++++ pyqtgraph/flowchart/library/__init__.py | 169 ++++++++++++------------ 4 files changed, 197 insertions(+), 99 deletions(-) create mode 100644 pyqtgraph/flowchart/NodeLibrary.py diff --git a/examples/FlowchartCustomNode.py b/examples/FlowchartCustomNode.py index bce37982..25ea5c77 100644 --- a/examples/FlowchartCustomNode.py +++ b/examples/FlowchartCustomNode.py @@ -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 diff --git a/pyqtgraph/flowchart/Flowchart.py b/pyqtgraph/flowchart/Flowchart.py index 81f9e163..f566e97c 100644 --- a/pyqtgraph/flowchart/Flowchart.py +++ b/pyqtgraph/flowchart/Flowchart.py @@ -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: diff --git a/pyqtgraph/flowchart/NodeLibrary.py b/pyqtgraph/flowchart/NodeLibrary.py new file mode 100644 index 00000000..356848f9 --- /dev/null +++ b/pyqtgraph/flowchart/NodeLibrary.py @@ -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() diff --git a/pyqtgraph/flowchart/library/__init__.py b/pyqtgraph/flowchart/library/__init__.py index 1e44edff..3ab4767e 100644 --- a/pyqtgraph/flowchart/library/__init__.py +++ b/pyqtgraph/flowchart/library/__init__.py @@ -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) \ No newline at end of file +#loadLibrary() + + +