mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -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`
|
||||
%End
|
||||
|
||||
QString asPythonCode() const;
|
||||
QStringList asPythonCode( QgsProcessing::PythonOutputType outputType, int indentSize ) const;
|
||||
%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
|
||||
|
||||
QList< QgsProcessingModelChildParameterSource > availableSourcesForChild( const QString &childId, const QStringList ¶meterTypes = QStringList(),
|
||||
|
@ -353,50 +353,132 @@ void QgsProcessingModelAlgorithm::setSourceFilePath( const QString &sourceFile )
|
||||
mSourceFile = sourceFile;
|
||||
}
|
||||
|
||||
QString QgsProcessingModelAlgorithm::asPythonCode() const
|
||||
QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::PythonOutputType outputType, const int indentSize ) const
|
||||
{
|
||||
QStringList lines;
|
||||
lines << QStringLiteral( "##%1=name" ).arg( name() );
|
||||
|
||||
QMap< QString, QgsProcessingModelParameter >::const_iterator paramIt = mParameterComponents.constBegin();
|
||||
for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt )
|
||||
QString indent = QString( ' ' ).repeated( indentSize );
|
||||
QString currentIndent;
|
||||
switch ( outputType )
|
||||
{
|
||||
QString name = paramIt.value().parameterName();
|
||||
if ( parameterDefinition( name ) )
|
||||
case QgsProcessing::PythonQgsProcessingAlgorithmSubclass:
|
||||
{
|
||||
lines << parameterDefinition( name )->asScriptCode();
|
||||
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();
|
||||
for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt )
|
||||
{
|
||||
QString name = paramIt.value().parameterName();
|
||||
if ( parameterDefinition( name ) )
|
||||
{
|
||||
// TODO - generic value to string method
|
||||
params.insert( name, parameterDefinition( name )->valueAsPythonString( parameterDefinition( name )->defaultValue(), context ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( !params.isEmpty() )
|
||||
{
|
||||
lines << QStringLiteral( "parameters = {" );
|
||||
for ( auto it = params.constBegin(); it != params.constEnd(); ++it )
|
||||
{
|
||||
lines << QStringLiteral( " '%1':%2," ).arg( it.key(), it.value() );
|
||||
}
|
||||
lines << QStringLiteral( "}" )
|
||||
<< QString();
|
||||
}
|
||||
|
||||
lines << QStringLiteral( "context = QgsProcessingContext()" )
|
||||
<< QStringLiteral( "context.setProject(QgsProject.instance())" )
|
||||
<< QStringLiteral( "feedback = QgsProcessingFeedback()" )
|
||||
<< QString();
|
||||
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
auto safeName = []( const QString & name )->QString
|
||||
{
|
||||
QString n = name.toLower().trimmed();
|
||||
QRegularExpression rx( QStringLiteral( "[^a-z_]" ) );
|
||||
n.replace( rx, QString() );
|
||||
return n;
|
||||
};
|
||||
|
||||
QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
|
||||
for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
|
||||
{
|
||||
if ( !childIt->isActive() || !childIt->algorithm() )
|
||||
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( "results={}" );
|
||||
lines << currentIndent + QStringLiteral( "results={}" );
|
||||
lines << currentIndent + QStringLiteral( "outputs={}" );
|
||||
|
||||
QSet< QString > toExecute;
|
||||
childIt = mChildAlgorithms.constBegin();
|
||||
for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
|
||||
for ( auto childIt = mChildAlgorithms.constBegin(); childIt != mChildAlgorithms.constEnd(); ++childIt )
|
||||
{
|
||||
if ( childIt->isActive() && childIt->algorithm() )
|
||||
toExecute.insert( childIt->childId() );
|
||||
@ -428,15 +510,66 @@ QString QgsProcessingModelAlgorithm::asPythonCode() const
|
||||
executedAlg = true;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -304,9 +304,13 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
|
||||
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
|
||||
|
@ -6244,10 +6244,8 @@ void TestQgsProcessing::modelerAlgorithm()
|
||||
child.removeModelOutput( QStringLiteral( "a" ) );
|
||||
QCOMPARE( child.modelOutputs().count(), 1 );
|
||||
|
||||
|
||||
// model algorithm tests
|
||||
|
||||
|
||||
QgsProcessingModelAlgorithm alg( "test", "testGroup" );
|
||||
QCOMPARE( alg.name(), 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_maxy" ).value.toDouble(), 46.8719, 0.001 );
|
||||
|
||||
QStringList actualParts = model2.asPythonCode().split( '\n' );
|
||||
QStringList expectedParts = QStringLiteral( "##model=name\n"
|
||||
"##DIST=number\n"
|
||||
"##SOURCE_LAYER=source\n"
|
||||
"##model_out_layer=output outputVector\n"
|
||||
"##my_out=output outputVector\n"
|
||||
"results={}\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"
|
||||
"results['MODEL_OUT_LAYER']=outputs['cx1']['OUTPUT']\n"
|
||||
"outputs['cx2']=processing.run('native:centroids', {'INPUT':outputs['cx1']['OUTPUT']}, context=context, feedback=feedback)\n"
|
||||
"outputs['cx3']=processing.run('native:extractbyexpression', {'EXPRESSION':true,'INPUT':outputs['cx1']['OUTPUT'],'OUTPUT':parameters['MY_OUT']}, context=context, feedback=feedback)\n"
|
||||
"results['MY_OUT']=outputs['cx3']['OUTPUT']\n"
|
||||
"return results" ).split( '\n' );
|
||||
QStringList actualParts = model2.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, 2 );
|
||||
QgsDebugMsg( actualParts.join( '\n' ) );
|
||||
QStringList expectedParts = QStringLiteral( "from qgis.core import QgsProcessing\n"
|
||||
"from qgis.core import QgsProcessingAlgorithm\n"
|
||||
"from qgis.core import QgsProcessingParameterFeatureSource\n"
|
||||
"from qgis.core import QgsProcessingParameterNumber\n"
|
||||
"from qgis.core import QgsProcessingParameterFeatureSink\n"
|
||||
"import processing\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"class model(QgsProcessingAlgorithm):\n"
|
||||
" def createInstance(self):\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 );
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user