Skip to content

Commit

Permalink
[processing] brought back 'export model as python' functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
volaya committed Nov 6, 2015
1 parent b05fb0b commit 227af8a
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 5 deletions.
37 changes: 37 additions & 0 deletions python/plugins/processing/core/parameters.py
Expand Up @@ -127,6 +127,9 @@ def setValue(self, value):
self.value = bool(value)
return True

def getAsScriptCode(self):
return '##' + self.name + '=boolean ' + str(self.default)


class ParameterCrs(Parameter):

Expand All @@ -153,6 +156,8 @@ def setValue(self, value):
def getValueAsCommandLineParameter(self):
return '"' + unicode(self.value) + '"'

def getAsScriptCode(self):
return '##' + self.name + '=crs ' + str(self.default)

class ParameterDataObject(Parameter):

Expand Down Expand Up @@ -197,6 +202,8 @@ def setValue(self, text):
def getValueAsCommandLineParameter(self):
return '"' + unicode(self.value) + '"'

def getAsScriptCode(self):
return '##' + self.name + '=extent'

class ParameterFile(Parameter):

Expand Down Expand Up @@ -225,6 +232,12 @@ def typeName(self):
else:
return 'file'

def getAsScriptCode(self):
if self.isFolder:
return '##' + self.name + '=folder'
else:
return '##' + self.name + '=file'


class ParameterFixedTable(Parameter):

Expand Down Expand Up @@ -406,6 +419,13 @@ def dataType(self):
else:
return 'any vectors'

def getAsScriptCode(self):
if self.datatype == self.TYPE_RASTER:
return '##' + self.name + '=multiple raster'
if self.datatype == self.TYPE_FILE:
return '##' + self.name + '=multiple file'
else:
return '##' + self.name + '=multiple vector'

class ParameterNumber(Parameter):

Expand Down Expand Up @@ -451,6 +471,9 @@ def setValue(self, n):
return False


def getAsScriptCode(self):
return '##' + self.name + '=number ' + str(self.default)

class ParameterRange(Parameter):

def __init__(self, name='', description='', default='0,1', optional=False):
Expand Down Expand Up @@ -543,6 +566,9 @@ def getFileFilter(self):
exts[i] = self.tr('%s files(*.%s)', 'ParameterRaster') % (exts[i].upper(), exts[i].lower())
return ';;'.join(exts)

def getAsScriptCode(self):
return '##' + self.name + '=raster'


class ParameterSelection(Parameter):

Expand Down Expand Up @@ -609,6 +635,8 @@ def getValueAsCommandLineParameter(self):
ParameterString.ESCAPED_NEWLINE)) + '"'
if self.value is not None else unicode(None))

def getAsScriptCode(self):
return '##' + self.name + '=string ' + self.default

class ParameterTable(ParameterDataObject):

Expand Down Expand Up @@ -674,6 +702,9 @@ def getFileFilter(self):
exts[i] = self.tr('%s files(*.%s)', 'ParameterTable') % (exts[i].upper(), exts[i].lower())
return ';;'.join(exts)

def getAsScriptCode(self):
return '##' + self.name + '=table'


class ParameterTableField(Parameter):

Expand Down Expand Up @@ -715,6 +746,9 @@ def dataType(self):
else:
return 'any'

def getAsScriptCode(self):
return '##' + self.name + '=field ' + self.parent


class ParameterVector(ParameterDataObject):

Expand Down Expand Up @@ -800,6 +834,9 @@ def dataType(self):

return types[:-2]

def getAsScriptCode(self):
return '##' + self.name + '=vector'


class ParameterGeometryPredicate(Parameter):

Expand Down
64 changes: 63 additions & 1 deletion python/plugins/processing/modeler/ModelerAlgorithm.py
Expand Up @@ -89,7 +89,7 @@ def __init__(self, consoleName=""):
#A dict of Input object. keys are param names
self.params = {}

