Skip to content

Commit e08fdaa

Browse files
committedOct 5, 2016
[processing] support for expressions in numerical values in modeler
includes cleanup of modeler, to adapt to latest changes in parameters architecture
1 parent fe5d016 commit e08fdaa

10 files changed

+127
-380
lines changed
 

‎python/plugins/processing/core/Processing.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
from processing.core.alglist import algList
5555

5656
from processing.modeler.ModelerAlgorithmProvider import ModelerAlgorithmProvider
57-
from processing.modeler.ModelerOnlyAlgorithmProvider import ModelerOnlyAlgorithmProvider
5857
from processing.algs.qgis.QGISAlgorithmProvider import QGISAlgorithmProvider
5958
from processing.algs.grass.GrassAlgorithmProvider import GrassAlgorithmProvider
6059
from processing.algs.grass7.Grass7AlgorithmProvider import Grass7AlgorithmProvider

‎python/plugins/processing/core/parameters.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import os
3333
from inspect import isclass
3434
from copy import deepcopy
35+
import numbers
3536

3637
from qgis.utils import iface
3738
from qgis.PyQt.QtCore import QCoreApplication
@@ -40,6 +41,7 @@
4041

4142
from processing.tools.vector import resolveFieldIndex, features
4243
from processing.tools import dataobjects
44+
from processing.core.outputs import OutputNumber
4345

4446
def parseBool(s):
4547
if s is None or s == str(None).lower():
@@ -192,6 +194,9 @@ def wrapper(self, dialog, row=0, col=0):
192194

193195
def evaluate(self, alg):
194196
pass
197+
198+
def evaluateForModeler(self, value, model):
199+
return value
195200

196201
class ParameterBoolean(Parameter):
197202

@@ -810,6 +815,7 @@ def setValue(self, n):
810815
self.value = float(v)
811816
return True
812817
except:
818+
raise
813819
return False
814820
else:
815821
try:
@@ -826,6 +832,7 @@ def setValue(self, n):
826832
self.value = value
827833
return True
828834
except:
835+
raise
829836
return False
830837

831838
def getAsScriptCode(self):
@@ -843,8 +850,8 @@ def fromScriptCode(self, line):
843850
default = definition.strip()[len('number') + 1:] or None
844851
return ParameterNumber(name, descName, default=default, optional=isOptional)
845852

846-
def _evaluate(self):
847-
exp = QgsExpression(self.value)
853+
def _evaluate(self, value):
854+
exp = QgsExpression(value)
848855
if exp.hasParserError():
849856
raise ValueError(self.tr("Error in parameter expression: ") + exp.parserErrorString())
850857
result = exp.evaluate(_expressionContext())
@@ -853,7 +860,26 @@ def _evaluate(self):
853860
return result
854861

855862
def evaluate(self, alg):
856-
self.value = self._evaluate(self.value)
863+
if isinstance(self.value, basestring):
864+
self.value = self._evaluate(self.value)
865+
866+
def evaluateForModeler(self, value, model):
867+
if isinstance(value, numbers.Number):
868+
return value
869+
variables = {}
870+
for param in model.parameters:
871+
if isinstance(param, ParameterNumber):
872+
variables["@" + param.name] = param.value
873+
for alg in model.algs.values():
874+
for out in alg.algorithm.outputs:
875+
if isinstance(out, OutputNumber):
876+
variables["@%s_%s" % (alg.name, out.name)] = out.value
877+
for k,v in variables.iteritems():
878+
print k,v
879+
value = value.replace(k,unicode(v))
880+
881+
print value
882+
return value
857883

858884
def expressionContext(self):
859885
return _expressionContext()
@@ -1427,9 +1453,12 @@ def getParameterFromString(s):
14271453
isAdvanced = True
14281454
tokens = s.split("|")
14291455
params = [t if unicode(t) != unicode(None) else None for t in tokens[1:]]
1430-
clazz = getattr(sys.modules[__name__], tokens[0])
1431-
param = clazz(*params)
1432-
param.isAdvanced = isAdvanced
1456+
try:
1457+
clazz = getattr(sys.modules[__name__], tokens[0])
1458+
param = clazz(*params)
1459+
param.isAdvanced = isAdvanced
1460+
except:
1461+
return None
14331462
else: # try script syntax
14341463
for paramClass in paramClasses:
14351464
try:

