Navigation Menu

Skip to content

Commit

Permalink
Port exporting model as python code to c++
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jun 26, 2017
1 parent d16f117 commit 0a32add
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 78 deletions.
18 changes: 18 additions & 0 deletions python/core/processing/qgsprocessingmodelalgorithm.sip
Expand Up @@ -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
Expand Down Expand Up @@ -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() );
Expand Down Expand Up @@ -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,
Expand Down
77 changes: 0 additions & 77 deletions python/plugins/processing/modeler/ModelerAlgorithm.py
Expand Up @@ -66,47 +66,6 @@
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=""):
Expand All @@ -124,9 +83,6 @@ def __eq__(self, other):
except:
return False

def asPythonParameter(self):
return self.name


class ValueFromOutput(object):

Expand All @@ -146,9 +102,6 @@ def __eq__(self, other):
def __str__(self):
return self.alg + ":" + self.output

def asPythonParameter(self):
return "outputs_%s['%s']" % (self.alg, self.output)


class CompoundValue(object):

Expand Down Expand Up @@ -223,33 +176,3 @@ def resolveValue(self, value, param):
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)
2 changes: 1 addition & 1 deletion python/plugins/processing/modeler/ModelerDialog.py
Expand Up @@ -425,7 +425,7 @@ def exportAsPython(self):
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)

Expand Down
129 changes: 129 additions & 0 deletions src/core/processing/qgsprocessingmodelalgorithm.cpp
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 )
Expand Down
15 changes: 15 additions & 0 deletions src/core/processing/qgsprocessingmodelalgorithm.h
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
14 changes: 14 additions & 0 deletions tests/src/core/testqgsprocessing.cpp
Expand Up @@ -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()
Expand Down

0 comments on commit 0a32add

Please sign in to comment.