Merge branch 'static_imports' into develop
No more dynamic imports; pg uses static imports throughout. Flowcharts and exporters use plugin systems
This commit is contained in:
commit
3488910810
32
CHANGELOG
32
CHANGELOG
@ -1,3 +1,35 @@
|
|||||||
|
pyqtgraph-0.9.9 [unreleased]
|
||||||
|
|
||||||
|
API / behavior changes:
|
||||||
|
- Dynamic import system abandoned; pg now uses static imports throughout.
|
||||||
|
- Flowcharts and exporters have new pluggin systems
|
||||||
|
- Version strings:
|
||||||
|
- __init__.py in git repo now contains latest release version string
|
||||||
|
(previously, only packaged releases had version strings).
|
||||||
|
- installing from git checkout that does not correspond to a release
|
||||||
|
commit will result in a more descriptive version string.
|
||||||
|
- Speed improvements in functions.makeARGB
|
||||||
|
- ImageItem is faster by avoiding makeQImage(transpose=True)
|
||||||
|
|
||||||
|
New Features:
|
||||||
|
- New HDF5 example for working with very large datasets
|
||||||
|
- Added Qt.loadUiType function for PySide
|
||||||
|
- Simplified Profilers; can be activated with environmental variables
|
||||||
|
- Added Dock.raiseDock() method
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
- PlotCurveItem now has correct clicking behavior--clicks within a few px
|
||||||
|
of the line will trigger a signal.
|
||||||
|
- Fixes related to CSV exporter:
|
||||||
|
- CSV headers include data names, if available
|
||||||
|
- Exporter correctly handles items with no data
|
||||||
|
- pg.plot() avoids creating empty data item
|
||||||
|
- removed call to reduce() from exporter; not available in python 3
|
||||||
|
- Gave .name() methods to PlotDataItem, PlotCurveItem, and ScatterPlotItem
|
||||||
|
- fixed ImageItem handling of rgb images
|
||||||
|
- fixed makeARGB re-ordering of color channels
|
||||||
|
|
||||||
|
|
||||||
pyqtgraph-0.9.8 2013-11-24
|
pyqtgraph-0.9.8 2013-11-24
|
||||||
|
|
||||||
API / behavior changes:
|
API / behavior changes:
|
||||||
|
@ -83,9 +83,8 @@ class ImageViewNode(Node):
|
|||||||
else:
|
else:
|
||||||
self.view.setImage(data)
|
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.
|
## We will define an unsharp masking filter node as a subclass of CtrlNode.
|
||||||
## CtrlNode is just a convenience class that automatically creates its
|
## CtrlNode is just a convenience class that automatically creates its
|
||||||
@ -114,10 +113,23 @@ class UnsharpMaskNode(CtrlNode):
|
|||||||
output = dataIn - (strength * scipy.ndimage.gaussian_filter(dataIn, (sigma,sigma)))
|
output = dataIn - (strength * scipy.ndimage.gaussian_filter(dataIn, (sigma,sigma)))
|
||||||
return {'dataOut': output}
|
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',)])
|
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
|
||||||
|
## 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.
|
## Now we will programmatically add nodes to define the function of the flowchart.
|
||||||
|
@ -130,56 +130,119 @@ if __version__ is None and not hasattr(sys, 'frozen') and sys.version_info[0] ==
|
|||||||
## Import almost everything to make it available from a single namespace
|
## Import almost everything to make it available from a single namespace
|
||||||
## don't import the more complex systems--canvas, parametertree, flowchart, dockarea
|
## don't import the more complex systems--canvas, parametertree, flowchart, dockarea
|
||||||
## these must be imported separately.
|
## these must be imported separately.
|
||||||
from . import frozenSupport
|
#from . import frozenSupport
|
||||||
def importModules(path, globals, locals, excludes=()):
|
#def importModules(path, globals, locals, excludes=()):
|
||||||
"""Import all modules residing within *path*, return a dict of name: module pairs.
|
#"""Import all modules residing within *path*, return a dict of name: module pairs.
|
||||||
|
|
||||||
Note that *path* MUST be relative to the module doing the import.
|
#Note that *path* MUST be relative to the module doing the import.
|
||||||
"""
|
#"""
|
||||||
d = os.path.join(os.path.split(globals['__file__'])[0], path)
|
#d = os.path.join(os.path.split(globals['__file__'])[0], path)
|
||||||
files = set()
|
#files = set()
|
||||||
for f in frozenSupport.listdir(d):
|
#for f in frozenSupport.listdir(d):
|
||||||
if frozenSupport.isdir(os.path.join(d, f)) and f not in ['__pycache__', 'tests']:
|
#if frozenSupport.isdir(os.path.join(d, f)) and f not in ['__pycache__', 'tests']:
|
||||||
files.add(f)
|
#files.add(f)
|
||||||
elif f[-3:] == '.py' and f != '__init__.py':
|
#elif f[-3:] == '.py' and f != '__init__.py':
|
||||||
files.add(f[:-3])
|
#files.add(f[:-3])
|
||||||
elif f[-4:] == '.pyc' and f != '__init__.pyc':
|
#elif f[-4:] == '.pyc' and f != '__init__.pyc':
|
||||||
files.add(f[:-4])
|
#files.add(f[:-4])
|
||||||
|
|
||||||
mods = {}
|
#mods = {}
|
||||||
path = path.replace(os.sep, '.')
|
#path = path.replace(os.sep, '.')
|
||||||
for modName in files:
|
#for modName in files:
|
||||||
if modName in excludes:
|
#if modName in excludes:
|
||||||
continue
|
#continue
|
||||||
try:
|
#try:
|
||||||
if len(path) > 0:
|
#if len(path) > 0:
|
||||||
modName = path + '.' + modName
|
#modName = path + '.' + modName
|
||||||
#mod = __import__(modName, globals, locals, fromlist=['*'])
|
#print( "from .%s import * " % modName)
|
||||||
mod = __import__(modName, globals, locals, ['*'], 1)
|
#mod = __import__(modName, globals, locals, ['*'], 1)
|
||||||
mods[modName] = mod
|
#mods[modName] = mod
|
||||||
except:
|
#except:
|
||||||
import traceback
|
#import traceback
|
||||||
traceback.print_stack()
|
#traceback.print_stack()
|
||||||
sys.excepthook(*sys.exc_info())
|
#sys.excepthook(*sys.exc_info())
|
||||||
print("[Error importing module: %s]" % modName)
|
#print("[Error importing module: %s]" % modName)
|
||||||
|
|
||||||
return mods
|
#return mods
|
||||||
|
|
||||||
def importAll(path, globals, locals, excludes=()):
|
#def importAll(path, globals, locals, excludes=()):
|
||||||
"""Given a list of modules, import all names from each module into the global namespace."""
|
#"""Given a list of modules, import all names from each module into the global namespace."""
|
||||||
mods = importModules(path, globals, locals, excludes)
|
#mods = importModules(path, globals, locals, excludes)
|
||||||
for mod in mods.values():
|
#for mod in mods.values():
|
||||||
if hasattr(mod, '__all__'):
|
#if hasattr(mod, '__all__'):
|
||||||
names = mod.__all__
|
#names = mod.__all__
|
||||||
else:
|
#else:
|
||||||
names = [n for n in dir(mod) if n[0] != '_']
|
#names = [n for n in dir(mod) if n[0] != '_']
|
||||||
for k in names:
|
#for k in names:
|
||||||
if hasattr(mod, k):
|
#if hasattr(mod, k):
|
||||||
globals[k] = getattr(mod, k)
|
#globals[k] = getattr(mod, k)
|
||||||
|
|
||||||
importAll('graphicsItems', globals(), locals())
|
# Dynamic imports are disabled. This causes too many problems.
|
||||||
importAll('widgets', globals(), locals(),
|
#importAll('graphicsItems', globals(), locals())
|
||||||
excludes=['MatplotlibWidget', 'RawImageWidget', 'RemoteGraphicsView'])
|
#importAll('widgets', globals(), locals(),
|
||||||
|
#excludes=['MatplotlibWidget', 'RawImageWidget', 'RemoteGraphicsView'])
|
||||||
|
|
||||||
|
from .graphicsItems.VTickGroup import *
|
||||||
|
from .graphicsItems.GraphicsWidget import *
|
||||||
|
from .graphicsItems.ScaleBar import *
|
||||||
|
from .graphicsItems.PlotDataItem import *
|
||||||
|
from .graphicsItems.GraphItem import *
|
||||||
|
from .graphicsItems.TextItem import *
|
||||||
|
from .graphicsItems.GraphicsLayout import *
|
||||||
|
from .graphicsItems.UIGraphicsItem import *
|
||||||
|
from .graphicsItems.GraphicsObject import *
|
||||||
|
from .graphicsItems.PlotItem import *
|
||||||
|
from .graphicsItems.ROI import *
|
||||||
|
from .graphicsItems.InfiniteLine import *
|
||||||
|
from .graphicsItems.HistogramLUTItem import *
|
||||||
|
from .graphicsItems.GridItem import *
|
||||||
|
from .graphicsItems.GradientLegend import *
|
||||||
|
from .graphicsItems.GraphicsItem import *
|
||||||
|
from .graphicsItems.BarGraphItem import *
|
||||||
|
from .graphicsItems.ViewBox import *
|
||||||
|
from .graphicsItems.ArrowItem import *
|
||||||
|
from .graphicsItems.ImageItem import *
|
||||||
|
from .graphicsItems.AxisItem import *
|
||||||
|
from .graphicsItems.LabelItem import *
|
||||||
|
from .graphicsItems.CurvePoint import *
|
||||||
|
from .graphicsItems.GraphicsWidgetAnchor import *
|
||||||
|
from .graphicsItems.PlotCurveItem import *
|
||||||
|
from .graphicsItems.ButtonItem import *
|
||||||
|
from .graphicsItems.GradientEditorItem import *
|
||||||
|
from .graphicsItems.MultiPlotItem import *
|
||||||
|
from .graphicsItems.ErrorBarItem import *
|
||||||
|
from .graphicsItems.IsocurveItem import *
|
||||||
|
from .graphicsItems.LinearRegionItem import *
|
||||||
|
from .graphicsItems.FillBetweenItem import *
|
||||||
|
from .graphicsItems.LegendItem import *
|
||||||
|
from .graphicsItems.ScatterPlotItem import *
|
||||||
|
from .graphicsItems.ItemGroup import *
|
||||||
|
|
||||||
|
from .widgets.MultiPlotWidget import *
|
||||||
|
from .widgets.ScatterPlotWidget import *
|
||||||
|
from .widgets.ColorMapWidget import *
|
||||||
|
from .widgets.FileDialog import *
|
||||||
|
from .widgets.ValueLabel import *
|
||||||
|
from .widgets.HistogramLUTWidget import *
|
||||||
|
from .widgets.CheckTable import *
|
||||||
|
from .widgets.BusyCursor import *
|
||||||
|
from .widgets.PlotWidget import *
|
||||||
|
from .widgets.ComboBox import *
|
||||||
|
from .widgets.GradientWidget import *
|
||||||
|
from .widgets.DataFilterWidget import *
|
||||||
|
from .widgets.SpinBox import *
|
||||||
|
from .widgets.JoystickButton import *
|
||||||
|
from .widgets.GraphicsLayoutWidget import *
|
||||||
|
from .widgets.TreeWidget import *
|
||||||
|
from .widgets.PathButton import *
|
||||||
|
from .widgets.VerticalLabel import *
|
||||||
|
from .widgets.FeedbackButton import *
|
||||||
|
from .widgets.ColorButton import *
|
||||||
|
from .widgets.DataTreeWidget import *
|
||||||
|
from .widgets.GraphicsView import *
|
||||||
|
from .widgets.LayoutWidget import *
|
||||||
|
from .widgets.TableWidget import *
|
||||||
|
from .widgets.ProgressDialog import *
|
||||||
|
|
||||||
from .imageview import *
|
from .imageview import *
|
||||||
from .WidgetGroup import *
|
from .WidgetGroup import *
|
||||||
@ -194,6 +257,7 @@ from .SignalProxy import *
|
|||||||
from .colormap import *
|
from .colormap import *
|
||||||
from .ptime import time
|
from .ptime import time
|
||||||
|
|
||||||
|
|
||||||
##############################################################
|
##############################################################
|
||||||
## PyQt and PySide both are prone to crashing on exit.
|
## PyQt and PySide both are prone to crashing on exit.
|
||||||
## There are two general approaches to dealing with this:
|
## There are two general approaches to dealing with this:
|
||||||
|
@ -60,6 +60,6 @@ class CSVExporter(Exporter):
|
|||||||
fd.write('\n')
|
fd.write('\n')
|
||||||
fd.close()
|
fd.close()
|
||||||
|
|
||||||
|
CSVExporter.register()
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,6 +11,14 @@ class Exporter(object):
|
|||||||
Abstract class used for exporting graphics to file / printer / whatever.
|
Abstract class used for exporting graphics to file / printer / whatever.
|
||||||
"""
|
"""
|
||||||
allowCopy = False # subclasses set this to True if they can use the copy buffer
|
allowCopy = False # subclasses set this to True if they can use the copy buffer
|
||||||
|
Exporters = []
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register(cls):
|
||||||
|
"""
|
||||||
|
Used to register Exporter classes to appear in the export dialog.
|
||||||
|
"""
|
||||||
|
Exporter.Exporters.append(cls)
|
||||||
|
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
"""
|
"""
|
||||||
@ -20,9 +28,6 @@ class Exporter(object):
|
|||||||
object.__init__(self)
|
object.__init__(self)
|
||||||
self.item = item
|
self.item = item
|
||||||
|
|
||||||
#def item(self):
|
|
||||||
#return self.item
|
|
||||||
|
|
||||||
def parameters(self):
|
def parameters(self):
|
||||||
"""Return the parameters used to configure this exporter."""
|
"""Return the parameters used to configure this exporter."""
|
||||||
raise Exception("Abstract method must be overridden in subclass.")
|
raise Exception("Abstract method must be overridden in subclass.")
|
||||||
@ -131,45 +136,4 @@ class Exporter(object):
|
|||||||
return preItems + rootItem + postItems
|
return preItems + rootItem + postItems
|
||||||
|
|
||||||
def render(self, painter, targetRect, sourceRect, item=None):
|
def render(self, painter, targetRect, sourceRect, item=None):
|
||||||
|
|
||||||
#if item is None:
|
|
||||||
#item = self.item
|
|
||||||
#preItems = []
|
|
||||||
#postItems = []
|
|
||||||
#if isinstance(item, QtGui.QGraphicsScene):
|
|
||||||
#childs = [i for i in item.items() if i.parentItem() is None]
|
|
||||||
#rootItem = []
|
|
||||||
#else:
|
|
||||||
#childs = item.childItems()
|
|
||||||
#rootItem = [item]
|
|
||||||
#childs.sort(lambda a,b: cmp(a.zValue(), b.zValue()))
|
|
||||||
#while len(childs) > 0:
|
|
||||||
#ch = childs.pop(0)
|
|
||||||
#if int(ch.flags() & ch.ItemStacksBehindParent) > 0 or (ch.zValue() < 0 and int(ch.flags() & ch.ItemNegativeZStacksBehindParent) > 0):
|
|
||||||
#preItems.extend(tree)
|
|
||||||
#else:
|
|
||||||
#postItems.extend(tree)
|
|
||||||
|
|
||||||
#for ch in preItems:
|
|
||||||
#self.render(painter, sourceRect, targetRect, item=ch)
|
|
||||||
### paint root here
|
|
||||||
#for ch in postItems:
|
|
||||||
#self.render(painter, sourceRect, targetRect, item=ch)
|
|
||||||
|
|
||||||
|
|
||||||
self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect))
|
self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect))
|
||||||
|
|
||||||
#def writePs(self, fileName=None, item=None):
|
|
||||||
#if fileName is None:
|
|
||||||
#self.fileSaveDialog(self.writeSvg, filter="PostScript (*.ps)")
|
|
||||||
#return
|
|
||||||
#if item is None:
|
|
||||||
#item = self
|
|
||||||
#printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
|
|
||||||
#printer.setOutputFileName(fileName)
|
|
||||||
#painter = QtGui.QPainter(printer)
|
|
||||||
#self.render(painter)
|
|
||||||
#painter.end()
|
|
||||||
|
|
||||||
#def writeToPrinter(self):
|
|
||||||
#pass
|
|
||||||
|
@ -98,4 +98,5 @@ class ImageExporter(Exporter):
|
|||||||
else:
|
else:
|
||||||
self.png.save(fileName)
|
self.png.save(fileName)
|
||||||
|
|
||||||
|
ImageExporter.register()
|
||||||
|
|
@ -57,6 +57,7 @@ class MatplotlibExporter(Exporter):
|
|||||||
else:
|
else:
|
||||||
raise Exception("Matplotlib export currently only works with plot items")
|
raise Exception("Matplotlib export currently only works with plot items")
|
||||||
|
|
||||||
|
MatplotlibExporter.register()
|
||||||
|
|
||||||
|
|
||||||
class MatplotlibWindow(QtGui.QMainWindow):
|
class MatplotlibWindow(QtGui.QMainWindow):
|
||||||
@ -72,3 +73,5 @@ class MatplotlibWindow(QtGui.QMainWindow):
|
|||||||
|
|
||||||
def closeEvent(self, ev):
|
def closeEvent(self, ev):
|
||||||
MatplotlibExporter.windows.remove(self)
|
MatplotlibExporter.windows.remove(self)
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,3 +63,6 @@ class PrintExporter(Exporter):
|
|||||||
finally:
|
finally:
|
||||||
self.setExportMode(False)
|
self.setExportMode(False)
|
||||||
painter.end()
|
painter.end()
|
||||||
|
|
||||||
|
|
||||||
|
#PrintExporter.register()
|
||||||
|
@ -404,6 +404,10 @@ def correctCoordinates(node, item):
|
|||||||
if removeTransform:
|
if removeTransform:
|
||||||
grp.removeAttribute('transform')
|
grp.removeAttribute('transform')
|
||||||
|
|
||||||
|
|
||||||
|
SVGExporter.register()
|
||||||
|
|
||||||
|
|
||||||
def itemTransform(item, root):
|
def itemTransform(item, root):
|
||||||
## Return the transformation mapping item to root
|
## Return the transformation mapping item to root
|
||||||
## (actually to parent coordinate system of root)
|
## (actually to parent coordinate system of root)
|
||||||
|
@ -1,27 +1,24 @@
|
|||||||
Exporters = []
|
#Exporters = []
|
||||||
from pyqtgraph import importModules
|
#from pyqtgraph import importModules
|
||||||
#from .. import frozenSupport
|
#import os
|
||||||
import os
|
#d = os.path.split(__file__)[0]
|
||||||
d = os.path.split(__file__)[0]
|
#for mod in importModules('', globals(), locals(), excludes=['Exporter']).values():
|
||||||
#files = []
|
#if hasattr(mod, '__all__'):
|
||||||
#for f in frozenSupport.listdir(d):
|
#names = mod.__all__
|
||||||
#if frozenSupport.isdir(os.path.join(d, f)) and f != '__pycache__':
|
#else:
|
||||||
#files.append(f)
|
#names = [n for n in dir(mod) if n[0] != '_']
|
||||||
#elif f[-3:] == '.py' and f not in ['__init__.py', 'Exporter.py']:
|
#for k in names:
|
||||||
#files.append(f[:-3])
|
#if hasattr(mod, k):
|
||||||
|
#Exporters.append(getattr(mod, k))
|
||||||
|
|
||||||
#for modName in files:
|
from .Exporter import Exporter
|
||||||
#mod = __import__(modName, globals(), locals(), fromlist=['*'])
|
from .ImageExporter import *
|
||||||
for mod in importModules('', globals(), locals(), excludes=['Exporter']).values():
|
from .SVGExporter import *
|
||||||
if hasattr(mod, '__all__'):
|
from .Matplotlib import *
|
||||||
names = mod.__all__
|
from .CSVExporter import *
|
||||||
else:
|
from .PrintExporter import *
|
||||||
names = [n for n in dir(mod) if n[0] != '_']
|
|
||||||
for k in names:
|
|
||||||
if hasattr(mod, k):
|
|
||||||
Exporters.append(getattr(mod, k))
|
|
||||||
|
|
||||||
|
|
||||||
def listExporters():
|
def listExporters():
|
||||||
return Exporters[:]
|
return Exporter.Exporters[:]
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ else:
|
|||||||
|
|
||||||
from .Terminal import Terminal
|
from .Terminal import Terminal
|
||||||
from numpy import ndarray
|
from numpy import ndarray
|
||||||
from . import library
|
from .library import LIBRARY
|
||||||
from pyqtgraph.debug import printExc
|
from pyqtgraph.debug import printExc
|
||||||
import pyqtgraph.configfile as configfile
|
import pyqtgraph.configfile as configfile
|
||||||
import pyqtgraph.dockarea as dockarea
|
import pyqtgraph.dockarea as dockarea
|
||||||
@ -67,7 +67,8 @@ class Flowchart(Node):
|
|||||||
sigChartLoaded = QtCore.Signal()
|
sigChartLoaded = QtCore.Signal()
|
||||||
sigStateChanged = 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:
|
if name is None:
|
||||||
name = "Flowchart"
|
name = "Flowchart"
|
||||||
if terminals is None:
|
if terminals is None:
|
||||||
@ -105,6 +106,10 @@ class Flowchart(Node):
|
|||||||
for name, opts in terminals.items():
|
for name, opts in terminals.items():
|
||||||
self.addTerminal(name, **opts)
|
self.addTerminal(name, **opts)
|
||||||
|
|
||||||
|
def setLibrary(self, lib):
|
||||||
|
self.library = lib
|
||||||
|
self.widget().chartWidget.buildMenu()
|
||||||
|
|
||||||
def setInput(self, **args):
|
def setInput(self, **args):
|
||||||
"""Set the input values of the flowchart. This will automatically propagate
|
"""Set the input values of the flowchart. This will automatically propagate
|
||||||
the new values throughout the flowchart, (possibly) causing the output to change.
|
the new values throughout the flowchart, (possibly) causing the output to change.
|
||||||
@ -194,7 +199,7 @@ class Flowchart(Node):
|
|||||||
break
|
break
|
||||||
n += 1
|
n += 1
|
||||||
|
|
||||||
node = library.getNodeType(nodeType)(name)
|
node = self.library.getNodeType(nodeType)(name)
|
||||||
self.addNode(node, name, pos)
|
self.addNode(node, name, pos)
|
||||||
return node
|
return node
|
||||||
|
|
||||||
@ -846,13 +851,13 @@ class FlowchartWidget(dockarea.DockArea):
|
|||||||
self.nodeMenu.triggered.disconnect(self.nodeMenuTriggered)
|
self.nodeMenu.triggered.disconnect(self.nodeMenuTriggered)
|
||||||
self.nodeMenu = None
|
self.nodeMenu = None
|
||||||
self.subMenus = []
|
self.subMenus = []
|
||||||
library.loadLibrary(reloadLibs=True)
|
self.chart.library.reload()
|
||||||
self.buildMenu()
|
self.buildMenu()
|
||||||
|
|
||||||
def buildMenu(self, pos=None):
|
def buildMenu(self, pos=None):
|
||||||
self.nodeMenu = QtGui.QMenu()
|
self.nodeMenu = QtGui.QMenu()
|
||||||
self.subMenus = []
|
self.subMenus = []
|
||||||
for section, nodes in library.getNodeTree().items():
|
for section, nodes in self.chart.library.getNodeTree().items():
|
||||||
menu = QtGui.QMenu(section)
|
menu = QtGui.QMenu(section)
|
||||||
self.nodeMenu.addMenu(menu)
|
self.nodeMenu.addMenu(menu)
|
||||||
for name in nodes:
|
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,102 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from pyqtgraph.pgcollections import OrderedDict
|
from pyqtgraph.pgcollections import OrderedDict
|
||||||
from pyqtgraph import importModules
|
#from pyqtgraph import importModules
|
||||||
import os, types
|
import os, types
|
||||||
from pyqtgraph.debug import printExc
|
from pyqtgraph.debug import printExc
|
||||||
from ..Node import Node
|
#from ..Node import Node
|
||||||
|
from ..NodeLibrary import NodeLibrary, isNodeClass
|
||||||
import pyqtgraph.reload as reload
|
import pyqtgraph.reload as reload
|
||||||
|
|
||||||
|
|
||||||
NODE_LIST = OrderedDict() ## maps name:class for all registered Node subclasses
|
# Build default library
|
||||||
NODE_TREE = OrderedDict() ## categorized tree of Node subclasses
|
LIBRARY = NodeLibrary()
|
||||||
|
|
||||||
def getNodeType(name):
|
# For backward compatibility, expose the default library's properties here:
|
||||||
try:
|
NODE_LIST = LIBRARY.nodeList
|
||||||
return NODE_LIST[name]
|
NODE_TREE = LIBRARY.nodeTree
|
||||||
except KeyError:
|
registerNodeType = LIBRARY.addNodeType
|
||||||
raise Exception("No node type called '%s'" % name)
|
getNodeTree = LIBRARY.getNodeTree
|
||||||
|
getNodeType = LIBRARY.getNodeType
|
||||||
|
|
||||||
def getNodeTree():
|
# Add all nodes to the default library
|
||||||
return NODE_TREE
|
from . import Data, Display, Filters, Operators
|
||||||
|
for mod in [Data, Display, Filters, Operators]:
|
||||||
|
#mod = getattr(__import__('', fromlist=[modName], level=1), modName)
|
||||||
|
#mod = __import__(modName, level=1)
|
||||||
|
nodes = [getattr(mod, name) for name in dir(mod) if isNodeClass(getattr(mod, name))]
|
||||||
|
for node in nodes:
|
||||||
|
LIBRARY.addNodeType(node, [(mod.__name__.split('.')[-1],)])
|
||||||
|
|
||||||
def registerNodeType(cls, paths, override=False):
|
#NODE_LIST = OrderedDict() ## maps name:class for all registered Node subclasses
|
||||||
"""
|
#NODE_TREE = OrderedDict() ## categorized tree of Node subclasses
|
||||||
Register a new node type. If the type's name is already in use,
|
|
||||||
an exception will be raised (unless override=True).
|
|
||||||
|
|
||||||
Arguments:
|
#def getNodeType(name):
|
||||||
cls - a subclass of Node (must have typ.nodeName)
|
#try:
|
||||||
paths - list of tuples specifying the location(s) this
|
#return NODE_LIST[name]
|
||||||
type will appear in the library tree.
|
#except KeyError:
|
||||||
override - if True, overwrite any class having the same name
|
#raise Exception("No node type called '%s'" % name)
|
||||||
"""
|
|
||||||
if not isNodeClass(cls):
|
|
||||||
raise Exception("Object %s is not a Node subclass" % str(cls))
|
|
||||||
|
|
||||||
name = cls.nodeName
|
#def getNodeTree():
|
||||||
if not override and name in NODE_LIST:
|
#return NODE_TREE
|
||||||
raise Exception("Node type name '%s' is already registered." % name)
|
|
||||||
|
|
||||||
NODE_LIST[name] = cls
|
#def registerNodeType(cls, paths, override=False):
|
||||||
for path in paths:
|
#"""
|
||||||
root = NODE_TREE
|
#Register a new node type. If the type's name is already in use,
|
||||||
for n in path:
|
#an exception will be raised (unless override=True).
|
||||||
if n not in root:
|
|
||||||
root[n] = OrderedDict()
|
#Arguments:
|
||||||
root = root[n]
|
#cls - a subclass of Node (must have typ.nodeName)
|
||||||
root[name] = cls
|
#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))
|
||||||
|
|
||||||
|
#name = cls.nodeName
|
||||||
|
#if not override and name in NODE_LIST:
|
||||||
|
#raise Exception("Node type name '%s' is already registered." % name)
|
||||||
|
|
||||||
|
#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):
|
#def isNodeClass(cls):
|
||||||
try:
|
#try:
|
||||||
if not issubclass(cls, Node):
|
#if not issubclass(cls, Node):
|
||||||
return False
|
#return False
|
||||||
except:
|
#except:
|
||||||
return False
|
#return False
|
||||||
return hasattr(cls, 'nodeName')
|
#return hasattr(cls, 'nodeName')
|
||||||
|
|
||||||
def loadLibrary(reloadLibs=False, libPath=None):
|
#def loadLibrary(reloadLibs=False, libPath=None):
|
||||||
"""Import all Node subclasses found within files in the library module."""
|
#"""Import all Node subclasses found within files in the library module."""
|
||||||
|
|
||||||
global NODE_LIST, NODE_TREE
|
#global NODE_LIST, NODE_TREE
|
||||||
#if libPath is None:
|
|
||||||
#libPath = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
|
|
||||||
if reloadLibs:
|
#if reloadLibs:
|
||||||
reload.reloadAll(libPath)
|
#reload.reloadAll(libPath)
|
||||||
|
|
||||||
mods = importModules('', globals(), locals())
|
#mods = importModules('', globals(), locals())
|
||||||
#for f in frozenSupport.listdir(libPath):
|
|
||||||
#pathName, ext = os.path.splitext(f)
|
#for name, mod in mods.items():
|
||||||
#if ext not in ('.py', '.pyc') or '__init__' in pathName or '__pycache__' in pathName:
|
#nodes = []
|
||||||
#continue
|
#for n in dir(mod):
|
||||||
#try:
|
#o = getattr(mod, n)
|
||||||
##print "importing from", f
|
#if isNodeClass(o):
|
||||||
#mod = __import__(pathName, globals(), locals())
|
#registerNodeType(o, [(name,)], override=reloadLibs)
|
||||||
#except:
|
|
||||||
#printExc("Error loading flowchart library %s:" % pathName)
|
#def reloadLibrary():
|
||||||
#continue
|
#loadLibrary(reloadLibs=True)
|
||||||
|
|
||||||
|
#loadLibrary()
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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)
|
|
@ -1,28 +1,20 @@
|
|||||||
from .GLViewWidget import GLViewWidget
|
from .GLViewWidget import GLViewWidget
|
||||||
|
|
||||||
from pyqtgraph import importAll
|
## dynamic imports cause too many problems.
|
||||||
#import os
|
#from pyqtgraph import importAll
|
||||||
#def importAll(path):
|
#importAll('items', globals(), locals())
|
||||||
#d = os.path.join(os.path.split(__file__)[0], path)
|
|
||||||
#files = []
|
|
||||||
#for f in os.listdir(d):
|
|
||||||
#if os.path.isdir(os.path.join(d, f)) and f != '__pycache__':
|
|
||||||
#files.append(f)
|
|
||||||
#elif f[-3:] == '.py' and f != '__init__.py':
|
|
||||||
#files.append(f[:-3])
|
|
||||||
|
|
||||||
#for modName in files:
|
from .items.GLGridItem import *
|
||||||
#mod = __import__(path+"."+modName, globals(), locals(), fromlist=['*'])
|
from .items.GLBarGraphItem import *
|
||||||
#if hasattr(mod, '__all__'):
|
from .items.GLScatterPlotItem import *
|
||||||
#names = mod.__all__
|
from .items.GLMeshItem import *
|
||||||
#else:
|
from .items.GLLinePlotItem import *
|
||||||
#names = [n for n in dir(mod) if n[0] != '_']
|
from .items.GLAxisItem import *
|
||||||
#for k in names:
|
from .items.GLImageItem import *
|
||||||
#if hasattr(mod, k):
|
from .items.GLSurfacePlotItem import *
|
||||||
#globals()[k] = getattr(mod, k)
|
from .items.GLBoxItem import *
|
||||||
|
from .items.GLVolumeItem import *
|
||||||
|
|
||||||
importAll('items', globals(), locals())
|
|
||||||
\
|
|
||||||
from .MeshData import MeshData
|
from .MeshData import MeshData
|
||||||
## for backward compatibility:
|
## for backward compatibility:
|
||||||
#MeshData.MeshData = MeshData ## breaks autodoc.
|
#MeshData.MeshData = MeshData ## breaks autodoc.
|
||||||
|
Loading…
Reference in New Issue
Block a user