Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[processing] Also generate friendly names for outputs when converting…
… models to scripts
  • Loading branch information
nyalldawson committed Feb 14, 2019
1 parent d342ce9 commit 7418c18
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 19 deletions.
Expand Up @@ -298,7 +298,8 @@ Loads this child from a QVariant.
.. seealso:: :py:func:`toVariant`
%End

QStringList asPythonCode( QgsProcessing::PythonOutputType outputType, const QgsStringMap &extraParameters, int currentIndent, int indentSize, const QMap<QString, QString> &friendlyChildNames ) const;
QStringList asPythonCode( QgsProcessing::PythonOutputType outputType, const QgsStringMap &extraParameters, int currentIndent, int indentSize,
const QMap<QString, QString> &friendlyChildNames, const QMap<QString, QString> &friendlyOutputNames ) const;
%Docstring
Attempts to convert the child to executable Python code, and returns a list of the generated lines of code.

Expand Down
21 changes: 16 additions & 5 deletions src/core/processing/models/qgsprocessingmodelalgorithm.cpp
Expand Up @@ -362,6 +362,7 @@ QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::Pyth
QString currentIndent;

QMap< QString, QString> friendlyChildNames;
QMap< QString, QString> friendlyOutputNames;
auto safeName = []( const QString & name, bool capitalize )->QString
{
QString n = name.toLower().trimmed();
Expand All @@ -374,12 +375,12 @@ QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::Pyth
return capitalize ? QgsStringUtils::capitalize( n, QgsStringUtils::UpperCamelCase ) : n;
};

auto uniqueSafeName = [&friendlyChildNames, &safeName ]( const QString & name, bool capitalize )->QString
auto uniqueSafeName = [ &safeName ]( const QString & name, bool capitalize, const QMap< QString, QString > &friendlyNames )->QString
{
const QString base = safeName( name, capitalize );
QString candidate = base;
int i = 1;
while ( friendlyChildNames.contains( candidate ) )
while ( friendlyNames.contains( candidate ) )
{
i++;
candidate = QStringLiteral( "%1_%2" ).arg( base ).arg( i );
Expand All @@ -395,7 +396,7 @@ QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::Pyth
if ( childIt->isActive() && childIt->algorithm() )
{
toExecute.insert( childIt->childId() );
friendlyChildNames.insert( childIt->childId(), uniqueSafeName( childIt->description().isEmpty() ? childIt->childId() : childIt->description(), !childIt->description().isEmpty() ) );
friendlyChildNames.insert( childIt->childId(), uniqueSafeName( childIt->description().isEmpty() ? childIt->childId() : childIt->description(), !childIt->description().isEmpty(), friendlyChildNames ) );
}
}
const int totalSteps = toExecute.count();
Expand Down Expand Up @@ -429,7 +430,16 @@ QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::Pyth
lines.reserve( lines.size() + params.size() );
for ( const QgsProcessingParameterDefinition *def : params )
{
lines << indent + indent + QStringLiteral( "self.addParameter(%1)" ).arg( def->asPythonString() );
std::unique_ptr< QgsProcessingParameterDefinition > defClone( def->clone() );

if ( defClone->isDestination() )
{
const QString &friendlyName = !defClone->description().isEmpty() ? uniqueSafeName( defClone->description(), true, friendlyOutputNames ) : defClone->name();
friendlyOutputNames.insert( defClone->name(), friendlyName );
defClone->setName( friendlyName );
}

lines << indent + indent + QStringLiteral( "self.addParameter(%1)" ).arg( defClone->asPythonString() );
}

lines << QString();
Expand Down Expand Up @@ -529,6 +539,7 @@ QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::Pyth
if ( outputIt->childOutputName() == destParam->name() )
{
QString paramName = child.childId() + ':' + outputIt.key();
paramName = friendlyOutputNames.value( paramName, paramName );
childParams.insert( destParam->name(), QStringLiteral( "parameters['%1']" ).arg( paramName ) );
isFinalOutput = true;
break;
Expand Down Expand Up @@ -556,7 +567,7 @@ QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::Pyth
}
}

