[processing] Allow configuration of order of outputs created by a model

This adds a new "Reorder Model Outputs" action to the model designer
menu (to accompany the existing "Reorder Model Inputs" action).
Selecting this option allows model creators to set a specific order
which the outputs from their model must use when loading the results
into a project. This gives the model creator a means of ensuring
that layers are logically ordered, eg placing a vector layer over
a raster layer and a point layer over a polygon layer.

Optionally, the model creator can also set a "Group name" for the
outputs. If this is specified then all outputs from the model will
be placed into a (newly created if necessary) layer tree group
with that name.

Sponsored by the QGIS Germany User Group
This commit is contained in:
Nyall Dawson 2023-05-03 16:27:27 +10:00
parent f1c0923fc1
commit f27195c165
11 changed files with 684 additions and 4 deletions

View File

@ -340,6 +340,45 @@ model :py:func:`~QgsProcessingModelAlgorithm.parameterComponents`.
.. seealso:: :py:func:`orderedParameters`
.. versionadded:: 3.14
%End
QList< QgsProcessingModelOutput > orderedOutputs() const;
%Docstring
Returns an ordered list of outputs for the model.
.. seealso:: :py:func:`setOutputOrder`
.. versionadded:: 3.32
%End
void setOutputOrder( const QStringList &order );
%Docstring
Sets the ``order`` for sorting outputs for the model.
The ``order`` list should consist of "output child algorithm id:output name" formatted strings corresponding to existing
model outputs.
.. seealso:: :py:func:`orderedOutputs`
.. versionadded:: 3.32
%End
QString outputGroup() const;
%Docstring
Returns the destination layer tree group name for outputs created by the model.
.. seealso:: :py:func:`setOutputGroup`
.. versionadded:: 3.32
%End
void setOutputGroup( const QString &group );
%Docstring
Sets the destination layer tree ``group`` name for outputs created by the model.
.. seealso:: :py:func:`outputGroup`
.. versionadded:: 3.32
%End
void updateDestinationParameters();

View File

@ -478,10 +478,10 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa
// look through child alg's outputs to determine whether any of these should be copied
// to the final model outputs
QMap<QString, QgsProcessingModelOutput> outputs = child.modelOutputs();
QMap<QString, QgsProcessingModelOutput>::const_iterator outputIt = outputs.constBegin();
for ( ; outputIt != outputs.constEnd(); ++outputIt )
const QMap<QString, QgsProcessingModelOutput> outputs = child.modelOutputs();
for ( auto outputIt = outputs.constBegin(); outputIt != outputs.constEnd(); ++outputIt )
{
const int outputSortKey = mOutputOrder.indexOf( QStringLiteral( "%1:%2" ).arg( childId, outputIt->childOutputName() ) );
switch ( mInternalVersion )
{
case QgsProcessingModelAlgorithm::InternalVersion::Version1:
@ -494,6 +494,14 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa
}
break;
}
if ( !results.value( outputIt->childOutputName() ).toString().isEmpty() )
{
QgsProcessingContext::LayerDetails &details = context.layerToLoadOnCompletionDetails( results.value( outputIt->childOutputName() ).toString() );
details.groupName = mOutputGroup;
if ( outputSortKey > 0 )
details.layerSortKey = outputSortKey;
}
}
executed.insert( childId );
@ -1373,6 +1381,62 @@ void QgsProcessingModelAlgorithm::setParameterOrder( const QStringList &order )
mParameterOrder = order;
}
QList<QgsProcessingModelOutput> QgsProcessingModelAlgorithm::orderedOutputs() const
{
QList< QgsProcessingModelOutput > res;
QSet< QString > found;
for ( const QString &output : mOutputOrder )
{
bool foundOutput = false;
for ( auto it = mChildAlgorithms.constBegin(); it != mChildAlgorithms.constEnd(); ++it )
{
const QMap<QString, QgsProcessingModelOutput> outputs = it.value().modelOutputs();
for ( auto outputIt = outputs.constBegin(); outputIt != outputs.constEnd(); ++outputIt )
{
if ( output == QStringLiteral( "%1:%2" ).arg( outputIt->childId(), outputIt->childOutputName() ) )
{
res << outputIt.value();
foundOutput = true;
found.insert( QStringLiteral( "%1:%2" ).arg( outputIt->childId(), outputIt->childOutputName() ) );
}
}
if ( foundOutput )
break;
}
}
// add any missing ones to end of list
for ( auto it = mChildAlgorithms.constBegin(); it != mChildAlgorithms.constEnd(); ++it )
{
const QMap<QString, QgsProcessingModelOutput> outputs = it.value().modelOutputs();
for ( auto outputIt = outputs.constBegin(); outputIt != outputs.constEnd(); ++outputIt )
{
if ( !found.contains( QStringLiteral( "%1:%2" ).arg( outputIt->childId(), outputIt->childOutputName() ) ) )
{
res << outputIt.value();
}
}
}
return res;
}
void QgsProcessingModelAlgorithm::setOutputOrder( const QStringList &order )
{
mOutputOrder = order;
}
QString QgsProcessingModelAlgorithm::outputGroup() const
{
return mOutputGroup;
}
void QgsProcessingModelAlgorithm::setOutputGroup( const QString &group )
{
mOutputGroup = group;
}
void QgsProcessingModelAlgorithm::updateDestinationParameters()
{
//delete existing destination parameters
@ -1517,6 +1581,8 @@ QVariant QgsProcessingModelAlgorithm::toVariant() const
map.insert( QStringLiteral( "designerParameterValues" ), mDesignerParameterValues );
map.insert( QStringLiteral( "parameterOrder" ), mParameterOrder );
map.insert( QStringLiteral( "outputOrder" ), mOutputOrder );
map.insert( QStringLiteral( "outputGroup" ), mOutputGroup );
return map;
}
@ -1536,6 +1602,8 @@ bool QgsProcessingModelAlgorithm::loadVariant( const QVariant &model )
mDesignerParameterValues = map.value( QStringLiteral( "designerParameterValues" ) ).toMap();
mParameterOrder = map.value( QStringLiteral( "parameterOrder" ) ).toStringList();
mOutputOrder = map.value( QStringLiteral( "outputOrder" ) ).toStringList();
mOutputGroup = map.value( QStringLiteral( "outputGroup" ) ).toString();
mChildAlgorithms.clear();
QVariantMap childMap = map.value( QStringLiteral( "children" ) ).toMap();

