Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[processing] When converting models to python, correctly use a
multi-step feedback object to give scripts accurate progress reports

And also add in checks for cancelation between child algorithm
execution to allow generated scripts to early exit
  • Loading branch information
nyalldawson committed Feb 13, 2019
1 parent 1e431f2 commit fc8fd1e
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 10 deletions.
33 changes: 24 additions & 9 deletions src/core/processing/models/qgsprocessingmodelalgorithm.cpp
Expand Up @@ -373,12 +373,21 @@ QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::Pyth

const QString algorithmClassName = safeName( name() );

QSet< QString > toExecute;
for ( auto childIt = mChildAlgorithms.constBegin(); childIt != mChildAlgorithms.constEnd(); ++childIt )
{
if ( childIt->isActive() && childIt->algorithm() )
toExecute.insert( childIt->childId() );
}
const int totalSteps = toExecute.count();

switch ( outputType )
{
case QgsProcessing::PythonQgsProcessingAlgorithmSubclass:
{
lines << QStringLiteral( "from qgis.core import QgsProcessing" );
lines << QStringLiteral( "from qgis.core import QgsProcessingAlgorithm" );
lines << QStringLiteral( "from qgis.core import QgsProcessingMultiStepFeedback" );
// add specific parameter type imports
const auto params = parameterDefinitions();
QStringList importLines; // not a set - we need regular ordering
Expand All @@ -405,8 +414,12 @@ QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::Pyth
}

lines << QString();
lines << indent + QStringLiteral( "def processAlgorithm(self, parameters, context, feedback):" );
lines << indent + QStringLiteral( "def processAlgorithm(self, parameters, context, model_feedback):" );
currentIndent = indent + indent;

lines << currentIndent + QStringLiteral( "# Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the" );
lines << currentIndent + QStringLiteral( "# overall progress through the model" );
lines << currentIndent + QStringLiteral( "feedback = QgsProcessingMultiStepFeedback(%1, model_feedback)" ).arg( totalSteps );
break;
}
#if 0
Expand Down Expand Up @@ -450,15 +463,9 @@ QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::Pyth
lines << currentIndent + QStringLiteral( "results = {}" );
lines << currentIndent + QStringLiteral( "outputs = {}" );

QSet< QString > toExecute;
for ( auto childIt = mChildAlgorithms.constBegin(); childIt != mChildAlgorithms.constEnd(); ++childIt )
{
if ( childIt->isActive() && childIt->algorithm() )
toExecute.insert( childIt->childId() );
}

QSet< QString > executed;
bool executedAlg = true;
int currentStep = 0;
while ( executedAlg && executed.count() < toExecute.count() )
{
executedAlg = false;
Expand Down Expand Up @@ -530,7 +537,15 @@ QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::Pyth
}

lines << child.asPythonCode( outputType, childParams, currentIndent.size(), indentSize );

currentStep++;
if ( currentStep < totalSteps )
{
lines << QString();
lines << currentIndent + QStringLiteral( "feedback.setCurrentStep(%1)" ).arg( currentStep );
lines << currentIndent + QStringLiteral( "if feedback.isCanceled():" );
lines << currentIndent + indent + QStringLiteral( "return {}" );
lines << QString();
}
executed.insert( childId );
}
}
Expand Down
16 changes: 15 additions & 1 deletion tests/src/analysis/testqgsprocessing.cpp
Expand Up @@ -6986,6 +6986,7 @@ void TestQgsProcessing::modelExecution()
QgsDebugMsg( actualParts.join( '\n' ) );
QStringList expectedParts = QStringLiteral( "from qgis.core import QgsProcessing\n"
"from qgis.core import QgsProcessingAlgorithm\n"
"from qgis.core import QgsProcessingMultiStepFeedback\n"
"from qgis.core import QgsProcessingParameterFeatureSource\n"
"from qgis.core import QgsProcessingParameterNumber\n"
"from qgis.core import QgsProcessingParameterFeatureSink\n"
Expand All @@ -7000,7 +7001,10 @@ void TestQgsProcessing::modelExecution()
" self.addParameter(QgsProcessingParameterFeatureSink('cx1:MODEL_OUT_LAYER', '', type=QgsProcessing.TypeVectorPolygon, createByDefault=True, defaultValue=None))\n"
" self.addParameter(QgsProcessingParameterFeatureSink('cx3:MY_OUT', '', type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=True, defaultValue=None))\n"
"\n"
" def processAlgorithm(self, parameters, context, feedback):\n"
" def processAlgorithm(self, parameters, context, model_feedback):\n"
" # Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the\n"
" # overall progress through the model\n"
" feedback = QgsProcessingMultiStepFeedback(3, model_feedback)\n"
" results = {}\n"
" outputs = {}\n"
" alg_params = {\n"
Expand All @@ -7014,11 +7018,21 @@ void TestQgsProcessing::modelExecution()
" }\n"
" outputs['cx1'] = processing.run('native:buffer', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n"
" results['cx1:MODEL_OUT_LAYER'] = outputs['cx1']['OUTPUT']\n"
"\n"
" feedback.setCurrentStep(1)\n"
" if feedback.isCanceled():\n"
" return {}\n"
"\n"
" alg_params = {\n"
" 'INPUT': outputs['cx1']['OUTPUT'],\n"
" 'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT\n"
" }\n"
" outputs['cx2'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n"
"\n"
" feedback.setCurrentStep(2)\n"
" if feedback.isCanceled():\n"
" return {}\n"
"\n"
" alg_params = {\n"
" 'EXPRESSION': 'true',\n"
" 'INPUT': outputs['cx1']['OUTPUT'],\n"
Expand Down

0 comments on commit fc8fd1e

Please sign in to comment.