Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[processing] Expose some model related variables to expressions
run inside models

Allows expressions to access important variables like the current
model path
  • Loading branch information
nyalldawson committed Feb 12, 2019
1 parent 2180b63 commit e533a40
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 1 deletion.
11 changes: 11 additions & 0 deletions python/core/auto_generated/qgsexpressioncontext.sip.in
Expand Up @@ -1079,6 +1079,17 @@ standard scopes such as the global and project scopes.
Creates a new scope which contains variables and functions relating to a processing ``algorithm``,
when used with the specified ``parameters`` and ``context``.
For instance, algorithm name and parameter functions.

.. seealso:: :py:func:`processingModelAlgorithmScope`
%End

static QgsExpressionContextScope *processingModelAlgorithmScope( const QgsProcessingModelAlgorithm *model, const QVariantMap &parameters, QgsProcessingContext &context ) /Factory/;
%Docstring
Creates a new scope which contains variables and functions relating to a processing ``model`` algorithm,
when used with the specified ``parameters`` and ``context``.
For instance, model name and path variables.

.. versionadded:: 3.6
%End

static QgsExpressionContextScope *notificationScope( const QString &message = QString() ) /Factory/;
Expand Down
4 changes: 4 additions & 0 deletions src/core/expression/qgsexpression.cpp
Expand Up @@ -799,6 +799,10 @@ void QgsExpression::initVariableHelp()

//processing variables
sVariableHelpTexts.insert( QStringLiteral( "algorithm_id" ), QCoreApplication::translate( "algorithm_id", "Unique ID for algorithm." ) );
sVariableHelpTexts.insert( QStringLiteral( "model_path" ), QCoreApplication::translate( "variable_help", "Full path (including file name) of current model (or project path if model is embedded in a project)." ) );
sVariableHelpTexts.insert( QStringLiteral( "model_folder" ), QCoreApplication::translate( "variable_help", "Folder containing current model (or project folder if model is embedded in a project)." ) );
sVariableHelpTexts.insert( QStringLiteral( "model_name" ), QCoreApplication::translate( "variable_help", "Name of current model." ) );
sVariableHelpTexts.insert( QStringLiteral( "model_group" ), QCoreApplication::translate( "variable_help", "Group for current model." ) );
sVariableHelpTexts.insert( QStringLiteral( "fullextent_minx" ), QCoreApplication::translate( "fullextent_minx", "Minimum x-value from full canvas extent (including all layers)." ) );
sVariableHelpTexts.insert( QStringLiteral( "fullextent_miny" ), QCoreApplication::translate( "fullextent_miny", "Minimum y-value from full canvas extent (including all layers)." ) );
sVariableHelpTexts.insert( QStringLiteral( "fullextent_maxx" ), QCoreApplication::translate( "fullextent_maxx", "Maximum x-value from full canvas extent (including all layers)." ) );
Expand Down
7 changes: 7 additions & 0 deletions src/core/processing/models/qgsprocessingmodelalgorithm.cpp
Expand Up @@ -1381,6 +1381,13 @@ QString QgsProcessingModelAlgorithm::asPythonCommand( const QVariantMap &paramet
return QgsProcessingAlgorithm::asPythonCommand( parameters, context );
}

QgsExpressionContext QgsProcessingModelAlgorithm::createExpressionContext( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeatureSource *source ) const
{
QgsExpressionContext res = QgsProcessingAlgorithm::createExpressionContext( parameters, context, source );
res << QgsExpressionContextUtils::processingModelAlgorithmScope( this, parameters, context );
return res;
}

