Skip to content

Commit

Permalink
Add function to return variables available for child algorithms during
Browse files Browse the repository at this point in the history
model execution

And use this function to determine in advance dependencies between
child algorithm parameters with expression based values and
which other child algorithms they depend upon.
  • Loading branch information
nyalldawson committed Jul 7, 2017
1 parent 3f9cfe0 commit 534844f
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 52 deletions.
47 changes: 47 additions & 0 deletions python/core/processing/qgsprocessingmodelalgorithm.sip
Expand Up @@ -877,10 +877,57 @@ Copies are protected to avoid slicing
:rtype: list of QgsProcessingModelAlgorithm.ChildParameterSource
%End

class VariableDefinition
{
%Docstring
Definition of a expression context variable available during model execution.
.. versionadded:: 3.0
%End

%TypeHeaderCode
#include "qgsprocessingmodelalgorithm.h"
%End
public:

VariableDefinition( const QVariant &value, const QgsProcessingModelAlgorithm::ChildParameterSource &source );
%Docstring
Constructor for a new VariableDefinition with the specified ``value`` and original
parameter ``source``.
%End

QVariant value;
%Docstring
Value of variable
%End

QgsProcessingModelAlgorithm::ChildParameterSource source;
%Docstring
Original source of variable's value
%End
};

QMap< QString, QgsProcessingModelAlgorithm::VariableDefinition > variablesForChildAlgorithm( const QString &childId, QgsProcessingContext &context, const QVariantMap &modelParameters = QVariantMap(),
const QVariantMap &results = QVariantMap() ) const;
%Docstring
Returns a map of variable name to variable definition for expression context variables which are available
for use by child algorithm during model execution.

The child algorithm ``childId`` and processing ``context``
are manadatory. If ``modelParameters`` and ``results`` are not specified, then only the variable names and sources
will be returned, but all variable values will be null. This can be used to determine in advance which variables
will be available for a specific child algorithm, e.g. for use in expression builder widgets.

In order to calculate the actual variable value, the input model ``modelParameters`` and already executed child
algorithm ``results`` must be passed.
.. seealso:: createExpressionContextScopeForChildAlgorithm()
:rtype: QMap< str, QgsProcessingModelAlgorithm.VariableDefinition >
%End

QgsExpressionContextScope *createExpressionContextScopeForChildAlgorithm( const QString &childId, QgsProcessingContext &context, const QVariantMap &modelParameters = QVariantMap(),
const QVariantMap &results = QVariantMap() ) const /Factory/;
%Docstring
Creates a new expression context scope for a child algorithm within the model.
.. seealso:: variablesForChildAlgorithm()
:rtype: QgsExpressionContextScope
%End

Expand Down
7 changes: 0 additions & 7 deletions python/core/qgsexpressioncontext.sip
Expand Up @@ -874,13 +874,6 @@ class QgsExpressionContextUtils
:rtype: QgsExpressionContextScope
%End

static QgsExpressionContextScope *processingModelResultsScope( const QVariantMap &results, QgsProcessingContext &context ) /Factory/;
%Docstring
Creates a new scope which contains variables and functions relating to processing model results
:rtype: QgsExpressionContextScope
%End


static void registerContextFunctions();
%Docstring
Registers all known core functions provided by QgsExpressionContextScope objects.
Expand Down
14 changes: 11 additions & 3 deletions python/plugins/processing/modeler/ModelerScene.py
Expand Up @@ -33,6 +33,7 @@
QgsExpression)
from processing.modeler.ModelerGraphicItem import ModelerGraphicItem
from processing.modeler.ModelerArrowItem import ModelerArrowItem
from processing.tools.dataobjects import createContext


class ModelerScene(QGraphicsScene):
Expand Down Expand Up @@ -63,11 +64,11 @@ def getOutputPositions(self):
pos[algName] = outputPos
return pos

