Port exporting model as python code to c++

This commit is contained in:
Nyall Dawson 2017-06-26 14:27:31 +10:00
parent d16f117b6c
commit 0a32add69e
6 changed files with 177 additions and 78 deletions

View File

@ -153,6 +153,12 @@ class QgsProcessingModelAlgorithm : QgsProcessingAlgorithm
:rtype: bool
%End
QString asPythonCode() const;
%Docstring
Attempts to convert the source to executable Python code.
:rtype: str
%End
};
class Component
@ -549,6 +555,12 @@ Copies are protected to avoid slicing
:rtype: bool
%End
QString asPythonCode() const;
%Docstring
Attempts to convert the child to executable Python code.
:rtype: str
%End
};
QgsProcessingModelAlgorithm( const QString &name = QString(), const QString &group = QString() );
@ -802,6 +814,12 @@ Copies are protected to avoid slicing
.. seealso:: sourceFilePath()
%End
QString asPythonCode() const;
%Docstring
Attempts to convert the model to executable Python code.
:rtype: str
%End
protected:
virtual QVariantMap processAlgorithm( const QVariantMap &parameters,

View File

@ -66,47 +66,6 @@ from processing.gui.Help2Html import getHtmlFromDescriptionsDict
pluginPath = os.path.split(os.path.dirname(__file__))[0]
class Algorithm(QgsProcessingModelAlgorithm.ChildAlgorithm):
def __init__(self, consoleName=None):
super().__init__(consoleName)
def todict(self):
return {k: v for k, v in list(self.__dict__.items()) if not k.startswith("_")}
def getOutputType(self, outputName):
output = self.algorithm().outputDefinition(outputName)
return "output " + output.__class__.__name__.split(".")[-1][6:].lower()
def toPython(self):
s = []
params = []
if not self.algorithm():
return None
for param in self.algorithm().parameterDefinitions():
value = self.parameterSources()[param.name()]
def _toString(v):
if isinstance(v, (ValueFromInput, ValueFromOutput)):
return v.asPythonParameter()
elif isinstance(v, str):
return "\\n".join(("'%s'" % v).splitlines())
elif isinstance(v, list):
return "[%s]" % ",".join([_toString(val) for val in v])
else:
return str(value)
params.append(_toString(value))
for out in self.algorithm().outputs:
if not out.flags() & QgsProcessingParameterDefinition.FlagHidden:
if out.name() in self.outputs:
params.append(safeName(self.outputs[out.name()].description()).lower())
else:
params.append(str(None))
s.append("outputs_%s=processing.run('%s', %s)" % (self.childId(), self.algorithmId(), ",".join(params)))
return s
class ValueFromInput(object):
def __init__(self, name=""):
@ -124,9 +83,6 @@ class ValueFromInput(object):
except:
return False
def asPythonParameter(self):
return self.name
class ValueFromOutput(object):
@ -146,9 +102,6 @@ class ValueFromOutput(object):
def __str__(self):
return self.alg + ":" + self.output
def asPythonParameter(self):
return "outputs_%s['%s']" % (self.alg, self.output)
class CompoundValue(object):
@ -223,33 +176,3 @@ class ModelerAlgorithm(QgsProcessingModelAlgorithm):
else:
v = value
return param.evaluateForModeler(v, self)
def toPython(self):
s = ['##%s=name' % self.name()]
for param in list(self.parameterComponents().values()):
s.append(param.param.asScriptCode())
for alg in list(self.algs.values()):
for name, out in list(alg.modelOutputs().items()):
s.append('##%s=%s' % (safeName(out.description()).lower(), alg.getOutputType(name)))
executed = []
toExecute = [alg for alg in list(self.algs.values()) if alg.isActive()]
while len(executed) < len(toExecute):
for alg in toExecute:
if alg.childId() not in executed:
canExecute = True
required = self.dependsOnChildAlgorithms(alg.childId())
for requiredAlg in required:
if requiredAlg != alg.childId() and requiredAlg not in executed:
canExecute = False
break
if canExecute:
s.extend(alg.toPython())
executed.append(alg.childId())
return '\n'.join(s)
def safeName(name):
validChars = 'abcdefghijklmnopqrstuvwxyz'
return ''.join(c for c in name.lower() if c in validChars)

View File

@ -425,7 +425,7 @@ class ModelerDialog(BASE, WIDGET):
if not filename.lower().endswith('.py'):
filename += '.py'
text = self.model.toPython()
text = self.model.asPythonCode()
with codecs.open(filename, 'w', encoding='utf-8') as fout:
fout.write(text)

View File

@ -213,6 +213,33 @@ bool QgsProcessingModelAlgorithm::ChildAlgorithm::loadVariant( const QVariant &c
return true;
}
QString QgsProcessingModelAlgorithm::ChildAlgorithm::asPythonCode() const
{
QStringList lines;
if ( !algorithm() )
return QString();
QStringList paramParts;
QMap< QString, QgsProcessingModelAlgorithm::ChildParameterSource >::const_iterator paramIt = mParams.constBegin();
for ( ; paramIt != mParams.constEnd(); ++paramIt )
{
QString part = paramIt->asPythonCode();
if ( !part.isEmpty() )
paramParts << QStringLiteral( "'%1':%2" ).arg( paramIt.key(), part );
}
lines << QStringLiteral( "outputs['%1']=processing.run('%2', {%3}, context=context, feedback=feedback)" ).arg( mId, mAlgorithmId, paramParts.join( ',' ) );
QMap< QString, QgsProcessingModelAlgorithm::ModelOutput >::const_iterator outputIt = mModelOutputs.constBegin();
for ( ; outputIt != mModelOutputs.constEnd(); ++outputIt )
{
lines << QStringLiteral( "results['%1']=outputs['%2']['%3']" ).arg( outputIt.key(), mId, outputIt.value().childOutputName() );
}
return lines.join( '\n' );
}
bool QgsProcessingModelAlgorithm::ChildAlgorithm::parametersCollapsed() const
{
return mParametersCollapsed;
@ -491,6 +518,92 @@ void QgsProcessingModelAlgorithm::setSourceFilePath( const QString &sourceFile )
mSourceFile = sourceFile;
}
QString QgsProcessingModelAlgorithm::asPythonCode() const
{
QStringList lines;
lines << QStringLiteral( "##%1=name" ).arg( name() );
QMap< QString, ModelParameter >::const_iterator paramIt = mParameterComponents.constBegin();
for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt )
{
QString name = paramIt.value().parameterName();
if ( parameterDefinition( name ) )
{
lines << parameterDefinition( name )->asScriptCode();
}
}
auto safeName = []( const QString & name )->QString
{
QString n = name.toLower().trimmed();
QRegularExpression rx( "[^a-z_]" );
n.replace( rx, QString() );
return n;
};
QMap< QString, ChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
{
if ( !childIt->isActive() || !childIt->algorithm() )
continue;
// look through all outputs for child
QMap<QString, QgsProcessingModelAlgorithm::ModelOutput> outputs = childIt->modelOutputs();
QMap<QString, QgsProcessingModelAlgorithm::ModelOutput>::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={}" );
QSet< QString > toExecute;
childIt = mChildAlgorithms.constBegin();
for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
{
if ( childIt->isActive() && childIt->algorithm() )
toExecute.insert( childIt->childId() );
}
QSet< QString > executed;
bool executedAlg = true;
while ( executedAlg && executed.count() < toExecute.count() )
{
executedAlg = false;
Q_FOREACH ( const QString &childId, toExecute )
{
if ( executed.contains( childId ) )
continue;
bool canExecute = true;
Q_FOREACH ( const QString &dependency, dependsOnChildAlgorithms( childId ) )
{
if ( !executed.contains( dependency ) )
{
canExecute = false;
break;
}
}
if ( !canExecute )
continue;
executedAlg = true;
const ChildAlgorithm &child = mChildAlgorithms[ childId ];
lines << child.asPythonCode();
executed.insert( childId );
}
}
lines << QStringLiteral( "return results" );
return lines.join( '\n' );
}
QVariantMap QgsProcessingModelAlgorithm::helpContent() const
{
return mHelpContent;
@ -1014,6 +1127,22 @@ bool QgsProcessingModelAlgorithm::ChildParameterSource::loadVariant( const QVari
return true;
}
QString QgsProcessingModelAlgorithm::ChildParameterSource::asPythonCode() const
{
switch ( mSource )
{
case ModelParameter:
return QStringLiteral( "parameters['%1']" ).arg( mParameterName );
case ChildOutput:
return QStringLiteral( "outputs['%1']['%2']" ).arg( mChildId, mOutputName );
case StaticValue:
return mStaticValue.toString();
}
return QString();
}
QgsProcessingModelAlgorithm::ModelOutput::ModelOutput( const QString &name, const QString &description )
: QgsProcessingModelAlgorithm::Component( description )
, mName( name )

View File

@ -152,6 +152,11 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
*/
bool loadVariant( const QVariantMap &map );
/**
* Attempts to convert the source to executable Python code.
*/
QString asPythonCode() const;
private:
Source mSource = StaticValue;
@ -536,6 +541,11 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
*/
bool loadVariant( const QVariant &child );
/**
* Attempts to convert the child to executable Python code.
*/
QString asPythonCode() const;
private:
QString mId;
@ -793,6 +803,11 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
*/
void setSourceFilePath( const QString &path );
/**
* Attempts to convert the model to executable Python code.
*/
QString asPythonCode() const;
protected:
QVariantMap processAlgorithm( const QVariantMap &parameters,

View File

@ -4639,12 +4639,26 @@ void TestQgsProcessing::modelExecution()
alg2c3.setAlgorithmId( "native:extractbyexpression" );
alg2c3.addParameterSource( "INPUT", QgsProcessingModelAlgorithm::ChildParameterSource::fromChildOutput( "cx1", "OUTPUT_LAYER" ) );
alg2c3.addParameterSource( "EXPRESSION", QgsProcessingModelAlgorithm::ChildParameterSource::fromStaticValue( "true" ) );
alg2c3.setDependencies( QStringList() << "cx2" );
model2.addChildAlgorithm( alg2c3 );
params = model2.parametersForChildAlgorithm( model2.childAlgorithm( "cx3" ), modelInputs, childResults );
QCOMPARE( params.value( "INPUT" ).toString(), QStringLiteral( "dest.shp" ) );
QCOMPARE( params.value( "EXPRESSION" ).toString(), QStringLiteral( "true" ) );
QCOMPARE( params.value( "OUTPUT" ).toString(), QStringLiteral( "memory:" ) );
QCOMPARE( params.count(), 3 ); // don't want FAIL_OUTPUT set!
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"
"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':16}, context=context, feedback=feedback)\n"
"results['MODEL_OUT_LAYER']=outputs['cx1']['OUTPUT_LAYER']\n"
"outputs['cx2']=processing.run('native:centroids', {'INPUT':outputs['cx1']['OUTPUT_LAYER']}, context=context, feedback=feedback)\n"
"outputs['cx3']=processing.run('native:extractbyexpression', {'EXPRESSION':true,'INPUT':outputs['cx1']['OUTPUT_LAYER']}, context=context, feedback=feedback)\n"
"return results" ).split( '\n' );
QCOMPARE( actualParts, expectedParts );
}
void TestQgsProcessing::tempUtils()