‎python/plugins/processing/gui/NumberInputPanel.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,13 @@
3535
from qgis.core import (QgsDataSourceUri,
3636
QgsCredentials,
3737
QgsExpression,
38-
QgsRasterLayer)
38+
QgsRasterLayer,
39+
QgsExpressionContextScope)
3940
from qgis.gui import QgsEncodingFileDialog, QgsExpressionBuilderDialog
4041
from qgis.utils import iface
42+
from processing.core.parameters import ParameterNumber
43+
from processing.core.outputs import OutputNumber
44+
from processing.modeler.ModelerAlgorithm import ValueFromInput, ValueFromOutput, CompoundValue
4145

4246
pluginPath = os.path.split(os.path.dirname(__file__))[0]
4347
WIDGET, BASE = uic.loadUiType(
@@ -48,18 +52,27 @@ class NumberInputPanel(BASE, WIDGET):
4852

4953
hasChanged = pyqtSignal()
5054

51-
def __init__(self, param):
55+
def __init__(self, param, modelParametersDialog=None):
5256
super(NumberInputPanel, self).__init__(None)
5357
self.setupUi(self)
5458

5559
self.param = param
56-
self.text = param.default
57-
60+
self.modelParametersDialog = modelParametersDialog
61+
if param.default:
62+
self.setValue(param.default)
5863
self.btnSelect.clicked.connect(self.showExpressionsBuilder)
5964
self.leText.textChanged.connect(lambda: self.hasChanged.emit())
6065

6166
def showExpressionsBuilder(self):
6267
context = self.param.expressionContext()
68+
if self.modelParametersDialog is not None:
69+
context.popScope()
70+
values = self.modelParametersDialog.getAvailableValuesOfType(ParameterNumber, OutputNumber)
71+
modelerScope = QgsExpressionContextScope()
72+
for value in values:
73+
name = value.name if isinstance(value, ValueFromInput) else "%s_%s" % (value.alg, value.output)
74+
modelerScope.setVariable(name, 1)
75+
context.appendScope(modelerScope)
6376
dlg = QgsExpressionBuilderDialog(None, self.leText.text(), self, 'generic', context)
6477
dlg.setWindowTitle(self.tr('Expression based input'))
6578
if dlg.exec_() == QDialog.Accepted:
@@ -69,7 +82,23 @@ def showExpressionsBuilder(self):
6982

7083

7184
def getValue(self):
72-
return self.leText.text()
85+
if self.modelParametersDialog:
86+
value = self.leText.text()
87+
values = []
88+
for param in self.modelParametersDialog.model.parameters:
89+
if isinstance(param, ParameterNumber):
90+
if "@" + param.name in value:
91+
values.append(ValueFromInput(param.name))
92+
for alg in self.modelParametersDialog.model.algs.values():
93+
for out in alg.algorithm.outputs:
94+
if isinstance(out, OutputNumber) and "@%s_%s" % (alg.name, out.name) in value:
95+
values.append(ValueFromOutput(alg.name, out.name))
96+
if values:
97+
return CompoundValue(values, value)
98+
else:
99+
return value
100+
else:
101+
return self.leText.text()
73102

74103
def setValue(self, value):
75104
self.leText.setText(unicode(value))

‎python/plugins/processing/gui/wrappers.py

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -455,39 +455,15 @@ class NumberWidgetWrapper(WidgetWrapper):
455455

456456
def createWidget(self):
457457
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
458-
return NumberInputPanel(self.param)
458+
return NumberInputPanel(self.param, None)
459459
else:
460-
widget = QComboBox()
461-
widget.setEditable(True)
462-
files = self.dialog.getAvailableValuesOfType(ParameterNumber, OutputNumber)
463-
for f in files:
464-
widget.addItem(self.dialog.resolveValueDescription(f), f)
465-
return widget
460+
return NumberInputPanel(self.param, self.dialog)
466461

467462
def setValue(self, value):
468-
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
469-
self.widget.setValue(value)
470-
else:
471-
self.setComboValue(value)
472-
463+
self.widget.setValue(value)
464+
473465
def value(self):
474-
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
475-
return self.widget.getValue()
476-
else:
477-
def validator(v):
478-
if str(v).strip():
479-
try:
480-
if self.param.isInteger:
481-
int(v)
482-
else:
483-
float(v)
484-
return True
485-
except:
486-
return False
487-
else:
488-
return self.param.optional
489-
return self.comboValue(validator)
490-
466+
return self.widget.getValue()
491467

492468
class RasterWidgetWrapper(WidgetWrapper):
493469

‎python/plugins/processing/modeler/CalculatorModelerAlgorithm.py

Lines changed: 0 additions & 145 deletions
This file was deleted.

‎python/plugins/processing/modeler/ModelerAlgorithm.py

Lines changed: 40 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
ParameterCrs,
5757
ParameterDataObject,
5858
ParameterMultipleInput)
59-
from processing.tools import dataobjects
59+
6060
from processing.gui.Help2Html import getHtmlFromDescriptionsDict
6161
from processing.core.alglist import algList
6262