#A dict of Output with final output descriptions. Keys are output names.
#A dict of ModelerOutput with final output descriptions. Keys are output names.
#Outputs not final are not stored in this dict
self.outputs = {}

Expand Down Expand Up @@ -119,6 +119,33 @@ def setName(self, model):
name = self.consoleName + "_" + unicode(i)
self.name = name

def getOutputType(self, outputName):
output = self.algorithm.getOutputFromName(outputName)
return "output " + output.__class__.__name__.split(".")[-1][6:].lower()

def toPython(self):
s = []
params = []
for param in self.algorithm.parameters:
value = self.params[param.name]
def _toString(v):
if isinstance(v, (ValueFromInput, ValueFromOutput)):
return v.asPythonParameter()
elif isinstance(v, basestring):
return "'%s'" % v
elif isinstance(v, list):
return "[%s]" % ",".join([_toString(val) for val in v])
else:
return unicode(value)
params.append(_toString(value))
for out in self.algorithm.outputs:
if out.name in self.outputs:
params.append(safeName(self.outputs[out.name].description).lower())
else:
params.append(str(None))
s.append("outputs_%s=processing.runalg('%s', %s)" % (self.name, self.consoleName, ",".join(params)))
return s


class ValueFromInput():

Expand All @@ -137,6 +164,8 @@ def __eq__(self, other):
except:
return False

def asPythonParameter(self):
return self.name

class ValueFromOutput():

Expand All @@ -156,6 +185,9 @@ def __eq__(self, other):
def __str__(self):
return self.alg + "," + self.output

def asPythonParameter(self):
return "outputs_%s['%s']" % (self.alg, self.output)


class ModelerAlgorithm(GeoAlgorithm):

Expand Down Expand Up @@ -654,3 +686,33 @@ def _tr(s):
raise e
else:
raise WrongModelException(_tr('Error in model definition line: ') + '%s\n%s' % (line.strip(), traceback.format_exc()))


def toPython(self):
s = ['##%s=name' % self.name]
for param in self.inputs.values():
s.append(param.param.getAsScriptCode())
for alg in self.algs.values():
for name, out in alg.outputs.iteritems():
s.append('##%s=%s' % (safeName(out.description).lower(), alg.getOutputType(name)))

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(alg.name)
for requiredAlg in required:
if requiredAlg != alg.name and requiredAlg not in executed:
canExecute = False
break
if canExecute:
s.extend(alg.toPython())
executed.append(alg.name)

return '\n'.join(s)

def safeName(name):
validChars = 'abcdefghijklmnopqrstuvwxyz'
return ''.join(c for c in name.lower() if c in validChars)
20 changes: 19 additions & 1 deletion python/plugins/processing/modeler/ModelerDialog.py
Expand Up @@ -39,7 +39,6 @@
from processing.core.ProcessingLog import ProcessingLog
from processing.gui.HelpEditionDialog import HelpEditionDialog
from processing.gui.AlgorithmDialog import AlgorithmDialog
import processing.gui.AlgorithmClassification
from processing.modeler.ModelerParameterDefinitionDialog import ModelerParameterDefinitionDialog
from processing.modeler.ModelerAlgorithm import ModelerAlgorithm, ModelerParameter
from processing.modeler.ModelerParametersDialog import ModelerParametersDialog
Expand Down Expand Up @@ -161,6 +160,7 @@ def _mimeDataAlgorithm(items):
self.btnSave.setIcon(QgsApplication.getThemeIcon('/mActionFileSave.svg'))
self.btnSaveAs.setIcon(QgsApplication.getThemeIcon('/mActionFileSaveAs.svg'))
self.btnExportImage.setIcon(QgsApplication.getThemeIcon('/mActionSaveMapAsImage.png'))
self.btnExportPython.setIcon(QgsApplication.getThemeIcon('/console/iconSaveAsConsole.png'))
self.btnEditHelp.setIcon(QIcon(os.path.join(pluginPath, 'images', 'edithelp.png')))
self.btnRun.setIcon(QIcon(os.path.join(pluginPath, 'images', 'runalgorithm.png')))