View File

@ -297,6 +297,41 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
*/
void setParameterOrder( const QStringList &order );
/**
* Returns an ordered list of outputs for the model.
*
* \see setOutputOrder()
* \since QGIS 3.32
*/
QList< QgsProcessingModelOutput > orderedOutputs() const;
/**
* Sets the \a order for sorting outputs for the model.
*
* The \a order list should consist of "output child algorithm id:output name" formatted strings corresponding to existing
* model outputs.
*
* \see orderedOutputs()
* \since QGIS 3.32
*/
void setOutputOrder( const QStringList &order );
/**
* Returns the destination layer tree group name for outputs created by the model.
*
* \see setOutputGroup()
* \since QGIS 3.32
*/
QString outputGroup() const;
/**
* Sets the destination layer tree \a group name for outputs created by the model.
*
* \see outputGroup()
* \since QGIS 3.32
*/
void setOutputGroup( const QString &group );
/**
* Updates the model's parameter definitions to include all relevant destination
* parameters as required by child algorithm ModelOutputs.
@ -585,6 +620,8 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
QMap< QString, QgsProcessingModelGroupBox > mGroupBoxes;
QStringList mParameterOrder;
QStringList mOutputOrder;
QString mOutputGroup;
void dependsOnChildAlgorithmsRecursive( const QString &childId, QSet<QString> &depends ) const;
void dependentChildAlgorithmsRecursive( const QString &childId, QSet<QString> &depends, const QString &branch ) const;

View File

@ -402,6 +402,7 @@ set(QGIS_GUI_SRCS
processing/models/qgsmodelgraphicsview.cpp
processing/models/qgsmodelgroupboxdefinitionwidget.cpp
processing/models/qgsmodelinputreorderwidget.cpp
processing/models/qgsmodeloutputreorderwidget.cpp
processing/models/qgsmodelsnapper.cpp
processing/models/qgsmodelundocommand.cpp
processing/models/qgsmodelviewmouseevent.cpp
@ -1325,6 +1326,7 @@ set(QGIS_GUI_HDRS
processing/models/qgsmodelgraphicsview.h
processing/models/qgsmodelgroupboxdefinitionwidget.h
processing/models/qgsmodelinputreorderwidget.h
processing/models/qgsmodeloutputreorderwidget.h
processing/models/qgsmodelsnapper.h
processing/models/qgsmodelundocommand.h
processing/models/qgsmodelviewmouseevent.h

View File

@ -30,6 +30,7 @@
#include "qgsmodelcomponentgraphicitem.h"
#include "processing/models/qgsprocessingmodelgroupbox.h"
#include "processing/models/qgsmodelinputreorderwidget.h"
#include "processing/models/qgsmodeloutputreorderwidget.h"
#include "qgsmessageviewer.h"
#include "qgsmessagebaritem.h"
#include "qgspanelwidget.h"
@ -152,6 +153,7 @@ QgsModelDesignerDialog::QgsModelDesignerDialog( QWidget *parent, Qt::WindowFlags
connect( mActionSnapSelected, &QAction::triggered, mView, &QgsModelGraphicsView::snapSelected );
connect( mActionValidate, &QAction::triggered, this, &QgsModelDesignerDialog::validate );
connect( mActionReorderInputs, &QAction::triggered, this, &QgsModelDesignerDialog::reorderInputs );
connect( mActionReorderOutputs, &QAction::triggered, this, &QgsModelDesignerDialog::reorderOutputs );
connect( mActionEditHelp, &QAction::triggered, this, &QgsModelDesignerDialog::editHelp );
connect( mReorderInputsButton, &QPushButton::clicked, this, &QgsModelDesignerDialog::reorderInputs );
@ -1028,6 +1030,20 @@ void QgsModelDesignerDialog::reorderInputs()
}
}
void QgsModelDesignerDialog::reorderOutputs()
{
QgsModelOutputReorderDialog dlg( this );
dlg.setModel( mModel.get() );
if ( dlg.exec() )
{
const QStringList outputOrder = dlg.outputOrder();
beginUndoCommand( tr( "Reorder Outputs" ) );
mModel->setOutputOrder( outputOrder );
mModel->setOutputGroup( dlg.outputGroup() );
endUndoCommand();
}
}
bool QgsModelDesignerDialog::isDirty() const
{
return mHasChanged && mUndoStack->index() != -1;

View File

@ -183,6 +183,7 @@ class GUI_EXPORT QgsModelDesignerDialog : public QMainWindow, public Ui::QgsMode
void populateZoomToMenu();
void validate();
void reorderInputs();
void reorderOutputs();
void setPanelVisibility( bool hidden );
void editHelp();

View File

@ -0,0 +1,123 @@
/***************************************************************************
qgsmodeloutputreorderwidget.cpp
------------------------------------
Date : April 2023
Copyright : (C) 2023 Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsmodeloutputreorderwidget.h"
#include "qgsgui.h"
#include "qgsprocessingmodelalgorithm.h"
#include <QDialogButtonBox>
#include <QStandardItemModel>
///@cond NOT_STABLE
QgsModelOutputReorderWidget::QgsModelOutputReorderWidget( QWidget *parent )
: QWidget( parent )
{
setupUi( this );
mItemModel = new QStandardItemModel( 0, 1, this );
mOutputsList->setModel( mItemModel );
mOutputsList->setDropIndicatorShown( true );
mOutputsList->setDragDropOverwriteMode( false );
mOutputsList->setDragEnabled( true );
mOutputsList->setDragDropMode( QAbstractItemView::InternalMove );
connect( mButtonUp, &QPushButton::clicked, this, [ = ]
{
int currentRow = mOutputsList->currentIndex().row();
if ( currentRow == 0 )
return;
mItemModel->insertRow( currentRow - 1, mItemModel->takeRow( currentRow ) );
mOutputsList->setCurrentIndex( mItemModel->index( currentRow - 1, 0 ) );
} );
connect( mButtonDown, &QPushButton::clicked, this, [ = ]
{
int currentRow = mOutputsList->currentIndex().row();
if ( currentRow == mItemModel->rowCount() - 1 )
return;
mItemModel->insertRow( currentRow + 1, mItemModel->takeRow( currentRow ) );
mOutputsList->setCurrentIndex( mItemModel->index( currentRow + 1, 0 ) );
} );
}
void QgsModelOutputReorderWidget::setModel( QgsProcessingModelAlgorithm *model )
{
mModel = model;
mOutputs = mModel->orderedOutputs();
mItemModel->clear();
for ( const QgsProcessingModelOutput &output : std::as_const( mOutputs ) )
{
QStandardItem *item = new QStandardItem( output.name() );
item->setData( QStringLiteral( "%1:%2" ).arg( output.childId(), output.childOutputName() ), Qt::UserRole + 1 );
item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
// we show the outputs list reversed in the gui, because we want the "higher" outputs to be at the top of the list
mItemModel->insertRow( 0, item );
}
mPlaceInGroupCheck->setChecked( !model->outputGroup().isEmpty() );
mGroupNameEdit->setText( model->outputGroup() );
}
QStringList QgsModelOutputReorderWidget::outputOrder() const
{
QStringList order;
order.reserve( mItemModel->rowCount( ) );
// we show the outputs list reversed in the gui, because we want the "higher" outputs to be at the top of the list
for ( int row = mItemModel->rowCount() - 1; row >= 0; --row )
{
order << mItemModel->data( mItemModel->index( row, 0 ), Qt::UserRole + 1 ).toString();
}
return order;
}
QString QgsModelOutputReorderWidget::outputGroup() const
{
return mPlaceInGroupCheck->isChecked() ? mGroupNameEdit->text() : QString();
}
QgsModelOutputReorderDialog::QgsModelOutputReorderDialog( QWidget *parent )
: QDialog( parent )
{
setWindowTitle( tr( "Reorder Model Outputs" ) );
mWidget = new QgsModelOutputReorderWidget();
QVBoxLayout *vl = new QVBoxLayout();
vl->addWidget( mWidget, 1 );
QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
connect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
connect( buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
vl->addWidget( buttonBox );
setLayout( vl );
}
void QgsModelOutputReorderDialog::setModel( QgsProcessingModelAlgorithm *model )
{
mWidget->setModel( model );
}
QStringList QgsModelOutputReorderDialog::outputOrder() const
{
return mWidget->outputOrder();
}
QString QgsModelOutputReorderDialog::outputGroup() const
{
return mWidget->outputGroup();
}
///@endcond

View File

@ -0,0 +1,111 @@
/***************************************************************************
qgsmodeloutputreorderwidget.h
----------------------------------
Date : April 2023
Copyright : (C) 2023 Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSMODELOUTPUTREORDERWIDGET_H
#define QGSMODELOUTPUTREORDERWIDGET_H
#define SIP_NO_FILE
#include "qgis.h"
#include "qgis_gui.h"
#include "ui_qgsmodeloutputreorderwidgetbase.h"
#include "qgsprocessingmodeloutput.h"
#include <QDialog>
class QStandardItemModel;
class QgsProcessingModelAlgorithm;
///@cond PRIVATE
/**
* A widget for reordering outputs for Processing models.
* \ingroup gui
* \note Not stable API
* \since QGIS 3.32
*/
class GUI_EXPORT QgsModelOutputReorderWidget : public QWidget, private Ui::QgsModelOutputReorderWidgetBase
{
Q_OBJECT
public:
/**
* Constructor for QgsModelOutputReorderWidget.
*/
QgsModelOutputReorderWidget( QWidget *parent = nullptr );
/**
* Sets the source \a model from which to obtain the list of outputs.
*/
void setModel( QgsProcessingModelAlgorithm *model );
/**
* Returns the ordered list of outputs.
*/
QStringList outputOrder() const;
/**
* Returns the destination group name for outputs.
*/
QString outputGroup() const;
private:
QgsProcessingModelAlgorithm *mModel;
QList< QgsProcessingModelOutput > mOutputs;
QStandardItemModel *mItemModel = nullptr;
};
/**
* A dialog for reordering outputs for Processing models.
* \ingroup gui
* \note Not stable API
* \since QGIS 3.32
*/
class GUI_EXPORT QgsModelOutputReorderDialog : public QDialog
{
Q_OBJECT
public:
/**
* Constructor for QgsModelOutputReorderDialog.
*/
QgsModelOutputReorderDialog( QWidget *parent = nullptr );
/**
* Sets the source \a model from which to obtain the list of outputs.
*/
void setModel( QgsProcessingModelAlgorithm *model );
/**
* Returns the ordered list of outputs (by name).
*/
QStringList outputOrder() const;
/**
* Returns the destination group name for outputs.
*/
QString outputGroup() const;
private:
QgsModelOutputReorderWidget *mWidget = nullptr;
};
///@endcond
#endif // QGSMODELOUTPUTREORDERWIDGET_H