@@ -204,6 +204,27 @@ def __str__(self):
204204
def asPythonParameter(self):
205205
return "outputs_%s['%s']" % (self.alg, self.output)
206206

207+
class CompoundValue():
208+
209+
def __init__(self, values = [], definition=""):
210+
self.values = values
211+
self.definition = definition
212+
213+
def todict(self):
214+
return self.__dict__
215+
216+
def __eq__(self, other):
217+
try:
218+
return self.values == other.values and self.definition == other.definition
219+
except:
220+
return False
221+
222+
def __str__(self):
223+
return self.definition
224+
225+
def asPythonParameter(self):
226+
return "" #TODO
227+
207228

208229
class ModelerAlgorithm(GeoAlgorithm):
209230

@@ -343,6 +364,12 @@ def getDependsOnAlgorithms(self, name):
343364
for value in list(alg.params.values()):
344365
if value is None:
345366
continue
367+
if isinstance(value, CompoundValue):
368+
print value
369+
for v in value.values:
370+
if isinstance(v, ValueFromOutput):
371+
algs.add(v.alg)
372+
algs.update(self.getDependsOnAlgorithms(v.alg))
346373
if isinstance(value, list):
347374
for v in value:
348375
if isinstance(v, ValueFromOutput):
@@ -387,7 +414,7 @@ def prepareAlgorithm(self, alg):
387414
for param in algInstance.parameters:
388415
if not param.hidden:
389416
if param.name in alg.params:
390-
value = self.resolveValue(alg.params[param.name])
417+
value = self.resolveValue(alg.params[param.name], param)
391418
else:
392419
if iface is not None:
393420
iface.messageBar().pushMessage(self.tr("Warning"),
@@ -430,17 +457,20 @@ def activateAlgorithm(self, algName):
430457
def getSafeNameForOutput(self, algName, outName):
431458
return outName + '_ALG' + algName
432459

433-
def resolveValue(self, value):
460+
def resolveValue(self, value, param):
434461
if value is None:
435-
return None
462+
v = None
436463
if isinstance(value, list):
437-
return ";".join([self.resolveValue(v) for v in value])
438-
if isinstance(value, ValueFromInput):
439-
return self.getParameterFromName(value.name).value
464+
v = ";".join([self.resolveValue(v, param) for v in value])
465+
elif isinstance(value, CompoundValue):
466+
v = self.resolveValue(value.definition, param)
467+
elif isinstance(value, ValueFromInput):
468+
v = self.getParameterFromName(value.name).value
440469
elif isinstance(value, ValueFromOutput):
441-
return self.algs[value.alg].algorithm.getOutputFromName(value.output).value
470+
v = self.algs[value.alg].algorithm.getOutputFromName(value.output).value
442471
else:
443-
return value
472+
v = value
473+
return param.evaluateForModeler(v, self)
444474

445475
def processAlgorithm(self, progress):
446476
executed = []
@@ -571,128 +601,13 @@ def _import(name):
571601
return model
572602

573603
@staticmethod
574-
def fromJsonFile(filename):
604+
def fromFile(filename):
575605
with open(filename) as f:
576606
s = f.read()
577607
alg = ModelerAlgorithm.fromJson(s)
578608
alg.descriptionFile = filename
579609
return alg
580610

581-
############LEGACY METHOD TO SUPPORT OLD FORMAT###########
582-
583-
LINE_BREAK_STRING = '%%%'
584-
585-
@staticmethod
586-
def fromFile(filename):
587-
try:
588-
alg = ModelerAlgorithm.fromJsonFile(filename)
589-
return alg
590-
except WrongModelException:
591-
alg = ModelerAlgorithm.fromOldFormatFile(filename)
592-
return alg
593-
594-
@staticmethod
595-
def fromOldFormatFile(filename):
596-
def _tr(s):
597-
return QCoreApplication.translate('ModelerAlgorithm', s)
598-
hardcodedValues = {}
599-
modelParameters = []
600-
modelAlgs = []
601-
model = ModelerAlgorithm()
602-
model.descriptionFile = filename
603-
lines = codecs.open(filename, 'r', encoding='utf-8')
604-
line = lines.readline().strip('\n').strip('\r')
605-
try:
606-
while line != '':
607-
if line.startswith('PARAMETER:'):
608-
paramLine = line[len('PARAMETER:'):]
609-
param = getParameterFromString(paramLine)
610-
if param:
611-
pass
612-
else:
613-
raise WrongModelException(
614-
_tr('Error in parameter line: %s', 'ModelerAlgorithm') % line)
615-
line = lines.readline().strip('\n')
616-
tokens = line.split(',')
617-
model.addParameter(ModelerParameter(param,
618-
QPointF(float(tokens[0]), float(tokens[1]))))
619-
modelParameters.append(param.name)
620-
elif line.startswith('VALUE:'):
621-
valueLine = line[len('VALUE:'):]
622-
tokens = valueLine.split('===')
623-
name = tokens[0]
624-
value = tokens[1].replace(ModelerAlgorithm.LINE_BREAK_STRING, '\n')
625-
hardcodedValues[name] = value
626-
elif line.startswith('NAME:'):
627-
model.name = line[len('NAME:'):]
628-
elif line.startswith('GROUP:'):
629-
model.group = line[len('GROUP:'):]
630-
elif line.startswith('ALGORITHM:'):
631-
algLine = line[len('ALGORITHM:'):]
632-
alg = algList.getAlgorithm(algLine)
633-
if alg is not None:
634-
modelAlg = Algorithm(alg.commandLineName())
635-
modelAlg.description = alg.name
636-
posline = lines.readline().strip('\n').strip('\r')
637-
tokens = posline.split(',')
638-
modelAlg.pos = QPointF(float(tokens[0]), float(tokens[1]))
639-
# dependenceline = lines.readline().strip('\n').strip('\r')
640-
for param in alg.parameters:
641-
if not param.hidden:
642-
line = lines.readline().strip('\n').strip('\r')
643-
if line == str(None):
644-
modelAlg.params[param.name] = None
645-
else:
646-
tokens = line.split('|')
647-
try:
648-
algIdx = int(tokens[0])
649-
except:
650-
raise WrongModelException(
651-
_tr('Number of parameters in the '
652-
'{} algorithm does not match '
653-
'current Processing '
654-
'implementation'.format(alg.name)))
655-
if algIdx == -1:
656-
if tokens[1] in modelParameters:
657-
modelAlg.params[param.name] = ValueFromInput(tokens[1])
658-
else:
659-
modelAlg.params[param.name] = hardcodedValues[tokens[1]]
660-
else:
661-
modelAlg.params[param.name] = ValueFromOutput(algIdx, tokens[1])
662-
663-
for out in alg.outputs:
664-
if not out.hidden:
665-
line = lines.readline().strip('\n').strip('\r')
666-
if str(None) != line:
667-
if '|' in line:
668-
tokens = line.split('|')
669-
name = tokens[0]
670-
tokens = tokens[1].split(',')
671-
pos = QPointF(float(tokens[0]), float(tokens[1]))
672-
else:
673-
name = line
674-
pos = None
675-
modelerOutput = ModelerOutput(name)
676-
modelerOutput.pos = pos
677-
modelAlg.outputs[out.name] = modelerOutput
678-
679-
model.addAlgorithm(modelAlg)
680-
modelAlgs.append(modelAlg.name)
681-
else:
682-
raise WrongModelException(
683-
_tr('Error in algorithm name: %s',) % algLine)
684-
line = lines.readline().strip('\n').strip('\r')
685-
for modelAlg in list(model.algs.values()):
686-
for name, value in modelAlg.params.items():
687-
if isinstance(value, ValueFromOutput):
688-
value.alg = modelAlgs[value.alg]
689-
return model
690-
except Exception as e:
691-
if isinstance(e, WrongModelException):
692-
raise e
693-
else:
694-
raise WrongModelException(_tr('Error in model definition line: ') + '%s\n%s' % (line.strip(), traceback.format_exc()))
695-
696611
def toPython(self):
697612
s = ['##%s=name' % self.name]
698613
for param in list(self.inputs.values()):

‎python/plugins/processing/modeler/ModelerOnlyAlgorithmProvider.py

Lines changed: 0 additions & 53 deletions
This file was deleted.

‎python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,8 @@ def setupUi(self):
212212
default = self.param.default
213213
if self.param.isInteger:
214214
default = int(math.floor(default))
215-
self.defaultTextBox.setText(str(default))
215+
if default:
216+
self.defaultTextBox.setText(str(default))
216217
self.horizontalLayoutDefault.addWidget(self.defaultTextBox)
217218
self.verticalLayout.addLayout(self.horizontalLayoutDefault)
218219
elif self.paramType == ModelerParameterDefinitionDialog.PARAMETER_STRING or \
@@ -294,7 +295,7 @@ def okPressed(self):
294295
or isinstance(self.param, ParameterBoolean):
295296
self.param = ParameterBoolean(name, description,
296297
self.state.isChecked())
297-
elif self.paramType in ModelerParameterDefinitionDialog.PARAMETER_TABLE_FIELD \
298+
elif self.paramType == ModelerParameterDefinitionDialog.PARAMETER_TABLE_FIELD \
298299
or isinstance(self.param, ParameterTableField):
299300
if self.parentCombo.currentIndex() < 0:
300301
QMessageBox.warning(self, self.tr('Unable to define parameter'),

‎python/plugins/processing/modeler/ModelerParametersDialog.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
OutputString,
5353
OutputExtent,
5454
OutputCrs)
55+
from processing.core.parameters import ParameterPoint, ParameterExtent
5556

5657
from processing.modeler.ModelerAlgorithm import (ValueFromInput,
5758
ValueFromOutput,
@@ -261,20 +262,15 @@ def showAdvancedParametersClicked(self):
261262
self.labels[param.name].setVisible(self.showAdvanced)
262263
self.widgets[param.name].setVisible(self.showAdvanced)
263264

264-
def getAvailableValuesForParam(self, param):
265-
outputType = None
266-
if isinstance(param, ParameterCrs):
267-
outputType = OutputCrs
268-
return self.getAvailableValuesOfType(param.__class__, outputType)
269-
270265
def getAvailableValuesOfType(self, paramType, outType=None, dataType=None):
271266
values = []
272267
inputs = self.model.inputs
273268
for i in list(inputs.values()):
274269
param = i.param
275270
if isinstance(param, paramType):
276-
if dataType is not None and param.datatype in dataType:
277-
values.append(ValueFromInput(param.name))
271+
if dataType is not None:
272+
if param.datatype in dataType:
273+
values.append(ValueFromInput(param.name))
278274
else:
279275
values.append(ValueFromInput(param.name))
280276
if outType is None:
@@ -340,12 +336,9 @@ def setPreviousValues(self):
340336
value = alg.params[param.name]
341337
else:
342338
value = param.default
343-
344-
wrapper = self.wrappers[param.name]
345-
wrapper.setValue(value)
346-
339+
self.wrappers[param.name].setValue(value)
347340
for name, out in alg.outputs.items():
348-
widget = self.valueItems[name].setText(out.description)
341+
self.valueItems[name].setText(out.description)
349342

350343
selected = []
351344
dependencies = self.getAvailableDependencies()

‎python/plugins/processing/modeler/ModelerScene.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from qgis.PyQt.QtWidgets import QGraphicsItem, QGraphicsScene
3131
from processing.modeler.ModelerGraphicItem import ModelerGraphicItem
3232
from processing.modeler.ModelerArrowItem import ModelerArrowItem
33-
from processing.modeler.ModelerAlgorithm import ValueFromInput, ValueFromOutput
33+
from processing.modeler.ModelerAlgorithm import ValueFromInput, ValueFromOutput, CompoundValue
3434

3535

3636
class ModelerScene(QGraphicsScene):
@@ -65,6 +65,9 @@ def getItemsFromParamValue(self, value):
6565
if isinstance(value, list):
6666
for v in value:
6767
items.extend(self.getItemsFromParamValue(v))
68+
elif isinstance(value, CompoundValue):
69+
for v in value.values:
70+
items.extend(self.getItemsFromParamValue(v))
6871
elif isinstance(value, ValueFromInput):
6972
items.append((self.paramItems[value.name], 0))
7073
elif isinstance(value, ValueFromOutput):

0 commit comments

Comments
 (0)
Please sign in to comment.