def getItemsFromParamValue(self, value):
def getItemsFromParamValue(self, value, child_id, context):
items = []
if isinstance(value, list):
for v in value:
items.extend(self.getItemsFromParamValue(v))
items.extend(self.getItemsFromParamValue(v, child_id, context))
elif isinstance(value, QgsProcessingModelAlgorithm.ChildParameterSource):
if value.source() == QgsProcessingModelAlgorithm.ChildParameterSource.ModelParameter:
items.append((self.paramItems[value.parameterName()], 0))
Expand All @@ -78,10 +79,17 @@ def getItemsFromParamValue(self, value):
break
if value.outputChildId() in self.algItems:
items.append((self.algItems[value.outputChildId()], i))
elif value.source() == QgsProcessingModelAlgorithm.ChildParameterSource.Expression:
variables = self.model.variablesForChildAlgorithm(child_id, context)
exp = QgsExpression(value.expression())
for v in exp.referencedVariables():
if v in variables:
items.extend(self.getItemsFromParamValue(variables[v].source, child_id, context))
return items

def paintModel(self, model, controls=True):
self.model = model
context = createContext()
# Inputs
for inp in list(model.parameterComponents().values()):
item = ModelerGraphicItem(inp, model, controls, scene=self)
Expand Down Expand Up @@ -127,7 +135,7 @@ def paintModel(self, model, controls=True):
else:
sources = []
for source in sources:
sourceItems = self.getItemsFromParamValue(source)
sourceItems = self.getItemsFromParamValue(source, alg.childId(), context)
for sourceItem, sourceIdx in sourceItems:
arrow = ModelerArrowItem(sourceItem, sourceIdx, self.algItems[alg.childId()], idx)
sourceItem.addArrow(arrow)
Expand Down
67 changes: 38 additions & 29 deletions src/core/processing/qgsprocessingmodelalgorithm.cpp
Expand Up @@ -663,42 +663,45 @@ QString QgsProcessingModelAlgorithm::asPythonCode() const
return lines.join( '\n' );
}

