From 05cfd69d13f1e758dab2f1f2718610000b99a695 Mon Sep 17 00:00:00 2001 From: Victor Olaya Date: Sun, 8 Jun 2014 00:21:12 +0200 Subject: [PATCH] [processing] overhaul of graphical modeler --- .../processing/algs/grass/GrassUtils.py | 2 - .../processing/algs/grass7/Grass7Utils.py | 2 - python/plugins/processing/core/Processing.py | 51 +- .../processing/gui/HelpEditionDialog.py | 53 +- .../processing/gui/MultipleInputPanel.py | 2 +- .../processing/gui/ProcessingToolbox.py | 5 +- .../processing/gui/ScriptEditorDialog.py | 6 +- .../modeler/AddModelFromFileAction.py | 5 +- .../modeler/CalculatorModelerAlgorithm.py | 99 +- .../CalculatorModelerParametersDialog.py | 164 --- .../processing/modeler/ModelerAlgorithm.py | 963 +++++++----------- .../modeler/ModelerAlgorithmProvider.py | 18 +- .../processing/modeler/ModelerArrowItem.py | 59 +- .../processing/modeler/ModelerDialog.py | 204 ++-- .../processing/modeler/ModelerGraphicItem.py | 191 ++-- .../ModelerParameterDefinitionDialog.py | 20 +- .../modeler/ModelerParametersDialog.py | 705 ++++--------- .../processing/modeler/ModelerScene.py | 155 ++- .../processing/modeler/ModelerUtils.py | 6 +- .../processing/modeler/MultilineTextPanel.py | 18 +- .../plugins/processing/modeler/Providers.py | 31 - .../modeler/SaveAsPythonScriptAction.py | 126 --- .../models/Extract_raster_to_CSV.model | 21 - .../models/Extract_raster_to_shape.model | 21 - .../processing/modeler/models/contours.model | 43 - .../modeler/models/emptystring.model | 20 - .../modeler/models/fieldautoextent.model | 21 - .../modeler/models/fieldextent.model | 23 - .../modeler/models/modelscript.model | 26 - .../processing/modeler/models/noinputs.model | 27 - .../modeler/models/notinorder.model | 39 - .../modeler/models/numericaloutput.model | 32 - .../modeler/models/optionalfield.model | 12 - .../processing/modeler/models/sagagrass.model | 29 - .../modeler/models/simplemodel.model | 15 - .../modeler/models/watersheds.model | 79 -- .../processing/parameters/Parameter.py | 5 + 37 files changed, 1038 insertions(+), 2260 deletions(-) delete mode 100644 python/plugins/processing/modeler/CalculatorModelerParametersDialog.py delete mode 100644 python/plugins/processing/modeler/Providers.py delete mode 100644 python/plugins/processing/modeler/SaveAsPythonScriptAction.py delete mode 100644 python/plugins/processing/modeler/models/Extract_raster_to_CSV.model delete mode 100644 python/plugins/processing/modeler/models/Extract_raster_to_shape.model delete mode 100644 python/plugins/processing/modeler/models/contours.model delete mode 100644 python/plugins/processing/modeler/models/emptystring.model delete mode 100644 python/plugins/processing/modeler/models/fieldautoextent.model delete mode 100644 python/plugins/processing/modeler/models/fieldextent.model delete mode 100644 python/plugins/processing/modeler/models/modelscript.model delete mode 100644 python/plugins/processing/modeler/models/noinputs.model delete mode 100644 python/plugins/processing/modeler/models/notinorder.model delete mode 100644 python/plugins/processing/modeler/models/numericaloutput.model delete mode 100644 python/plugins/processing/modeler/models/optionalfield.model delete mode 100644 python/plugins/processing/modeler/models/sagagrass.model delete mode 100644 python/plugins/processing/modeler/models/simplemodel.model delete mode 100644 python/plugins/processing/modeler/models/watersheds.model diff --git a/python/plugins/processing/algs/grass/GrassUtils.py b/python/plugins/processing/algs/grass/GrassUtils.py index f2d0688913e..2a1b952c22c 100644 --- a/python/plugins/processing/algs/grass/GrassUtils.py +++ b/python/plugins/processing/algs/grass/GrassUtils.py @@ -28,7 +28,6 @@ __revision__ = '$Format:%H$' import stat import shutil import codecs -import traceback import subprocess from qgis.core import QgsApplication from PyQt4.QtCore import * @@ -385,7 +384,6 @@ class GrassUtils: configured in your system.\nPlease install it before \ running GRASS algorithms.' except: - s = traceback.format_exc() return 'Error while checking GRASS installation. GRASS might not \ be correctly configured.\n' + s diff --git a/python/plugins/processing/algs/grass7/Grass7Utils.py b/python/plugins/processing/algs/grass7/Grass7Utils.py index 68c31c7592e..c88ea11b05c 100644 --- a/python/plugins/processing/algs/grass7/Grass7Utils.py +++ b/python/plugins/processing/algs/grass7/Grass7Utils.py @@ -27,7 +27,6 @@ __revision__ = '$Format:%H$' import stat import shutil -import traceback import subprocess from qgis.core import QgsApplication from PyQt4.QtCore import * @@ -387,7 +386,6 @@ class Grass7Utils: configured in your system.\nPlease install it before \ running GRASS GIS 7 algorithms.' except: - s = traceback.format_exc() return 'Error while checking GRASS GIS 7 installation. GRASS GIS 7 might not \ be correctly configured.\n' + s diff --git a/python/plugins/processing/core/Processing.py b/python/plugins/processing/core/Processing.py index f93a5fbf656..d4268d04d3e 100644 --- a/python/plugins/processing/core/Processing.py +++ b/python/plugins/processing/core/Processing.py @@ -16,6 +16,7 @@ * * *************************************************************************** """ +from processing.modeler.ModelerUtils import ModelerUtils __author__ = 'Victor Olaya' __date__ = 'August 2012' @@ -41,7 +42,6 @@ from processing.gui.RenderingStyles import RenderingStyles from processing.gui.Postprocessing import handleAlgorithmResults from processing.gui.UnthreadedAlgorithmExecutor import \ UnthreadedAlgorithmExecutor -from processing.modeler.Providers import Providers from processing.modeler.ModelerAlgorithmProvider import \ ModelerAlgorithmProvider from processing.modeler.ModelerOnlyAlgorithmProvider import \ @@ -119,8 +119,7 @@ class Processing: @staticmethod def getProviderFromName(name): - """Returns the provider with the given name. - """ + """Returns the provider with the given name.""" for provider in Processing.providers: if provider.getName() == name: return provider @@ -139,8 +138,9 @@ class Processing: Processing.addProvider(SagaAlgorithmProvider()) Processing.addProvider(GrassAlgorithmProvider()) Processing.addProvider(Grass7AlgorithmProvider()) - Processing.addProvider(ScriptAlgorithmProvider()) + Processing.addProvider(ScriptAlgorithmProvider()) Processing.addProvider(TauDEMAlgorithmProvider()) + Processing.addProvider(ModelerAlgorithmProvider()) Processing.modeler.initializeSettings() # And initialize @@ -173,7 +173,8 @@ class Processing: @staticmethod def addAlgListListener(listener): - """Listener should implement a algsListHasChanged() method. + """ + Listener should implement a algsListHasChanged() method. Whenever the list of algorithms changes, that method will be called for all registered listeners. @@ -196,33 +197,11 @@ class Processing: algs[alg.commandLineName()] = alg Processing.algs[provider.getName()] = algs - # This is a special provider, since it depends on others. - # TODO: Fix circular imports, so this provider can be - # incorporated as a normal one. - provider = Processing.modeler - provider.setAlgsList(Processing.algs) - provider.loadAlgorithms() - providerAlgs = provider.algs - algs = {} - for alg in providerAlgs: - algs[alg.commandLineName()] = alg - Processing.algs[provider.getName()] = algs - - # And we do it again, in case there are models containing - # models. - # TODO: Improve this - provider.setAlgsList(Processing.algs) - provider.loadAlgorithms() - providerAlgs = provider.algs - algs = {} - for alg in providerAlgs: - algs[alg.commandLineName()] = alg - Processing.algs[provider.getName()] = algs provs = {} for provider in Processing.providers: provs[provider.getName()] = provider - provs[Processing.modeler.getName()] = Processing.modeler - Providers.providers = provs + ModelerUtils.allAlgs = Processing.algs + ModelerUtils.providers = provs @staticmethod def loadActions(): @@ -232,11 +211,7 @@ class Processing: for action in providerActions: actions.append(action) Processing.actions[provider.getName()] = actions - - provider = Processing.modeler - actions = list() - for action in provider.actions: - actions.append(action) + Processing.actions[provider.getName()] = actions @staticmethod @@ -247,11 +222,6 @@ class Processing: for action in providerActions: Processing.contextMenuActions.append(action) - provider = Processing.modeler - providerActions = provider.contextMenuActions - for action in providerActions: - Processing.contextMenuActions.append(action) - @staticmethod def getAlgorithm(name): for provider in Processing.algs.values(): @@ -269,8 +239,7 @@ class Processing: @staticmethod def getObject(uri): - """Returns the QGIS object identified by the given URI. - """ + """Returns the QGIS object identified by the given URI.""" return dataobjects.getObjectFromUri(uri) @staticmethod diff --git a/python/plugins/processing/gui/HelpEditionDialog.py b/python/plugins/processing/gui/HelpEditionDialog.py index 7fc80c1f408..5688ea32bd2 100644 --- a/python/plugins/processing/gui/HelpEditionDialog.py +++ b/python/plugins/processing/gui/HelpEditionDialog.py @@ -16,6 +16,7 @@ * * *************************************************************************** """ +from processing.modeler.ModelerAlgorithm import ModelerAlgorithm __author__ = 'Victor Olaya' @@ -44,21 +45,21 @@ class HelpEditionDialog(QDialog, Ui_DlgHelpEdition): def __init__(self, alg): QDialog.__init__(self) - self.setupUi(self) - self.alg = alg self.descriptions = {} - if self.alg.descriptionFile is not None: - helpfile = alg.descriptionFile + '.help' - if os.path.exists(helpfile): - try: - with open(helpfile) as f: - self.descriptions = json.load(f) - except Exception, e: - print e - ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, "Cannot open help file: " + helpfile) - + if isinstance(self.alg, ModelerAlgorithm): + self.descriptions = self.alg.helpContent + else: + if self.alg.descriptionFile is not None: + helpfile = alg.descriptionFile + '.help' + if os.path.exists(helpfile): + try: + with open(helpfile) as f: + self.descriptions = json.load(f) + except Exception, e: + ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, "Cannot open help file: " + helpfile) + self.currentName = self.ALG_DESC if self.ALG_DESC in self.descriptions: self.text.setText(self.descriptions[self.ALG_DESC]) @@ -73,17 +74,20 @@ class HelpEditionDialog(QDialog, Ui_DlgHelpEdition): def accept(self): self.descriptions[self.currentName] = unicode(self.text.toPlainText()) - if self.alg.descriptionFile is not None: - try: - with open(self.alg.descriptionFile + '.help', 'w') as f: - json.dump(self.descriptions, f) - except Exception, e: - QMessageBox.warning(self, 'Error saving help file', - 'Help file could not be saved.\n' - 'Check that you have permission to modify the help\n' - 'file. You might not have permission if you are \n' - 'editing an example model or script, since they \n' - 'are stored on the installation folder') + if isinstance(self.alg, ModelerAlgorithm): + self.alg.helpContent = self.descriptions + else: + if self.alg.descriptionFile is not None: + try: + with open(self.alg.descriptionFile + '.help', 'w') as f: + json.dump(self.descriptions, f) + except Exception, e: + QMessageBox.warning(self, 'Error saving help file', + 'Help file could not be saved.\n' + 'Check that you have permission to modify the help\n' + 'file. You might not have permission if you are \n' + 'editing an example model or script, since they \n' + 'are stored on the installation folder') QDialog.accept(self) @@ -126,8 +130,7 @@ class HelpEditionDialog(QDialog, Ui_DlgHelpEdition): item = self.tree.currentItem() if isinstance(item, TreeDescriptionItem): if self.currentName: - self.descriptions[self.currentName] = \ - unicode(self.text.toPlainText()) + self.descriptions[self.currentName] = unicode(self.text.toPlainText()) name = item.name if name: self.text.setEnabled(True) diff --git a/python/plugins/processing/gui/MultipleInputPanel.py b/python/plugins/processing/gui/MultipleInputPanel.py index e4b1e1feddb..aace2727fac 100644 --- a/python/plugins/processing/gui/MultipleInputPanel.py +++ b/python/plugins/processing/gui/MultipleInputPanel.py @@ -25,7 +25,7 @@ __copyright__ = '(C) 2012, Victor Olaya' __revision__ = '$Format:%H$' -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from processing.gui.MultipleInputDialog import MultipleInputDialog diff --git a/python/plugins/processing/gui/ProcessingToolbox.py b/python/plugins/processing/gui/ProcessingToolbox.py index d33c077adee..de722569f12 100644 --- a/python/plugins/processing/gui/ProcessingToolbox.py +++ b/python/plugins/processing/gui/ProcessingToolbox.py @@ -17,6 +17,7 @@ *************************************************************************** """ + __author__ = 'Victor Olaya' __date__ = 'August 2012' __copyright__ = '(C) 2012, Victor Olaya' @@ -28,6 +29,7 @@ __revision__ = '$Format:%H$' from PyQt4.QtCore import * from PyQt4.QtGui import * from qgis.utils import iface +from processing.modeler.ModelerUtils import ModelerUtils from processing.core.Processing import Processing from processing.core.ProcessingLog import ProcessingLog from processing.core.ProcessingConfig import ProcessingConfig @@ -37,7 +39,6 @@ from processing.gui.AlgorithmClassification import AlgorithmDecorator from processing.gui.ParametersDialog import ParametersDialog from processing.gui.BatchProcessingDialog import BatchProcessingDialog from processing.gui.EditRenderingStylesDialog import EditRenderingStylesDialog -from processing.modeler.Providers import Providers from processing.ui.ui_ProcessingToolbox import Ui_ProcessingToolbox @@ -259,7 +260,7 @@ class ProcessingToolbox(QDockWidget, Ui_ProcessingToolbox): if not ProcessingConfig.getSetting(name): continue if providerName in providersToExclude \ - or len(Providers.providers[providerName].actions) != 0: + or len(ModelerUtils.providers[providerName].actions) != 0: continue algs = provider.values() diff --git a/python/plugins/processing/gui/ScriptEditorDialog.py b/python/plugins/processing/gui/ScriptEditorDialog.py index fb0dfab3c90..6ec62f3759f 100644 --- a/python/plugins/processing/gui/ScriptEditorDialog.py +++ b/python/plugins/processing/gui/ScriptEditorDialog.py @@ -16,6 +16,7 @@ * * *************************************************************************** """ +from processing.modeler.ModelerUtils import ModelerUtils __author__ = 'Alexander Bruy' __date__ = 'December 2012' @@ -38,7 +39,6 @@ from qgis.utils import iface from processing.gui.ParametersDialog import ParametersDialog from processing.gui.HelpEditionDialog import HelpEditionDialog -from processing.modeler.Providers import Providers from processing.algs.r.RAlgorithm import RAlgorithm from processing.algs.r.RUtils import RUtils from processing.script.ScriptAlgorithm import ScriptAlgorithm @@ -180,10 +180,10 @@ class ScriptEditorDialog(QDialog, Ui_DlgScriptEditor): def runAlgorithm(self): if self.algType == self.SCRIPT_PYTHON: alg = ScriptAlgorithm(None, unicode(self.editor.text())) - alg.provider = Providers.providers['script'] + alg.provider = ModelerUtils.providers['script'] if self.algType == self.SCRIPT_R: alg = RAlgorithm(None, unicode(self.editor.text())) - alg.provider = Providers.providers['r'] + alg.provider = ModelerUtils.providers['r'] dlg = alg.getCustomParametersDialog() if not dlg: diff --git a/python/plugins/processing/modeler/AddModelFromFileAction.py b/python/plugins/processing/modeler/AddModelFromFileAction.py index aad8e990438..3bdddcdeb76 100644 --- a/python/plugins/processing/modeler/AddModelFromFileAction.py +++ b/python/plugins/processing/modeler/AddModelFromFileAction.py @@ -47,11 +47,12 @@ class AddModelFromFileAction(ToolboxAction): '*.model') if filename: try: - model = ModelerAlgorithm() - model.openModel(filename) + ModelerAlgorithm.fromJsonFile(filename) except WrongModelException: QtGui.QMessageBox.warning(self.toolbox, "Error reading model", "The selected file does not contain a valid model") return + except: + QtGui.QMessageBox.warning(self.toolbox, "Error reading model", "Cannot read file") destFilename = os.path.join(ModelerUtils.modelsFolder(), os.path.basename(filename)) shutil.copyfile(filename,destFilename) self.toolbox.updateProvider('script') \ No newline at end of file diff --git a/python/plugins/processing/modeler/CalculatorModelerAlgorithm.py b/python/plugins/processing/modeler/CalculatorModelerAlgorithm.py index 3f33156a8e0..45e531af591 100644 --- a/python/plugins/processing/modeler/CalculatorModelerAlgorithm.py +++ b/python/plugins/processing/modeler/CalculatorModelerAlgorithm.py @@ -25,45 +25,114 @@ __copyright__ = '(C) 2012, Victor Olaya' __revision__ = '$Format:%H$' +from PyQt4 import QtCore, QtGui from processing.core.GeoAlgorithm import GeoAlgorithm from processing.core.GeoAlgorithmExecutionException import \ GeoAlgorithmExecutionException -from processing.modeler.CalculatorModelerParametersDialog import \ - CalculatorModelerParametersDialog from processing.parameters.ParameterString import ParameterString from processing.parameters.ParameterNumber import ParameterNumber from processing.outputs.OutputNumber import OutputNumber +from processing.modeler.ModelerParametersDialog import ModelerParametersDialog +from processing.modeler.ModelerAlgorithm import Algorithm +FORMULA = 'FORMULA' +NUMBER = 'NUMBER' +RESULT = 'RESULT' +AVAILABLE_VARIABLES = 10 + class CalculatorModelerAlgorithm(GeoAlgorithm): - FORMULA = 'FORMULA' - NUMBER = 'NUMBER' - RESULT = 'RESULT' - AVAILABLE_VARIABLES = 10 - def defineCharacteristics(self): self.showInModeler = True self.showInToolbox = False self.name = 'Calculator' self.group = 'Modeler-only tools' - self.addParameter(ParameterString(self.FORMULA, 'Formula', '')) - for i in range(self.AVAILABLE_VARIABLES): - self.addParameter(ParameterNumber(CalculatorModelerAlgorithm.NUMBER + self.addParameter(ParameterString(FORMULA, 'Formula', '')) + for i in range(AVAILABLE_VARIABLES): + self.addParameter(ParameterNumber(NUMBER + str(i), 'dummy')) - self.addOutput(OutputNumber(self.RESULT, 'Result')) + self.addOutput(OutputNumber(RESULT, 'Result')) def processAlgorithm(self, progress): - formula = self.getParameterValue(self.FORMULA) - for i in range(self.AVAILABLE_VARIABLES): - name = CalculatorModelerAlgorithm.NUMBER + str(i) + formula = self.getParameterValue(FORMULA) + for i in range(AVAILABLE_VARIABLES): + name = NUMBER + str(i) num = self.getParameterValue(name) formula = formula.replace(chr(97 + i), str(num)) try: result = eval(formula) - self.setOutputValue(self.RESULT, result) + self.setOutputValue(RESULT, result) except: raise GeoAlgorithmExecutionException('Wrong formula: ' + formula) def getCustomModelerParametersDialog(self, modelAlg, algIndex=None): return CalculatorModelerParametersDialog(self, modelAlg, algIndex) + + +class CalculatorModelerParametersDialog(ModelerParametersDialog): + + def setupUi(self): + self.valueItems = {} + self.dependentItems = {} + self.resize(650, 450) + self.buttonBox = QtGui.QDialogButtonBox() + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel + | QtGui.QDialogButtonBox.Ok) + self.infoText = QtGui.QTextEdit() + numbers = self.getAvailableValuesOfType(ParameterNumber, OutputNumber) + text = ('You can refer to model values in you formula, using' + 'single-letter variables, as follows:\n') + ichar = 97 + for number in numbers: + text += chr(ichar) + '->' + self.resolveValueDescription(number) + '\n' + ichar += 1 + self.infoText.setText(text) + self.infoText.setEnabled(False) + self.formulaText = QtGui.QLineEdit() + if hasattr(self.formulaText, 'setPlaceholderText'): + self.formulaText.setPlaceholderText('[Enter your formula here]') + self.setWindowTitle("Calculator") + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setSpacing(2) + self.verticalLayout.setMargin(0) + self.verticalLayout.addWidget(self.infoText) + self.verticalLayout.addWidget(self.formulaText) + self.verticalLayout.addWidget(self.buttonBox) + self.setLayout(self.verticalLayout) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL('accepted()'), + self.okPressed) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL('rejected()'), + self.cancelPressed) + QtCore.QMetaObject.connectSlotsByName(self) + + + + def createAlgorithm(self): + alg = Algorithm('modelertools:calculator') + alg.setName(self.model) + alg.description = "Calculator" + + formula = self.formulaText.text() + alg.params[FORMULA] = formula + + for i in xrange(AVAILABLE_VARIABLES): + paramname = NUMBER + str(i) + alg.params[paramname] = None + + numbers = self.getAvailableValuesOfType(ParameterNumber, OutputNumber) + used = [] + for i in range(len(numbers)): + if str(chr(i + 97)) in formula: + used.append(numbers[i]) + + for i, variable in enumerate(used): + paramname = NUMBER + str(i) + alg.params[paramname] = variable + + #TODO check formula is correct + return alg + + + diff --git a/python/plugins/processing/modeler/CalculatorModelerParametersDialog.py b/python/plugins/processing/modeler/CalculatorModelerParametersDialog.py deleted file mode 100644 index 8b0aec47200..00000000000 --- a/python/plugins/processing/modeler/CalculatorModelerParametersDialog.py +++ /dev/null @@ -1,164 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - CalculatorModelerParametersDialog.py - --------------------- - Date : August 2012 - Copyright : (C) 2012 by Victor Olaya - Email : volayaf at gmail dot com -*************************************************************************** -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) any later version. * -* * -*************************************************************************** -""" - -__author__ = 'Victor Olaya' -__date__ = 'August 2012' -__copyright__ = '(C) 2012, Victor Olaya' - -# This will get replaced with a git SHA1 when you do a git archive - -__revision__ = '$Format:%H$' - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from PyQt4 import QtCore, QtGui -from processing.parameters.ParameterNumber import ParameterNumber -from processing.modeler.ModelerAlgorithm import AlgorithmAndParameter -from processing.outputs.OutputNumber import OutputNumber - - -class CalculatorModelerParametersDialog(QtGui.QDialog): - - def __init__(self, alg, model, algIndex): - QtGui.QDialog.__init__(self) - self.setModal(True) - self.alg = alg - self.model = model - self.algIndex = algIndex - self.setupUi() - self.params = None - - def setupUi(self): - self.valueItems = {} - self.dependentItems = {} - self.resize(650, 450) - self.buttonBox = QtGui.QDialogButtonBox() - self.buttonBox.setOrientation(QtCore.Qt.Horizontal) - self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel - | QtGui.QDialogButtonBox.Ok) - self.infoText = QtGui.QTextEdit() - numbers = self.getNumbers() - text = 'You can refer to model values in you formula, using \ - single-letter variables, as follows:' - ichar = 97 - for number in numbers: - text += chr(ichar) + '->' + number.name() + '\n' - ichar += 1 - self.infoText.setText(text) - self.infoText.setEnabled(False) - self.formulaText = QtGui.QLineEdit() - if hasattr(self.formulaText, 'setPlaceholderText'): - self.formulaText.setPlaceholderText('[Enter your formula here]') - self.setWindowTitle(self.alg.name) - self.verticalLayout = QtGui.QVBoxLayout() - self.verticalLayout.setSpacing(2) - self.verticalLayout.setMargin(0) - self.verticalLayout.addWidget(self.infoText) - self.verticalLayout.addWidget(self.formulaText) - self.verticalLayout.addWidget(self.buttonBox) - self.setLayout(self.verticalLayout) - QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL('accepted()'), - self.okPressed) - QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL('rejected()'), - self.cancelPressed) - QtCore.QMetaObject.connectSlotsByName(self) - - def getNumbers(self): - numbers = [] - params = self.model.parameters - for param in params: - if isinstance(param, ParameterNumber): - numbers.append(AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, - param.name, '', param.description)) - - if self.algIndex is None: - dependent = [] - else: - dependent = self.model.getDependentAlgorithms(self.algIndex) - dependent.append(self.algIndex) - - i = 0 - for alg in self.model.algs: - if i not in dependent: - for out in alg.outputs: - if isinstance(out, OutputNumber): - numbers.append(AlgorithmAndParameter(i, out.name, - alg.name, out.description)) - i += 1 - return numbers - - def setParamValues(self): - self.params = {} - self.values = {} - self.outputs = {} - - name = self.getSafeNameForHarcodedParameter( - self.alg.getParameterFromName(self.alg.FORMULA)) - value = AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, name) - self.params[self.alg.FORMULA] = value - formula = str(self.formulaText.text()) - self.values[name] = formula - - numbers = self.getNumbers() - used = [] - for i in range(len(numbers)): - if str(chr(i + 97)) in formula: - used.append(numbers[i]) - i = 0 - for variable in used: - paramname = self.alg.NUMBER + str(i) - self.params[paramname] = variable - i += 1 - - # We create a dummy harcoded value for all unused variable slots - paramname = self.alg.NUMBER + str(i) - name = self.getSafeNameForHarcodedParameter( - self.alg.getParameterFromName(paramname)) - value = AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, name) - self.values[name] = 0 - for i in range(len(used), self.alg.AVAILABLE_VARIABLES): - paramname = self.alg.NUMBER + str(i) - self.params[paramname] = value - - self.outputs[self.alg.RESULT] = None - self.dependencies = [] - return True - - def getSafeNameForHarcodedParameter(self, param): - if self.algIndex is not None: - return 'HARDCODEDPARAMVALUE_' + param.name + '_' \ - + str(self.algIndex) - else: - return 'HARDCODEDPARAMVALUE_' + param.name + '_' \ - + str(len(self.model.algs)) - - def okPressed(self): - if self.setParamValues(): - self.close() - else: - QMessageBox.critical(self, 'Unable to add algorithm', - 'Wrong or missing parameter values') - self.params = None - - def cancelPressed(self): - self.params = None - self.close() diff --git a/python/plugins/processing/modeler/ModelerAlgorithm.py b/python/plugins/processing/modeler/ModelerAlgorithm.py index f51c6f5d506..28650223b1c 100644 --- a/python/plugins/processing/modeler/ModelerAlgorithm.py +++ b/python/plugins/processing/modeler/ModelerAlgorithm.py @@ -26,511 +26,353 @@ __copyright__ = '(C) 2012, Victor Olaya' __revision__ = '$Format:%H$' -import traceback -import copy import os.path -import codecs +import sys +import copy import time +import json from PyQt4 import QtCore, QtGui from qgis.core import * - from processing.core.GeoAlgorithm import GeoAlgorithm +from processing.modeler.WrongModelException import WrongModelException from processing.core.GeoAlgorithmExecutionException import \ GeoAlgorithmExecutionException from processing.gui.Help2Html import getHtmlFromHelpFile -from processing.modeler.WrongModelException import WrongModelException from processing.modeler.ModelerUtils import ModelerUtils -from processing.parameters.ParameterFactory import ParameterFactory from processing.parameters.ParameterRaster import ParameterRaster from processing.parameters.ParameterDataObject import ParameterDataObject from processing.parameters.ParameterExtent import ParameterExtent from processing.parameters.ParameterMultipleInput import ParameterMultipleInput -from processing.parameters.Parameter import Parameter from processing.parameters.ParameterVector import ParameterVector -from processing.parameters.ParameterTableField import ParameterTableField -from processing.outputs.OutputRaster import OutputRaster -from processing.outputs.OutputHTML import OutputHTML -from processing.outputs.OutputTable import OutputTable -from processing.outputs.OutputVector import OutputVector -from processing.outputs.OutputNumber import OutputNumber -from processing.outputs.OutputString import OutputString from processing.tools import dataobjects +class Input(): + + def __init__(self, param=None, pos=None): + self.param = param + self.pos = pos + + def todict(self): + return self.__dict__ + + @staticmethod + def fromdict(d): + return Input(d["param"], d["pos"]) + + +class Output(): + + def __init__(self, description=""): + self.description = description + self.pos = None + + def todict(self): + return self.__dict__ + + + + +class Algorithm(): + + def __init__(self, consoleName=""): + + self.name = None + self.description = "" + + #The type of the algorithm, indicated as a string, which corresponds + #to the string used to refer to it in the python console + self.consoleName = consoleName + + self._algInstance = None + + #A dict of Input object. keys are param names + self.params = {} + + #A dict of Output with final output descriptions. Keys are output names. + #Outputs not final are not stored in this dict + self.outputs = {} + + self.pos = None + + self.dependencies = [] + + self.paramsFolded = True + self.outputsFolded = True + self.active = True + + + def todict(self): + return {k:v for k,v in self.__dict__.iteritems() if not k.startswith("_")} + + @property + def algorithm(self): + if self._algInstance is None: + self._algInstance = ModelerUtils.getAlgorithm(self.consoleName).getCopy(); + return self._algInstance + + def setName(self, model): + if self.name is None: + i = 1 + name = self.consoleName + "_" + str(i) + while name in model.algs: + i += 1 + name = self.consoleName + "_" + str(i) + self.name = name + +class ValueFromInput(): + + def __init__(self, name=""): + self.name = name + + def todict(self): + return self.__dict__ + + def __str__(self): + return self.name + + def __eq__(self, other): + try: + return self.name == other.name + except: + return False + +class ValueFromOutput(): + + def __init__(self, alg="", output=""): + self.alg = alg + self.output = output + + def todict(self): + return self.__dict__ + + def __eq__(self, other): + try: + return self.alg == other.alg and self.output == other.output + except: + return False + + def __str__(self): + return self.alg + "," + self.output + class ModelerAlgorithm(GeoAlgorithm): CANVAS_SIZE = 4000 - LINE_BREAK_STRING = '%%%' def getCopy(self): newone = ModelerAlgorithm() - newone.openModel(self.descriptionFile) newone.provider = self.provider - newone.deactivated = self.deactivated + newone.algs = copy.deepcopy(self.algs) + newone.inputs = copy.deepcopy(self.inputs) + newone.defineCharacteristics() + newone.name = self.name + newone.group = self.group + newone.descriptionFile = self.descriptionFile return newone def __init__(self): - GeoAlgorithm.__init__(self) - + self.name = "Model" # The dialog where this model is being edited self.modelerdialog = None self.descriptionFile = None - - # Geoalgorithms in this model - self.algs = [] - - # Parameters of Geoalgorithms in self.algs. Each entry is a - # map with (paramname, paramvalue) values for algs[i]. - # paramvalues are instances of AlgorithmAndParameter. - self.algParameters = [] - - # Algorithms that each algorithm depends on. This is just a - # list of dependencies not set by outputs and inputs but - # explicitly entered instead, meaning that an algorithm must - # 'wait' for another to finish. Each entry is a list with - # algorithm indexes - self.dependencies = [] - - # Outputs of Geoalgorithms in self.algs. Each entry is a map - # with (output, outputvalue) values for algs[i]. outputvalue - # is the name of the output if final. None if is an - # intermediate output - self.algOutputs = [] - - # Hardcoded parameter values entered by the user when defining - # the model. Keys are value names. - self.paramValues = {} - - # Position of items in canvas - self.algPos = [] - self.paramPos = [] - self.outputPos = [] # same structure as algOutputs - - # Deactivated algorithms that should not be executed - self.deactivated = [] + self.helpContent = {} + + # Geoalgorithms in this model. A dict of Algorithm objects, with names as keys + self.algs = {} + + #Input parameters. A dict of Input objects, with names as keys + self.inputs = {} + GeoAlgorithm.__init__(self) def getIcon(self): return QtGui.QIcon(os.path.dirname(__file__) + '/../images/model.png') - def openModel(self, filename): - self.algPos = [] - self.paramPos = [] - self.outputOutputs = [] - self.algs = [] - self.algParameters = [] - self.algOutputs = [] - self.paramValues = {} - self.dependencies = [] - self.descriptionFile = filename - lines = codecs.open(filename, 'r', encoding='utf-8') - line = lines.readline().strip('\n').strip('\r') - iAlg = 0 - try: - while line != '': - if line.startswith('PARAMETER:'): - paramLine = line[len('PARAMETER:'):] - param = ParameterFactory.getFromString(paramLine) - if param: - self.parameters.append(param) - else: - raise WrongModelException('Error in parameter line: ' - + line) - line = lines.readline().strip('\n') - tokens = line.split(',') - self.paramPos.append(QtCore.QPointF(float(tokens[0]), - float(tokens[1]))) - elif line.startswith('VALUE:'): - valueLine = line[len('VALUE:'):] - tokens = valueLine.split('===') - self.paramValues[tokens[0]] = \ - tokens[1].replace(ModelerAlgorithm.LINE_BREAK_STRING, - '\n') - elif line.startswith('NAME:'): - self.name = line[len('NAME:'):] - elif line.startswith('GROUP:'): - self.group = line[len('GROUP:'):] - if self.group == '[Test models]': - self.showInModeler = False - self.showInToolbox = False - elif line.startswith('ALGORITHM:'): - algParams = {} - algOutputs = {} - algLine = line[len('ALGORITHM:'):] - alg = ModelerUtils.getAlgorithm(algLine) - if alg is not None: - posline = lines.readline().strip('\n').strip('\r') - tokens = posline.split(',') - self.algPos.append(QtCore.QPointF(float(tokens[0]), - float(tokens[1]))) - self.algs.append(alg) - dependenceline = lines.readline().strip('\n' - ).strip('\r') - dependencies = [] - if dependenceline != str(None): - for index in dependenceline.split(','): - try: - dependencies.append(int(index)) - except: - # A quick fix while I figure out - # how to solve problems when - # parsing this - pass - for param in alg.parameters: - if not param.hidden: - line = lines.readline().strip('\n').strip('\r') - if line == str(None): - algParams[param.name] = None - else: - tokens = line.split('|') - algParams[param.name] = \ - AlgorithmAndParameter(int(tokens[0]), - tokens[1]) - outputPos = {} - for out in alg.outputs: - if not out.hidden: - line = lines.readline().strip('\n').strip('\r') - if str(None) != line: - if '|' in line: - tokens = line.split('|') - name = tokens[0] - tokens = tokens[1].split(',') - outputPos[out.name] = QtCore.QPointF( - float(tokens[0]), float(tokens[1])) - else: - name = line - outputPos[out.name] = None - algOutputs[out.name] = name - - # We add the output to the algorithm, - # with a name indicating where it comes - # from that guarantees that the name is - # unique - output = copy.deepcopy(out) - output.description = name - output.name = self.getSafeNameForOutput(iAlg, - output) - self.addOutput(output) - else: - algOutputs[out.name] = None - self.outputPos.append(outputPos) - self.algOutputs.append(algOutputs) - self.algParameters.append(algParams) - self.dependencies.append(dependencies) - iAlg += 1 - else: - raise WrongModelException('Error in algorithm name: ' - + algLine) - line = lines.readline().strip('\n').strip('\r') - except Exception, e: - if isinstance(e, WrongModelException): - raise e - else: - raise WrongModelException('Error in model definition line:' - + line.strip() + ' : ' + traceback.format_exc()) - + def defineCharacteristics(self): + self.parameters = [inp.param for inp in self.inputs.values()] + self.outputs = [] + for alg in self.algs.values(): + if alg.active: + for out in alg.outputs: + modelOutput = copy.deepcopy(alg.algorithm.getOutputFromName(out)) + modelOutput.name = self.getSafeNameForOutput(alg.name, out) + modelOutput.description = alg.outputs[out].description + self.outputs.append(modelOutput) + def addParameter(self, param): - self.parameters.append(param) - self.paramPos.append(self.getPositionForParameterItem()) + self.inputs[param.param.name] = param - def updateParameter(self, paramIndex, param): - self.parameters[paramIndex] = param + def updateParameter(self, param): + self.inputs[param.name].param = param - def addAlgorithm(self, alg, parametersMap, valuesMap, outputsMap, - dependencies): - self.algs.append(alg) - self.algParameters.append(parametersMap) - self.algOutputs.append(outputsMap) - self.dependencies.append(dependencies) - for value in valuesMap.keys(): - self.paramValues[value] = valuesMap[value] - algPos = self.getPositionForAlgorithmItem() - self.algPos.append(algPos) - pos = {} - i = 0 - from processing.modeler.ModelerGraphicItem import ModelerGraphicItem - for out in outputsMap: - pos[out] = algPos + QtCore.QPointF(ModelerGraphicItem.BOX_WIDTH, i - * ModelerGraphicItem.BOX_HEIGHT) + def addAlgorithm(self, alg): + name = self.getNameForAlgorithm(alg) + alg.name = name + self.algs[name] = alg + + def getNameForAlgorithm(self, alg): + i = 1 + while alg.consoleName.upper().replace(":", "") + "_" + str(i) in self.algs.keys(): i += 1 - self.outputPos.append(pos) - - def updateAlgorithm(self, algIndex, parametersMap, valuesMap, outputsMap, - dependencies): - self.algParameters[algIndex] = parametersMap - self.algOutputs[algIndex] = outputsMap - self.dependencies[algIndex] = dependencies - for value in valuesMap.keys(): - self.paramValues[value] = valuesMap[value] - self.updateModelerView() - algPos = self.algPos[algIndex] - pos = {} - i = 0 + return alg.consoleName.upper().replace(":", "") + "_" + str(i) + + def updateAlgorithm(self, alg): + alg.pos = self.algs[alg.name].pos + self.algs[alg.name] = alg + from processing.modeler.ModelerGraphicItem import ModelerGraphicItem - for out in outputsMap: - pos[out] = algPos + QtCore.QPointF(ModelerGraphicItem.BOX_WIDTH, i - * ModelerGraphicItem.BOX_HEIGHT) - i += 1 - self.outputPos[algIndex] = pos + for i, out in enumerate(alg.outputs): + alg.outputs[out].pos = (alg.outputs[out].pos or + alg.pos + QtCore.QPointF(ModelerGraphicItem.BOX_WIDTH, + (i + 1.5) * ModelerGraphicItem.BOX_HEIGHT)) - def removeAlgorithm(self, index): + def removeAlgorithm(self, name): """Returns True if the algorithm could be removed, False if others depend on it and could not be removed. """ - if self.hasDependencies(self.algs[index], index): + if self.hasDependencies(name): return False - for out in self.algs[index].outputs: - val = self.algOutputs[index][out.name] - if val: - name = self.getSafeNameForOutput(index, out) - self.removeOutputFromName(name) - del self.algs[index] - del self.algParameters[index] - del self.algOutputs[index] - del self.algPos[index] - del self.outputPos[index] - - i = -1 - for paramValues in self.algParameters: - i += 1 - newValues = {} - for (name, value) in paramValues.iteritems(): - if value: - if value.alg > index: - newValues[name] = AlgorithmAndParameter(value.alg - 1, - value.param, value.algName, value.paramName) - else: - newValues[name] = value - else: - newValues[name] = value - self.algParameters[i] = newValues - self.updateModelerView() + del self.algs[name] + self.modelerdialog.hasChanged = True return True - def removeParameter(self, index): + def removeParameter(self, name): """Returns True if the parameter could be removed, False if others depend on it and could not be removed. """ - if self.hasDependencies(self.parameters[index], index): + if self.hasDependencies(name): return False - del self.parameters[index] - del self.paramPos[index] - self.updateModelerView() + del self.inputs[name] + self.modelerdialog.hasChanged = True return True - def hasDependencies(self, element, elementIndex): + def hasDependencies(self, name): """This method returns True if some other element depends on the passed one. - """ - if isinstance(element, Parameter): - for alg in self.algParameters: - for aap in alg.values(): - if aap: - if aap.alg == \ - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM: - if aap.param == element.name: + """ + for alg in self.algs.values(): + for value in alg.params.values(): + if value is None: + continue + if isinstance(value, list): + for v in value: + if isinstance(v, ValueFromInput): + if v.name == name: return True - elif aap.param in self.paramValues: - # Check for multiple inputs - aap2 = self.paramValues[aap.param] - if element.name in aap2: - return True - if isinstance(element, ParameterVector): - for param in self.parameters: - if isinstance(param, ParameterTableField): - if param.parent == element.name: - return True - else: - for alg in self.algParameters: - for aap in alg.values(): - if aap: - if aap.alg == elementIndex: - return True - + elif isinstance(v, ValueFromOutput): + if v.alg == name: + return True + if isinstance(value, ValueFromInput): + if value.name == name: + return True + elif isinstance(value, ValueFromOutput): + if value.alg == name: + return True return False - def deactivateAlgorithm(self, algIndex, update=False): - if algIndex not in self.deactivated: - dependent = self.getDependentAlgorithms(algIndex) - self.deactivated.extend(dependent) - if update: - self.updateModelerView() - def activateAlgorithm(self, algIndex, update=False): - if algIndex in self.deactivated: - dependsOn = self.getDependsOnAlgorithms(algIndex) - for alg in dependsOn: - if alg in self.deactivated and alg != algIndex: - return False - self.deactivated.remove(algIndex) - dependent = self.getDependentAlgorithms(algIndex) - for alg in dependent: - if alg in self.deactivated: - self.deactivated.remove(alg) - if update: - self.updateModelerView() - return True - - def getDependsOnAlgorithms(self, algIndex): - """This method returns a list with the indexes of algorithms + def getDependsOnAlgorithms(self, name): + """This method returns a list with names of algorithms a given one depends on. """ - algs = [] - algs.extend(self.dependencies[algIndex]) - index = -1 - for aap in self.algParameters[algIndex].values(): - index += 1 - if aap is not None: - if aap.alg != AlgorithmAndParameter.PARENT_MODEL_ALGORITHM \ - and aap.alg not in algs: - algs.append(aap.alg) - dep = self.getDependsOnAlgorithms(aap.alg) - for alg in dep: - if alg not in algs: - algs.append(alg) + alg = self.algs[name] + algs = set() + algs.update(set(alg.dependencies)) + for value in alg.params.values(): + if value is None: + continue + if isinstance(value, list): + for v in value: + if isinstance(v, ValueFromOutput): + algs.add(v.alg) + algs.update(self.getDependsOnAlgorithms(v.alg)) + elif isinstance(value, ValueFromOutput): + algs.add(value.alg) + algs.update(self.getDependsOnAlgorithms(value.alg)) + + return algs - def getDependentAlgorithms(self, algIndex): - """This method returns a list with the indexes of algorithms - depending on a given one. - """ - dependent = [algIndex] - index = -1 - for alg in self.algParameters: - index += 1 - if index in dependent: - continue - for aap in alg.values(): - if aap is not None: - if aap.alg == algIndex: - dep = self.getDependentAlgorithms(index) - for alg in dep: - if alg not in dependent: - dependent.append(alg) - break - index = -1 - for dep in self.dependencies: - index += 1 - if algIndex in dep: - dep = self.getDependentAlgorithms(index) - for alg in dep: - if alg not in dependent: - dependent.append(alg) + def getDependentAlgorithms(self, name): + """This method returns a list with the names of algorithms + depending on a given one. It includes the algorithm itself + """ + algs = set() + algs.add(name) + for alg in self.algs.values(): + for value in alg.params.values(): + if value is None: + continue + if isinstance(value, list): + for v in value: + if isinstance(v, ValueFromOutput) and v.alg == name: + algs.update(self.getDependentAlgorithms(alg.name)) + elif isinstance(value, ValueFromOutput) and value.alg == name: + algs.update(self.getDependentAlgorithms(alg.name)) + + return algs - return dependent + def setPositions(self, paramPos, algPos, outputsPos): + for param, pos in paramPos.iteritems(): + self.inputs[param].pos = pos + for alg, pos in algPos.iteritems(): + self.algs[alg].pos = pos + for alg, positions in outputsPos.iteritems(): + for output, pos in positions.iteritems(): + self.algs[alg].outputs[output].pos = pos - def getPositionForAlgorithmItem(self): - MARGIN = 20 - BOX_WIDTH = 200 - BOX_HEIGHT = 80 - if len(self.algPos) != 0: - maxX = max([pos.x() for pos in self.algPos]) - maxY = max([pos.y() for pos in self.algPos]) - newX = min(MARGIN + BOX_WIDTH + maxX, self.CANVAS_SIZE - BOX_WIDTH) - newY = min(MARGIN + BOX_HEIGHT + maxY, self.CANVAS_SIZE - - BOX_HEIGHT) - else: - newX = MARGIN + BOX_WIDTH / 2 - newY = MARGIN * 2 + BOX_HEIGHT + BOX_HEIGHT / 2 - return QtCore.QPointF(newX, newY) - - def getPositionForParameterItem(self): - MARGIN = 20 - BOX_WIDTH = 200 - BOX_HEIGHT = 80 - if len(self.paramPos) != 0: - maxX = max([pos.x() for pos in self.paramPos]) - newX = min(MARGIN + BOX_WIDTH + maxX, self.CANVAS_SIZE - BOX_WIDTH) - else: - newX = MARGIN + BOX_WIDTH / 2 - return QtCore.QPointF(newX, MARGIN + BOX_HEIGHT / 2) - - def serialize(self): - s = 'NAME:' + unicode(self.name) + '\n' - s += 'GROUP:' + unicode(self.group) + '\n' - - i = 0 - for param in self.parameters: - s += 'PARAMETER:' + param.serialize() + '\n' - pt = self.paramPos[i] - s += str(pt.x()) + ',' + str(pt.y()) + '\n' - i += 1 - for key in self.paramValues.keys(): - s += 'VALUE:' + key + '===' \ - + str(self.paramValues[key]).replace('\n', - ModelerAlgorithm.LINE_BREAK_STRING) + '\n' - for i in range(len(self.algs)): - alg = self.algs[i] - s += 'ALGORITHM:' + alg.commandLineName() + '\n' - pt = self.algPos[i] - s += str(pt.x()) + ',' + str(pt.y()) + '\n' - if len(self.dependencies[i]) != 0: - s += ','.join([str(index) for index in self.dependencies[i]]) + '\n' - else: - s += str(None) + '\n' - for param in alg.parameters: - if not param.hidden: - value = self.algParameters[i][param.name] - if value: - s += value.serialize() + '\n' - else: - s += str(None) + '\n' - for out in alg.outputs: - if not out.hidden: - value = self.algOutputs[i][out.name] - s += unicode(value) - if value is not None: - pt = self.outputPos[i][out.name] - s += '|' + str(pt.x()) + ',' + str(pt.y()) - s += '\n' - - return s - - def setPositions(self, paramPos, algPos, outputPos): - self.paramPos = paramPos - self.algPos = algPos - self.outputPos = outputPos - - def prepareAlgorithm(self, alg, iAlg): - for param in alg.parameters: + def prepareAlgorithm(self, alg): + algInstance = alg.algorithm + for param in algInstance.parameters: if not param.hidden: - aap = self.algParameters[iAlg][param.name] - if aap is None: - if isinstance(param, ParameterExtent): - value = self.getMinCoveringExtent() - if not param.setValue(value): - raise GeoAlgorithmExecutionException('Wrong value: ' - + str(value)) - else: - param.setValue(None) - continue - if isinstance(param, ParameterMultipleInput): - value = self.getValueFromAlgorithmAndParameter(aap) - tokens = value.split(';') - layerslist = [] - for token in tokens: - (i, paramname) = token.split('|') - aap = AlgorithmAndParameter(int(i), paramname) - value = self.getValueFromAlgorithmAndParameter(aap) - layerslist.append(str(value)) - value = ';'.join(layerslist) - else: - value = self.getValueFromAlgorithmAndParameter(aap) - + value = self.resolveValue(alg.params[param.name]) + if value is None and isinstance(param, ParameterExtent): + value = self.getMinCoveringExtent() # We allow unexistent filepaths, since that allows # algorithms to skip some conversion routines if not param.setValue(value) and not isinstance(param, ParameterDataObject): raise GeoAlgorithmExecutionException('Wrong value: ' + str(value)) - for out in alg.outputs: - if not out.hidden: - val = self.algOutputs[iAlg][out.name] - if val: - name = self.getSafeNameForOutput(iAlg, out) - out.value = self.getOutputFromName(name).value + for out in algInstance.outputs: + if not out.hidden: + if out.name in alg.outputs: + name = self.getSafeNameForOutput(alg.name, out.name) + modelOut = self.getOutputFromName(name) + if modelOut: + out.value = modelOut.value else: out.value = None - + + return algInstance + + def deactivateAlgorithm(self, algName): + dependent = self.getDependentAlgorithms(algName) + for alg in dependent: + self.algs[alg].active = False + + def activateAlgorithm(self, algName): + parents = self.getDependsOnAlgorithms(algName) + for alg in parents: + if not self.algs[alg].active: + return False + self.algs[algName].active = True + return True + + def getSafeNameForOutput(self, algName, outName): + return outName + '_ALG' + algName + + def resolveValue(self, value): + if value is None: + return None + if isinstance(value, list): + return ";".join([self.resolveValue(v) for v in value]) + if isinstance(value, ValueFromInput): + return self.getParameterFromName(value.name).value + elif isinstance(value, ValueFromOutput): + return self.algs[value.alg].algorithm.getOutputFromName(value.output).value + else: + return value + def getMinCoveringExtent(self): first = True found = False @@ -538,8 +380,7 @@ class ModelerAlgorithm(GeoAlgorithm): if param.value: if isinstance(param, (ParameterRaster, ParameterVector)): found = True - if isinstance(param.value, (QgsRasterLayer, - QgsVectorLayer)): + if isinstance(param.value, (QgsRasterLayer, QgsVectorLayer)): layer = param.value else: layer = dataobjects.getObjectFromUri(param.value) @@ -553,8 +394,7 @@ class ModelerAlgorithm(GeoAlgorithm): self.addToRegion(layer, first) first = False if found: - return str(self.xmin) + ',' + str(self.xmax) + ',' \ - + str(self.ymin) + ',' + str(self.ymax) + return ','.join([str(v) for v in [self.xmin, self.xmax, self.ymin, self.ymax]]) else: return None @@ -570,159 +410,41 @@ class ModelerAlgorithm(GeoAlgorithm): self.ymin = min(self.ymin, layer.extent().yMinimum()) self.ymax = max(self.ymax, layer.extent().yMaximum()) - def getSafeNameForOutput(self, ialg, out): - return out.name + '_ALG' + str(ialg) - def getValueFromAlgorithmAndParameter(self, aap): - if aap is None: - return None - if float(aap.alg) \ - == float(AlgorithmAndParameter.PARENT_MODEL_ALGORITHM): - if aap.param in self.paramValues.keys(): - return self.paramValues[aap.param] - for param in self.parameters: - if aap.param == param.name: - return param.value - else: - return self.producedOutputs[int(aap.alg)][aap.param] - - def processAlgorithm(self, progress): - self.producedOutputs = {} + def processAlgorithm(self, progress): executed = [] - while len(executed) < len(self.algs) - len(self.deactivated): - iAlg = 0 - for alg in self.algs: - if iAlg not in self.deactivated and iAlg not in executed: + toExecute = [alg for alg in self.algs.values() if alg.active] + while len(executed) < len(toExecute): + for alg in toExecute: + if alg.name not in executed: canExecute = True - required = self.getDependsOnAlgorithms(iAlg) + required = self.getDependsOnAlgorithms(alg.name) for requiredAlg in required: - if requiredAlg != iAlg and requiredAlg not in executed: + if requiredAlg != alg.name and requiredAlg not in executed: canExecute = False break if canExecute: try: - alg = alg.getCopy() - progress.setDebugInfo('Prepare algorithm %i: %s' - % (iAlg, alg.name)) - self.prepareAlgorithm(alg, iAlg) - progress.setText('Running ' + alg.name + ' [' - + str(iAlg + 1) + '/' + str(len(self.algs) - - len(self.deactivated)) + ']') - outputs = {} - progress.setDebugInfo('Parameters: ' - + ', '.join([unicode(p).strip() + '=' - + unicode(p.value) for p in - alg.parameters])) + progress.setDebugInfo('Prepare algorithm: ' + alg.name) + self.prepareAlgorithm(alg) + progress.setText('Running %s [%i/%i]' % ( alg.description, len(executed) + 1 ,len(toExecute))) + progress.setDebugInfo('Parameters: ' + ', '.join([unicode(p).strip() + + '=' + unicode(p.value) for p in alg.algorithm.parameters])) t0 = time.time() - alg.execute(progress, self) - dt = time.time() - t0 - for out in alg.outputs: - outputs[out.name] = out.value - progress.setDebugInfo('Outputs: ' - + ', '.join([unicode(out).strip() + '=' - + unicode(outputs[out.name]) for out in - alg.outputs])) - self.producedOutputs[iAlg] = outputs - executed.append(iAlg) + alg.algorithm.execute(progress, self) + dt = time.time() - t0 + executed.append(alg.name) progress.setDebugInfo( 'OK. Execution took %0.3f ms (%i outputs).' - % (dt, len(outputs))) + % (dt, len(alg.algorithm.outputs))) except GeoAlgorithmExecutionException, e: progress.setDebugInfo('Failed') raise GeoAlgorithmExecutionException( - 'Error executing algorithm ' + str(iAlg) - + '\n' + e.msg) - else: - pass - - iAlg += 1 + 'Error executing algorithm %s\n%s' % (alg.description, e.msg)) + progress.setDebugInfo( - 'Model processed ok. Executed %i algorithms total' % iAlg) + 'Model processed ok. Executed %i algorithms total' % len(executed)) - def getOutputType(self, i, outname): - for out in self.algs[i].outputs: - if out.name == outname: - if isinstance(out, OutputRaster): - return 'output raster' - elif isinstance(out, OutputVector): - return 'output vector' - elif isinstance(out, OutputTable): - return 'output table' - elif isinstance(out, OutputHTML): - return 'output html' - elif isinstance(out, OutputNumber): - return 'output number' - elif isinstance(out, OutputString): - return 'output string' - - def getAsPythonCode(self): - s = [] - for param in self.parameters: - s.append(str(param.getAsScriptCode().lower())) - i = 0 - for outs in self.algOutputs: - for out in outs.keys(): - if outs[out]: - s.append('##' + out.lower() + '_alg' + str(i) + '=' - + self.getOutputType(i, out)) - i += 1 - i = 0 - iMultiple = 0 - for alg in self.algs: - multiple = [] - runline = 'outputs_' + str(i) + '=Processing.runalg("' \ - + alg.commandLineName() + '"' - for param in alg.parameters: - aap = self.algParameters[i][param.name] - if aap is None: - runline += ', None' - elif isinstance(param, ParameterMultipleInput): - value = self.paramValues[aap.param] - tokens = value.split(';') - layerslist = [] - for token in tokens: - (iAlg, paramname) = token.split('|') - if float(iAlg) == float( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM): - if self.ismodelparam(paramname): - value = paramname.lower() - else: - value = self.paramValues[paramname] - else: - value = 'outputs_' + str(iAlg) + "['" + paramname \ - + "']" - layerslist.append(str(value)) - - multiple.append('multiple_' + str(iMultiple) + '=[' - + ','.join(layerslist) + ']') - runline += ', ";".join(multiple_' + str(iMultiple) + ') ' - else: - if float(aap.alg) == float( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM): - if self.ismodelparam(aap.param): - runline += ', ' + aap.param.lower() - else: - runline += ', ' + str(self.paramValues[aap.param]) - else: - runline += ', outputs_' + str(aap.alg) + "['" \ - + aap.param + "']" - for out in alg.outputs: - value = self.algOutputs[i][out.name] - if value: - name = out.name.lower() + '_alg' + str(i) - else: - name = str(None) - runline += ', ' + name - i += 1 - s += multiple - s.append(str(runline + ')')) - return '\n'.join(s) - - def ismodelparam(self, paramname): - for modelparam in self.parameters: - if modelparam.name == paramname: - return True - return False def getAsCommand(self): if self.descriptionFile: @@ -731,7 +453,10 @@ class ModelerAlgorithm(GeoAlgorithm): return None def commandLineName(self): - return 'modeler:' + os.path.basename(self.descriptionFile)[:-6].lower() + if self.descriptionFile is None: + return '' + else: + return 'modeler:' + os.path.basename(self.descriptionFile)[:-6].lower() def setModelerView(self, dialog): self.modelerdialog = dialog @@ -741,39 +466,63 @@ class ModelerAlgorithm(GeoAlgorithm): self.modelerdialog.repaintModel() def help(self): - helpfile = self.descriptionFile + '.help' - if os.path.exists(helpfile): + try: + helpfile = self.descriptionFile + '.help' return True, getHtmlFromHelpFile(self, helpfile) - else: + except: return False, None + + def todict(self): + keys = ["inputs", "group", "name", "algs"] + return {k:v for k,v in self.__dict__.iteritems() if k in keys} + + def toJson(self): + def todict(o): + if isinstance(o, QtCore.QPointF): + return {"class": "point", "values": {"x": o.x(), "y": o.y()}} + try: + d = o.todict() + return {"class": o.__class__.__module__ + "." + o.__class__.__name__, "values": d} + except Exception, e: + pass + return json.dumps(self, default=todict, indent=4) - -class AlgorithmAndParameter: - - PARENT_MODEL_ALGORITHM = -1 - - # alg is the index of the algorithm in the list in - # ModelerAlgorithm.algs. - # -1 if the value is not taken from the output of an algorithm, - # but from an input of the model or a hardcoded value. - # Names are just used for decoration, and are not needed to - # create a hardcoded value. - - def __init__(self, alg, param, algName='', paramName=''): - self.alg = alg - self.param = param - self.algName = algName - self.paramName = paramName - - def serialize(self): - return str(self.alg) + '|' + str(self.param) - - def name(self): - if self.alg != AlgorithmAndParameter.PARENT_MODEL_ALGORITHM: - return self.paramName + ' from algorithm ' + str(self.alg) + '(' \ - + self.algName + ')' - else: - return self.paramName - - def __str__(self): - return str(self.alg) + '|' + str(self.param) + @staticmethod + def fromJson(s): + def fromdict(d): + try: + fullClassName = d["class"] + tokens = fullClassName.split(".") + className = tokens[-1] + moduleName = ".".join(tokens[:-1]) + values = d["values"] + if className == "point": + return QtCore.QPointF(values["x"], values["y"]) + def _import(name): + __import__(name) + return sys.modules[name] + module = _import(moduleName) + clazz = getattr(module, className) + instance = clazz() + for k,v in values.iteritems(): + instance.__dict__[k] = v + return instance + except KeyError: + return d + except Exception, e: + raise e + try: + model = json.loads(s, object_hook = fromdict) + except Exception, e: + raise WrongModelException(e.args[0]) + return model + + + @staticmethod + def fromJsonFile(filename): + with open(filename) as f: + s = f.read() + alg = ModelerAlgorithm.fromJson(s) + alg.descriptionFile = filename + return alg + diff --git a/python/plugins/processing/modeler/ModelerAlgorithmProvider.py b/python/plugins/processing/modeler/ModelerAlgorithmProvider.py index 656c8ad7bb6..f15118573f7 100644 --- a/python/plugins/processing/modeler/ModelerAlgorithmProvider.py +++ b/python/plugins/processing/modeler/ModelerAlgorithmProvider.py @@ -31,8 +31,6 @@ from PyQt4.QtGui import * from processing.core.AlgorithmProvider import AlgorithmProvider from processing.core.ProcessingConfig import ProcessingConfig, Setting from processing.core.ProcessingLog import ProcessingLog -from processing.modeler.SaveAsPythonScriptAction import \ - SaveAsPythonScriptAction from processing.modeler.ModelerUtils import ModelerUtils from processing.modeler.ModelerAlgorithm import ModelerAlgorithm from processing.modeler.WrongModelException import WrongModelException @@ -48,8 +46,7 @@ class ModelerAlgorithmProvider(AlgorithmProvider): def __init__(self): AlgorithmProvider.__init__(self) self.actions = [CreateNewModelAction(), AddModelFromFileAction(), GetModelsAction()] - self.contextMenuActions = [EditModelAction(), DeleteModelAction(), - SaveAsPythonScriptAction()] + self.contextMenuActions = [EditModelAction(), DeleteModelAction()] def initializeSettings(self): AlgorithmProvider.initializeSettings(self) @@ -75,8 +72,6 @@ class ModelerAlgorithmProvider(AlgorithmProvider): def _loadAlgorithms(self): folder = ModelerUtils.modelsFolder() self.loadFromFolder(folder) - folder = os.path.join(os.path.dirname(__file__), 'models') - self.loadFromFolder(folder) def loadFromFolder(self, folder): if not os.path.exists(folder): @@ -84,14 +79,13 @@ class ModelerAlgorithmProvider(AlgorithmProvider): for path, subdirs, files in os.walk(folder): for descriptionFile in files: if descriptionFile.endswith('model'): - try: - alg = ModelerAlgorithm() + try: fullpath = os.path.join(path, descriptionFile) - alg.openModel(fullpath) - if alg.name.strip() != '': + alg = ModelerAlgorithm.fromJsonFile(fullpath) + if alg: alg.provider = self self.algs.append(alg) except WrongModelException, e: ProcessingLog.addToLog(ProcessingLog.LOG_ERROR, - 'Could not load model ' + descriptionFile + '\n' - + e.msg) + 'Could not load model ' + descriptionFile + '\n' + + e.msg) diff --git a/python/plugins/processing/modeler/ModelerArrowItem.py b/python/plugins/processing/modeler/ModelerArrowItem.py index 6008e1cc26a..cee49721cbc 100644 --- a/python/plugins/processing/modeler/ModelerArrowItem.py +++ b/python/plugins/processing/modeler/ModelerArrowItem.py @@ -48,74 +48,65 @@ import math from PyQt4 import QtCore, QtGui -from processing.core.GeoAlgorithm import GeoAlgorithm from processing.modeler.ModelerGraphicItem import ModelerGraphicItem - +from processing.modeler.ModelerAlgorithm import Algorithm class ModelerArrowItem(QtGui.QGraphicsPathItem): - def __init__(self, startItem, outputIndex, endItem, paramIndex, + def __init__(self, startItem, startIndex, endItem, endIndex, parent=None, scene=None): super(ModelerArrowItem, self).__init__(parent, scene) self.arrowHead = QtGui.QPolygonF() - self.paramIndex = paramIndex - self.outputIndex = outputIndex - self.myStartItem = startItem - self.myEndItem = endItem + self.endIndex = endIndex + self.startIndex = startIndex + self.startItem = startItem + self.endItem = endItem self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, False) self.myColor = QtCore.Qt.gray self.setPen(QtGui.QPen(self.myColor, 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)) self.setZValue(0) - def startItem(self): - return self.myStartItem - - def endItem(self): - return self.myEndItem - def paint(self, painter, option, widget=None): - startItem = self.myStartItem - endItem = self.myEndItem myPen = self.pen() myPen.setColor(self.myColor) painter.setPen(myPen) painter.setBrush(self.myColor) controlPoints = [] - endPt = self.endItem().getLinkPointForParameter(self.paramIndex) - startPt = self.startItem().getLinkPointForOutput(self.outputIndex) - if isinstance(self.startItem().element, GeoAlgorithm): - if self.startItem().element.outputs: - controlPoints.append(startItem.pos() + startPt) - controlPoints.append(startItem.pos() + startPt + endPt = self.endItem.getLinkPointForParameter(self.endIndex) + startPt = self.startItem.getLinkPointForOutput(self.startIndex) + if isinstance(self.startItem.element, Algorithm): + if self.startIndex != -1: + controlPoints.append(self.startItem.pos() + startPt) + controlPoints.append(self.startItem.pos() + startPt + QtCore.QPointF(ModelerGraphicItem.BOX_WIDTH / 2, 0)) - controlPoints.append(endItem.pos() + endPt + controlPoints.append(self.endItem.pos() + endPt - QtCore.QPointF(ModelerGraphicItem.BOX_WIDTH / 2, 0)) - controlPoints.append(endItem.pos() + endPt) - pt = QtCore.QPointF(startItem.pos() + startPt + controlPoints.append(self.endItem.pos() + endPt) + pt = QtCore.QPointF(self.startItem.pos() + startPt + QtCore.QPointF(-3, -3)) painter.drawEllipse(pt.x(), pt.y(), 6, 6) - pt = QtCore.QPointF(endItem.pos() + endPt + + pt = QtCore.QPointF(self.endItem.pos() + endPt + QtCore.QPointF(-3, -3)) painter.drawEllipse(pt.x(), pt.y(), 6, 6) else: # Case where there is a dependency on an algorithm not # on an output - controlPoints.append(startItem.pos() + startPt) - controlPoints.append(startItem.pos() + startPt + controlPoints.append(self.startItem.pos() + startPt) + controlPoints.append(self.startItem.pos() + startPt + QtCore.QPointF(ModelerGraphicItem.BOX_WIDTH / 2, 0)) - controlPoints.append(endItem.pos() + endPt + controlPoints.append(self.endItem.pos() + endPt - QtCore.QPointF(ModelerGraphicItem.BOX_WIDTH / 2, 0)) - controlPoints.append(endItem.pos() + endPt) + controlPoints.append(self.endItem.pos() + endPt) else: - controlPoints.append(startItem.pos()) - controlPoints.append(startItem.pos() + controlPoints.append(self.startItem.pos()) + controlPoints.append(self.startItem.pos() + QtCore.QPointF(ModelerGraphicItem.BOX_WIDTH / 2, 0)) - controlPoints.append(endItem.pos() + endPt + controlPoints.append(self.endItem.pos() + endPt - QtCore.QPointF(ModelerGraphicItem.BOX_WIDTH / 2, 0)) - controlPoints.append(endItem.pos() + endPt) - pt = QtCore.QPointF(endItem.pos() + endPt + QtCore.QPointF(-3, -3)) + controlPoints.append(self.endItem.pos() + endPt) + pt = QtCore.QPointF(self.endItem.pos() + endPt + QtCore.QPointF(-3, -3)) painter.drawEllipse(pt.x(), pt.y(), 6, 6) path = QtGui.QPainterPath() diff --git a/python/plugins/processing/modeler/ModelerDialog.py b/python/plugins/processing/modeler/ModelerDialog.py index 5d8648550fd..41126bea32c 100644 --- a/python/plugins/processing/modeler/ModelerDialog.py +++ b/python/plugins/processing/modeler/ModelerDialog.py @@ -16,7 +16,7 @@ * * *************************************************************************** """ -import json +from processing.modeler.WrongModelException import WrongModelException __author__ = 'Victor Olaya' __date__ = 'August 2012' @@ -27,21 +27,20 @@ __copyright__ = '(C) 2012, Victor Olaya' __revision__ = '$Format:%H$' import codecs +import json from PyQt4.QtCore import * from PyQt4.QtGui import * - from processing.core.ProcessingConfig import ProcessingConfig from processing.core.GeoAlgorithm import GeoAlgorithm from processing.gui.HelpEditionDialog import HelpEditionDialog from processing.gui.ParametersDialog import ParametersDialog from processing.gui.AlgorithmClassification import AlgorithmDecorator from processing.modeler.ModelerParameterDefinitionDialog import ModelerParameterDefinitionDialog -from processing.modeler.ModelerAlgorithm import ModelerAlgorithm +from processing.modeler.ModelerAlgorithm import ModelerAlgorithm, Input from processing.modeler.ModelerParametersDialog import ModelerParametersDialog from processing.modeler.ModelerUtils import ModelerUtils -from processing.modeler.WrongModelException import WrongModelException from processing.modeler.ModelerScene import ModelerScene -from processing.modeler.Providers import Providers +from processing.core.ProcessingLog import ProcessingLog from processing.tools.system import * from processing.ui.ui_DlgModeler import Ui_DlgModeler @@ -50,11 +49,14 @@ from processing.ui.ui_DlgModeler import Ui_DlgModeler class ModelerDialog(QDialog, Ui_DlgModeler): USE_CATEGORIES = '/Processing/UseSimplifiedInterface' + CANVAS_SIZE = 4000 def __init__(self, alg=None): QDialog.__init__(self) self.setupUi(self) + + self.zoom = 1 self.setWindowFlags(Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint | @@ -62,7 +64,8 @@ class ModelerDialog(QDialog, Ui_DlgModeler): self.tabWidget.setCurrentIndex(0) self.scene = ModelerScene(self) - self.scene.setSceneRect(QRectF(0, 0, 4000, 4000)) + self.scene.setSceneRect(QRectF(0, 0, self.CANVAS_SIZE, self.CANVAS_SIZE)) + self.view.setScene(self.scene) self.view.setAcceptDrops(True) self.view.ensureVisible(0, 0, 10, 10) @@ -77,11 +80,11 @@ class ModelerDialog(QDialog, Ui_DlgModeler): if event.mimeData().hasText(): text = event.mimeData().text() if text in ModelerParameterDefinitionDialog.paramTypes: - self.addInputOfType(text) + self.addInputOfType(text, event.pos()) else: alg = ModelerUtils.getAlgorithm(text); if alg is not None: - self._addAlgorithm(alg) + self._addAlgorithm(alg.getCopy(), event.pos()) event.accept() else: event.ignore() @@ -92,9 +95,33 @@ class ModelerDialog(QDialog, Ui_DlgModeler): else: event.ignore() + def _wheelEvent(event): + self.view.setTransformationAnchor(QGraphicsView.AnchorUnderMouse); + factor = 1.05 + if event.delta() > 0: + factor = 1/factor + self.view.scale(factor, factor) + self.view.centerOn(event.pos().x(), event.pos().y()) + self.repaintModel() + + def _enterEvent(e): + QGraphicsView.enterEvent(self.view, e) + self.view.viewport().setCursor(Qt.ArrowCursor) + def _mousePressEvent(e): + QGraphicsView.mousePressEvent(self.view, e) + self.view.viewport().setCursor(Qt.ArrowCursor) + def _mouseReleaseEvent(e): + QGraphicsView.mouseReleaseEvent(self.view, e) + self.view.viewport().setCursor(Qt.ArrowCursor) + + self.view.setDragMode(QGraphicsView.ScrollHandDrag); self.view.dragEnterEvent = _dragEnterEvent self.view.dropEvent = _dropEvent self.view.dragMoveEvent = _dragMoveEvent + self.view.wheelEvent = _wheelEvent + self.view.enterEvent = _enterEvent + self.view.mousePressEvent = _mousePressEvent + self.view.mouseReleaseEvent = _mouseReleaseEvent def _mimeDataInput(items): @@ -147,7 +174,6 @@ class ModelerDialog(QDialog, Ui_DlgModeler): self.inputsTree.doubleClicked.connect(self.addInput) self.searchBox.textChanged.connect(self.fillAlgorithmTree) self.algorithmTree.doubleClicked.connect(self.addAlgorithm) - self.scene.changed.connect(self.changeModel) self.btnOpen.clicked.connect(self.openModel) self.btnSave.clicked.connect(self.save) @@ -164,6 +190,7 @@ class ModelerDialog(QDialog, Ui_DlgModeler): else: self.alg = ModelerAlgorithm() + self.alg.modelerdialog = self self.view.centerOn(0, 0) self.alg.setModelerView(self) @@ -174,9 +201,6 @@ class ModelerDialog(QDialog, Ui_DlgModeler): self.hasChanged = False - def changeModel(self): - self.hasChanged = True - def closeEvent(self, evt): if self.hasChanged: ret = QMessageBox.question(self, self.tr('Message'), @@ -193,42 +217,22 @@ class ModelerDialog(QDialog, Ui_DlgModeler): evt.accept() def editHelp(self): - dlg = HelpEditionDialog(self.alg) + dlg = HelpEditionDialog(self.alg.getCopy()) dlg.exec_() - # We store the description string in case there were not - # saved because there was no filename defined yet - if self.alg.descriptionFile is None and dlg.descriptions: - self.help = dlg.descriptions - def runModel(self): - # TODO: enable alg cloning without saving to file if len(self.alg.algs) == 0: QMessageBox.warning(self, self.tr('Empty model'), self.tr("Model doesn't contains any algorithms and/or " "parameters and can't be executed")) return - if self.alg.descriptionFile is None: - self.alg.descriptionFile = getTempFilename('model') - text = self.alg.serialize() - fout = open(self.alg.descriptionFile, 'w') - fout.write(text) - fout.close() - self.alg.provider = Providers.providers['model'] - alg = self.alg.getCopy() - dlg = ParametersDialog(alg) - dlg.exec_() - self.alg.descriptionFile = None - alg.descriptionFile = None - else: - self.save() - if self.alg.provider is None: - # Might happen if model is opened from modeler dialog - self.alg.provider = Providers.providers['model'] - alg = self.alg.getCopy() - dlg = ParametersDialog(alg) - dlg.exec_() + if self.alg.provider is None: + # Might happen if model is opened from modeler dialog + self.alg.provider = ModelerUtils.providers['model'] + alg = self.alg.getCopy() + dlg = ParametersDialog(alg) + dlg.exec_() def save(self): self.saveModel(False) @@ -269,9 +273,6 @@ class ModelerDialog(QDialog, Ui_DlgModeler): self.tr('Please enter group and model names before saving' )) return - self.alg.setPositions(self.scene.getParameterPositions(), - self.scene.getAlgorithmPositions(), - self.scene.getOutputPositions()) self.alg.name = unicode(self.textName.text()) self.alg.group = unicode(self.textGroup.text()) if self.alg.descriptionFile is not None and not saveAs: @@ -286,7 +287,7 @@ class ModelerDialog(QDialog, Ui_DlgModeler): filename += '.model' self.alg.descriptionFile = filename if filename: - text = self.alg.serialize() + text = self.alg.toJson() try: fout = codecs.open(filename, 'w', encoding='utf-8') except: @@ -303,14 +304,7 @@ class ModelerDialog(QDialog, Ui_DlgModeler): return fout.write(text) fout.close() - self.update = True - - # If help strings were defined before saving the model - # for the first time, we do it here. - if self.help: - with open(self.descriptionFile + '.help', 'w') as f: - json.dump(self.help, f) - self.help = None + self.update = True QMessageBox.information(self, self.tr('Model saved'), self.tr('Model was correctly saved.')) @@ -322,21 +316,32 @@ class ModelerDialog(QDialog, Ui_DlgModeler): self.tr('Processing models (*.model)'))) if filename: try: - alg = ModelerAlgorithm() - alg.openModel(filename) + alg = ModelerAlgorithm.fromJsonFile(filename) self.alg = alg self.alg.setModelerView(self) self.textGroup.setText(alg.group) self.textName.setText(alg.name) self.repaintModel() - if self.scene.getLastAlgorithmItem(): - self.view.ensureVisible(self.scene.getLastAlgorithmItem()) + #=============================================================== + # if self.scene.getLastAlgorithmItem(): + # self.view.ensureVisible(self.scene.getLastAlgorithmItem()) + #=============================================================== self.view.centerOn(0, 0) self.hasChanged = False except WrongModelException, e: + ProcessingLog.addToLog(ProcessingLog.LOG_ERROR, + 'Could not load model ' + filename + '\n' + + e.msg) QMessageBox.critical(self, self.tr('Could not open model'), self.tr('The selected model could not be loaded.\n' - 'Wrong line: %s') % e.msg) + 'See the log for more information.')) + except Exception, e: + ProcessingLog.addToLog(ProcessingLog.LOG_ERROR, + 'Could not load model ' + filename + '\n' + + e.args[0]) + QMessageBox.critical(self, self.tr('Could not open model'), + self.tr('The selected model could not be loaded.\n' + 'See the log for more information.')) def repaintModel(self): self.scene = ModelerScene() @@ -349,27 +354,41 @@ class ModelerDialog(QDialog, Ui_DlgModeler): def addInput(self): item = self.inputsTree.currentItem() paramType = str(item.text(0)) - self.addInputOfType(paramType) + self.addInputOfType(paramType) - def addInputOfType(self, paramType): + def addInputOfType(self, paramType, pos=None): if paramType in ModelerParameterDefinitionDialog.paramTypes: dlg = ModelerParameterDefinitionDialog(self.alg, paramType) dlg.exec_() if dlg.param is not None: - self.alg.setPositions(self.scene.getParameterPositions(), - self.scene.getAlgorithmPositions(), - self.scene.getOutputPositions()) - self.alg.addParameter(dlg.param) + if pos is None: + pos = self.getPositionForParameterItem() + if isinstance(pos, QPoint): + pos = QPointF(pos) + self.alg.addParameter(Input(dlg.param, pos)) self.repaintModel() - self.view.ensureVisible(self.scene.getLastParameterItem()) + #self.view.ensureVisible(self.scene.getLastParameterItem()) self.hasChanged = True + + def getPositionForParameterItem(self): + MARGIN = 20 + BOX_WIDTH = 200 + BOX_HEIGHT = 80 + if self.alg.inputs: + maxX = max([i.pos.x() for i in self.alg.inputs.values()]) + newX = min(MARGIN + BOX_WIDTH + maxX, self.CANVAS_SIZE - BOX_WIDTH) + else: + newX = MARGIN + BOX_WIDTH / 2 + return QPointF(newX, MARGIN + BOX_HEIGHT / 2) def fillInputsTree(self): + icon = QIcon(os.path.dirname(__file__) + '/../images/input.png') parametersItem = QTreeWidgetItem() parametersItem.setText(0, self.tr('Parameters')) for paramType in ModelerParameterDefinitionDialog.paramTypes: paramItem = QTreeWidgetItem() paramItem.setText(0, paramType) + paramItem.setIcon(0, icon) paramItem.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled) parametersItem.addChild(paramItem) self.inputsTree.addTopLevelItem(parametersItem) @@ -379,23 +398,44 @@ class ModelerDialog(QDialog, Ui_DlgModeler): item = self.algorithmTree.currentItem() if isinstance(item, TreeAlgorithmItem): alg = ModelerUtils.getAlgorithm(item.alg.commandLineName()) - self._addAlgorithm(alg) + self._addAlgorithm(alg.getCopy()) - def _addAlgorithm(self, alg): - alg = alg.getCopy() + def _addAlgorithm(self, alg, pos=None): dlg = alg.getCustomModelerParametersDialog(self.alg) if not dlg: dlg = ModelerParametersDialog(alg, self.alg) dlg.exec_() - if dlg.params is not None: - self.alg.setPositions(self.scene.getParameterPositions(), - self.scene.getAlgorithmPositions(), - self.scene.getOutputPositions()) - self.alg.addAlgorithm(alg, dlg.params, dlg.values, - dlg.outputs, dlg.dependencies) + if dlg.alg is not None: + if pos is None: + dlg.alg.pos = self.getPositionForAlgorithmItem() + else: + dlg.alg.pos = pos + if isinstance(dlg.alg.pos, QPoint): + dlg.alg.pos = QPointF(pos) + from processing.modeler.ModelerGraphicItem import ModelerGraphicItem + for i, out in enumerate(dlg.alg.outputs): + dlg.alg.outputs[out].pos = dlg.alg.pos + QPointF(ModelerGraphicItem.BOX_WIDTH, (i + 1.5) + * ModelerGraphicItem.BOX_HEIGHT) + self.alg.addAlgorithm(dlg.alg) self.repaintModel() - self.view.ensureVisible(self.scene.getLastAlgorithmItem()) - self.hasChanged = False + #self.view.ensureVisible(self.scene.getLastAlgorithmItem()) + self.hasChanged = True + + def getPositionForAlgorithmItem(self): + MARGIN = 20 + BOX_WIDTH = 200 + BOX_HEIGHT = 80 + if self.alg.algs: + maxX = max([alg.pos.x() for alg in self.alg.algs.values()]) + maxY = max([alg.pos.y() for alg in self.alg.algs.values()]) + newX = min(MARGIN + BOX_WIDTH + maxX, self.CANVAS_SIZE - BOX_WIDTH) + newY = min(MARGIN + BOX_HEIGHT + maxY, self.CANVAS_SIZE + - BOX_HEIGHT) + else: + newX = MARGIN + BOX_WIDTH / 2 + newY = MARGIN * 2 + BOX_HEIGHT + BOX_HEIGHT / 2 + return QPointF(newX, newY) + def fillAlgorithmTree(self): settings = QSettings() @@ -416,14 +456,14 @@ class ModelerDialog(QDialog, Ui_DlgModeler): self.algorithmTree.clear() text = unicode(self.searchBox.text()) groups = {} - allAlgs = ModelerUtils.getAlgorithms() + allAlgs = ModelerUtils.allAlgs for providerName in allAlgs.keys(): provider = allAlgs[providerName] name = 'ACTIVATE_' + providerName.upper().replace(' ', '_') if not ProcessingConfig.getSetting(name): continue if providerName in providersToExclude \ - or len(Providers.providers[providerName].actions) != 0: + or len(ModelerUtils.providers[providerName].actions) != 0: continue algs = provider.values() @@ -494,9 +534,9 @@ class ModelerDialog(QDialog, Ui_DlgModeler): if len(groups) > 0: providerItem = QTreeWidgetItem() providerItem.setText(0, - Providers.providers[providerName].getDescription()) + ModelerUtils.providers[providerName].getDescription()) providerItem.setIcon(0, - Providers.providers[providerName].getIcon()) + ModelerUtils.providers[providerName].getIcon()) providerItem.setToolTip(0, providerItem.text(0)) for groupItem in groups.values(): providerItem.addChild(groupItem) @@ -509,7 +549,7 @@ class ModelerDialog(QDialog, Ui_DlgModeler): def fillAlgorithmTreeUsingProviders(self): self.algorithmTree.clear() text = unicode(self.searchBox.text()) - allAlgs = ModelerUtils.getAlgorithms() + allAlgs = ModelerUtils.allAlgs for providerName in allAlgs.keys(): groups = {} provider = allAlgs[providerName] @@ -533,11 +573,11 @@ class ModelerDialog(QDialog, Ui_DlgModeler): if len(groups) > 0: providerItem = QTreeWidgetItem() providerItem.setText(0, - Providers.providers[providerName].getDescription()) + ModelerUtils.providers[providerName].getDescription()) providerItem.setToolTip(0, - Providers.providers[providerName].getDescription()) + ModelerUtils.providers[providerName].getDescription()) providerItem.setIcon(0, - Providers.providers[providerName].getIcon()) + ModelerUtils.providers[providerName].getIcon()) for groupItem in groups.values(): providerItem.addChild(groupItem) self.algorithmTree.addTopLevelItem(providerItem) diff --git a/python/plugins/processing/modeler/ModelerGraphicItem.py b/python/plugins/processing/modeler/ModelerGraphicItem.py index ec72dc8210d..8b521918e42 100644 --- a/python/plugins/processing/modeler/ModelerGraphicItem.py +++ b/python/plugins/processing/modeler/ModelerGraphicItem.py @@ -17,6 +17,7 @@ *************************************************************************** """ + __author__ = 'Victor Olaya' __date__ = 'August 2012' __copyright__ = '(C) 2012, Victor Olaya' @@ -27,8 +28,7 @@ __revision__ = '$Format:%H$' import os from PyQt4 import QtCore, QtGui -from processing.core.GeoAlgorithm import GeoAlgorithm -from processing.parameters.Parameter import Parameter +from processing.modeler.ModelerAlgorithm import Input, Algorithm, Output from processing.modeler.ModelerParameterDefinitionDialog import \ ModelerParameterDefinitionDialog from processing.modeler.ModelerParametersDialog import ModelerParametersDialog @@ -39,36 +39,31 @@ class ModelerGraphicItem(QtGui.QGraphicsItem): BOX_HEIGHT = 30 BOX_WIDTH = 200 - def __init__(self, element, elementIndex, model): + def __init__(self, element, model): super(ModelerGraphicItem, self).__init__(None, None) self.model = model self.element = element - self.elementIndex = elementIndex - self.inputFolded = True - self.outputFolded = True - if isinstance(element, Parameter): + if isinstance(element, Input): icon = QtGui.QIcon(os.path.dirname(__file__) + '/../images/input.png') self.pixmap = icon.pixmap(20, 20, state=QtGui.QIcon.On) - self.text = element.description - elif isinstance(element, basestring): + self.text = element.param.description + elif isinstance(element, Output): # Output name icon = QtGui.QIcon(os.path.dirname(__file__) + '/../images/output.png') self.pixmap = icon.pixmap(20, 20, state=QtGui.QIcon.On) - self.text = element + self.text = element.description else: - state = QtGui.QIcon.On - if self.elementIndex in self.model.deactivated: - state = QtGui.QIcon.Off - self.text = element.name - self.pixmap = element.getIcon().pixmap(15, 15, state=state) + self.text = element.description + self.pixmap = element.algorithm.getIcon().pixmap(15, 15) self.arrows = [] self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True) self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True) + self.setFlag(QtGui.QGraphicsItem.ItemSendsGeometryChanges, True) self.setZValue(1000) - if not isinstance(element, basestring): + if not isinstance(element, Output): icon = QtGui.QIcon(os.path.dirname(__file__) + '/../images/edit.png') pt = QtCore.QPointF(ModelerGraphicItem.BOX_WIDTH / 2 @@ -87,32 +82,30 @@ class ModelerGraphicItem(QtGui.QGraphicsItem): self.removeElement) self.deleteButton.setParentItem(self) - if isinstance(element, GeoAlgorithm): - if element.parameters: - pt = self.getLinkPointForParameter(-1) - x = self.getXPositionForFoldButton() - pt = QtCore.QPointF(x, pt.y() + 2) - self.inButton = FoldButtonGraphicItem(pt, self.foldInput) + if isinstance(element, Algorithm): + alg = element.algorithm + if alg.parameters: + pt = self.getLinkPointForParameter(-1) + pt = QtCore.QPointF(0, pt.y() + 2) + self.inButton = FoldButtonGraphicItem(pt, self.foldInput, self.element.paramsFolded) self.inButton.setParentItem(self) - if element.outputs: + if alg.outputs: pt = self.getLinkPointForOutput(-1) - x = self.getXPositionForFoldButton() - pt = QtCore.QPointF(x, pt.y() + 2) - self.outButton = FoldButtonGraphicItem(pt, self.foldOutput) + pt = QtCore.QPointF(0, pt.y() + 2) + self.outButton = FoldButtonGraphicItem(pt, self.foldOutput, self.element.outputsFolded) self.outButton.setParentItem(self) def foldInput(self, folded): - self.inputFolded = folded + self.element.paramsFolded = folded self.prepareGeometryChange() - if self.element.outputs: + if self.element.algorithm.outputs: pt = self.getLinkPointForOutput(-1) - x = self.getXPositionForFoldButton() - pt = QtCore.QPointF(x, pt.y()) + pt = QtCore.QPointF(0, pt.y()) self.outButton.position = pt self.update() def foldOutput(self, folded): - self.outputFolded = folded + self.element.outputsFolded = folded self.prepareGeometryChange() self.update() @@ -122,8 +115,11 @@ class ModelerGraphicItem(QtGui.QGraphicsItem): def boundingRect(self): font = QtGui.QFont('Verdana', 8) fm = QtGui.QFontMetricsF(font) - numParams = (0 if self.inputFolded else len(self.element.parameters)) - numOutputs = (0 if self.outputFolded else len(self.element.outputs)) + unfolded = isinstance(self.element, Algorithm) and not self.element.paramsFolded + numParams = len(self.element.algorithm.parameters) if unfolded else 0 + unfolded = isinstance(self.element, Algorithm) and not self.element.outputsFolded + numOutputs = len(self.element.algorithm.outputs) if unfolded else 0 + hUp = fm.height() * 1.2 * (numParams + 2) hDown = fm.height() * 1.2 * (numOutputs + 2) rect = QtCore.QRectF(-(ModelerGraphicItem.BOX_WIDTH + 2) / 2, @@ -136,13 +132,15 @@ class ModelerGraphicItem(QtGui.QGraphicsItem): self.editElement() def contextMenuEvent(self, event): + if isinstance(self.element, Output): + return popupmenu = QtGui.QMenu() removeAction = popupmenu.addAction('Remove') removeAction.triggered.connect(self.removeElement) editAction = popupmenu.addAction('Edit') - editAction.triggered.connect(self.editElement) - if isinstance(self.element, GeoAlgorithm): - if self.elementIndex in self.model.deactivated: + editAction.triggered.connect(self.editElement) + if isinstance(self.element, Algorithm): + if not self.element.active: removeAction = popupmenu.addAction('Activate') removeAction.triggered.connect(self.activateAlgorithm) else: @@ -151,56 +149,52 @@ class ModelerGraphicItem(QtGui.QGraphicsItem): popupmenu.exec_(event.screenPos()) def deactivateAlgorithm(self): - self.model.setPositions(self.scene().getParameterPositions(), - self.scene().getAlgorithmPositions(), - self.scene().getOutputPositions()) - self.model.deactivateAlgorithm(self.elementIndex, True) + self.model.deactivateAlgorithm(self.element.name) + self.model.updateModelerView() def activateAlgorithm(self): - self.model.setPositions(self.scene().getParameterPositions(), - self.scene().getAlgorithmPositions(), - self.scene().getOutputPositions()) - if not self.model.activateAlgorithm(self.elementIndex, True): + if self.model.activateAlgorithm(self.element.name): + self.model.updateModelerView() + else: QtGui.QMessageBox.warning(None, 'Could not activate Algorithm', - 'The selected algorithm depends on other currently \ - non-active algorithms.\nActivate them them before trying \ - to activate it.') + 'The selected algorithm depends on other currently non-active algorithms.\n' + 'Activate them them before trying to activate it.') def editElement(self): - self.model.setPositions(self.scene().getParameterPositions(), - self.scene().getAlgorithmPositions(), - self.scene().getOutputPositions()) - if isinstance(self.element, Parameter): + if isinstance(self.element, Input): dlg = ModelerParameterDefinitionDialog(self.model, - param=self.element) + param=self.element.param) dlg.exec_() if dlg.param is not None: - self.model.updateParameter(self.elementIndex, dlg.param) - self.element = dlg.param - self.text = self.element.description + self.model.updateParameter(dlg.param) + self.element.param = dlg.param + self.text = dlg.param.description self.update() - elif isinstance(self.element, GeoAlgorithm): - dlg = self.element.getCustomModelerParametersDialog(self.model, - self.elementIndex) + elif isinstance(self.element, Algorithm): + dlg = self.element.algorithm.getCustomModelerParametersDialog(self.model, self.element.name) if not dlg: - dlg = ModelerParametersDialog(self.element, self.model, - self.elementIndex) + dlg = ModelerParametersDialog(self.element.algorithm, self.model, self.element.name) dlg.exec_() - if dlg.params is not None: - self.model.updateAlgorithm(self.elementIndex, dlg.params, - dlg.values, dlg.outputs, dlg.dependencies) + if dlg.alg is not None: + dlg.alg.name = self.element.name + self.model.updateAlgorithm(dlg.alg) + self.model.updateModelerView() def removeElement(self): - if isinstance(self.element, Parameter): - if not self.model.removeParameter(self.elementIndex): + if isinstance(self.element, Input): + if not self.model.removeParameter(self.element.param.name): QtGui.QMessageBox.warning(None, 'Could not remove element', - 'Other elements depend on the selected one.\nRemove \ - them before trying to remove it.') - elif isinstance(self.element, GeoAlgorithm): - if not self.model.removeAlgorithm(self.elementIndex): + 'Other elements depend on the selected one.\n' + 'Remove them before trying to remove it.') + else: + self.model.updateModelerView() + elif isinstance(self.element, Algorithm): + if not self.model.removeAlgorithm(self.element.name): QtGui.QMessageBox.warning(None, 'Could not remove element', - 'Other elements depend on the selected one.\nRemove \ - them before trying to remove it.') + 'Other elements depend on the selected one.\n' + 'Remove them before trying to remove it.') + else: + self.model.updateModelerView() def getAdjustedText(self, text): font = QtGui.QFont('Verdana', 8) @@ -223,34 +217,35 @@ class ModelerGraphicItem(QtGui.QGraphicsItem): ModelerGraphicItem.BOX_HEIGHT + 2) painter.setPen(QtGui.QPen(QtCore.Qt.gray, 1)) color = QtGui.QColor(125, 232, 232) - if isinstance(self.element, Parameter): + if isinstance(self.element, Input): color = QtGui.QColor(179, 179, 255) - elif isinstance(self.element, GeoAlgorithm): + elif isinstance(self.element, Algorithm): color = QtCore.Qt.white painter.setBrush(QtGui.QBrush(color, QtCore.Qt.SolidPattern)) painter.drawRect(rect) font = QtGui.QFont('Verdana', 8) painter.setFont(font) painter.setPen(QtGui.QPen(QtCore.Qt.black)) - if self.isSelected(): + text = self.getAdjustedText(self.text) + if isinstance(self.element, Algorithm) and not self.element.active: + painter.setPen(QtGui.QPen(QtCore.Qt.gray)) + text = text + "\n(deactivated)" + elif self.isSelected(): painter.setPen(QtGui.QPen(QtCore.Qt.blue)) - if isinstance(self.element, GeoAlgorithm): - if self.elementIndex in self.model.deactivated: - painter.setPen(QtGui.QPen(QtCore.Qt.lightGray)) fm = QtGui.QFontMetricsF(font) text = self.getAdjustedText(self.text) h = fm.height() pt = QtCore.QPointF(-ModelerGraphicItem.BOX_WIDTH / 2 + 25, h / 2.0) painter.drawText(pt, text) painter.setPen(QtGui.QPen(QtCore.Qt.black)) - if isinstance(self.element, GeoAlgorithm): + if isinstance(self.element, Algorithm): h = -(fm.height() * 1.2) h = h - ModelerGraphicItem.BOX_HEIGHT / 2.0 + 5 pt = QtCore.QPointF(-ModelerGraphicItem.BOX_WIDTH / 2 + 25, h) painter.drawText(pt, 'In') i = 1 - if not self.inputFolded: - for param in self.element.parameters: + if not self.element.paramsFolded: + for param in self.element.algorithm.parameters: if not param.hidden: text = self.getAdjustedText(param.description) h = -(fm.height() * 1.2) * (i + 1) @@ -258,63 +253,59 @@ class ModelerGraphicItem(QtGui.QGraphicsItem): pt = QtCore.QPointF(-ModelerGraphicItem.BOX_WIDTH / 2 + 33, h) painter.drawText(pt, text) - i += 1 - i = 1 + i += 1 h = fm.height() * 1.2 h = h + ModelerGraphicItem.BOX_HEIGHT / 2.0 pt = QtCore.QPointF(-ModelerGraphicItem.BOX_WIDTH / 2 + 25, h) painter.drawText(pt, 'Out') - if not self.outputFolded: - for out in self.element.outputs: - if not out.hidden: + if not self.element.outputsFolded: + for i, out in enumerate(self.element.algorithm.outputs): text = self.getAdjustedText(out.description) - h = fm.height() * 1.2 * (i + 1) + h = fm.height() * 1.2 * (i + 2) h = h + ModelerGraphicItem.BOX_HEIGHT / 2.0 pt = QtCore.QPointF(-ModelerGraphicItem.BOX_WIDTH / 2 + 33, h) painter.drawText(pt, text) - i += 1 if self.pixmap: painter.drawPixmap(-(ModelerGraphicItem.BOX_WIDTH / 2.0) + 3, -8, self.pixmap) def getLinkPointForParameter(self, paramIndex): offsetX = 25 - if self.inputFolded: + if isinstance(self.element, Algorithm) and self.element.paramsFolded: paramIndex = -1 offsetX = 17 font = QtGui.QFont('Verdana', 8) fm = QtGui.QFontMetricsF(font) - if isinstance(self.element, GeoAlgorithm): + if isinstance(self.element, Algorithm): h = -(fm.height() * 1.2) * (paramIndex + 2) - fm.height() / 2.0 + 8 h = h - ModelerGraphicItem.BOX_HEIGHT / 2.0 else: h = 0 return QtCore.QPointF(-ModelerGraphicItem.BOX_WIDTH / 2 + offsetX, h) - def getXPositionForFoldButton(self): - return 0 def getLinkPointForOutput(self, outputIndex): - if isinstance(self.element, GeoAlgorithm): - outputIndex = (outputIndex if not self.outputFolded else -1) + if isinstance(self.element, Algorithm): + outputIndex = (outputIndex if not self.element.outputsFolded else -1) text = self.getAdjustedText( - self.element.outputs[outputIndex].description) + self.element.algorithm.outputs[outputIndex].description) font = QtGui.QFont('Verdana', 8) fm = QtGui.QFontMetricsF(font) w = fm.width(text) h = fm.height() * 1.2 * (outputIndex + 1) + fm.height() / 2.0 y = h + ModelerGraphicItem.BOX_HEIGHT / 2.0 + 5 x = (-ModelerGraphicItem.BOX_WIDTH / 2 + 33 + w - + 5 if not self.outputFolded else 10) + + 5 if not self.element.outputsFolded else 10) return QtCore.QPointF(x, y) else: return QtCore.QPointF(0, 0) def itemChange(self, change, value): - if change == QtGui.QGraphicsItem.ItemPositionChange: + if change == QtGui.QGraphicsItem.ItemPositionHasChanged: for arrow in self.arrows: - arrow.updatePosition() + arrow.updatePosition() + self.element.pos = self.pos() return value @@ -399,9 +390,9 @@ class FoldButtonGraphicItem(FlatButtonGraphicItem): False: QtGui.QIcon(os.path.dirname(__file__) + '/../images/minus.png')} - def __init__(self, position, action): - self.folded = True - icon = self.icons[True] + def __init__(self, position, action, folded): + self.folded = folded + icon = self.icons[self.folded] super(FoldButtonGraphicItem, self).__init__(icon, position, action) def mousePressEvent(self, event): diff --git a/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py b/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py index ce69375702f..22101410eaf 100644 --- a/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py +++ b/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py @@ -26,8 +26,6 @@ __copyright__ = '(C) 2012, Victor Olaya' __revision__ = '$Format:%H$' from PyQt4 import QtCore, QtGui -from PyQt4.QtCore import * -from PyQt4.QtGui import * from processing.parameters.Parameter import Parameter from processing.parameters.ParameterBoolean import ParameterBoolean from processing.parameters.ParameterRaster import ParameterRaster @@ -120,11 +118,11 @@ class ModelerParameterDefinitionDialog(QtGui.QDialog): self.horizontalLayout2.addWidget(QtGui.QLabel('Parent layer')) self.parentCombo = QtGui.QComboBox() idx = 0 - for param in self.alg.parameters: - if isinstance(param, (ParameterVector, ParameterTable)): - self.parentCombo.addItem(param.description, param.name) + for param in self.alg.inputs.values(): + if isinstance(param.param, (ParameterVector, ParameterTable)): + self.parentCombo.addItem(param.param.description, param.param.name) if self.param is not None: - if self.param.parent == param.name: + if self.param.parent == param.param.name: self.parentCombo.setCurrentIndex(idx) idx += 1 self.horizontalLayout2.addWidget(self.parentCombo) @@ -241,9 +239,9 @@ class ModelerParameterDefinitionDialog(QtGui.QDialog): self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok) self.buttonBox.setObjectName('buttonBox') - QObject.connect(self.buttonBox, QtCore.SIGNAL('accepted()'), + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL('accepted()'), self.okPressed) - QObject.connect(self.buttonBox, QtCore.SIGNAL('rejected()'), + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL('rejected()'), self.cancelPressed) self.verticalLayout.addWidget(self.buttonBox) @@ -253,7 +251,7 @@ class ModelerParameterDefinitionDialog(QtGui.QDialog): def okPressed(self): description = unicode(self.nameTextBox.text()) if description.strip() == '': - QMessageBox.critical(self, 'Unable to define parameter', + QtGui.QMessageBox.warning(self, 'Unable to define parameter', 'Invalid parameter name') return if self.param is None: @@ -273,7 +271,7 @@ class ModelerParameterDefinitionDialog(QtGui.QDialog): == ModelerParameterDefinitionDialog.PARAMETER_TABLE_FIELD \ or isinstance(self.param, ParameterTableField): if self.parentCombo.currentIndex() < 0: - QMessageBox.critical(self, 'Unable to define parameter', + QtGui.QMessageBox.warning(self, 'Unable to define parameter', 'Wrong or missing parameter values') return parent = self.parentCombo.itemData(self.parentCombo.currentIndex()) @@ -317,7 +315,7 @@ class ModelerParameterDefinitionDialog(QtGui.QDialog): self.param = ParameterNumber(name, description, vmin, vmax, float(str(self.defaultTextBox.text()))) except: - QMessageBox.critical(self, 'Unable to define parameter', + QtGui.QMessageBox.warning(self, 'Unable to define parameter', 'Wrong or missing parameter values') return elif self.paramType \ diff --git a/python/plugins/processing/modeler/ModelerParametersDialog.py b/python/plugins/processing/modeler/ModelerParametersDialog.py index 87c1a433d43..59f921b8878 100644 --- a/python/plugins/processing/modeler/ModelerParametersDialog.py +++ b/python/plugins/processing/modeler/ModelerParametersDialog.py @@ -28,13 +28,13 @@ __revision__ = '$Format:%H$' from PyQt4.QtCore import * from PyQt4.QtGui import * from PyQt4 import QtCore, QtGui, QtWebKit - +from processing.modeler.ModelerAlgorithm import ValueFromInput,\ + ValueFromOutput, Algorithm, Output from processing.core.WrongHelpFileException import WrongHelpFileException from processing.gui.CrsSelectionPanel import CrsSelectionPanel from processing.gui.MultipleInputPanel import MultipleInputPanel from processing.gui.FixedTablePanel import FixedTablePanel from processing.gui.RangePanel import RangePanel -from processing.modeler.ModelerAlgorithm import AlgorithmAndParameter from processing.modeler.MultilineTextPanel import MultilineTextPanel from processing.parameters.ParameterCrs import ParameterCrs from processing.parameters.ParameterRaster import ParameterRaster @@ -67,12 +67,17 @@ class ModelerParametersDialog(QtGui.QDialog): NOT_SELECTED = '[Not selected]' USE_MIN_COVERING_EXTENT = '[Use min covering extent]' - def __init__(self, alg, model, algIndex=None): + def __init__(self, alg, model, algName=None): QtGui.QDialog.__init__(self) self.setModal(True) - self.alg = alg + #The algorithm to define in this dialog. It is an instance of GeoAlgorithm + self._alg = alg + #The resulting algorithm after the user clicks on OK. it is an instance of the container Algorithm class + self.alg = None + #The model this algorithm is going to be added to self.model = model - self.algIndex = algIndex + #The name of the algorithm in the model, in case we are editing it and not defining it for the first time + self._algName = algName self.setupUi() self.params = None @@ -88,13 +93,28 @@ class ModelerParametersDialog(QtGui.QDialog): self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok) - tooltips = self.alg.getParameterDescriptions() + tooltips = self._alg.getParameterDescriptions() self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.verticalLayout = QtGui.QVBoxLayout() self.verticalLayout.setSpacing(5) self.verticalLayout.setMargin(20) - for param in self.alg.parameters: + + hLayout = QtGui.QHBoxLayout() + hLayout.setSpacing(5) + hLayout.setMargin(0) + descriptionLabel = QtGui.QLabel("Description") + self.descriptionBox = QtGui.QLineEdit() + self.descriptionBox.setText(self._alg.name) + hLayout.addWidget(descriptionLabel) + hLayout.addWidget(self.descriptionBox) + self.verticalLayout.addLayout(hLayout) + line = QtGui.QFrame() + line.setFrameShape(QtGui.QFrame.HLine) + line.setFrameShadow(QtGui.QFrame.Sunken) + self.verticalLayout.addWidget(line) + + for param in self._alg.parameters: if param.isAdvanced: self.advancedButton = QtGui.QPushButton() self.advancedButton.setText('Show advanced parameters') @@ -104,7 +124,7 @@ class ModelerParametersDialog(QtGui.QDialog): self.showAdvancedParametersClicked) self.verticalLayout.addWidget(self.advancedButton) break - for param in self.alg.parameters: + for param in self._alg.parameters: if param.hidden: continue desc = param.description @@ -127,7 +147,7 @@ class ModelerParametersDialog(QtGui.QDialog): self.verticalLayout.addWidget(label) self.verticalLayout.addWidget(widget) - for output in self.alg.outputs: + for output in self._alg.outputs: if output.hidden: continue if isinstance(output, (OutputRaster, OutputVector, OutputTable, @@ -152,7 +172,7 @@ class ModelerParametersDialog(QtGui.QDialog): self.setLayout(self.verticalLayout) self.setPreviousValues() - self.setWindowTitle(self.alg.name) + self.setWindowTitle(self._alg.name) self.verticalLayout2 = QtGui.QVBoxLayout() self.verticalLayout2.setSpacing(2) self.verticalLayout2.setMargin(0) @@ -169,7 +189,7 @@ class ModelerParametersDialog(QtGui.QDialog): html = None url = None try: - isText, help = self.alg.help() + isText, help = self._alg.help() if help is not None: if isText: html = help; @@ -198,16 +218,14 @@ class ModelerParametersDialog(QtGui.QDialog): QtCore.QMetaObject.connectSlotsByName(self) def getAvailableDependencies(self): - if self.algIndex is None: + if self._algName is None: dependent = [] else: - dependent = self.model.getDependentAlgorithms(self.algIndex) + dependent = self.model.getDependentAlgorithms(self._algName) opts = [] - i = 0 - for alg in self.model.algs: - if i not in dependent: - opts.append(str(i + 1) + ':' + alg.name) - i += 1 + for alg in self.model.algs.values(): + if alg.name not in dependent: + opts.append(alg.algorithm.name) return opts def getDependenciesPanel(self): @@ -219,235 +237,70 @@ class ModelerParametersDialog(QtGui.QDialog): self.advancedButton.setText('Hide advanced parameters') else: self.advancedButton.setText('Show advanced parameters') - for param in self.alg.parameters: + for param in self._alg.parameters: if param.isAdvanced: self.labels[param.name].setVisible(self.showAdvanced) self.widgets[param.name].setVisible(self.showAdvanced) - def getRasterLayers(self): - layers = [] - params = self.model.parameters - for param in params: - if isinstance(param, ParameterRaster): - layers.append(AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, - param.name, '', param.description)) - - if self.algIndex is None: + def getAvailableValuesOfType(self, paramType, outType = None): + values = [] + inputs = self.model.inputs + for i in inputs.values(): + param = i.param + if isinstance(param, paramType): + values.append(ValueFromInput(param.name)) + if outType is None: + return values + if self._algName is None: dependent = [] else: - dependent = self.model.getDependentAlgorithms(self.algIndex) + dependent = self.model.getDependentAlgorithms(self._algName) + for alg in self.model.algs.values(): + if alg.name not in dependent: + for out in alg.algorithm.outputs: + if isinstance(out, outType): + values.append(ValueFromOutput(alg.name, out.name)) - i = 0 - for alg in self.model.algs: - if i not in dependent: - for out in alg.outputs: - if isinstance(out, OutputRaster): - layers.append(AlgorithmAndParameter(i, out.name, - alg.name, out.description)) - i += 1 - - return layers - - def getVectorLayers(self): - layers = [] - params = self.model.parameters - for param in params: - if isinstance(param, ParameterVector): - layers.append(AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, - param.name, '', param.description)) - - if self.algIndex is None: - dependent = [] + return values + + def resolveValueDescription(self, value): + if isinstance(value, ValueFromInput): + return self.model.inputs[value.name].param.description else: - dependent = self.model.getDependentAlgorithms(self.algIndex) + alg = self.model.algs[value.alg] + return "'%s' from algorithm '%s'" % (alg.algorithm.getOutputFromName(value.output).description, alg.description) - i = 0 - for alg in self.model.algs: - if i not in dependent: - for out in alg.outputs: - if isinstance(out, OutputVector): - layers.append(AlgorithmAndParameter(i, out.name, - alg.name, out.description)) - i += 1 - - return layers - - def getTables(self): - tables = [] - params = self.model.parameters - for param in params: - if isinstance(param, ParameterTable): - tables.append(AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, - param.name, '', param.description)) - - if self.algIndex is None: - dependent = [] - else: - dependent = self.model.getDependentAlgorithms(self.algIndex) - - i = 0 - for alg in self.model.algs: - if i not in dependent: - for out in alg.outputs: - if isinstance(out, OutputTable): - tables.append(AlgorithmAndParameter(i, out.name, - alg.name, out.description)) - i += 1 - - return tables - - def getExtents(self): - extents = [] - params = self.model.parameters - for param in params: - if isinstance(param, ParameterExtent): - extents.append(AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, - param.name, '', param.description)) - - if self.algIndex is None: - dependent = [] - else: - dependent = self.model.getDependentAlgorithms(self.algIndex) - - i = 0 - for alg in self.model.algs: - if i not in dependent: - for out in alg.outputs: - if isinstance(out, OutputExtent): - extents.append(AlgorithmAndParameter(i, out.name, - alg.name, out.description)) - i += 1 - - return extents - - def getNumbers(self): - numbers = [] - params = self.model.parameters - for param in params: - if isinstance(param, ParameterNumber): - numbers.append(AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, - param.name, '', param.description)) - - if self.algIndex is None: - dependent = [] - else: - dependent = self.model.getDependentAlgorithms(self.algIndex) - - i = 0 - for alg in self.model.algs: - if i not in dependent: - for out in alg.outputs: - if isinstance(out, OutputNumber): - numbers.append(AlgorithmAndParameter(i, out.name, - alg.name, out.description)) - i += 1 - return numbers - - def getFiles(self): - files = [] - params = self.model.parameters - for param in params: - if isinstance(param, ParameterFile): - files.append(AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, - param.name, '', param.description)) - - if self.algIndex is None: - dependent = [] - else: - dependent = self.model.getDependentAlgorithms(self.algIndex) - - i = 0 - for alg in self.model.algs: - if i not in dependent: - for out in alg.outputs: - if isinstance(out, OutputFile): - files.append(AlgorithmAndParameter(i, out.name, - alg.name, out.description)) - i += 1 - return files - - def getBooleans(self): - booleans = [] - params = self.model.parameters - for param in params: - if isinstance(param, ParameterBoolean): - booleans.append(AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, - param.name, '', param.description)) - return booleans - - def getStrings(self): - strings = [] - params = self.model.parameters - for param in params: - if isinstance(param, ParameterString): - strings.append(AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, - param.name, '', param.description)) - - if self.algIndex is None: - dependent = [] - else: - dependent = self.model.getDependentAlgorithms(self.algIndex) - - i = 0 - for alg in self.model.algs: - if i not in dependent: - for out in alg.outputs: - if isinstance(out, OutputString): - strings.append(AlgorithmAndParameter(i, out.name, - alg.name, out.description)) - i += 1 - return strings - - def getTableFields(self): - strings = [] - params = self.model.parameters - for param in params: - if isinstance(param, ParameterTableField): - strings.append(AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, - param.name, '', param.description)) - return strings def getWidgetFromParameter(self, param): if isinstance(param, ParameterRaster): item = QtGui.QComboBox() - - layers = self.getRasterLayers() + layers = self.getAvailableValuesOfType(ParameterRaster, OutputRaster) if param.optional: item.addItem(self.NOT_SELECTED, None) for layer in layers: - item.addItem(layer.name(), layer) + item.addItem(self.resolveValueDescription(layer), layer) elif isinstance(param, ParameterVector): item = QtGui.QComboBox() - - layers = self.getVectorLayers() + layers = self.getAvailableValuesOfType(ParameterVector, OutputVector) if param.optional: item.addItem(self.NOT_SELECTED, None) for layer in layers: - item.addItem(layer.name(), layer) + item.addItem(self.resolveValueDescription(layer), layer) elif isinstance(param, ParameterTable): item = QtGui.QComboBox() item.setEditable(True) - layers = self.getTables() + layers = self.getAvailableValuesOfType(ParameterTable, OutputTable) if param.optional: item.addItem(self.NOT_SELECTED, None) for layer in layers: - item.addItem(layer.name(), layer) + item.addItem(self.resolveValueDescription(layer), layer) elif isinstance(param, ParameterBoolean): item = QtGui.QComboBox() item.addItem('Yes') item.addItem('No') - bools = self.getBooleans() + bools = self.getAvailableValuesOfType(ParameterBoolean, None) for b in bools: - item.addItem(b.name(), b) + item.addItem(self.resolveValueDescription(b), b) elif isinstance(param, ParameterSelection): item = QtGui.QComboBox() item.addItems(param.options) @@ -457,15 +310,15 @@ class ModelerParametersDialog(QtGui.QDialog): item = RangePanel(param) elif isinstance(param, ParameterMultipleInput): if param.datatype == ParameterMultipleInput.TYPE_VECTOR_ANY: - options = self.getVectorLayers() + options = self.getAvailableValuesOfType(ParameterVector, OutputVector) else: - options = self.getRasterLayers() + options = self.getAvailableValuesOfType(ParameterRaster, OutputRaster) opts = [] for opt in options: - opts.append(opt.name()) + opts.append(self.resolveValueDescription(opt)) item = MultipleInputPanel(opts) elif isinstance(param, ParameterString): - strings = self.getStrings() + strings = self.getAvailableValuesOfType(ParameterString, OutputString) if param.multiline: item = MultilineTextPanel(strings, self.model) item.setText(str(param.default)) @@ -473,31 +326,31 @@ class ModelerParametersDialog(QtGui.QDialog): item = QtGui.QComboBox() item.setEditable(True) for s in strings: - item.addItem(s.name(), s) + item.addItem(self.resolveValueDescription(s), s) item.setEditText(str(param.default)) elif isinstance(param, ParameterTableField): item = QtGui.QComboBox() item.setEditable(True) - fields = self.getTableFields() + fields = self.getAvailableValuesOfType(ParameterTableField, None) for f in fields: - item.addItem(f.name(), f) + item.addItem(self.resolveValueDescription(f), f) elif isinstance(param, ParameterNumber): item = QtGui.QComboBox() item.setEditable(True) - numbers = self.getNumbers() + numbers = self.getAvailableValuesOfType(ParameterNumber, OutputNumber) for n in numbers: - item.addItem(n.name(), n) + item.addItem(self.resolveValueDescription(n), n) item.setEditText(str(param.default)) elif isinstance(param, ParameterCrs): item = CrsSelectionPanel(param.default) elif isinstance(param, ParameterExtent): item = QtGui.QComboBox() item.setEditable(True) - extents = self.getExtents() + extents = self.getAvailableValuesOfType(ParameterExtent, OutputExtent) if self.canUseAutoExtent(): item.addItem(self.USE_MIN_COVERING_EXTENT, None) for ex in extents: - item.addItem(ex.name(), ex) + item.addItem(self.resolveValueDescription(ex), ex) if not self.canUseAutoExtent(): item.setEditText(str(param.default)) elif isinstance(param, ParameterFile): @@ -505,7 +358,7 @@ class ModelerParametersDialog(QtGui.QDialog): item.setEditable(True) files = self.getFiles() for f in files: - item.addItem(f.name(), f) + item.addItem(self.resolveValueDescription(f), f) else: item = QtGui.QLineEdit() try: @@ -515,81 +368,72 @@ class ModelerParametersDialog(QtGui.QDialog): return item def canUseAutoExtent(self): - for param in self.alg.parameters: - if isinstance(param, (ParameterRaster, ParameterVector)): - return True - if isinstance(param, ParameterMultipleInput): + for param in self._alg.parameters: + if isinstance(param, (ParameterRaster, ParameterVector, ParameterMultipleInput)): return True + return False def setTableContent(self): - params = self.alg.parameters - outputs = self.alg.outputs - numParams = 0 - for param in params: - if not param.hidden: - numParams += 1 - numOutputs = 0 - for output in outputs: - if not output.hidden: - numOutputs += 1 - self.tableWidget.setRowCount(numParams + numOutputs) + params = self._alg.parameters + outputs = self._alg.outputs + visibleParams = [p for p in params if not p.hidden] + visibleOutputs = [p for o in outputs if not o.hidden] + self.tableWidget.setRowCount(len(visibleParams) + len(visibleOutputs)) + + for i, param in visibleParams: + item = QtGui.QTableWidgetItem(param.description) + item.setFlags(QtCore.Qt.ItemIsEnabled) + self.tableWidget.setItem(i, 0, item) + item = self.getWidgetFromParameter(param) + self.valueItems[param.name] = item + self.tableWidget.setCellWidget(i, 1, item) + self.tableWidget.setRowHeight(i, 22) - i = 0 - for param in params: - if not param.hidden: - item = QtGui.QTableWidgetItem(param.description) - item.setFlags(QtCore.Qt.ItemIsEnabled) - self.tableWidget.setItem(i, 0, item) - item = self.getWidgetFromParameter(param) - self.valueItems[param.name] = item - self.tableWidget.setCellWidget(i, 1, item) - self.tableWidget.setRowHeight(i, 22) - i += 1 - - for output in outputs: - if not output.hidden: - item = QtGui.QTableWidgetItem(output.description + '<' - + output.__module__.split('.')[-1] + '>') - item.setFlags(QtCore.Qt.ItemIsEnabled) - self.tableWidget.setItem(i, 0, item) - item = QLineEdit() - if hasattr(item, 'setPlaceholderText'): - item.setPlaceholderText(ModelerParametersDialog.ENTER_NAME) - self.valueItems[output.name] = item - self.tableWidget.setCellWidget(i, 1, item) - self.tableWidget.setRowHeight(i, 22) - i += 1 + for i, output in visibleOutputs: + item = QtGui.QTableWidgetItem(output.description + '<' + + output.__module__.split('.')[-1] + '>') + item.setFlags(QtCore.Qt.ItemIsEnabled) + self.tableWidget.setItem(i, 0, item) + item = QLineEdit() + if hasattr(item, 'setPlaceholderText'): + item.setPlaceholderText(ModelerParametersDialog.ENTER_NAME) + self.valueItems[output.name] = item + self.tableWidget.setCellWidget(i, 1, item) + self.tableWidget.setRowHeight(i, 22) def setComboBoxValue(self, combo, value, param): + if isinstance(value, list): + value = value[0] + print param.name + print value items = [combo.itemData(i) for i in range(combo.count())] - idx = 0 - for item in items: - if item and value: - if item.alg == value.alg and item.param == value.param: - combo.setCurrentIndex(idx) - return - idx += 1 - if combo.isEditable(): - value = self.model.getValueFromAlgorithmAndParameter(value) - if value: - combo.setEditText(str(value)) - elif isinstance(param, ParameterSelection): - value = self.model.getValueFromAlgorithmAndParameter(value) + print items + try: + idx = items.index(value) + combo.setCurrentIndex(idx) + return + except ValueError: + pass + if combo.isEditable(): + if value is not None: + combo.setEditText(unicode(value)) + elif isinstance(param, ParameterSelection): combo.setCurrentIndex(int(value)) - elif isinstance(param, ParameterBoolean): - value = self.model.getValueFromAlgorithmAndParameter(value) \ - == str(True) + elif isinstance(param, ParameterBoolean): if value: combo.setCurrentIndex(0) else: combo.setCurrentIndex(1) - def setPreviousValues(self): - if self.algIndex is not None: - for (name, value) in \ - self.model.algParameters[self.algIndex].items(): - widget = self.valueItems[name] - param = self.alg.getParameterFromName(name) + def setPreviousValues(self): + if self._algName is not None: + alg = self.model.algs[self._algName] + self.descriptionBox.setText(alg.description) + for param in alg.algorithm.parameters: + if param.hidden: + continue + widget = self.valueItems[param.name] + value = alg.params[param.name] if isinstance(param, ( ParameterRaster, ParameterVector, @@ -607,210 +451,140 @@ class ModelerParametersDialog(QtGui.QDialog): else: self.setComboBoxValue(widget, value, param) elif isinstance(param, ParameterCrs): - value = self.model.getValueFromAlgorithmAndParameter(value) widget.setAuthid(value) elif isinstance(param, ParameterFixedTable): - pass + pass #TODO! elif isinstance(param, ParameterMultipleInput): - value = self.model.getValueFromAlgorithmAndParameter(value) - values = value.split(';') - selectedoptions = [] - if param.datatype \ - == ParameterMultipleInput.TYPE_VECTOR_ANY: - options = self.getVectorLayers() + if param.datatype == ParameterMultipleInput.TYPE_VECTOR_ANY: + options = self.getAvailableValuesOfType(ParameterVector, OutputVector) else: - options = self.getRasterLayers() - for i in range(len(options)): - option = options[i] - for aap in values: - if str(option) == aap: - selectedoptions.append(i) - widget.setSelectedItems(selectedoptions) - else: - pass + options = self.getAvailableValuesOfType(ParameterRaster, OutputRaster) + selected = [] + for i, opt in enumerate(options): + if opt in value: + selected.append(i) + widget.setSelectedItems(selected) - for out in self.alg.outputs: - if not out.hidden: - value = self.model.algOutputs[self.algIndex][out.name] - if value is not None: - widget = \ - self.valueItems[out.name].setText(unicode(value)) + for name, out in alg.outputs.iteritems(): + widget = self.valueItems[name].setText(out.description) selected = [] dependencies = self.getAvailableDependencies() - index = -1 - for dependency in dependencies: - index += 1 - n = int(dependency[:dependency.find(':')]) - 1 - if n in self.model.dependencies[self.algIndex]: - selected.append(index) + for idx, dependency in enumerate(dependencies): + if dependency in alg.dependencies: + selected.append(idx) self.dependenciesPanel.setSelectedItems(selected) - def setParamValues(self): - self.params = {} - self.values = {} - self.outputs = {} - - params = self.alg.parameters - outputs = self.alg.outputs - + def createAlgorithm(self): + alg = Algorithm(self._alg.commandLineName()) + alg.setName(self.model) + alg.description = self.descriptionBox.text() + params = self._alg.parameters + outputs = self._alg.outputs for param in params: if param.hidden: continue - if not self.setParamValue(param, self.valueItems[param.name]): - return False + if not self.setParamValue(alg, param, self.valueItems[param.name]): + return None for output in outputs: - if output.hidden: - self.outputs[output.name] = None - else: + if not output.hidden: name = unicode(self.valueItems[output.name].text()) - if name.strip() != '' and name \ - != ModelerParametersDialog.ENTER_NAME: - self.outputs[output.name] = name - else: - self.outputs[output.name] = None + if name.strip() != '' and name != ModelerParametersDialog.ENTER_NAME: + alg.outputs[output.name] = Output(name) selectedOptions = self.dependenciesPanel.selectedoptions - - # This index are based on the list of available dependencies. - # we translate them into indices based on the whole set of - # algorithm in the model. We just take the values in the - # beginning of the string representing the algorithm. availableDependencies = self.getAvailableDependencies() self.dependencies = [] for selected in selectedOptions: s = availableDependencies[selected] - n = int(s[:s.find(':')]) - 1 - self.dependencies.append(n) + alg.dependencies.append(s) - return True + return alg - def setParamValueLayerOrTable(self, param, widget): + def setParamValueLayerOrTable(self, alg, param, widget): idx = widget.currentIndex() if idx < 0: return False else: value = widget.itemData(widget.currentIndex()) - self.params[param.name] = value + alg.params[param.name] = value return True - def setParamBooleanValue(self, param, widget): - if widget.currentIndex() < 2: - name = self.getSafeNameForHarcodedParameter(param) - value = AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, name) - self.params[param.name] = value - self.values[name] = str(widget.currentIndex() == 0) - else: - value = widget.itemData(widget.currentIndex()) - self.params[param.name] = value - return True - def setParamTableFieldValue(self, param, widget): + def setParamTableFieldValue(self, alg, param, widget): idx = widget.findText(widget.currentText()) - if idx < 0: - name = self.getSafeNameForHarcodedParameter(param) - value = AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, name) + if idx < 0: s = str(widget.currentText()).strip() if s == '': if param.optional: - self.params[param.name] = None + alg.params[param.name] = None return True else: return False - else: - self.params[param.name] = value - self.values[name] = str(widget.currentText()) + else: + alg.values[param.name] = s return True else: - value = widget.itemData(widget.currentIndex()) - self.params[param.name] = value + alg.params[param.name] = widget.itemData(widget.currentIndex()) return True - def setParamStringValue(self, param, widget): - if param.multiline: - name = self.getSafeNameForHarcodedParameter(param) - paramValue = AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, name) + def setParamStringValue(self, alg, param, widget): + if param.multiline: value = widget.getValue() option = widget.getOption() if option == MultilineTextPanel.USE_TEXT: if value == '': if param.optional: - self.params[param.name] = None + alg.params[param.name] = None return True else: return False else: - self.values[name] = value - - self.params[param.name] = paramValue + alg.params[param.name] = value else: - self.params[param.name] = value - else: - #if widget.currentText() == '': - # return False + alg.params[param.name] = value + else: idx = widget.findText(widget.currentText()) - if idx < 0: - name = self.getSafeNameForHarcodedParameter(param) - value = AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, name) - self.params[param.name] = value - value = str(widget.currentText()).strip() + if idx < 0: + value = widget.currentText().strip() if value == '': if param.optional: - self.values[name] = None + alg.params[param.name] = None return True else: return False else: - self.values[name] = str(widget.currentText()) + alg.params[param.name] = value else: - value = widget.itemData(widget.currentIndex()) - self.params[param.name] = value + alg.params[param.name] = widget.itemData(widget.currentIndex()) return True - def setParamFileValue(self, param, widget): + def setParamFileValue(self, alg, param, widget): idx = widget.findText(widget.currentText()) - if idx < 0: - name = self.getSafeNameForHarcodedParameter(param) - value = AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, name) - self.params[param.name] = value - s = str(widget.currentText()) - self.values[name] = s + if idx < 0: + value = widget.currentText() else: value = widget.itemData(widget.currentIndex()) - self.params[param.name] = value + alg.params[param.name] = value return True - def setParamNumberValue(self, param, widget): + def setParamNumberValue(self, alg, param, widget): idx = widget.findText(widget.currentText()) - if idx < 0: - name = self.getSafeNameForHarcodedParameter(param) - value = AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, name) - self.params[param.name] = value - s = str(widget.currentText()) + if idx < 0: + s = widget.currentText() try: - float(s) - self.values[name] = s + value = float(s) except: return False else: value = widget.itemData(widget.currentIndex()) - self.params[param.name] = value + alg.params[param.name] = value return True - def setParamExtentValue(self, param, widget): + def setParamExtentValue(self, alg, param, widget): idx = widget.findText(widget.currentText()) - if idx < 0: - name = self.getSafeNameForHarcodedParameter(param) - value = AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, name) - self.params[param.name] = value + if idx < 0: s = str(widget.currentText()) try: tokens = s.split(',') @@ -819,101 +593,72 @@ class ModelerParametersDialog(QtGui.QDialog): for token in tokens: float(token) except: - return False - self.values[name] = s + return False + alg.params[param.name] = [s] else: value = widget.itemData(widget.currentIndex()) - self.params[param.name] = value + alg.params[param.name] = value return True - def setParamValue(self, param, widget): + def setParamValue(self, alg, param, widget): if isinstance(param, (ParameterRaster, ParameterVector, ParameterTable)): - return self.setParamValueLayerOrTable(param, widget) + return self.setParamValueLayerOrTable(alg, param, widget) elif isinstance(param, ParameterBoolean): - return self.setParamBooleanValue(param, widget) + if widget.currentIndex() < 2: + value = widget.currentIndex() == 0 + else: + value = widget.itemData(widget.currentIndex()) + alg.params[param.name] = value + return True elif isinstance(param, ParameterString): - return self.setParamStringValue(param, widget) + return self.setParamStringValue(alg, param, widget) elif isinstance(param, ParameterNumber): - return self.setParamNumberValue(param, widget) + return self.setParamNumberValue(alg, param, widget) elif isinstance(param, ParameterExtent): - return self.setParamExtentValue(param, widget) + return self.setParamExtentValue(alg, param, widget) elif isinstance(param, ParameterFile): - return self.setParamFileValue(param, widget) + return self.setParamFileValue(alg, param, widget) elif isinstance(param, ParameterSelection): - name = self.getSafeNameForHarcodedParameter(param) - value = AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, name) - self.params[param.name] = value - self.values[name] = str(widget.currentIndex()) - return True + alg.params[param.name] = widget.currentIndex() + return True elif isinstance(param, ParameterRange): - name = self.getSafeNameForHarcodedParameter(param) - value = AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, name) - self.params[param.name] = value - self.values[name] = str(widget.getValue()) - return True + alg.params[param.name] = widget.getValue() + return True elif isinstance(param, ParameterCrs): authid = widget.getValue() if authid is None: - self.params[param.name] = None + alg.params[param.name] = None else: - name = self.getSafeNameForHarcodedParameter(param) - value = AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, name) - self.params[param.name] = value - self.values[name] = authid + alg.params[param.name] = authid return True elif isinstance(param, ParameterFixedTable): - name = self.getSafeNameForHarcodedParameter(param) - value = AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, name) - self.params[param.name] = value - self.values[name] = ParameterFixedTable.tableToString(widget.table) - return True + alg.params[param.name] = ParameterFixedTable.tableToString(widget.table) + return True elif isinstance(param, ParameterTableField): - return self.setParamTableFieldValue(param, widget) + return self.setParamTableFieldValue(alg, param, widget) elif isinstance(param, ParameterMultipleInput): if param.datatype == ParameterMultipleInput.TYPE_VECTOR_ANY: - options = self.getVectorLayers() + options = self.getAvailableValuesOfType(ParameterVector, OutputVector) else: - options = self.getRasterLayers() - values = [] - for index in widget.selectedoptions: - values.append(options[index].serialize()) + options = self.getAvailableValuesOfType(ParameterRaster, OutputRaster) + values = [options[i] for i in widget.selectedoptions] if len(values) == 0 and not param.optional: - return False - name = self.getSafeNameForHarcodedParameter(param) - value = AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, name) - self.params[param.name] = value - self.values[name] = ';'.join(values) + return False + alg.params[param.name] = values return True else: - name = self.getSafeNameForHarcodedParameter(param) - value = AlgorithmAndParameter( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, name) - self.params[param.name] = value - self.values[name] = unicode(widget.text()) + alg.params[param.name] = unicode(widget.text()) return True - def getSafeNameForHarcodedParameter(self, param): - if self.algIndex is None: - return 'HARDCODEDPARAMVALUE_' + param.name + '_' \ - + str(len(self.model.algs)) - else: - return 'HARDCODEDPARAMVALUE_' + param.name + '_' \ - + str(self.algIndex) - def okPressed(self): - if self.setParamValues(): + self.alg = self.createAlgorithm() + if self.alg is not None: self.close() else: - QMessageBox.critical(self, 'Unable to add algorithm', - 'Wrong or missing parameter values') - self.params = None + QMessageBox.warning(self, 'Unable to add algorithm', + 'Wrong or missing parameter values') def cancelPressed(self): - self.params = None + self.alg = None self.close() diff --git a/python/plugins/processing/modeler/ModelerScene.py b/python/plugins/processing/modeler/ModelerScene.py index ca4320acf65..45ba72bebb1 100644 --- a/python/plugins/processing/modeler/ModelerScene.py +++ b/python/plugins/processing/modeler/ModelerScene.py @@ -17,6 +17,7 @@ *************************************************************************** """ + __author__ = 'Victor Olaya' __date__ = 'August 2012' __copyright__ = '(C) 2012, Victor Olaya' @@ -26,160 +27,118 @@ __copyright__ = '(C) 2012, Victor Olaya' __revision__ = '$Format:%H$' from PyQt4 import QtCore, QtGui -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from processing.modeler.ModelerAlgorithm import AlgorithmAndParameter from processing.modeler.ModelerGraphicItem import ModelerGraphicItem from processing.modeler.ModelerArrowItem import ModelerArrowItem -from processing.parameters.ParameterMultipleInput import ParameterMultipleInput - +from processing.modeler.ModelerAlgorithm import ValueFromInput, ValueFromOutput class ModelerScene(QtGui.QGraphicsScene): def __init__(self, parent=None): super(ModelerScene, self).__init__(parent) - self.paramItems = [] - self.algItems = [] - self.outputItems = [] + self.paramItems = {} + self.algItems = {} + self.outputItems = {} self.setItemIndexMethod(QtGui.QGraphicsScene.NoIndex) def getParameterPositions(self): - pos = [] - for item in self.paramItems: - pos.append(item.pos()) - return pos + return {key : item.pos() for key,item in self.paramItems.iteritems()} def getAlgorithmPositions(self): - pos = [] - for item in self.algItems: - pos.append(item.pos()) - return pos + return {key : item.pos() for key,item in self.algItems.iteritems()} def getOutputPositions(self): - pos = [] - for alg in self.outputItems: + pos = {} + for algName, outputs in self.outputItems.iteritems(): outputPos = {} - for (key, value) in alg.iteritems(): + for (key, value) in outputs.iteritems(): if value is not None: outputPos[key] = value.pos() else: outputPos[key] = None - pos.append(outputPos) + pos[algName] = outputPos return pos - def getLastParameterItem(self): - return self.paramItems[-1] - - def getLastAlgorithmItem(self): - if self.algItems: - return self.algItems[-1] - else: - return None - - def getItemsFromAAP(self, aap, isMultiple): + def getItemsFromParamValue(self, value): items = [] - start = int(aap.alg) - if aap.alg == AlgorithmAndParameter.PARENT_MODEL_ALGORITHM: - if isMultiple: - multi = self.model.paramValues[aap.param] - tokens = multi.split(';') - for token in tokens: - aap = AlgorithmAndParameter(float(token.split('|')[0]), - token.split('|')[1]) - ret = self.getItemsFromAAP(aap, False) - if ret: - for item in ret: - items.append(item) - else: - iModelParam = 0 - for modelparam in self.model.parameters: - if modelparam.name == aap.param: - items.append((self.paramItems[iModelParam], -1)) - break - iModelParam += 1 - else: - idx = 0 - for output in self.model.algs[start].outputs: - if output.name == aap.param: - items.append((self.algItems[start], idx)) - break - idx += 1 - - return items - + if isinstance(value, list): + for v in value: + items.extend(self.getItemsFromParamValue(v)) + elif isinstance(value, ValueFromInput): + items.append((self.paramItems[value.name], 0)) + elif isinstance(value, ValueFromOutput): + outputs = self.model.algs[value.alg].algorithm.outputs + for i, out in enumerate(outputs): + if out.name == value.output: + break + items.append((self.algItems[value.alg], i)) + return items + def paintModel(self, model): self.model = model - i = 0 - # Inputs - for param in model.parameters: - item = ModelerGraphicItem(param, i, model) + for inp in model.inputs.values(): + item = ModelerGraphicItem(inp, model) item.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True) item.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True) self.addItem(item) - item.setPos(model.paramPos[i]) - self.paramItems.append(item) - i += 1 + item.setPos(inp.pos.x(), inp.pos.y()) + self.paramItems[inp.param.name] = item - # We add the algs - iAlg = 0 - for alg in model.algs: - item = ModelerGraphicItem(alg, iAlg, model) + # We add the algs + for alg in model.algs.values(): + item = ModelerGraphicItem(alg, model) item.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True) item.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True) self.addItem(item) - item.setPos(model.algPos[iAlg]) - self.algItems.append(item) - iAlg += 1 + item.setPos(alg.pos.x(), alg.pos.y()) + self.algItems[alg.name] = item # And then the arrows - iAlg = 0 - for alg in model.algs: - params = model.algParameters[iAlg] + for alg in model.algs.values(): idx = 0 - for parameter in alg.parameters: + for parameter in alg.algorithm.parameters: if not parameter.hidden: - param = params[parameter.name] - if param: - sourceItems = self.getItemsFromAAP(param, - isinstance(alg.getParameterFromName(parameter.name), ParameterMultipleInput)) - for sourceItem in sourceItems: - arrow = ModelerArrowItem(sourceItem[0], sourceItem[1], self.algItems[iAlg], idx) - self.addItem(arrow) + value = alg.params[parameter.name] + sourceItems = self.getItemsFromParamValue(value) + for sourceItem, sourceIdx in sourceItems: + arrow = ModelerArrowItem(sourceItem, sourceIdx, self.algItems[alg.name], idx) + self.addItem(arrow) idx += 1 - for depend in model.dependencies[iAlg]: + for depend in alg.dependencies: arrow = ModelerArrowItem(self.algItems[depend], -1, - self.algItems[iAlg], -1) + self.algItems[alg.name], -1) self.addItem(arrow) - iAlg += 1 # And finally the outputs - for (iAlg, alg) in enumerate(model.algs): - outputs = model.algOutputs[iAlg] + for alg in model.algs.values(): + outputs = alg.outputs outputItems = {} - for (idx, key) in enumerate(outputs.keys()): + idx = 0 + for key in outputs: out = outputs[key] if out is not None: - item = ModelerGraphicItem(out, idx, model) + item = ModelerGraphicItem(out, model) item.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True) item.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True) self.addItem(item) - pos = model.outputPos[iAlg][key] + pos = alg.outputs[key].pos if pos is None: - pos = self.algItems[iAlg].pos() \ - + QtCore.QPointF(ModelerGraphicItem.BOX_WIDTH, 0) \ - + self.algItems[iAlg].getLinkPointForOutput(idx) + pos = (alg.pos() + QtCore.QPointF(ModelerGraphicItem.BOX_WIDTH, 0) + + self.algItems[alg.name].getLinkPointForOutput(idx)) item.setPos(pos) outputItems[key] = item - arrow = ModelerArrowItem(self.algItems[iAlg], idx, item, + arrow = ModelerArrowItem(self.algItems[alg.name], idx, item, -1) self.addItem(arrow) + idx += 1 else: outputItems[key] = None - self.outputItems.append(outputItems) + self.outputItems[alg.name] = outputItems def mousePressEvent(self, mouseEvent): if mouseEvent.button() != QtCore.Qt.LeftButton: return - super(ModelerScene, self).mousePressEvent(mouseEvent) + + + diff --git a/python/plugins/processing/modeler/ModelerUtils.py b/python/plugins/processing/modeler/ModelerUtils.py index a664ae17385..f5279a40b66 100644 --- a/python/plugins/processing/modeler/ModelerUtils.py +++ b/python/plugins/processing/modeler/ModelerUtils.py @@ -31,6 +31,9 @@ from processing.core.ProcessingConfig import ProcessingConfig class ModelerUtils: + + allAlgs = {} + providers = {} MODELS_FOLDER = 'MODELS_FOLDER' ACTIVATE_MODELS = 'ACTIVATE_MODELS' @@ -51,6 +54,3 @@ class ModelerUtils: return provider[name] return None - @staticmethod - def getAlgorithms(): - return ModelerUtils.allAlgs diff --git a/python/plugins/processing/modeler/MultilineTextPanel.py b/python/plugins/processing/modeler/MultilineTextPanel.py index 15450176ebf..d2d3cac8a24 100644 --- a/python/plugins/processing/modeler/MultilineTextPanel.py +++ b/python/plugins/processing/modeler/MultilineTextPanel.py @@ -63,15 +63,11 @@ class MultilineTextPanel(QtGui.QWidget): return self.combo.itemData(self.combo.currentIndex()) def setValue(self, value): - items = [self.combo.itemData(i) for i in range(1, self.combo.count())] - idx = 0 - for item in items: - idx += 1 - if item and value: - if item.alg == value.alg and item.param == value.param: - self.combo.setCurrentIndex(idx) - return - self.combo.setCurrentIndex(0) - value = self.model.getValueFromAlgorithmAndParameter(value) + items = [self.combo.itemData(i) for i in range(1, self.combo.count())] + for idx, item in enumerate(items): + if item == value: + self.combo.setCurrentIndex(idx) + return + self.combo.setCurrentIndex(0) if value: - self.textBox.setPlainText(str(value)) + self.textBox.setPlainText(value) diff --git a/python/plugins/processing/modeler/Providers.py b/python/plugins/processing/modeler/Providers.py deleted file mode 100644 index f7a6390ae8b..00000000000 --- a/python/plugins/processing/modeler/Providers.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - Providers.py - --------------------- - Date : August 2012 - Copyright : (C) 2012 by Victor Olaya - Email : volayaf at gmail dot com -*************************************************************************** -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) any later version. * -* * -*************************************************************************** -""" - -__author__ = 'Victor Olaya' -__date__ = 'August 2012' -__copyright__ = '(C) 2012, Victor Olaya' - -# This will get replaced with a git SHA1 when you do a git archive - -__revision__ = '$Format:%H$' - - -class Providers: - - providers = {} diff --git a/python/plugins/processing/modeler/SaveAsPythonScriptAction.py b/python/plugins/processing/modeler/SaveAsPythonScriptAction.py deleted file mode 100644 index de24162c640..00000000000 --- a/python/plugins/processing/modeler/SaveAsPythonScriptAction.py +++ /dev/null @@ -1,126 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - SaveAsPythonScriptAction.py - --------------------- - Date : April 2013 - Copyright : (C) 2013 by Victor Olaya - Email : volayaf at gmail dot com -*************************************************************************** -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) any later version. * -* * -*************************************************************************** -""" - -__author__ = 'Victor Olaya' -__date__ = 'April 2013' -__copyright__ = '(C) 2013, Victor Olaya' - -# This will get replaced with a git SHA1 when you do a git archive - -__revision__ = '$Format:%H$' - -import sys -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from processing.gui.ContextAction import ContextAction -from processing.modeler.ModelerAlgorithm import ModelerAlgorithm, \ - AlgorithmAndParameter -from processing.script.ScriptUtils import ScriptUtils -from processing.parameters.ParameterMultipleInput import ParameterMultipleInput - -class SaveAsPythonScriptAction(ContextAction): - - def __init__(self): - self.name = 'Save as Python script' - - def isEnabled(self): - return isinstance(self.alg, ModelerAlgorithm) - - def execute(self): - filename = str(QFileDialog.getSaveFileName(None, 'Save Script', - ScriptUtils.scriptsFolder(), 'Python scripts (*.py)')) - - if filename: - if not filename.endswith('.py'): - filename += '.py' - text = self.translateToPythonCode(self.alg) - try: - fout = open(filename, 'w') - fout.write(text) - fout.close() - if filename.replace('\\', '/').startswith( - ScriptUtils.scriptsFolder().replace('\\', '/')): - self.toolbox.updateProvider('script') - except: - QMessageBox.warning(self, self.tr('I/O error'), - self.tr('Unable to save edits. Reason:\n %s') - % unicode(sys.exc_info()[1])) - - def translateToPythonCode(self, model): - s = ['##' + model.name + '=name'] - for param in model.parameters: - s.append(str(param.getAsScriptCode().lower())) - i = 0 - for outs in model.algOutputs: - for out in outs.keys(): - if outs[out]: - s.append('##' + out.lower() + '_alg' + str(i) + '=' - + model.getOutputType(i, out)) - i += 1 - i = 0 - iMultiple = 0 - for alg in model.algs: - multiple = [] - runline = 'outputs_' + str(i) + '=processing.runalg("' \ - + alg.commandLineName() + '"' - for param in alg.parameters: - aap = model.algParameters[i][param.name] - if aap is None: - runline += ', None' - elif isinstance(param, ParameterMultipleInput): - value = model.paramValues[aap.param] - tokens = value.split(';') - layerslist = [] - for token in tokens: - (iAlg, paramname) = token.split('|') - if float(iAlg) == float( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM): - if model.ismodelparam(paramname): - value = paramname.lower() - else: - value = model.paramValues[paramname] - else: - value = 'outputs_' + str(iAlg) + "['" + paramname \ - + "']" - layerslist.append(str(value)) - - multiple.append('multiple_' + str(iMultiple) + '=[' - + ','.join(layerslist) + ']') - runline += ', ";".join(multiple_' + str(iMultiple) + ') ' - else: - if float(aap.alg) == float( - AlgorithmAndParameter.PARENT_MODEL_ALGORITHM): - if model.ismodelparam(aap.param): - runline += ', ' + aap.param.lower() - else: - runline += ', ' + str(model.paramValues[aap.param]) - else: - runline += ', outputs_' + str(aap.alg) + "['" \ - + aap.param + "']" - for out in alg.outputs: - value = model.algOutputs[i][out.name] - if value: - name = out.name.lower() + '_alg' + str(i) - else: - name = str(None) - runline += ', ' + name - i += 1 - s += multiple - s.append(str(runline + ')')) - return '\n'.join(s) diff --git a/python/plugins/processing/modeler/models/Extract_raster_to_CSV.model b/python/plugins/processing/modeler/models/Extract_raster_to_CSV.model deleted file mode 100644 index 42923c80ad8..00000000000 --- a/python/plugins/processing/modeler/models/Extract_raster_to_CSV.model +++ /dev/null @@ -1,21 +0,0 @@ -NAME:Extract raster values (CSV) -GROUP:[Example models] -PARAMETER:ParameterVector|VECTORLAYER_INPUTVECTOR|Input vector|-1|False -114.0,29.0 -PARAMETER:ParameterRaster|RASTERLAYER_INPUTRASTER|Input raster|False -333.0,30.0 -PARAMETER:ParameterBoolean|BOOLEAN_REPROJECTVECTORTORASTERCRS|Reproject vector to raster CRS|False -552.0,31.0 -ALGORITHM:script:pointsfromvector -123.0,146.0 -None --1|RASTERLAYER_INPUTRASTER --1|VECTORLAYER_INPUTVECTOR -None -ALGORITHM:script:extractrastervaluestocsv -398.0,201.0 -None --1|RASTERLAYER_INPUTRASTER -0|Output_layer --1|BOOLEAN_REPROJECTVECTORTORASTERCRS -Output CSV file|555.0,284.0 diff --git a/python/plugins/processing/modeler/models/Extract_raster_to_shape.model b/python/plugins/processing/modeler/models/Extract_raster_to_shape.model deleted file mode 100644 index 56078923e41..00000000000 --- a/python/plugins/processing/modeler/models/Extract_raster_to_shape.model +++ /dev/null @@ -1,21 +0,0 @@ -NAME:Extract raster values (shapefile) -GROUP:[Example models] -PARAMETER:ParameterVector|VECTORLAYER_INPUTVECTOR|Input vector|-1|False -114.0,29.0 -PARAMETER:ParameterRaster|RASTERLAYER_INPUTRASTER|Input raster|False -333.0,30.0 -PARAMETER:ParameterBoolean|BOOLEAN_REPROJECTVECTORTORASTERCRS|Reproject vector to raster CRS|False -552.0,31.0 -ALGORITHM:script:pointsfromvector -123.0,146.0 -None --1|RASTERLAYER_INPUTRASTER --1|VECTORLAYER_INPUTVECTOR -None -ALGORITHM:script:extractrastervaluestoshapefile -343.0,246.0 -None --1|RASTERLAYER_INPUTRASTER -0|Output_layer --1|BOOLEAN_REPROJECTVECTORTORASTERCRS -Output layer|533.0,321.0 diff --git a/python/plugins/processing/modeler/models/contours.model b/python/plugins/processing/modeler/models/contours.model deleted file mode 100644 index 38de252e36f..00000000000 --- a/python/plugins/processing/modeler/models/contours.model +++ /dev/null @@ -1,43 +0,0 @@ -NAME:Contour lines from points -GROUP:[Example models] -PARAMETER:ParameterVector|VECTORLAYER_SHAPES|Shapes|-1|False -120.0,60.0 -PARAMETER:ParameterNumber|NUMBER_INTERVAL|Interval|0.0|None|10.0 -633.0,163.0 -PARAMETER:ParameterNumber|NUMBER_CELLSIZEFORINTERMEDIATELAYER|Cellsize for intermediate layer|0.0|None|10.0 -411.0,84.0 -PARAMETER:ParameterTableField|TABLEFIELD_FIELD|Field|VECTORLAYER_SHAPES|-1|False -104.0,202.0 -VALUE:HARDCODEDPARAMVALUE_LINE_TYPE_0===0 -VALUE:HARDCODEDPARAMVALUE_ZMAX_2===10000.0 -VALUE:HARDCODEDPARAMVALUE_USER_SIZE_0===1.0 -VALUE:HARDCODEDPARAMVALUE_ZMIN_2===0.0 -VALUE:HARDCODEDPARAMVALUE_THRESHOLD_1===0.1 -VALUE:HARDCODEDPARAMVALUE_MULTIPLE_0===0 -VALUE:HARDCODEDPARAMVALUE_GRID_TYPE_0===3 -ALGORITHM:saga:shapestogrid -348.0,211.0 -None --1|VECTORLAYER_SHAPES --1|TABLEFIELD_FIELD --1|HARDCODEDPARAMVALUE_MULTIPLE_0 --1|HARDCODEDPARAMVALUE_LINE_TYPE_0 --1|HARDCODEDPARAMVALUE_GRID_TYPE_0 -None --1|NUMBER_CELLSIZEFORINTERMEDIATELAYER -None -ALGORITHM:saga:closegaps -450.0,379.0 -None -0|USER_GRID -None --1|HARDCODEDPARAMVALUE_THRESHOLD_1 -None -ALGORITHM:saga:contourlinesfromgrid -630.0,279.0 -None -1|RESULT --1|HARDCODEDPARAMVALUE_ZMIN_2 --1|HARDCODEDPARAMVALUE_ZMAX_2 --1|NUMBER_INTERVAL -Contour lines diff --git a/python/plugins/processing/modeler/models/emptystring.model b/python/plugins/processing/modeler/models/emptystring.model deleted file mode 100644 index 00575bd2ae3..00000000000 --- a/python/plugins/processing/modeler/models/emptystring.model +++ /dev/null @@ -1,20 +0,0 @@ -NAME:A model with an empty string -GROUP:[Test models] -PARAMETER:ParameterVector|VECTORLAYER_V|v|-1|False -120.0,60.0 -VALUE:HARDCODEDPARAMVALUE_FORMULA_0===value = 10 -VALUE:HARDCODEDPARAMVALUE_FIELD_PRECISION_0===0 -VALUE:HARDCODEDPARAMVALUE_FIELD_TYPE_0===0 -VALUE:HARDCODEDPARAMVALUE_FIELD_LENGTH_0===10 -VALUE:HARDCODEDPARAMVALUE_FIELD_NAME_0===NewField -ALGORITHM:qgis:advancedpythonfieldcalculator -120.0,160.0 -None --1|VECTORLAYER_V --1|HARDCODEDPARAMVALUE_FIELD_NAME_0 --1|HARDCODEDPARAMVALUE_FIELD_TYPE_0 --1|HARDCODEDPARAMVALUE_FIELD_LENGTH_0 --1|HARDCODEDPARAMVALUE_FIELD_PRECISION_0 -None --1|HARDCODEDPARAMVALUE_FORMULA_0 -out diff --git a/python/plugins/processing/modeler/models/fieldautoextent.model b/python/plugins/processing/modeler/models/fieldautoextent.model deleted file mode 100644 index ddfe5c90568..00000000000 --- a/python/plugins/processing/modeler/models/fieldautoextent.model +++ /dev/null @@ -1,21 +0,0 @@ -NAME:Model using field input and autoextent -GROUP:[Test models] -PARAMETER:ParameterVector|VECTORLAYER_VECTOR|vector|2|False -120.0,60.0 -PARAMETER:ParameterTableField|TABLEFIELD_FIELD|field|VECTORLAYER_VECTOR|-1|False -340.0,60.0 -VALUE:HARDCODEDPARAMVALUE_LINE_TYPE_0===0 -VALUE:HARDCODEDPARAMVALUE_GRID_TYPE_0===3 -VALUE:HARDCODEDPARAMVALUE_MULTIPLE_0===0 -VALUE:HARDCODEDPARAMVALUE_USER_SIZE_0===10 -ALGORITHM:saga:shapestogrid -207.0,202.0 -None --1|VECTORLAYER_VECTOR --1|TABLEFIELD_FIELD --1|HARDCODEDPARAMVALUE_MULTIPLE_0 --1|HARDCODEDPARAMVALUE_LINE_TYPE_0 --1|HARDCODEDPARAMVALUE_GRID_TYPE_0 -None --1|HARDCODEDPARAMVALUE_USER_SIZE_0 -grid diff --git a/python/plugins/processing/modeler/models/fieldextent.model b/python/plugins/processing/modeler/models/fieldextent.model deleted file mode 100644 index c5593177774..00000000000 --- a/python/plugins/processing/modeler/models/fieldextent.model +++ /dev/null @@ -1,23 +0,0 @@ -NAME:Model using field input and extent input -GROUP:[Test models] -PARAMETER:ParameterVector|VECTORLAYER_LAYER|layer|-1|False -120.0,60.0 -PARAMETER:ParameterExtent|EXTENT_EXTENT|extent|0,1,0,1 -340.0,60.0 -PARAMETER:ParameterTableField|TABLEFIELD_FIELD|field|VECTORLAYER_LAYER|-1|False -560.0,60.0 -VALUE:HARDCODEDPARAMVALUE_LINE_TYPE_0===0 -VALUE:HARDCODEDPARAMVALUE_GRID_TYPE_0===3 -VALUE:HARDCODEDPARAMVALUE_MULTIPLE_0===0 -VALUE:HARDCODEDPARAMVALUE_USER_SIZE_0===25 -ALGORITHM:saga:shapestogrid -338.0,247.0 -None --1|VECTORLAYER_LAYER --1|TABLEFIELD_FIELD --1|HARDCODEDPARAMVALUE_MULTIPLE_0 --1|HARDCODEDPARAMVALUE_LINE_TYPE_0 --1|HARDCODEDPARAMVALUE_GRID_TYPE_0 --1|EXTENT_EXTENT --1|HARDCODEDPARAMVALUE_USER_SIZE_0 -rasterized diff --git a/python/plugins/processing/modeler/models/modelscript.model b/python/plugins/processing/modeler/models/modelscript.model deleted file mode 100644 index e78cd201772..00000000000 --- a/python/plugins/processing/modeler/models/modelscript.model +++ /dev/null @@ -1,26 +0,0 @@ -NAME:A model with a script -GROUP:[Test models] -PARAMETER:ParameterVector|VECTORLAYER_VECTOR|vector|-1|False -238.0,94.0 -VALUE:HARDCODEDPARAMVALUE_DY_1===0.0 -VALUE:HARDCODEDPARAMVALUE_ANGLE_1===0.0 -VALUE:HARDCODEDPARAMVALUE_SCALEX_1===1.0 -VALUE:HARDCODEDPARAMVALUE_ANCHORX_1===0.0 -VALUE:HARDCODEDPARAMVALUE_ANCHORY_1===0.0 -VALUE:HARDCODEDPARAMVALUE_SCALEY_1===1.0 -ALGORITHM:script:ascriptthatreturnsanumber -546.0,150.0 -None -None -ALGORITHM:saga:transformshapes -310.0,304.0 -None --1|VECTORLAYER_VECTOR -0|number --1|HARDCODEDPARAMVALUE_DY_1 --1|HARDCODEDPARAMVALUE_ANGLE_1 --1|HARDCODEDPARAMVALUE_SCALEX_1 --1|HARDCODEDPARAMVALUE_SCALEY_1 --1|HARDCODEDPARAMVALUE_ANCHORX_1 --1|HARDCODEDPARAMVALUE_ANCHORY_1 -displaced diff --git a/python/plugins/processing/modeler/models/noinputs.model b/python/plugins/processing/modeler/models/noinputs.model deleted file mode 100644 index bede65c1f54..00000000000 --- a/python/plugins/processing/modeler/models/noinputs.model +++ /dev/null @@ -1,27 +0,0 @@ -NAME:A model without inputs -GROUP:[Test models] -VALUE:HARDCODEDPARAMVALUE_HSPACING_0===10.0 -VALUE:HARDCODEDPARAMVALUE_CENTERY_0===0.0 -VALUE:HARDCODEDPARAMVALUE_CRS_0===EPSG:4326 -VALUE:HARDCODEDPARAMVALUE_HEIGHT_0===180.0 -VALUE:HARDCODEDPARAMVALUE_GRIDTYPE_0===0 -VALUE:HARDCODEDPARAMVALUE_CENTERX_0===0.0 -VALUE:HARDCODEDPARAMVALUE_WIDTH_0===360.0 -VALUE:HARDCODEDPARAMVALUE_VSPACING_0===10.0 -ALGORITHM:qgis:creategrid -113.0,48.0 -None --1|HARDCODEDPARAMVALUE_HSPACING_0 --1|HARDCODEDPARAMVALUE_VSPACING_0 --1|HARDCODEDPARAMVALUE_WIDTH_0 --1|HARDCODEDPARAMVALUE_HEIGHT_0 --1|HARDCODEDPARAMVALUE_CENTERX_0 --1|HARDCODEDPARAMVALUE_CENTERY_0 --1|HARDCODEDPARAMVALUE_GRIDTYPE_0 --1|HARDCODEDPARAMVALUE_CRS_0 -None -ALGORITHM:qgis:polygoncentroids -333.0,148.0 -None -0|SAVENAME -centroids|531.0,259.0 diff --git a/python/plugins/processing/modeler/models/notinorder.model b/python/plugins/processing/modeler/models/notinorder.model deleted file mode 100644 index f1069bcac42..00000000000 --- a/python/plugins/processing/modeler/models/notinorder.model +++ /dev/null @@ -1,39 +0,0 @@ -NAME:Model with algorithms not in running order -GROUP:[Test models] -PARAMETER:ParameterRaster|RASTERLAYER_RASTER|raster|False -120.0,60.0 -VALUE:HARDCODEDPARAMVALUE_MINSLOPE_1===0.01 -VALUE:HARDCODEDPARAMVALUE_Method_0===0 -VALUE:HARDCODEDPARAMVALUE_STEP_0===1 -VALUE:HARDCODEDPARAMVALUE_DOLINEAR _0===True -VALUE:HARDCODEDPARAMVALUE_LINEARTHRS_0===500.0 -VALUE:HARDCODEDPARAMVALUE_CONVERGENCE_0===1.0 -ALGORITHM:saga:catchmentareaparallel -154.0,415.0 -None -1|RESULT -None -None -None -None --1|HARDCODEDPARAMVALUE_STEP_0 --1|HARDCODEDPARAMVALUE_Method_0 --1|HARDCODEDPARAMVALUE_DOLINEAR _0 --1|HARDCODEDPARAMVALUE_LINEARTHRS_0 -None -None --1|HARDCODEDPARAMVALUE_CONVERGENCE_0 -catchment area -None -None -None -None -None -None -None -ALGORITHM:saga:fillsinks -340.0,260.0 -None --1|RASTERLAYER_RASTER --1|HARDCODEDPARAMVALUE_MINSLOPE_1 -None diff --git a/python/plugins/processing/modeler/models/numericaloutput.model b/python/plugins/processing/modeler/models/numericaloutput.model deleted file mode 100644 index b7d76022978..00000000000 --- a/python/plugins/processing/modeler/models/numericaloutput.model +++ /dev/null @@ -1,32 +0,0 @@ -NAME:Model using numerical output -GROUP:[Test models] -PARAMETER:ParameterVector|VECTORLAYER_LAYER|layer|-1|False -120.0,60.0 -PARAMETER:ParameterTableField|TABLEFIELD_FIELD|field|VECTORLAYER_LAYER|-1|False -340.0,60.0 -VALUE:HARDCODEDPARAMVALUE_SEGMENTS_1===5 -VALUE:HARDCODEDPARAMVALUE_DISSOLVE_1===True -ALGORITHM:qgis:basicstatisticsfornumericfields -343.0,220.0 -None --1|VECTORLAYER_LAYER --1|TABLEFIELD_FIELD -None -None -None -None -None -None -None -None -None -None -None -ALGORITHM:qgis:fixeddistancebuffer -120.0,327.0 -None --1|VECTORLAYER_LAYER -0|MEAN --1|HARDCODEDPARAMVALUE_SEGMENTS_1 --1|HARDCODEDPARAMVALUE_DISSOLVE_1 -buffer diff --git a/python/plugins/processing/modeler/models/optionalfield.model b/python/plugins/processing/modeler/models/optionalfield.model deleted file mode 100644 index fe3247b38fe..00000000000 --- a/python/plugins/processing/modeler/models/optionalfield.model +++ /dev/null @@ -1,12 +0,0 @@ -NAME:A model with an optional field -GROUP:[Test models] -PARAMETER:ParameterVector|VECTORLAYER_V|v|-1|False -120.0,60.0 -VALUE:HARDCODEDPARAMVALUE_METHOD_0===0 -ALGORITHM:qgis:convexhull -135.0,186.0 -None --1|VECTORLAYER_V -None --1|HARDCODEDPARAMVALUE_METHOD_0 -result diff --git a/python/plugins/processing/modeler/models/sagagrass.model b/python/plugins/processing/modeler/models/sagagrass.model deleted file mode 100644 index a705eb061d3..00000000000 --- a/python/plugins/processing/modeler/models/sagagrass.model +++ /dev/null @@ -1,29 +0,0 @@ -NAME:A SAGA and GRASS model -GROUP:[Test models] -PARAMETER:ParameterVector|VECTORLAYER_VECTOR|vector|-1|False -120.0,60.0 -VALUE:HARDCODEDPARAMVALUE_GRASS_MIN_AREA_PARAMETER_0===0.0001 -VALUE:HARDCODEDPARAMVALUE_GRASS_SNAP_TOLERANCE_PARAMETER_0===-1.0 -VALUE:HARDCODEDPARAMVALUE_METHOD _1===False -VALUE:HARDCODEDPARAMVALUE_-c_0===True -VALUE:HARDCODEDPARAMVALUE_tolerance_0===0.01 -VALUE:HARDCODEDPARAMVALUE_-s_0===True -VALUE:HARDCODEDPARAMVALUE_distance_0===2 -ALGORITHM:grass:v.buffer.distance -120.0,160.0 -None --1|VECTORLAYER_VECTOR --1|HARDCODEDPARAMVALUE_distance_0 --1|HARDCODEDPARAMVALUE_tolerance_0 --1|HARDCODEDPARAMVALUE_-s_0 --1|HARDCODEDPARAMVALUE_-c_0 -None --1|HARDCODEDPARAMVALUE_GRASS_SNAP_TOLERANCE_PARAMETER_0 --1|HARDCODEDPARAMVALUE_GRASS_MIN_AREA_PARAMETER_0 -None -ALGORITHM:saga:polygoncentroids -340.0,260.0 -None -0|output --1|HARDCODEDPARAMVALUE_METHOD _1 -output diff --git a/python/plugins/processing/modeler/models/simplemodel.model b/python/plugins/processing/modeler/models/simplemodel.model deleted file mode 100644 index 409bc11676b..00000000000 --- a/python/plugins/processing/modeler/models/simplemodel.model +++ /dev/null @@ -1,15 +0,0 @@ -NAME:A basic model -GROUP:[Test models] -PARAMETER:ParameterRaster|RASTERLAYER_RASTER|raster|False -120.0,60.0 -VALUE:HARDCODEDPARAMVALUE_METHOD_0===5 -ALGORITHM:saga:slopeaspectcurvature -120.0,160.0 -None --1|RASTERLAYER_RASTER --1|HARDCODEDPARAMVALUE_METHOD_0 -slope -None -None -None -None diff --git a/python/plugins/processing/modeler/models/watersheds.model b/python/plugins/processing/modeler/models/watersheds.model deleted file mode 100644 index b17237ea680..00000000000 --- a/python/plugins/processing/modeler/models/watersheds.model +++ /dev/null @@ -1,79 +0,0 @@ -NAME:Watershed from DEM and threshold -GROUP:[Example models] -PARAMETER:ParameterRaster|RASTERLAYER_DEM|DEM|False -233.0,49.0 -PARAMETER:ParameterNumber|NUMBER_THRESHOLDFORCHANNELDEFINITION|Threshold for channel definition|0.0|None|1000000.0 -330.0,411.0 -VALUE:HARDCODEDPARAMVALUE_Method_1===0 -VALUE:HARDCODEDPARAMVALUE_MINSLOPE_0===0.01 -VALUE:HARDCODEDPARAMVALUE_SPLIT_4===1 -VALUE:HARDCODEDPARAMVALUE_CLASS_ID_4===0 -VALUE:HARDCODEDPARAMVALUE_INIT_METHOD_2===2 -VALUE:HARDCODEDPARAMVALUE_STEP_1===1 -VALUE:HARDCODEDPARAMVALUE_DOLINEAR _1===True -VALUE:HARDCODEDPARAMVALUE_MINSIZE_3===0 -VALUE:HARDCODEDPARAMVALUE_CLASS_ALL_4===1 -VALUE:HARDCODEDPARAMVALUE_LINEARTHRS_1===500.0 -VALUE:HARDCODEDPARAMVALUE_DIV_CELLS_2===10 -VALUE:HARDCODEDPARAMVALUE_CONVERGENCE_1===1.0 -VALUE:HARDCODEDPARAMVALUE_MINLEN_2===10 -ALGORITHM:saga:fillsinks -120.0,160.0 -None --1|RASTERLAYER_DEM --1|HARDCODEDPARAMVALUE_MINSLOPE_0 -None -ALGORITHM:saga:catchmentareaparallel -119.0,285.0 -None -0|RESULT -None -None -None -None --1|HARDCODEDPARAMVALUE_STEP_1 --1|HARDCODEDPARAMVALUE_Method_1 --1|HARDCODEDPARAMVALUE_DOLINEAR _1 --1|HARDCODEDPARAMVALUE_LINEARTHRS_1 -None -None --1|HARDCODEDPARAMVALUE_CONVERGENCE_1 -None -None -None -None -None -None -None -None -ALGORITHM:saga:channelnetwork -398.0,283.0 -None --1|RASTERLAYER_DEM -None -1|CAREA --1|HARDCODEDPARAMVALUE_INIT_METHOD_2 --1|NUMBER_THRESHOLDFORCHANNELDEFINITION -None --1|HARDCODEDPARAMVALUE_DIV_CELLS_2 -None --1|HARDCODEDPARAMVALUE_MINLEN_2 -None -None -None -ALGORITHM:saga:watershedbasins -528.0,48.0 -None --1|RASTERLAYER_DEM -2|CHNLNTWRK -None --1|HARDCODEDPARAMVALUE_MINSIZE_3 -None -ALGORITHM:saga:vectorisinggridclasses -649.0,285.0 -None -3|BASINS --1|HARDCODEDPARAMVALUE_CLASS_ALL_4 --1|HARDCODEDPARAMVALUE_CLASS_ID_4 --1|HARDCODEDPARAMVALUE_SPLIT_4 -Watersheds diff --git a/python/plugins/processing/parameters/Parameter.py b/python/plugins/processing/parameters/Parameter.py index 56669e642ef..cc06161dbda 100644 --- a/python/plugins/processing/parameters/Parameter.py +++ b/python/plugins/processing/parameters/Parameter.py @@ -68,3 +68,8 @@ class Parameter: def parameterName(self): return self.__module__.split('.')[-1] + + def todict(self): + return self.__dict__ + +