QgsProcessingAlgorithm *QgsProcessingModelAlgorithm::createInstance() const
{
QgsProcessingModelAlgorithm *alg = new QgsProcessingModelAlgorithm();
Expand Down
1 change: 1 addition & 0 deletions src/core/processing/models/qgsprocessingmodelalgorithm.h
Expand Up @@ -57,6 +57,7 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm

bool canExecute( QString *errorMessage SIP_OUT = nullptr ) const override;
QString asPythonCommand( const QVariantMap &parameters, QgsProcessingContext &context ) const override;
QgsExpressionContext createExpressionContext( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeatureSource *source = nullptr ) const override;

/**
* Sets the model \a name.
Expand Down
25 changes: 24 additions & 1 deletion src/core/qgsexpressioncontext.cpp
Expand Up @@ -35,7 +35,7 @@
#include "qgsexpressionutils.h"
#include "qgslayoutrendercontext.h"
#include "qgsxmlutils.h"

#include "qgsprocessingmodelalgorithm.h"
#include <QSettings>
#include <QDir>

Expand Down Expand Up @@ -1329,6 +1329,29 @@ QgsExpressionContextScope *QgsExpressionContextUtils::processingAlgorithmScope(
return scope.release();
}

QgsExpressionContextScope *QgsExpressionContextUtils::processingModelAlgorithmScope( const QgsProcessingModelAlgorithm *model, const QVariantMap &, QgsProcessingContext &context )
{
std::unique_ptr< QgsExpressionContextScope > modelScope( new QgsExpressionContextScope( QObject::tr( "Model" ) ) );
QString modelPath;
if ( !model->sourceFilePath().isEmpty() )
{
modelPath = model->sourceFilePath();
}
else if ( context.project() )
{
// fallback to project path -- the model may be embedded in a project, OR an unsaved model. In either case the
// project path is a logical value to fall back to
modelPath = context.project()->projectStorage() ? context.project()->fileName() : context.project()->absoluteFilePath();
}

const QString modelFolder = QFileInfo( modelPath ).path();
modelScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "model_path" ), QDir::toNativeSeparators( modelPath ), true ) );
modelScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "model_folder" ), QDir::toNativeSeparators( modelFolder ), true, true ) );
modelScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "model_name" ), model->displayName(), true ) );
modelScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "model_group" ), model->group(), true ) );
return modelScope.release();
}

QgsExpressionContextScope *QgsExpressionContextUtils::notificationScope( const QString &message )
{
std::unique_ptr< QgsExpressionContextScope > scope( new QgsExpressionContextScope() );
Expand Down
10 changes: 10 additions & 0 deletions src/core/qgsexpressioncontext.h
Expand Up @@ -41,6 +41,7 @@ class QgsProcessingAlgorithm;
class QgsProcessingContext;
class QgsLayoutAtlas;
class QgsLayoutItem;
class QgsProcessingModelAlgorithm;

/**
* \ingroup core
Expand Down Expand Up @@ -987,9 +988,18 @@ class CORE_EXPORT QgsExpressionContextUtils
* Creates a new scope which contains variables and functions relating to a processing \a algorithm,
* when used with the specified \a parameters and \a context.
* For instance, algorithm name and parameter functions.
* \see processingModelAlgorithmScope()
*/
static QgsExpressionContextScope *processingAlgorithmScope( const QgsProcessingAlgorithm *algorithm, const QVariantMap &parameters, QgsProcessingContext &context ) SIP_FACTORY;

/**
* Creates a new scope which contains variables and functions relating to a processing \a model algorithm,
* when used with the specified \a parameters and \a context.
* For instance, model name and path variables.
* \since QGIS 3.6
*/
static QgsExpressionContextScope *processingModelAlgorithmScope( const QgsProcessingModelAlgorithm *model, const QVariantMap &parameters, QgsProcessingContext &context ) SIP_FACTORY;

/**
* Creates a new scope which contains variables and functions relating to provider notifications
* \param message the notification message
Expand Down
2 changes: 2 additions & 0 deletions src/gui/processing/qgsprocessingmodelerparameterwidget.cpp
Expand Up @@ -189,6 +189,8 @@ QgsExpressionContext QgsProcessingModelerParameterWidget::createExpressionContex
alg = mModel->childAlgorithm( mChildId ).algorithm();
QgsExpressionContextScope *algorithmScope = QgsExpressionContextUtils::processingAlgorithmScope( alg, QVariantMap(), mContext );
c << algorithmScope;
QgsExpressionContextScope *modelScope = QgsExpressionContextUtils::processingModelAlgorithmScope( mModel, QVariantMap(), mContext );
c << modelScope;
QgsExpressionContextScope *childScope = mModel->createExpressionContextScopeForChildAlgorithm( mChildId, mContext, QVariantMap(), QVariantMap() );
c << childScope;

Expand Down
2 changes: 2 additions & 0 deletions src/gui/processing/qgsprocessingwidgetwrapper.cpp
Expand Up @@ -239,6 +239,8 @@ QgsExpressionContext QgsAbstractProcessingParameterWidgetWrapper::createExpressi

if ( mWidgetContext.model() )
{
c << QgsExpressionContextUtils::processingModelAlgorithmScope( mWidgetContext.model(), QVariantMap(), *context );

const QgsProcessingAlgorithm *alg = nullptr;
if ( mWidgetContext.model()->childAlgorithms().contains( mWidgetContext.modelChildAlgorithmId() ) )
alg = mWidgetContext.model()->childAlgorithm( mWidgetContext.modelChildAlgorithmId() ).algorithm();
Expand Down
34 changes: 34 additions & 0 deletions tests/src/analysis/testqgsprocessing.cpp
Expand Up @@ -562,6 +562,7 @@ class TestQgsProcessing: public QObject
void processingFeatureSource();
void processingFeatureSink();
void algorithmScope();
void modelScope();
void validateInputCrs();
void generateIteratingDestination();
void asPythonCommand();
Expand Down Expand Up @@ -5999,6 +6000,39 @@ void TestQgsProcessing::algorithmScope()
QCOMPARE( exp2.evaluate( &context ).toInt(), 5 );
}

void TestQgsProcessing::modelScope()
{
QgsProcessingContext pc;

QgsProcessingModelAlgorithm alg( "test", "testGroup" );
QVariantMap params;
params.insert( QStringLiteral( "a_param" ), 5 );
std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::processingModelAlgorithmScope( &alg, params, pc ) );
QVERIFY( scope.get() );
QCOMPARE( scope->variable( QStringLiteral( "model_name" ) ).toString(), QStringLiteral( "test" ) );
QCOMPARE( scope->variable( QStringLiteral( "model_group" ) ).toString(), QStringLiteral( "testGroup" ) );
QVERIFY( scope->hasVariable( QStringLiteral( "model_path" ) ) );
QVERIFY( scope->hasVariable( QStringLiteral( "model_folder" ) ) );
QVERIFY( scope->variable( QStringLiteral( "model_path" ) ).toString().isEmpty() );
QVERIFY( scope->variable( QStringLiteral( "model_folder" ) ).toString().isEmpty() );

QgsProject p;
pc.setProject( &p );
p.setFileName( TEST_DATA_DIR + QStringLiteral( "/test_file.qgs" ) );
scope.reset( QgsExpressionContextUtils::processingModelAlgorithmScope( &alg, params, pc ) );
QCOMPARE( scope->variable( QStringLiteral( "model_path" ) ).toString(), TEST_DATA_DIR + QStringLiteral( "/test_file.qgs" ) );
QCOMPARE( scope->variable( QStringLiteral( "model_folder" ) ).toString(), TEST_DATA_DIR );

alg.setSourceFilePath( TEST_DATA_DIR + QStringLiteral( "/processing/my_model.model3" ) );
scope.reset( QgsExpressionContextUtils::processingModelAlgorithmScope( &alg, params, pc ) );
QCOMPARE( scope->variable( QStringLiteral( "model_path" ) ).toString(), TEST_DATA_DIR + QStringLiteral( "/processing/my_model.model3" ) );
QCOMPARE( scope->variable( QStringLiteral( "model_folder" ) ).toString(), TEST_DATA_DIR + QStringLiteral( "/processing" ) );

QgsExpressionContext ctx = alg.createExpressionContext( QVariantMap(), pc );
QVERIFY( scope->hasVariable( QStringLiteral( "model_path" ) ) );
QVERIFY( scope->hasVariable( QStringLiteral( "model_folder" ) ) );
}

void TestQgsProcessing::validateInputCrs()
{
DummyAlgorithm alg( "test" );
Expand Down

0 comments on commit e533a40

Please sign in to comment.