QgsExpressionContextScope *QgsProcessingModelAlgorithm::createExpressionContextScopeForChildAlgorithm( 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
{
QVariantMap variables;
std::unique_ptr< QgsExpressionContextScope > scope( new QgsExpressionContextScope() );
QMap<QString, QgsProcessingModelAlgorithm::VariableDefinition> variables;

auto safeName = []( const QString & name )->QString
{
QString s = name;
return s.replace( QRegularExpression( "[\\s'\"\\(\\):]" ), QStringLiteral( "_" ) );
};

// numeric sources
ChildParameterSources sources = availableSourcesForChild( childId, QStringList() << QgsProcessingParameterNumber::typeName(),
QStringList() << QgsProcessingOutputNumber::typeName() );
Q_FOREACH ( const ChildParameterSource &source, sources )
{
QString name;
QVariant value;

switch ( source.source() )
{
case ChildParameterSource::ModelParameter:
{
name = source.parameterName();
value = modelParameters.value( source.parameterName() );
break;
variables.insert( safeName( source.parameterName() ), VariableDefinition( modelParameters.value( source.parameterName() ), source ) );
continue;
}
case ChildParameterSource::ChildOutput:
{
name = QStringLiteral( "%1_%2" ).arg( mChildAlgorithms.value( source.outputChildId() ).description().isEmpty() ?
source.outputChildId() : mChildAlgorithms.value( source.outputChildId() ).description(), source.outputName() );
value = results.value( source.outputChildId() ).toMap().value( source.outputName() );
break;
QString name = QStringLiteral( "%1_%2" ).arg( mChildAlgorithms.value( source.outputChildId() ).description().isEmpty() ?
source.outputChildId() : mChildAlgorithms.value( source.outputChildId() ).description(), source.outputName() );
variables.insert( safeName( name ),
VariableDefinition( results.value( source.outputChildId() ).toMap().value( source.outputName() ),
source ) );
continue;
}

case ChildParameterSource::Expression:
case ChildParameterSource::StaticValue:
continue;

};

variables.insert( name, value );
}

// layer sources
sources = availableSourcesForChild( childId, QStringList()
<< QgsProcessingParameterVectorLayer::typeName()
<< QgsProcessingParameterRasterLayer::typeName(),
Expand Down Expand Up @@ -736,10 +739,12 @@ QgsExpressionContextScope *QgsProcessingModelAlgorithm::createExpressionContextS
if ( !layer )
layer = QgsProcessingUtils::mapLayerFromString( value.toString(), context );

variables.insert( QStringLiteral( "%1_minx" ).arg( name ), layer ? layer->extent().xMinimum() : QVariant() );
variables.insert( QStringLiteral( "%1_miny" ).arg( name ), layer ? layer->extent().yMinimum() : QVariant() );
variables.insert( QStringLiteral( "%1_maxx" ).arg( name ), layer ? layer->extent().xMaximum() : QVariant() );
variables.insert( QStringLiteral( "%1_maxy" ).arg( name ), layer ? layer->extent().yMaximum() : QVariant() );
variables.insert( safeName( QStringLiteral( "%1_minx" ).arg( name ) ), VariableDefinition( layer ? layer->extent().xMinimum() : QVariant(), source ) );
variables.insert( safeName( QStringLiteral( "%1_miny" ).arg( name ) ), VariableDefinition( layer ? layer->extent().yMinimum() : QVariant(), source ) );
variables.insert( safeName( QStringLiteral( "%1_maxx" ).arg( name ) ), VariableDefinition( layer ? layer->extent().xMaximum() : QVariant(), source ) );
variables.insert( safeName( QStringLiteral( "%1_maxy" ).arg( name ) ), VariableDefinition( layer ? layer->extent().yMaximum() : QVariant(), source ) );

continue;
}

sources = availableSourcesForChild( childId, QStringList()
Expand Down Expand Up @@ -787,20 +792,24 @@ QgsExpressionContextScope *QgsProcessingModelAlgorithm::createExpressionContextS
featureSource = vl;
}

variables.insert( QStringLiteral( "%1_minx" ).arg( name ), featureSource ? featureSource->sourceExtent().xMinimum() : QVariant() );
variables.insert( QStringLiteral( "%1_miny" ).arg( name ), featureSource ? featureSource->sourceExtent().yMinimum() : QVariant() );
variables.insert( QStringLiteral( "%1_maxx" ).arg( name ), featureSource ? featureSource->sourceExtent().xMaximum() : QVariant() );
variables.insert( QStringLiteral( "%1_maxy" ).arg( name ), featureSource ? featureSource->sourceExtent().yMaximum() : QVariant() );
variables.insert( safeName( QStringLiteral( "%1_minx" ).arg( name ) ), VariableDefinition( featureSource ? featureSource->sourceExtent().xMinimum() : QVariant(), source ) );
variables.insert( safeName( QStringLiteral( "%1_miny" ).arg( name ) ), VariableDefinition( featureSource ? featureSource->sourceExtent().yMinimum() : QVariant(), source ) );
variables.insert( safeName( QStringLiteral( "%1_maxx" ).arg( name ) ), VariableDefinition( featureSource ? featureSource->sourceExtent().xMaximum() : QVariant(), source ) );
variables.insert( safeName( QStringLiteral( "%1_maxy" ).arg( name ) ), VariableDefinition( featureSource ? featureSource->sourceExtent().yMaximum() : QVariant(), source ) );
}

QVariantMap::const_iterator varIt = variables.constBegin();
return variables;
}