lines << child.asPythonCode( outputType, childParams, currentIndent.size(), indentSize, friendlyChildNames );
lines << child.asPythonCode( outputType, childParams, currentIndent.size(), indentSize, friendlyChildNames, friendlyOutputNames );
currentStep++;
if ( currentStep < totalSteps )
{
Expand Down
Expand Up @@ -162,7 +162,8 @@ bool QgsProcessingModelChildAlgorithm::loadVariant( const QVariant &child )
return true;
}

QStringList QgsProcessingModelChildAlgorithm::asPythonCode( const QgsProcessing::PythonOutputType outputType, const QgsStringMap &extraParameters, int currentIndent, int indentSize, const QMap<QString, QString> &friendlyChildNames ) const
QStringList QgsProcessingModelChildAlgorithm::asPythonCode( const QgsProcessing::PythonOutputType outputType, const QgsStringMap &extraParameters,
int currentIndent, int indentSize, const QMap<QString, QString> &friendlyChildNames, const QMap<QString, QString> &friendlyOutputNames ) const
{
QStringList lines;
const QString baseIndent = QString( ' ' ).repeated( currentIndent );
Expand Down Expand Up @@ -211,7 +212,9 @@ QStringList QgsProcessingModelChildAlgorithm::asPythonCode( const QgsProcessing:

for ( auto outputIt = mModelOutputs.constBegin(); outputIt != mModelOutputs.constEnd(); ++outputIt )
{
lines << baseIndent + QStringLiteral( "results['%1:%2'] = outputs['%3']['%4']" ).arg( mId, outputIt.key(), friendlyChildNames.value( mId, mId ), outputIt.value().childOutputName() );
QString outputName = QStringLiteral( "%1:%2" ).arg( mId, outputIt.key() );
outputName = friendlyOutputNames.value( outputName, outputName );
lines << baseIndent + QStringLiteral( "results['%1'] = outputs['%2']['%3']" ).arg( outputName, friendlyChildNames.value( mId, mId ), outputIt.value().childOutputName() );
}

return lines;
Expand Down
Expand Up @@ -288,7 +288,8 @@ class CORE_EXPORT QgsProcessingModelChildAlgorithm : public QgsProcessingModelCo
*
* The \a friendlyChildNames argument gives a map of child id to a friendly algorithm name, to be used in the code to identify that algorithm instead of the raw child id.
*/
QStringList asPythonCode( QgsProcessing::PythonOutputType outputType, const QgsStringMap &extraParameters, int currentIndent, int indentSize, const QMap<QString, QString> &friendlyChildNames ) const;
QStringList asPythonCode( QgsProcessing::PythonOutputType outputType, const QgsStringMap &extraParameters, int currentIndent, int indentSize,
const QMap<QString, QString> &friendlyChildNames, const QMap<QString, QString> &friendlyOutputNames ) const;

private:

Expand Down
23 changes: 13 additions & 10 deletions tests/src/analysis/testqgsprocessing.cpp
Expand Up @@ -6182,7 +6182,7 @@ void TestQgsProcessing::modelerAlgorithm()
QVERIFY( QgsProcessingModelChildParameterSource::fromExpression( QStringLiteral( "a" ) ) !=
QgsProcessingModelChildParameterSource::fromStaticValue( QStringLiteral( "b" ) ) );


QMap< QString, QString > friendlyOutputNames;
QgsProcessingModelChildAlgorithm child( QStringLiteral( "some_id" ) );
QCOMPARE( child.algorithmId(), QStringLiteral( "some_id" ) );
QVERIFY( !child.algorithm() );
Expand All @@ -6191,11 +6191,11 @@ void TestQgsProcessing::modelerAlgorithm()
QVERIFY( child.setAlgorithmId( QStringLiteral( "native:centroids" ) ) );
QVERIFY( child.algorithm() );
QCOMPARE( child.algorithm()->id(), QStringLiteral( "native:centroids" ) );
QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, QgsStringMap(), 4, 2, friendlyNames ).join( '\n' ), QStringLiteral( " alg_params = {\n }\n outputs[''] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)" ) );
QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, QgsStringMap(), 4, 2, friendlyNames, friendlyOutputNames ).join( '\n' ), QStringLiteral( " alg_params = {\n }\n outputs[''] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)" ) );
QgsStringMap extraParams;
extraParams[QStringLiteral( "SOMETHING" )] = QStringLiteral( "SOMETHING_ELSE" );
extraParams[QStringLiteral( "SOMETHING2" )] = QStringLiteral( "SOMETHING_ELSE2" );
QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames ).join( '\n' ), QStringLiteral( " alg_params = {\n 'SOMETHING': SOMETHING_ELSE,\n 'SOMETHING2': SOMETHING_ELSE2\n }\n outputs[''] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)" ) );
QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames, friendlyOutputNames ).join( '\n' ), QStringLiteral( " alg_params = {\n 'SOMETHING': SOMETHING_ELSE,\n 'SOMETHING2': SOMETHING_ELSE2\n }\n outputs[''] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)" ) );
// bit of a hack -- but try to simulate an algorithm not originally available!
child.mAlgorithm.reset();
QVERIFY( !child.algorithm() );
Expand Down Expand Up @@ -6238,7 +6238,7 @@ void TestQgsProcessing::modelerAlgorithm()
QCOMPARE( child.parameterSources().value( QStringLiteral( "b" ) ).at( 0 ).staticValue().toInt(), 7 );
QCOMPARE( child.parameterSources().value( QStringLiteral( "b" ) ).at( 1 ).staticValue().toInt(), 9 );

QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames ).join( '\n' ), QStringLiteral( " # desc\n alg_params = {\n 'a': 5,\n 'b': [7,9],\n 'SOMETHING': SOMETHING_ELSE,\n 'SOMETHING2': SOMETHING_ELSE2\n }\n outputs['my_id'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)" ) );
QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames, friendlyOutputNames ).join( '\n' ), QStringLiteral( " # desc\n alg_params = {\n 'a': 5,\n 'b': [7,9],\n 'SOMETHING': SOMETHING_ELSE,\n 'SOMETHING2': SOMETHING_ELSE2\n }\n outputs['my_id'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)" ) );

QgsProcessingModelOutput testModelOut;
testModelOut.setChildId( QStringLiteral( "my_id" ) );
Expand Down Expand Up @@ -6274,13 +6274,14 @@ void TestQgsProcessing::modelerAlgorithm()
QCOMPARE( child.modelOutput( "a" ).description(), QStringLiteral( "my output" ) );
child.modelOutput( "a" ).setDescription( QStringLiteral( "my output 2" ) );
QCOMPARE( child.modelOutput( "a" ).description(), QStringLiteral( "my output 2" ) );
QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames ).join( '\n' ), QStringLiteral( " # desc\n alg_params = {\n 'a': 5,\n 'b': [7,9],\n 'SOMETHING': SOMETHING_ELSE,\n 'SOMETHING2': SOMETHING_ELSE2\n }\n outputs['my_id'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n results['my_id:a'] = outputs['my_id']['']" ) );
qDebug() << child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames, friendlyOutputNames ).join( '\n' );
QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames, friendlyOutputNames ).join( '\n' ), QStringLiteral( " # desc\n alg_params = {\n 'a': 5,\n 'b': [7,9],\n 'SOMETHING': SOMETHING_ELSE,\n 'SOMETHING2': SOMETHING_ELSE2\n }\n outputs['my_id'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n results['my_id:a'] = outputs['my_id']['']" ) );

