Flowchart:
* Replaced dynamic imports with static * Added NodeLibrary allowing multiple customized collections of Node types
This commit is contained in:
parent
59f07a03ee
commit
19be6959f3
@ -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
|
||||
|
@ -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:
|
||||
|
84
pyqtgraph/flowchart/NodeLibrary.py
Normal file
84
pyqtgraph/flowchart/NodeLibrary.py
Normal 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()
|
@ -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()
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user