QgsExpressionContextScope *QgsProcessingModelAlgorithm::createExpressionContextScopeForChildAlgorithm( const QString &childId, QgsProcessingContext &context, const QVariantMap &modelParameters, const QVariantMap &results ) const
{
std::unique_ptr< QgsExpressionContextScope > scope( new QgsExpressionContextScope() );
QMap< QString, QgsProcessingModelAlgorithm::VariableDefinition> variables = variablesForChildAlgorithm( childId, context, modelParameters, results );
QMap< QString, QgsProcessingModelAlgorithm::VariableDefinition>::const_iterator varIt = variables.constBegin();
for ( ; varIt != variables.constEnd(); ++varIt )
{
QString name = varIt.key();
name = name.replace( QRegularExpression( "[\\s'\"\\(\\):]" ), QStringLiteral( "_" ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( name, varIt.value(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( varIt.key(), varIt->value, true ) );
}

return scope.release();
}

Expand Down
42 changes: 42 additions & 0 deletions src/core/processing/qgsprocessingmodelalgorithm.h
Expand Up @@ -867,8 +867,50 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
QList< QgsProcessingModelAlgorithm::ChildParameterSource > availableSourcesForChild( const QString &childId, const QStringList &parameterTypes = QStringList(),
const QStringList &outputTypes = QStringList(), const QList< int > dataTypes = QList< int >() ) const;

/**
* Definition of a expression context variable available during model execution.
* \since QGIS 3.0
* \ingroup core
*/
class CORE_EXPORT VariableDefinition
{
public:

/**
* Constructor for a new VariableDefinition with the specified \a value and original
* parameter \a source.
*/
VariableDefinition( const QVariant &value, const QgsProcessingModelAlgorithm::ChildParameterSource &source )
: value( value )
, source( source )
{}

//! Value of variable
QVariant value;

//! Original source of variable's value
QgsProcessingModelAlgorithm::ChildParameterSource source;
};

/**
* Returns a map of variable name to variable definition for expression context variables which are available
* for use by child algorithm during model execution.
*
* The child algorithm \a childId and processing \a context
* are manadatory. If \a modelParameters and \a results are not specified, then only the variable names and sources
* will be returned, but all variable values will be null. This can be used to determine in advance which variables
* will be available for a specific child algorithm, e.g. for use in expression builder widgets.
*
* In order to calculate the actual variable value, the input model \a modelParameters and already executed child
* algorithm \a results must be passed.
* \see createExpressionContextScopeForChildAlgorithm()
*/
QMap< QString, QgsProcessingModelAlgorithm::VariableDefinition > variablesForChildAlgorithm( const QString &childId, QgsProcessingContext &context, const QVariantMap &modelParameters = QVariantMap(),
const QVariantMap &results = QVariantMap() ) const;

/**
* Creates a new expression context scope for a child algorithm within the model.
* \see variablesForChildAlgorithm()
*/
QgsExpressionContextScope *createExpressionContextScopeForChildAlgorithm( const QString &childId, QgsProcessingContext &context, const QVariantMap &modelParameters = QVariantMap(),
const QVariantMap &results = QVariantMap() ) const SIP_FACTORY;
Expand Down
7 changes: 0 additions & 7 deletions src/core/qgsexpressioncontext.cpp
Expand Up @@ -1120,13 +1120,6 @@ QgsExpressionContextScope *QgsExpressionContextUtils::processingAlgorithmScope(
return scope.release();
}

QgsExpressionContextScope *QgsExpressionContextUtils::processingModelResultsScope( const QVariantMap &results, QgsProcessingContext &context )
{
std::unique_ptr< QgsExpressionContextScope > scope( new QgsExpressionContextScope( QObject::tr( "Model results" ) ) );

return scope.release();
}

void QgsExpressionContextUtils::registerContextFunctions()
{
QgsExpression::registerFunction( new GetNamedProjectColor( nullptr ) );
Expand Down
6 changes: 0 additions & 6 deletions src/core/qgsexpressioncontext.h
Expand Up @@ -797,12 +797,6 @@ class CORE_EXPORT QgsExpressionContextUtils
*/
static QgsExpressionContextScope *processingAlgorithmScope( const QgsProcessingAlgorithm *algorithm, const QVariantMap &parameters, QgsProcessingContext &context ) SIP_FACTORY;

/**
* Creates a new scope which contains variables and functions relating to processing model results
*/
static QgsExpressionContextScope *processingModelResultsScope( const QVariantMap &results, QgsProcessingContext &context ) SIP_FACTORY;


/** Registers all known core functions provided by QgsExpressionContextScope objects.
*/
static void registerContextFunctions();
Expand Down

0 comments on commit 534844f

Please sign in to comment.