View File

@ -41,7 +41,7 @@
<x>0</x>
<y>0</y>
<width>786</width>
<height>24</height>
<height>23</height>
</rect>
</property>
<widget class="QMenu" name="menu_Model">
@ -61,6 +61,7 @@
<addaction name="mActionValidate"/>
<addaction name="mActionRun"/>
<addaction name="mActionReorderInputs"/>
<addaction name="mActionReorderOutputs"/>
<addaction name="separator"/>
<addaction name="mActionNew"/>
<addaction name="mActionOpen"/>
@ -756,6 +757,11 @@
<string>New Model…</string>
</property>
</action>
<action name="mActionReorderOutputs">
<property name="text">
<string>Reorder Model Outputs...</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsModelOutputReorderWidgetBase</class>
<widget class="QWidget" name="QgsModelOutputReorderWidgetBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>356</width>
<height>253</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QListView" name="mOutputsList">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="pushBtnBox_2">
<property name="spacing">
<number>4</number>
</property>
<item>
<widget class="QPushButton" name="mButtonUp">
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Move up</string>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mActionArrowUp.svg</normaloff>:/images/themes/default/mActionArrowUp.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="mButtonDown">
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Move down</string>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mActionArrowDown.svg</normaloff>:/images/themes/default/mActionArrowDown.svg</iconset>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="mPlaceInGroupCheck">
<property name="title">
<string>Place outputs in a group</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Group name</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="mGroupNameEdit"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -101,6 +101,7 @@ class TestQgsProcessingModelAlgorithm: public QObject
void modelAcceptableValues();
void modelValidate();
void modelInputs();
void modelOutputs();
void modelDependencies();
void modelSource();
void modelNameMatchesFileName();
@ -2089,6 +2090,159 @@ void TestQgsProcessingModelAlgorithm::modelInputs()
QCOMPARE( m2.parameterDefinitions().at( 2 )->name(), QStringLiteral( "string" ) );
}
void TestQgsProcessingModelAlgorithm::modelOutputs()
{
QgsProcessingModelAlgorithm m;
QVERIFY( m.orderedOutputs().isEmpty() );
QVERIFY( m.outputGroup().isEmpty() );
m.setOutputGroup( QStringLiteral( "output group" ) );
QCOMPARE( m.outputGroup(), QStringLiteral( "output group" ) );
const QgsProcessingModelParameter sourceParam( "INPUT" );
m.addModelParameter( new QgsProcessingParameterFeatureSource( "INPUT" ), sourceParam );
QgsProcessingModelChildAlgorithm algc1;
algc1.setChildId( QStringLiteral( "cx1" ) );
algc1.setAlgorithmId( "native:buffer" );
algc1.addParameterSources( "INPUT", { QgsProcessingModelChildParameterSource::fromModelParameter( "INPUT" ) } );
m.addChildAlgorithm( algc1 );
QVERIFY( m.orderedOutputs().isEmpty() );
QgsProcessingModelChildAlgorithm algc2;
algc2.setChildId( QStringLiteral( "cx2" ) );
algc2.setAlgorithmId( "native:buffer" );
algc2.addParameterSources( "INPUT", { QgsProcessingModelChildParameterSource::fromModelParameter( "INPUT" ) } );
QMap<QString, QgsProcessingModelOutput> outputs;
QgsProcessingModelOutput out1;
out1.setChildOutputName( QStringLiteral( "OUTPUT" ) );
outputs.insert( QStringLiteral( "a" ), out1 );
algc2.setModelOutputs( outputs );
m.addChildAlgorithm( algc2 );
QCOMPARE( m.orderedOutputs().size(), 1 );
QCOMPARE( m.orderedOutputs().at( 0 ).childId(), QStringLiteral( "cx2" ) );
QCOMPARE( m.orderedOutputs().at( 0 ).childOutputName(), QStringLiteral( "OUTPUT" ) );
QCOMPARE( m.orderedOutputs().at( 0 ).name(), QStringLiteral( "a" ) );
QgsProcessingModelChildAlgorithm algc3;
algc3.setChildId( QStringLiteral( "cx3" ) );
algc3.setAlgorithmId( "native:buffer" );
algc3.addParameterSources( "INPUT", { QgsProcessingModelChildParameterSource::fromModelParameter( "INPUT" ) } );
outputs.clear();
QgsProcessingModelOutput out2;
out2.setChildOutputName( QStringLiteral( "OUTPUT" ) );
outputs.insert( QStringLiteral( "b" ), out2 );
algc3.setModelOutputs( outputs );
m.addChildAlgorithm( algc3 );
QCOMPARE( m.orderedOutputs().size(), 2 );
QCOMPARE( m.orderedOutputs().at( 0 ).childId(), QStringLiteral( "cx2" ) );
QCOMPARE( m.orderedOutputs().at( 0 ).childOutputName(), QStringLiteral( "OUTPUT" ) );
QCOMPARE( m.orderedOutputs().at( 0 ).name(), QStringLiteral( "a" ) );
QCOMPARE( m.orderedOutputs().at( 1 ).childId(), QStringLiteral( "cx3" ) );
QCOMPARE( m.orderedOutputs().at( 1 ).childOutputName(), QStringLiteral( "OUTPUT" ) );
QCOMPARE( m.orderedOutputs().at( 1 ).name(), QStringLiteral( "b" ) );
QgsProcessingModelChildAlgorithm algc4;
algc4.setChildId( QStringLiteral( "cx4" ) );
algc4.setAlgorithmId( "native:buffer" );
algc4.addParameterSources( "INPUT", { QgsProcessingModelChildParameterSource::fromModelParameter( "INPUT" ) } );
outputs.clear();
QgsProcessingModelOutput out3;
out3.setChildOutputName( QStringLiteral( "OUTPUT" ) );
outputs.insert( QStringLiteral( "c" ), out2 );
algc4.setModelOutputs( outputs );
m.addChildAlgorithm( algc4 );
QCOMPARE( m.orderedOutputs().size(), 3 );
QCOMPARE( m.orderedOutputs().at( 0 ).childId(), QStringLiteral( "cx2" ) );
QCOMPARE( m.orderedOutputs().at( 0 ).childOutputName(), QStringLiteral( "OUTPUT" ) );
QCOMPARE( m.orderedOutputs().at( 0 ).name(), QStringLiteral( "a" ) );
QCOMPARE( m.orderedOutputs().at( 1 ).childId(), QStringLiteral( "cx3" ) );
QCOMPARE( m.orderedOutputs().at( 1 ).childOutputName(), QStringLiteral( "OUTPUT" ) );
QCOMPARE( m.orderedOutputs().at( 1 ).name(), QStringLiteral( "b" ) );
QCOMPARE( m.orderedOutputs().at( 2 ).childId(), QStringLiteral( "cx4" ) );
QCOMPARE( m.orderedOutputs().at( 2 ).childOutputName(), QStringLiteral( "OUTPUT" ) );
QCOMPARE( m.orderedOutputs().at( 2 ).name(), QStringLiteral( "c" ) );
// set specific output order (incomplete, and with some non-matching values)
m.setOutputOrder( { QStringLiteral( "cx3:OUTPUT" ), QStringLiteral( "cx2:OUTPUT" ), QStringLiteral( "cx1:OUTPUT" ) } );
QCOMPARE( m.orderedOutputs().size(), 3 );
QCOMPARE( m.orderedOutputs().at( 0 ).childId(), QStringLiteral( "cx3" ) );
QCOMPARE( m.orderedOutputs().at( 0 ).childOutputName(), QStringLiteral( "OUTPUT" ) );
QCOMPARE( m.orderedOutputs().at( 0 ).name(), QStringLiteral( "b" ) );
QCOMPARE( m.orderedOutputs().at( 1 ).childId(), QStringLiteral( "cx2" ) );
QCOMPARE( m.orderedOutputs().at( 1 ).childOutputName(), QStringLiteral( "OUTPUT" ) );
QCOMPARE( m.orderedOutputs().at( 1 ).name(), QStringLiteral( "a" ) );
QCOMPARE( m.orderedOutputs().at( 2 ).childId(), QStringLiteral( "cx4" ) );
QCOMPARE( m.orderedOutputs().at( 2 ).childOutputName(), QStringLiteral( "OUTPUT" ) );
QCOMPARE( m.orderedOutputs().at( 2 ).name(), QStringLiteral( "c" ) );
// set specific output order, complete
m.setOutputOrder( { QStringLiteral( "cx3:OUTPUT" ), QStringLiteral( "cx4:OUTPUT" ), QStringLiteral( "cx2:OUTPUT" ) } );
QCOMPARE( m.orderedOutputs().size(), 3 );
QCOMPARE( m.orderedOutputs().at( 0 ).childId(), QStringLiteral( "cx3" ) );
QCOMPARE( m.orderedOutputs().at( 0 ).childOutputName(), QStringLiteral( "OUTPUT" ) );
QCOMPARE( m.orderedOutputs().at( 0 ).name(), QStringLiteral( "b" ) );
QCOMPARE( m.orderedOutputs().at( 1 ).childId(), QStringLiteral( "cx4" ) );
QCOMPARE( m.orderedOutputs().at( 1 ).childOutputName(), QStringLiteral( "OUTPUT" ) );
QCOMPARE( m.orderedOutputs().at( 1 ).name(), QStringLiteral( "c" ) );
QCOMPARE( m.orderedOutputs().at( 2 ).childId(), QStringLiteral( "cx2" ) );
QCOMPARE( m.orderedOutputs().at( 2 ).childOutputName(), QStringLiteral( "OUTPUT" ) );
QCOMPARE( m.orderedOutputs().at( 2 ).name(), QStringLiteral( "a" ) );
// save/restore
QgsProcessingModelAlgorithm m2;
m2.loadVariant( m.toVariant() );
QCOMPARE( m2.outputGroup(), QStringLiteral( "output group" ) );
QCOMPARE( m2.orderedOutputs().size(), 3 );
QCOMPARE( m2.orderedOutputs().at( 0 ).childId(), QStringLiteral( "cx3" ) );
QCOMPARE( m2.orderedOutputs().at( 0 ).childOutputName(), QStringLiteral( "OUTPUT" ) );
QCOMPARE( m2.orderedOutputs().at( 0 ).name(), QStringLiteral( "b" ) );
QCOMPARE( m2.orderedOutputs().at( 1 ).childId(), QStringLiteral( "cx4" ) );
QCOMPARE( m2.orderedOutputs().at( 1 ).childOutputName(), QStringLiteral( "OUTPUT" ) );
QCOMPARE( m2.orderedOutputs().at( 1 ).name(), QStringLiteral( "c" ) );
QCOMPARE( m2.orderedOutputs().at( 2 ).childId(), QStringLiteral( "cx2" ) );
QCOMPARE( m2.orderedOutputs().at( 2 ).childOutputName(), QStringLiteral( "OUTPUT" ) );
QCOMPARE( m2.orderedOutputs().at( 2 ).name(), QStringLiteral( "a" ) );
// also run and check context details
QgsProcessingContext context;
QgsProcessingFeedback feedback;
QVariantMap params;
QgsVectorLayer *layer3111 = new QgsVectorLayer( "Point?crs=epsg:3111", "v1", "memory" );
QgsProject p;
p.addMapLayer( layer3111 );
context.setProject( &p );
params.insert( QStringLiteral( "INPUT" ), QStringLiteral( "v1" ) );
params.insert( QStringLiteral( "cx2:a" ), QgsProcessing::TEMPORARY_OUTPUT );
params.insert( QStringLiteral( "cx3:b" ), QgsProcessing::TEMPORARY_OUTPUT );
params.insert( QStringLiteral( "cx4:c" ), QgsProcessing::TEMPORARY_OUTPUT );
QVariantMap results = m.run( params, context, &feedback );
const QString destA = results.value( QStringLiteral( "cx2:a" ) ).toString();
QVERIFY( !destA.isEmpty() );
QCOMPARE( context.layerToLoadOnCompletionDetails( destA ).groupName, QStringLiteral( "output group" ) );
QCOMPARE( context.layerToLoadOnCompletionDetails( destA ).layerSortKey, 2 );
const QString destB = results.value( QStringLiteral( "cx3:b" ) ).toString();
QVERIFY( !destB.isEmpty() );
QCOMPARE( context.layerToLoadOnCompletionDetails( destB ).groupName, QStringLiteral( "output group" ) );
QCOMPARE( context.layerToLoadOnCompletionDetails( destB ).layerSortKey, 0 );
const QString destC = results.value( QStringLiteral( "cx4:c" ) ).toString();
QVERIFY( !destC.isEmpty() );
QCOMPARE( context.layerToLoadOnCompletionDetails( destC ).groupName, QStringLiteral( "output group" ) );
QCOMPARE( context.layerToLoadOnCompletionDetails( destC ).layerSortKey, 1 );
}
void TestQgsProcessingModelAlgorithm::modelDependencies()
{
const QgsProcessingModelChildDependency dep( QStringLiteral( "childId" ), QStringLiteral( "branch" ) );