// ensure friendly name is used if present
child.addParameterSources( QStringLiteral( "b" ), QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromChildOutput( "a", "out" ) );
QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames ).join( '\n' ), QStringLiteral( " # desc\n alg_params = {\n 'a': 5,\n 'b': outputs['alga']['out'],\n 'SOMETHING': SOMETHING_ELSE,\n 'SOMETHING2': SOMETHING_ELSE2\n }\n outputs['my_id'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n results['my_id:a'] = outputs['my_id']['']" ) );
QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames, friendlyOutputNames ).join( '\n' ), QStringLiteral( " # desc\n alg_params = {\n 'a': 5,\n 'b': outputs['alga']['out'],\n 'SOMETHING': SOMETHING_ELSE,\n 'SOMETHING2': SOMETHING_ELSE2\n }\n outputs['my_id'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n results['my_id:a'] = outputs['my_id']['']" ) );
friendlyNames.remove( "a" );
QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames ).join( '\n' ), QStringLiteral( " # desc\n alg_params = {\n 'a': 5,\n 'b': outputs['a']['out'],\n 'SOMETHING': SOMETHING_ELSE,\n 'SOMETHING2': SOMETHING_ELSE2\n }\n outputs['my_id'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n results['my_id:a'] = outputs['my_id']['']" ) );
QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames, friendlyOutputNames ).join( '\n' ), QStringLiteral( " # desc\n alg_params = {\n 'a': 5,\n 'b': outputs['a']['out'],\n 'SOMETHING': SOMETHING_ELSE,\n 'SOMETHING2': SOMETHING_ELSE2\n }\n outputs['my_id'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n results['my_id:a'] = outputs['my_id']['']" ) );

// no existent
child.modelOutput( "b" ).setDescription( QStringLiteral( "my output 3" ) );
Expand Down Expand Up @@ -6992,6 +6993,8 @@ void TestQgsProcessing::modelExecution()
QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxy" ).value.toDouble(), 46.8719, 0.001 );

model2.setName( QStringLiteral( "2my model" ) );
model2.childAlgorithm( "cx1" ).modelOutput( QStringLiteral( "MODEL_OUT_LAYER" ) ).setDescription( "my model output" );
model2.updateDestinationParameters();
model2.childAlgorithm( "cx1" ).setDescription( "first step in my model" );
QStringList actualParts = model2.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, 2 );
QgsDebugMsg( actualParts.join( '\n' ) );
Expand All @@ -7009,7 +7012,7 @@ void TestQgsProcessing::modelExecution()
" def initAlgorithm(self, config=None):\n"
" self.addParameter(QgsProcessingParameterFeatureSource('SOURCE_LAYER', '', defaultValue=None))\n"
" self.addParameter(QgsProcessingParameterNumber('DIST', '', type=QgsProcessingParameterNumber.Double, defaultValue=None))\n"
" self.addParameter(QgsProcessingParameterFeatureSink('cx1:MODEL_OUT_LAYER', '', type=QgsProcessing.TypeVectorPolygon, createByDefault=True, defaultValue=None))\n"
" self.addParameter(QgsProcessingParameterFeatureSink('MyModelOutput', 'my model output', 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, model_feedback):\n"
Expand All @@ -7027,10 +7030,10 @@ void TestQgsProcessing::modelExecution()
" 'INPUT': parameters['SOURCE_LAYER'],\n"
" 'JOIN_STYLE': 2,\n"
" 'SEGMENTS': QgsExpression('@myvar*2').evaluate(),\n"
" 'OUTPUT': parameters['cx1:MODEL_OUT_LAYER']\n"
" 'OUTPUT': parameters['MyModelOutput']\n"
" }\n"
" outputs['FirstStepInMyModel'] = processing.run('native:buffer', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n"
" results['cx1:MODEL_OUT_LAYER'] = outputs['FirstStepInMyModel']['OUTPUT']\n"
" results['MyModelOutput'] = outputs['FirstStepInMyModel']['OUTPUT']\n"
"\n"
" feedback.setCurrentStep(1)\n"
" if feedback.isCanceled():\n"
Expand Down

0 comments on commit 7418c18

Please sign in to comment.