Expand All @@ -184,6 +184,7 @@ def _mimeDataAlgorithm(items):
self.btnSave.clicked.connect(self.save)
self.btnSaveAs.clicked.connect(self.saveAs)
self.btnExportImage.clicked.connect(self.exportAsImage)
self.btnExportPython.clicked.connect(self.exportAsPython)
self.btnEditHelp.clicked.connect(self.editHelp)
self.btnRun.clicked.connect(self.runModel)

Expand Down Expand Up @@ -281,6 +282,23 @@ def exportAsImage(self):

img.save(filename)

def exportAsPython(self):
filename = unicode(QFileDialog.getSaveFileName(self,
self.tr('Save Model As Python Script'), '',
self.tr('Python files (*.py *.PY)')))
if not filename:
return

if not filename.lower().endswith('.py'):
filename += '.py'

text = self.alg.toPython()
with codecs.open(filename, 'w', encoding='utf-8') as fout:
fout.write(text)
QMessageBox.information(self, self.tr('Model exported'),
self.tr('Model was correctly exported.'))


def saveModel(self, saveAs):
if unicode(self.textGroup.text()).strip() == '' \
or unicode(self.textName.text()).strip() == '':
Expand Down
Expand Up @@ -198,7 +198,7 @@ def setupUi(self):
self.yesNoCombo.setCurrentIndex(
1 if self.param.optional else 0)
self.verticalLayout.addLayout(self.horizontalLayout2)

self.buttonBox = QDialogButtonBox(self)
self.buttonBox.setOrientation(Qt.Horizontal)
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel
Expand All @@ -221,8 +221,10 @@ def okPressed(self):
validChars = \
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
safeName = ''.join(c for c in description if c in validChars)
name = self.paramType.upper().replace(' ', '') + '_' \
+ safeName.upper()
name = safeName.lower()
i = 2
while name in self.alg.inputs:
name = safeName.lower() + str(i)
else:
name = self.param.name
if self.paramType \
Expand Down
10 changes: 10 additions & 0 deletions python/plugins/processing/ui/DlgModeler.ui
Expand Up @@ -96,6 +96,16 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="btnExportPython">
<property name="text">
<string>...</string>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
Expand Down

8 comments on commit 227af8a

@joaandrade
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello,

I installed the new QGIS release (2.14.0) and I was expecting that this new version brought back the feature 'export model as python'. Can you tell me if you are planning to bring it back on a next release?

Thank in advance, best regards,
João Andrade

@gioman
Copy link
Contributor

@gioman gioman commented on 227af8a Mar 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joaandrade do you have a "processing" folder inside .qgis2/python/plugins ? if yes you are probably masking the Processing version shipped with 2.14. Delete that folder and restart qgis.

@joaandrade
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gioman I reinstalled QGIS and now I don't have that folder. However, the option still does not appear.

@gioman
Copy link
Contributor

@gioman gioman commented on 227af8a Mar 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joaandrade that folder is not removed with (re)install so you probably looked in the wrong place. See in c:\users\yourusername.qgis2\python\plugins

@joaandrade
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gioman The only folder inside the directory C:\Users\myuser.qgis2\python is expressions.
I don't have the directory c:\users\myuser.qgis2\python\plugins in this QGIS installation.

@gioman
Copy link
Contributor

@gioman gioman commented on 227af8a Mar 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joaandrade ummm... anyway... here I see without issues the "export to python script" in the modeler window...

@joaandrade
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gioman a colleague tried to build QGIS 2.14 from source on a VM with Linux and the same is happening.

@gioman
Copy link
Contributor

@gioman gioman commented on 227af8a Mar 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exp1

seems ok here on both 2.14 and master

Please sign in to comment.