2014-11-12 19:36:12 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
"""
|
|
|
|
***************************************************************************
|
|
|
|
BatchAlgorithmDialog.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'
|
|
|
|
|
2017-06-13 12:32:30 +10:00
|
|
|
from pprint import pformat
|
|
|
|
import time
|
|
|
|
|
2019-05-31 17:38:14 +02:00
|
|
|
from qgis.PyQt.QtWidgets import QPushButton, QDialogButtonBox
|
2018-02-15 22:30:52 +01:00
|
|
|
from qgis.PyQt.QtCore import Qt, QCoreApplication
|
2014-11-12 19:36:12 +02:00
|
|
|
|
2019-05-31 17:38:14 +02:00
|
|
|
from qgis.core import (QgsProcessingOutputHtml,
|
2017-06-13 12:32:30 +10:00
|
|
|
QgsProcessingOutputNumber,
|
2017-06-13 12:47:11 +10:00
|
|
|
QgsProcessingOutputString,
|
2019-05-31 08:29:13 +10:00
|
|
|
QgsProcessingOutputBoolean,
|
2018-02-05 22:11:34 -04:00
|
|
|
QgsProject,
|
2018-09-07 10:33:26 +10:00
|
|
|
QgsProcessingMultiStepFeedback,
|
2019-07-11 09:43:27 +10:00
|
|
|
QgsScopedProxyProgressTask,
|
|
|
|
QgsProcessingException)
|
2017-06-13 12:32:30 +10:00
|
|
|
|
2018-02-15 22:30:52 +01:00
|
|
|
from qgis.gui import QgsProcessingAlgorithmDialogBase
|
2018-09-25 21:37:41 +10:00
|
|
|
from qgis.utils import OverrideCursor, iface
|
2016-09-16 14:32:00 +02:00
|
|
|
|
2014-11-12 19:36:12 +02:00
|
|
|
from processing.gui.BatchPanel import BatchPanel
|
2017-03-22 17:17:14 +02:00
|
|
|
from processing.gui.AlgorithmExecutor import execute
|
2014-11-12 19:36:12 +02:00
|
|
|
from processing.gui.Postprocessing import handleAlgorithmResults
|
|
|
|
|
2017-06-13 12:32:30 +10:00
|
|
|
from processing.core.ProcessingResults import resultsList
|
2014-11-12 19:36:12 +02:00
|
|
|
|
fix python pep8 warnings and fix some revealed errors
pep8 --ignore=E111,E128,E201,E202,E203,E211,E221,E222,E225,E226,E227,E231,E241,E261,E265,E272,E302,E303,E501,E701 \
--exclude="ui_*.py,debian/*,python/ext-libs/*" \
.
2015-02-01 14:15:42 +01:00
|
|
|
from processing.tools.system import getTempFilename
|
2017-04-26 14:33:53 +10:00
|
|
|
from processing.tools import dataobjects
|
fix python pep8 warnings and fix some revealed errors
pep8 --ignore=E111,E128,E201,E202,E203,E211,E221,E222,E225,E226,E227,E231,E241,E261,E265,E272,E302,E303,E501,E701 \
--exclude="ui_*.py,debian/*,python/ext-libs/*" \
.
2015-02-01 14:15:42 +01:00
|
|
|
|
|
|
|
import codecs
|
|
|
|
|
2015-08-22 14:29:41 +02:00
|
|
|
|
2019-07-11 09:43:27 +10:00
|
|
|
class BatchFeedback(QgsProcessingMultiStepFeedback):
|
|
|
|
|
|
|
|
def __init__(self, steps, feedback):
|
|
|
|
super().__init__(steps, feedback)
|
|
|
|
self.errors = []
|
|
|
|
|
|
|
|
def reportError(self, error: str, fatalError: bool):
|
|
|
|
self.errors.append(error)
|
|
|
|
super().reportError(error, fatalError)
|
|
|
|
|
|
|
|
|
2017-11-29 14:38:08 +10:00
|
|
|
class BatchAlgorithmDialog(QgsProcessingAlgorithmDialogBase):
|
2014-11-12 19:36:12 +02:00
|
|
|
|
2018-09-25 21:37:41 +10:00
|
|
|
def __init__(self, alg, parent=None):
|
|
|
|
super().__init__(parent)
|
2014-11-12 19:36:12 +02:00
|
|
|
|
2017-11-29 15:09:19 +10:00
|
|
|
self.setAlgorithm(alg)
|
2014-11-12 19:36:12 +02:00
|
|
|
|
2017-11-29 15:09:19 +10:00
|
|
|
self.setWindowTitle(self.tr('Batch Processing - {0}').format(self.algorithm().displayName()))
|
|
|
|
self.setMainWidget(BatchPanel(self, self.algorithm()))
|
|
|
|
self.hideShortHelp()
|
2016-01-08 08:27:22 +01:00
|
|
|
|
2019-06-17 09:38:12 +02:00
|
|
|
self.btnRunSingle = QPushButton(QCoreApplication.translate('BatchAlgorithmDialog', "Run as Single Process…"))
|
2019-05-23 14:50:42 +03:00
|
|
|
self.btnRunSingle.clicked.connect(self.runAsSingle)
|
2019-07-11 09:43:27 +10:00
|
|
|
self.buttonBox().addButton(self.btnRunSingle, QDialogButtonBox.ResetRole) # reset role to ensure left alignment
|
2019-05-23 14:50:42 +03:00
|
|
|
|
|
|
|
def runAsSingle(self):
|
|
|
|
self.close()
|
|
|
|
|
|
|
|
from processing.gui.AlgorithmDialog import AlgorithmDialog
|
|
|
|
dlg = AlgorithmDialog(self.algorithm().create(), parent=iface.mainWindow())
|
|
|
|
dlg.show()
|
|
|
|
dlg.exec_()
|
|
|
|
|
2018-11-29 09:27:47 +10:00
|
|
|
def runAlgorithm(self):
|
2017-06-13 12:32:30 +10:00
|
|
|
alg_parameters = []
|
2014-11-12 19:36:12 +02:00
|
|
|
|
2017-06-13 12:32:30 +10:00
|
|
|
feedback = self.createFeedback()
|
2017-05-16 15:21:41 +10:00
|
|
|
|
2017-11-29 15:09:19 +10:00
|
|
|
load_layers = self.mainWidget().checkLoadLayersOnCompletion.isChecked()
|
2017-11-10 14:42:29 +10:00
|
|
|
project = QgsProject.instance() if load_layers else None
|
|
|
|
|
2019-04-26 15:59:58 +10:00
|
|
|
for row in range(self.mainWidget().batchRowCount()):
|
2019-04-27 07:56:04 +10:00
|
|
|
parameters = self.mainWidget().parametersForRow(row, destinationProject=project, warnOnInvalid=True)
|
2017-06-13 12:32:30 +10:00
|
|
|
alg_parameters.append(parameters)
|
2014-11-12 19:36:12 +02:00
|
|
|
|
2018-09-07 10:25:27 +10:00
|
|
|
task = QgsScopedProxyProgressTask(self.tr('Batch Processing - {0}').format(self.algorithm().displayName()))
|
2019-07-11 09:43:27 +10:00
|
|
|
multi_feedback = BatchFeedback(len(alg_parameters), feedback)
|
2018-09-07 10:37:23 +10:00
|
|
|
feedback.progressChanged.connect(task.setProgress)
|
2018-09-07 10:25:27 +10:00
|
|
|
|
2019-07-11 09:43:27 +10:00
|
|
|
algorithm_results = []
|
|
|
|
errors = []
|
|
|
|
|
2017-11-10 11:33:58 +10:00
|
|
|
with OverrideCursor(Qt.WaitCursor):
|
|
|
|
|
2017-11-29 15:09:19 +10:00
|
|
|
self.mainWidget().setEnabled(False)
|
|
|
|
self.cancelButton().setEnabled(True)
|
2017-11-10 11:33:58 +10:00
|
|
|
|
|
|
|
# Make sure the Log tab is visible before executing the algorithm
|
|
|
|
try:
|
2017-11-29 15:09:19 +10:00
|
|
|
self.showLog()
|
2017-11-10 11:33:58 +10:00
|
|
|
self.repaint()
|
2019-05-31 17:38:14 +02:00
|
|
|
except Exception: # FIXME which one?
|
2017-11-10 11:33:58 +10:00
|
|
|
pass
|
|
|
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
|
|
|
for count, parameters in enumerate(alg_parameters):
|
|
|
|
if feedback.isCanceled():
|
|
|
|
break
|
2019-04-26 13:28:36 +10:00
|
|
|
self.setProgressText(
|
|
|
|
QCoreApplication.translate('BatchAlgorithmDialog', '\nProcessing algorithm {0}/{1}…').format(
|
|
|
|
count + 1, len(alg_parameters)))
|
|
|
|
self.setInfo(self.tr('<b>Algorithm {0} starting…</b>').format(self.algorithm().displayName()),
|
|
|
|
escapeHtml=False)
|
2018-09-07 10:33:26 +10:00
|
|
|
multi_feedback.setCurrentStep(count)
|
2017-11-10 11:33:58 +10:00
|
|
|
|
2018-03-21 19:22:14 +10:00
|
|
|
parameters = self.algorithm().preprocessParameters(parameters)
|
|
|
|
|
2017-11-10 11:33:58 +10:00
|
|
|
feedback.pushInfo(self.tr('Input parameters:'))
|
|
|
|
feedback.pushCommandInfo(pformat(parameters))
|
2017-06-13 12:32:30 +10:00
|
|
|
feedback.pushInfo('')
|
2014-11-12 19:36:12 +02:00
|
|
|
|
2017-11-10 14:34:34 +10:00
|
|
|
# important - we create a new context for each iteration
|
|
|
|
# this avoids holding onto resources and layers from earlier iterations,
|
|
|
|
# and allows batch processing of many more items then is possible
|
|
|
|
# if we hold on to these layers
|
|
|
|
context = dataobjects.createContext(feedback)
|
|
|
|
|
2017-11-10 11:33:58 +10:00
|
|
|
alg_start_time = time.time()
|
2019-08-08 09:03:57 +10:00
|
|
|
multi_feedback.errors = []
|
2019-07-11 09:43:27 +10:00
|
|
|
results, ok = self.algorithm().run(parameters, context, multi_feedback)
|
|
|
|
if ok:
|
2019-04-26 13:28:36 +10:00
|
|
|
self.setInfo(
|
|
|
|
QCoreApplication.translate('BatchAlgorithmDialog', 'Algorithm {0} correctly executed…').format(
|
|
|
|
self.algorithm().displayName()), escapeHtml=False)
|
2017-11-10 11:33:58 +10:00
|
|
|
feedback.pushInfo(
|
|
|
|
self.tr('Execution completed in {0:0.2f} seconds'.format(time.time() - alg_start_time)))
|
|
|
|
feedback.pushInfo(self.tr('Results:'))
|
|
|
|
feedback.pushCommandInfo(pformat(results))
|
|
|
|
feedback.pushInfo('')
|
2019-06-10 11:25:50 +10:00
|
|
|
algorithm_results.append({'parameters': parameters, 'results': results})
|
2017-11-10 11:33:58 +10:00
|
|
|
|
2019-07-11 09:43:27 +10:00
|
|
|
handleAlgorithmResults(self.algorithm(), context, multi_feedback, False, parameters)
|
|
|
|
else:
|
|
|
|
err = [e for e in multi_feedback.errors]
|
|
|
|
self.setInfo(
|
|
|
|
QCoreApplication.translate('BatchAlgorithmDialog', 'Algorithm {0} failed…').format(
|
|
|
|
self.algorithm().displayName()), escapeHtml=False)
|
|
|
|
feedback.reportError(
|
|
|
|
self.tr('Execution failed after {0:0.2f} seconds'.format(time.time() - alg_start_time)),
|
|
|
|
fatalError=False)
|
|
|
|
errors.append({'parameters': parameters, 'errors': err})
|
2014-11-12 19:36:12 +02:00
|
|
|
|
2017-11-10 14:34:34 +10:00
|
|
|
feedback.pushInfo(self.tr('Batch execution completed in {0:0.2f} seconds'.format(time.time() - start_time)))
|
2019-07-11 09:43:27 +10:00
|
|
|
if errors:
|
|
|
|
feedback.reportError(self.tr('{} executions failed. See log for further details.').format(len(errors)), fatalError=True)
|
2018-09-07 10:25:27 +10:00
|
|
|
task = None
|
2014-11-12 19:36:12 +02:00
|
|
|
|
2019-07-11 09:43:27 +10:00
|
|
|
self.finish(algorithm_results, errors)
|
2017-11-29 15:09:19 +10:00
|
|
|
self.cancelButton().setEnabled(False)
|
2017-06-13 12:32:30 +10:00
|
|
|
|
2019-07-11 09:43:27 +10:00
|
|
|
def finish(self, algorithm_results, errors):
|
2017-06-13 12:32:30 +10:00
|
|
|
for count, results in enumerate(algorithm_results):
|
2019-06-10 11:25:50 +10:00
|
|
|
self.loadHTMLResults(results['results'], count)
|
2017-06-13 12:32:30 +10:00
|
|
|
|
2019-07-11 09:43:27 +10:00
|
|
|
self.createSummaryTable(algorithm_results, errors)
|
2017-11-29 15:09:19 +10:00
|
|
|
self.mainWidget().setEnabled(True)
|
2019-04-26 13:29:45 +10:00
|
|
|
self.resetGui()
|
2014-11-12 19:36:12 +02:00
|
|
|
|
2017-06-13 12:32:30 +10:00
|
|
|
def loadHTMLResults(self, results, num):
|
2017-11-29 15:09:19 +10:00
|
|
|
for out in self.algorithm().outputDefinitions():
|
2017-06-13 12:32:30 +10:00
|
|
|
if isinstance(out, QgsProcessingOutputHtml) and out.name() in results and results[out.name()]:
|
2017-11-29 15:09:19 +10:00
|
|
|
resultsList.addResult(icon=self.algorithm().icon(), name='{} [{}]'.format(out.description(), num),
|
2017-06-13 12:32:30 +10:00
|
|
|
result=results[out.name()])
|
2014-11-12 19:36:12 +02:00
|
|
|
|
2019-07-11 09:43:27 +10:00
|
|
|
def createSummaryTable(self, algorithm_results, errors):
|
2014-11-12 19:36:12 +02:00
|
|
|
createTable = False
|
|
|
|
|
2017-11-29 15:09:19 +10:00
|
|
|
for out in self.algorithm().outputDefinitions():
|
2019-05-15 15:06:27 +02:00
|
|
|
if isinstance(out, (QgsProcessingOutputNumber, QgsProcessingOutputString, QgsProcessingOutputBoolean)):
|
2014-11-12 19:36:12 +02:00
|
|
|
createTable = True
|
|
|
|
break
|
|
|
|
|
2019-07-11 09:43:27 +10:00
|
|
|
if not createTable and not errors:
|
2014-11-12 19:36:12 +02:00
|
|
|
return
|
|
|
|
|
|
|
|
outputFile = getTempFilename('html')
|
|
|
|
with codecs.open(outputFile, 'w', encoding='utf-8') as f:
|
2019-07-11 09:43:27 +10:00
|
|
|
if createTable:
|
|
|
|
for i, res in enumerate(algorithm_results):
|
|
|
|
results = res['results']
|
|
|
|
params = res['parameters']
|
|
|
|
if i > 0:
|
|
|
|
f.write('<hr>\n')
|
|
|
|
f.write(self.tr('<h3>Parameters</h3>\n'))
|
|
|
|
f.write('<table>\n')
|
|
|
|
for param in self.algorithm().parameterDefinitions():
|
|
|
|
if not param.isDestination():
|
|
|
|
if param.name() in params:
|
|
|
|
f.write('<tr><th>{}</th><td>{}</td></tr>\n'.format(param.description(),
|
|
|
|
params[param.name()]))
|
|
|
|
f.write('</table>\n')
|
|
|
|
f.write(self.tr('<h3>Results</h3>\n'))
|
|
|
|
f.write('<table>\n')
|
|
|
|
for out in self.algorithm().outputDefinitions():
|
|
|
|
if out.name() in results:
|
|
|
|
f.write('<tr><th>{}</th><td>{}</td></tr>\n'.format(out.description(), results[out.name()]))
|
|
|
|
f.write('</table>\n')
|
|
|
|
if errors:
|
|
|
|
f.write('<h2 style="color: red">{}</h2>\n'.format(self.tr('Errors')))
|
|
|
|
for i, res in enumerate(errors):
|
|
|
|
errors = res['errors']
|
2019-06-10 11:25:50 +10:00
|
|
|
params = res['parameters']
|
|
|
|
if i > 0:
|
|
|
|
f.write('<hr>\n')
|
|
|
|
f.write(self.tr('<h3>Parameters</h3>\n'))
|
|
|
|
f.write('<table>\n')
|
|
|
|
for param in self.algorithm().parameterDefinitions():
|
|
|
|
if not param.isDestination():
|
|
|
|
if param.name() in params:
|
2019-07-11 09:43:27 +10:00
|
|
|
f.write(
|
|
|
|
'<tr><th>{}</th><td>{}</td></tr>\n'.format(param.description(), params[param.name()]))
|
2019-06-10 11:25:50 +10:00
|
|
|
f.write('</table>\n')
|
2019-07-11 09:43:27 +10:00
|
|
|
f.write('<h3>{}</h3>\n'.format(self.tr('Error')))
|
|
|
|
f.write('<p style="color: red">{}</p>\n'.format('<br>'.join(errors)))
|
2014-11-12 19:36:12 +02:00
|
|
|
|
2019-04-26 13:28:36 +10:00
|
|
|
resultsList.addResult(icon=self.algorithm().icon(),
|
|
|
|
name='{} [summary]'.format(self.algorithm().name()), timestamp=time.localtime(),
|
|
|
|
result=outputFile)
|