mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-16 00:03:12 -04:00
[processing] Update QgsProcessingModelAlgorithm::asPythonCode for 3.x API
This commit is contained in:
parent
fb519ead46
commit
9b2e60130f
@ -346,9 +346,13 @@ Sets the source file ``path`` for the model, if available.
|
|||||||
.. seealso:: :py:func:`sourceFilePath`
|
.. seealso:: :py:func:`sourceFilePath`
|
||||||
%End
|
%End
|
||||||
|
|
||||||
QString asPythonCode() const;
|
QStringList asPythonCode( QgsProcessing::PythonOutputType outputType, int indentSize ) const;
|
||||||
%Docstring
|
%Docstring
|
||||||
Attempts to convert the model to executable Python code.
|
Attempts to convert the model to executable Python code, and returns the generated lines of code.
|
||||||
|
|
||||||
|
The ``outputType`` argument dictates the desired script type.
|
||||||
|
|
||||||
|
The ``indentSize`` arguments specifies the size of indented lines.
|
||||||
%End
|
%End
|
||||||
|
|
||||||
QList< QgsProcessingModelChildParameterSource > availableSourcesForChild( const QString &childId, const QStringList ¶meterTypes = QStringList(),
|
QList< QgsProcessingModelChildParameterSource > availableSourcesForChild( const QString &childId, const QStringList ¶meterTypes = QStringList(),
|
||||||
|
@ -353,50 +353,132 @@ void QgsProcessingModelAlgorithm::setSourceFilePath( const QString &sourceFile )
|
|||||||
mSourceFile = sourceFile;
|
mSourceFile = sourceFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QgsProcessingModelAlgorithm::asPythonCode() const
|
QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::PythonOutputType outputType, const int indentSize ) const
|
||||||
{
|
{
|
||||||
QStringList lines;
|
QStringList lines;
|
||||||
lines << QStringLiteral( "##%1=name" ).arg( name() );
|
QString indent = QString( ' ' ).repeated( indentSize );
|
||||||
|
QString currentIndent;
|
||||||
|
switch ( outputType )
|
||||||
|
{
|
||||||
|
case QgsProcessing::PythonQgsProcessingAlgorithmSubclass:
|
||||||
|
{
|
||||||
|
lines << QStringLiteral( "from qgis.core import QgsProcessing" );
|
||||||
|
lines << QStringLiteral( "from qgis.core import QgsProcessingAlgorithm" );
|
||||||
|
// add specific parameter type imports
|
||||||
|
const auto params = parameterDefinitions();
|
||||||
|
QStringList importLines; // not a set - we need regular ordering
|
||||||
|
for ( const QgsProcessingParameterDefinition *def : params )
|
||||||
|
{
|
||||||
|
const QString importString = QgsApplication::processingRegistry()->parameterType( def->type() )->pythonImportString();
|
||||||
|
if ( !importString.isEmpty() && !importLines.contains( importString ) )
|
||||||
|
importLines << importString;
|
||||||
|
}
|
||||||
|
lines << importLines;
|
||||||
|
lines << QStringLiteral( "import processing" );
|
||||||
|
lines << QString() << QString();
|
||||||
|
|
||||||
|
auto safeName = []( const QString & name )->QString
|
||||||
|
{
|
||||||
|
QString n = name.toLower().trimmed();
|
||||||
|
QRegularExpression rx( QStringLiteral( "[^a-z_A-Z0-9]" ) );
|
||||||
|
n.replace( rx, QString() );
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
|
const QString algorithmClassName = safeName( name() );
|
||||||
|
lines << QStringLiteral( "class %1(QgsProcessingAlgorithm):" ).arg( algorithmClassName );
|
||||||
|
|
||||||
|
// createInstance
|
||||||
|
lines << indent + QStringLiteral( "def createInstance(self):" );
|
||||||
|
lines << indent + indent + QStringLiteral( "return %1()" ).arg( algorithmClassName );
|
||||||
|
lines << QString();
|
||||||
|
|
||||||
|
// name, displayName
|
||||||
|
lines << indent + QStringLiteral( "def name(self):" );
|
||||||
|
lines << indent + indent + QStringLiteral( "return '%1'" ).arg( mModelName );
|
||||||
|
lines << QString();
|
||||||
|
lines << indent + QStringLiteral( "def displayName(self):" );
|
||||||
|
lines << indent + indent + QStringLiteral( "return '%1'" ).arg( mModelName );
|
||||||
|
lines << QString();
|
||||||
|
|
||||||
|
// group, groupId
|
||||||
|
lines << indent + QStringLiteral( "def group(self):" );
|
||||||
|
lines << indent + indent + QStringLiteral( "return '%1'" ).arg( mModelGroup );
|
||||||
|
lines << QString();
|
||||||
|
lines << indent + QStringLiteral( "def groupId(self):" );
|
||||||
|
lines << indent + indent + QStringLiteral( "return '%1'" ).arg( mModelGroupId );
|
||||||
|
lines << QString();
|
||||||
|
|
||||||
|
// help
|
||||||
|
if ( !shortHelpString().isEmpty() )
|
||||||
|
{
|
||||||
|
lines << indent + QStringLiteral( "def shortHelpString(self):" );
|
||||||
|
lines << indent + indent + QStringLiteral( "return '%1'" ).arg( shortHelpString() );
|
||||||
|
lines << QString();
|
||||||
|
}
|
||||||
|
if ( !helpUrl().isEmpty() )
|
||||||
|
{
|
||||||
|
lines << indent + QStringLiteral( "def helpUrl(self):" );
|
||||||
|
lines << indent + indent + QStringLiteral( "return '%1'" ).arg( helpUrl() );
|
||||||
|
lines << QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// initAlgorithm, parameter definitions
|
||||||
|
lines << indent + QStringLiteral( "def initAlgorithm(self, config=None):" );
|
||||||
|
lines.reserve( lines.size() + params.size() );
|
||||||
|
for ( const QgsProcessingParameterDefinition *def : params )
|
||||||
|
{
|
||||||
|
lines << indent + indent + QStringLiteral( "self.addParameter(%1)" ).arg( def->asPythonString() );
|
||||||
|
}
|
||||||
|
|
||||||
|
lines << QString();
|
||||||
|
lines << indent + QStringLiteral( "def processAlgorithm(self, parameters, context, feedback):" );
|
||||||
|
currentIndent = indent + indent;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
|
case Script:
|
||||||
|
{
|
||||||
|
QgsStringMap params;
|
||||||
|
QgsProcessingContext context;
|
||||||
QMap< QString, QgsProcessingModelParameter >::const_iterator paramIt = mParameterComponents.constBegin();
|
QMap< QString, QgsProcessingModelParameter >::const_iterator paramIt = mParameterComponents.constBegin();
|
||||||
for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt )
|
for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt )
|
||||||
{
|
{
|
||||||
QString name = paramIt.value().parameterName();
|
QString name = paramIt.value().parameterName();
|
||||||
if ( parameterDefinition( name ) )
|
if ( parameterDefinition( name ) )
|
||||||
{
|
{
|
||||||
lines << parameterDefinition( name )->asScriptCode();
|
// TODO - generic value to string method
|
||||||
|
params.insert( name, parameterDefinition( name )->valueAsPythonString( parameterDefinition( name )->defaultValue(), context ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto safeName = []( const QString & name )->QString
|
if ( !params.isEmpty() )
|
||||||
{
|
{
|
||||||
QString n = name.toLower().trimmed();
|
lines << QStringLiteral( "parameters = {" );
|
||||||
QRegularExpression rx( QStringLiteral( "[^a-z_]" ) );
|
for ( auto it = params.constBegin(); it != params.constEnd(); ++it )
|
||||||
n.replace( rx, QString() );
|
|
||||||
return n;
|
|
||||||
};
|
|
||||||
|
|
||||||
QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
|
|
||||||
for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
|
|
||||||
{
|
{
|
||||||
if ( !childIt->isActive() || !childIt->algorithm() )
|
lines << QStringLiteral( " '%1':%2," ).arg( it.key(), it.value() );
|
||||||
continue;
|
|
||||||
|
|
||||||
// look through all outputs for child
|
|
||||||
QMap<QString, QgsProcessingModelOutput> outputs = childIt->modelOutputs();
|
|
||||||
QMap<QString, QgsProcessingModelOutput>::const_iterator outputIt = outputs.constBegin();
|
|
||||||
for ( ; outputIt != outputs.constEnd(); ++outputIt )
|
|
||||||
{
|
|
||||||
const QgsProcessingOutputDefinition *output = childIt->algorithm()->outputDefinition( outputIt->childOutputName() );
|
|
||||||
lines << QStringLiteral( "##%1=output %2" ).arg( safeName( outputIt->name() ), output->type() );
|
|
||||||
}
|
}
|
||||||
|
lines << QStringLiteral( "}" )
|
||||||
|
<< QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
lines << QStringLiteral( "results={}" );
|
lines << QStringLiteral( "context = QgsProcessingContext()" )
|
||||||
|
<< QStringLiteral( "context.setProject(QgsProject.instance())" )
|
||||||
|
<< QStringLiteral( "feedback = QgsProcessingFeedback()" )
|
||||||
|
<< QString();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
lines << currentIndent + QStringLiteral( "results={}" );
|
||||||
|
lines << currentIndent + QStringLiteral( "outputs={}" );
|
||||||
|
|
||||||
QSet< QString > toExecute;
|
QSet< QString > toExecute;
|
||||||
childIt = mChildAlgorithms.constBegin();
|
for ( auto childIt = mChildAlgorithms.constBegin(); childIt != mChildAlgorithms.constEnd(); ++childIt )
|
||||||
for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
|
|
||||||
{
|
{
|
||||||
if ( childIt->isActive() && childIt->algorithm() )
|
if ( childIt->isActive() && childIt->algorithm() )
|
||||||
toExecute.insert( childIt->childId() );
|
toExecute.insert( childIt->childId() );
|
||||||
@ -428,15 +510,66 @@ QString QgsProcessingModelAlgorithm::asPythonCode() const
|
|||||||
executedAlg = true;
|
executedAlg = true;
|
||||||
|
|
||||||
const QgsProcessingModelChildAlgorithm &child = mChildAlgorithms[ childId ];
|
const QgsProcessingModelChildAlgorithm &child = mChildAlgorithms[ childId ];
|
||||||
lines << child.asPythonCode();
|
|
||||||
|
// fill in temporary outputs
|
||||||
|
const QgsProcessingParameterDefinitions childDefs = child.algorithm()->parameterDefinitions();
|
||||||
|
QgsStringMap childParams;
|
||||||
|
for ( const QgsProcessingParameterDefinition *def : childDefs )
|
||||||
|
{
|
||||||
|
if ( def->isDestination() )
|
||||||
|
{
|
||||||
|
const QgsProcessingDestinationParameter *destParam = static_cast< const QgsProcessingDestinationParameter * >( def );
|
||||||
|
|
||||||
|
// is destination linked to one of the final outputs from this model?
|
||||||
|
bool isFinalOutput = false;
|
||||||
|
QMap<QString, QgsProcessingModelOutput> outputs = child.modelOutputs();
|
||||||
|
QMap<QString, QgsProcessingModelOutput>::const_iterator outputIt = outputs.constBegin();
|
||||||
|
for ( ; outputIt != outputs.constEnd(); ++outputIt )
|
||||||
|
{
|
||||||
|
if ( outputIt->childOutputName() == destParam->name() )
|
||||||
|
{
|
||||||
|
QString paramName = child.childId() + ':' + outputIt.key();
|
||||||
|
childParams.insert( destParam->name(), QStringLiteral( "parameters['%1']" ).arg( paramName ) );
|
||||||
|
isFinalOutput = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !isFinalOutput )
|
||||||
|
{
|
||||||
|
// output is temporary
|
||||||
|
|
||||||
|
// check whether it's optional, and if so - is it required?
|
||||||
|
bool required = true;
|
||||||
|
if ( destParam->flags() & QgsProcessingParameterDefinition::FlagOptional )
|
||||||
|
{
|
||||||
|
required = childOutputIsRequired( child.childId(), destParam->name() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// not optional, or required elsewhere in model
|
||||||
|
if ( required )
|
||||||
|
{
|
||||||
|
|
||||||
|
childParams.insert( destParam->name(), QStringLiteral( "QgsProcessing.TEMPORARY_OUTPUT" ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines << child.asPythonCode( outputType, childParams, currentIndent.size(), indentSize );
|
||||||
|
|
||||||
executed.insert( childId );
|
executed.insert( childId );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lines << QStringLiteral( "return results" );
|
switch ( outputType )
|
||||||
|
{
|
||||||
|
case QgsProcessing::PythonQgsProcessingAlgorithmSubclass:
|
||||||
|
lines << currentIndent + QStringLiteral( "return results" );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return lines.join( '\n' );
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
QMap<QString, QgsProcessingModelAlgorithm::VariableDefinition> QgsProcessingModelAlgorithm::variablesForChildAlgorithm( const QString &childId, QgsProcessingContext &context, const QVariantMap &modelParameters, const QVariantMap &results ) const
|
QMap<QString, QgsProcessingModelAlgorithm::VariableDefinition> QgsProcessingModelAlgorithm::variablesForChildAlgorithm( const QString &childId, QgsProcessingContext &context, const QVariantMap &modelParameters, const QVariantMap &results ) const
|
||||||
|
@ -304,9 +304,13 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
|
|||||||
void setSourceFilePath( const QString &path );
|
void setSourceFilePath( const QString &path );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to convert the model to executable Python code.
|
* Attempts to convert the model to executable Python code, and returns the generated lines of code.
|
||||||
|
*
|
||||||
|
* The \a outputType argument dictates the desired script type.
|
||||||
|
*
|
||||||
|
* The \a indentSize arguments specifies the size of indented lines.
|
||||||
*/
|
*/
|
||||||
QString asPythonCode() const;
|
QStringList asPythonCode( QgsProcessing::PythonOutputType outputType, int indentSize ) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of possible sources which can be used for the parameters for a child
|
* Returns a list of possible sources which can be used for the parameters for a child
|
||||||
|
@ -6244,10 +6244,8 @@ void TestQgsProcessing::modelerAlgorithm()
|
|||||||
child.removeModelOutput( QStringLiteral( "a" ) );
|
child.removeModelOutput( QStringLiteral( "a" ) );
|
||||||
QCOMPARE( child.modelOutputs().count(), 1 );
|
QCOMPARE( child.modelOutputs().count(), 1 );
|
||||||
|
|
||||||
|
|
||||||
// model algorithm tests
|
// model algorithm tests
|
||||||
|
|
||||||
|
|
||||||
QgsProcessingModelAlgorithm alg( "test", "testGroup" );
|
QgsProcessingModelAlgorithm alg( "test", "testGroup" );
|
||||||
QCOMPARE( alg.name(), QStringLiteral( "test" ) );
|
QCOMPARE( alg.name(), QStringLiteral( "test" ) );
|
||||||
QCOMPARE( alg.displayName(), QStringLiteral( "test" ) );
|
QCOMPARE( alg.displayName(), QStringLiteral( "test" ) );
|
||||||
@ -6948,19 +6946,66 @@ void TestQgsProcessing::modelExecution()
|
|||||||
QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxx" ).value.toDouble(), -83.3333, 0.001 );
|
QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxx" ).value.toDouble(), -83.3333, 0.001 );
|
||||||
QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxy" ).value.toDouble(), 46.8719, 0.001 );
|
QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxy" ).value.toDouble(), 46.8719, 0.001 );
|
||||||
|
|
||||||
QStringList actualParts = model2.asPythonCode().split( '\n' );
|
QStringList actualParts = model2.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, 2 );
|
||||||
QStringList expectedParts = QStringLiteral( "##model=name\n"
|
QgsDebugMsg( actualParts.join( '\n' ) );
|
||||||
"##DIST=number\n"
|
QStringList expectedParts = QStringLiteral( "from qgis.core import QgsProcessing\n"
|
||||||
"##SOURCE_LAYER=source\n"
|
"from qgis.core import QgsProcessingAlgorithm\n"
|
||||||
"##model_out_layer=output outputVector\n"
|
"from qgis.core import QgsProcessingParameterFeatureSource\n"
|
||||||
"##my_out=output outputVector\n"
|
"from qgis.core import QgsProcessingParameterNumber\n"
|
||||||
"results={}\n"
|
"from qgis.core import QgsProcessingParameterFeatureSink\n"
|
||||||
"outputs['cx1']=processing.run('native:buffer', {'DISSOLVE':false,'DISTANCE':parameters['DIST'],'END_CAP_STYLE':1,'INPUT':parameters['SOURCE_LAYER'],'JOIN_STYLE':2,'SEGMENTS':QgsExpression('@myvar*2').evaluate()}, context=context, feedback=feedback)\n"
|
"import processing\n"
|
||||||
"results['MODEL_OUT_LAYER']=outputs['cx1']['OUTPUT']\n"
|
"\n"
|
||||||
"outputs['cx2']=processing.run('native:centroids', {'INPUT':outputs['cx1']['OUTPUT']}, context=context, feedback=feedback)\n"
|
"\n"
|
||||||
"outputs['cx3']=processing.run('native:extractbyexpression', {'EXPRESSION':true,'INPUT':outputs['cx1']['OUTPUT'],'OUTPUT':parameters['MY_OUT']}, context=context, feedback=feedback)\n"
|
"class model(QgsProcessingAlgorithm):\n"
|
||||||
"results['MY_OUT']=outputs['cx3']['OUTPUT']\n"
|
" def createInstance(self):\n"
|
||||||
"return results" ).split( '\n' );
|
" return model()\n"
|
||||||
|
"\n"
|
||||||
|
" def name(self):\n"
|
||||||
|
" return 'model'\n"
|
||||||
|
"\n"
|
||||||
|
" def displayName(self):\n"
|
||||||
|
" return 'model'\n"
|
||||||
|
"\n"
|
||||||
|
" def group(self):\n"
|
||||||
|
" return ''\n"
|
||||||
|
"\n"
|
||||||
|
" def groupId(self):\n"
|
||||||
|
" return ''\n"
|
||||||
|
"\n"
|
||||||
|
" 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('cx3:MY_OUT', '', type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=True, defaultValue=None))\n"
|
||||||
|
"\n"
|
||||||
|
" def processAlgorithm(self, parameters, context, feedback):\n"
|
||||||
|
" results={}\n"
|
||||||
|
" outputs={}\n"
|
||||||
|
" alg_params = {\n"
|
||||||
|
" 'DISSOLVE':False,\n"
|
||||||
|
" 'DISTANCE':parameters['DIST'],\n"
|
||||||
|
" 'END_CAP_STYLE':1,\n"
|
||||||
|
" 'INPUT':parameters['SOURCE_LAYER'],\n"
|
||||||
|
" 'JOIN_STYLE':2,\n"
|
||||||
|
" 'SEGMENTS':QgsExpression('@myvar*2').evaluate(),\n"
|
||||||
|
" 'OUTPUT':parameters['cx1:MODEL_OUT_LAYER'],\n"
|
||||||
|
" }\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"
|
||||||
|
" 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"
|
||||||
|
" alg_params = {\n"
|
||||||
|
" 'EXPRESSION':'true',\n"
|
||||||
|
" 'INPUT':outputs['cx1']['OUTPUT'],\n"
|
||||||
|
" 'OUTPUT':parameters['MY_OUT'],\n"
|
||||||
|
" 'OUTPUT':parameters['cx3:MY_OUT'],\n"
|
||||||
|
" }\n"
|
||||||
|
" outputs['cx3']=processing.run('native:extractbyexpression', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n"
|
||||||
|
" results['cx3:MY_OUT']=outputs['cx3']['OUTPUT']\n"
|
||||||
|
" return results" ).split( '\n' );
|
||||||
QCOMPARE( actualParts, expectedParts );
|
QCOMPARE( actualParts, expectedParts );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user