QGIS API Documentation 4.3.0-Master (52aa6503bed)
Loading...
Searching...
No Matches
qgsprocessingmodelalgorithm.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsprocessingmodelalgorithm.cpp
3 ------------------------------
4 begin : June 2017
5 copyright : (C) 2017 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
20#include "qgis.h"
21#include "qgsapplication.h"
22#include "qgsexception.h"
24#include "qgsmessagelog.h"
30#include "qgsprocessingutils.h"
31#include "qgsscopedconnection.h"
32#include "qgsstringutils.h"
33#include "qgsvectorlayer.h"
34#include "qgsxmlutils.h"
35
36#include <QFile>
37#include <QRegularExpression>
38#include <QString>
39#include <QTextStream>
40
41#include "moc_qgsprocessingmodelalgorithm.cpp"
42
43using namespace Qt::StringLiterals;
44
46
47QgsProcessingModelAlgorithm::QgsProcessingModelAlgorithm( const QString &name, const QString &group, const QString &groupId )
48 : mModelName( name.isEmpty() ? QObject::tr( "model" ) : name )
49 , mModelGroup( group )
50 , mModelGroupId( groupId )
51{}
52
53void QgsProcessingModelAlgorithm::initAlgorithm( const QVariantMap & )
54{}
55
56Qgis::ProcessingAlgorithmFlags QgsProcessingModelAlgorithm::flags() const
57{
59
60 // don't force algorithm attachment here, that's potentially too expensive
61 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
62 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
63 {
64 if ( childIt->algorithm() && childIt->algorithm()->flags().testFlag( Qgis::ProcessingAlgorithmFlag::SecurityRisk ) )
65 {
66 // security risk flag propagates from child algorithms to model
68 }
69 }
70 return res;
71}
72
73QString QgsProcessingModelAlgorithm::name() const
74{
75 return mModelName;
76}
77
78QString QgsProcessingModelAlgorithm::displayName() const
79{
80 return mModelName;
81}
82
83QString QgsProcessingModelAlgorithm::group() const
84{
85 return mModelGroup;
86}
87
88QString QgsProcessingModelAlgorithm::groupId() const
89{
90 return mModelGroupId;
91}
92
93QIcon QgsProcessingModelAlgorithm::icon() const
94{
95 return QgsApplication::getThemeIcon( u"/processingModel.svg"_s );
96}
97
98QString QgsProcessingModelAlgorithm::svgIconPath() const
99{
100 return QgsApplication::iconPath( u"processingModel.svg"_s );
101}
102
103QString QgsProcessingModelAlgorithm::shortHelpString() const
104{
105 if ( mHelpContent.empty() )
106 return QString();
107
108 return QgsProcessingUtils::formatHelpMapAsHtml( mHelpContent, this );
109}
110
111QString QgsProcessingModelAlgorithm::shortDescription() const
112{
113 return mHelpContent.value( u"SHORT_DESCRIPTION"_s ).toString();
114}
115
116QString QgsProcessingModelAlgorithm::helpUrl() const
117{
118 return mHelpContent.value( u"HELP_URL"_s ).toString();
119}
120
121QVariantMap QgsProcessingModelAlgorithm::parametersForChildAlgorithm(
122 const QgsProcessingModelChildAlgorithm &child, const QVariantMap &modelParameters, const QVariantMap &results, const QgsExpressionContext &expressionContext, QString &error, const QgsProcessingContext *context
123) const
124{
125 error.clear();
126 auto evaluateSources = [&child, &modelParameters, &results, &error, &expressionContext]( const QgsProcessingParameterDefinition *def ) -> QVariant {
127 const QgsProcessingModelChildParameterSources paramSources = child.parameterSources().value( def->name() );
128
129 QString expressionText;
130 QVariantList paramParts;
131 for ( const QgsProcessingModelChildParameterSource &source : paramSources )
132 {
133 switch ( source.source() )
134 {
136 paramParts << source.staticValue();
137 break;
138
140 paramParts << modelParameters.value( source.parameterName() );
141 break;
142
144 {
145 QVariantMap linkedChildResults = results.value( source.outputChildId() ).toMap();
146 paramParts << linkedChildResults.value( source.outputName() );
147 break;
148 }
149
151 {
152 QgsExpression exp( source.expression() );
153 paramParts << exp.evaluate( &expressionContext );
154 if ( exp.hasEvalError() )
155 {
156 error = QObject::tr( "Could not evaluate expression for parameter %1 for %2: %3" ).arg( def->name(), child.description(), exp.evalErrorString() );
157 }
158 break;
159 }
161 {
163 expressionText = QgsExpression::replaceExpressionText( source.expressionText(), &expressionContext );
165 break;
166 }
167
169 break;
170 }
171 }
172
173 if ( !expressionText.isEmpty() )
174 {
175 return expressionText;
176 }
177 else if ( paramParts.count() == 1 )
178 return paramParts.at( 0 );
179 else
180 return paramParts;
181 };
182
183
184 QVariantMap childParams;
185 const QList< const QgsProcessingParameterDefinition * > childParameterDefinitions = child.algorithm()->parameterDefinitions();
186 for ( const QgsProcessingParameterDefinition *def : childParameterDefinitions )
187 {
188 if ( !def->isDestination() )
189 {
190 if ( !child.parameterSources().contains( def->name() ) )
191 continue; // use default value
192
193 const QVariant value = evaluateSources( def );
194 childParams.insert( def->name(), value );
195 }
196 else
197 {
198 const QgsProcessingDestinationParameter *destParam = static_cast< const QgsProcessingDestinationParameter * >( def );
199
200 // is destination linked to one of the final outputs from this model?
201 bool isFinalOutput = false;
202 QMap<QString, QgsProcessingModelOutput> outputs = child.modelOutputs();
203 QMap<QString, QgsProcessingModelOutput>::const_iterator outputIt = outputs.constBegin();
204 for ( ; outputIt != outputs.constEnd(); ++outputIt )
205 {
206 if ( outputIt->childOutputName() == destParam->name() )
207 {
208 QString paramName = child.childId() + ':' + outputIt.key();
209 bool foundParam = false;
210 QVariant value;
211
212 // if parameter was specified using child_id:child_name directly, take that
213 if ( modelParameters.contains( paramName ) )
214 {
215 value = modelParameters.value( paramName );
216 foundParam = true;
217 }
218
219 // ...otherwise we need to find the corresponding model parameter which matches this output
220 if ( !foundParam )
221 {
222 if ( const QgsProcessingParameterDefinition *modelParam = modelParameterFromChildIdAndOutputName( child.childId(), outputIt.key() ) )
223 {
224 if ( modelParameters.contains( modelParam->name() ) )
225 {
226 value = modelParameters.value( modelParam->name() );
227 foundParam = true;
228 }
229 }
230 }
231
232 if ( foundParam )
233 {
234 if ( value.userType() == qMetaTypeId<QgsProcessingOutputLayerDefinition>() )
235 {
236 // make sure layer output name is correctly set
237 QgsProcessingOutputLayerDefinition fromVar = qvariant_cast<QgsProcessingOutputLayerDefinition>( value );
238 fromVar.destinationName = outputIt.key();
239 value = QVariant::fromValue( fromVar );
240 }
241
242 childParams.insert( destParam->name(), value );
243 }
244 isFinalOutput = true;
245 break;
246 }
247 }
248
249 bool hasExplicitDefinition = false;
250 if ( !isFinalOutput && child.parameterSources().contains( def->name() ) )
251 {
252 // explicitly defined source for output
253 const QVariant value = evaluateSources( def );
254 if ( value.isValid() )
255 {
256 childParams.insert( def->name(), value );
257 hasExplicitDefinition = true;
258 }
259 }
260
261 if ( !isFinalOutput && !hasExplicitDefinition )
262 {
263 // output is temporary
264
265 // check whether it's optional, and if so - is it required?
266 bool required = true;
268 {
269 required = childOutputIsRequired( child.childId(), destParam->name() );
270 }
271
272 // not optional, or required elsewhere in model
273 if ( required )
274 childParams.insert( destParam->name(), destParam->generateTemporaryDestination( context ) );
275 }
276 }
277 }
278 return childParams;
279}
280
281const QgsProcessingParameterDefinition *QgsProcessingModelAlgorithm::modelParameterFromChildIdAndOutputName( const QString &childId, const QString &childOutputName ) const
282{
283 for ( const QgsProcessingParameterDefinition *definition : mParameters )
284 {
285 if ( !definition->isDestination() )
286 continue;
287
288 const QString modelChildId = definition->metadata().value( u"_modelChildId"_s ).toString();
289 const QString modelOutputName = definition->metadata().value( u"_modelChildOutputName"_s ).toString();
290
291 if ( modelChildId == childId && modelOutputName == childOutputName )
292 return definition;
293 }
294 return nullptr;
295}
296
297bool QgsProcessingModelAlgorithm::childOutputIsRequired( const QString &childId, const QString &outputName ) const
298{
299 // look through all child algs
300 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
301 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
302 {
303 if ( childIt->childId() == childId || !childIt->isActive() )
304 continue;
305
306 // look through all sources for child
307 QMap<QString, QgsProcessingModelChildParameterSources> candidateChildParams = childIt->parameterSources();
308 QMap<QString, QgsProcessingModelChildParameterSources>::const_iterator childParamIt = candidateChildParams.constBegin();
309 for ( ; childParamIt != candidateChildParams.constEnd(); ++childParamIt )
310 {
311 const auto constValue = childParamIt.value();
312 for ( const QgsProcessingModelChildParameterSource &source : constValue )
313 {
314 if ( source.source() == Qgis::ProcessingModelChildParameterSource::ChildOutput && source.outputChildId() == childId && source.outputName() == outputName )
315 {
316 return true;
317 }
318 }
319 }
320 }
321 return false;
322}
323
324QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
325{
326 // warning -- may be nullptr! QgsProcessingModelFeedback is only used when
327 // executing the model directly through the model designer dialog
328 QgsProcessingModelFeedback *modelFeedback = qobject_cast< QgsProcessingModelFeedback * >( feedback );
329
330 QSet< QString > toExecute;
331 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
332 QSet< QString > broken;
333 const QSet<QString> childSubset = context.modelInitialRunConfig() ? context.modelInitialRunConfig()->childAlgorithmSubset() : QSet<QString>();
334 const bool useSubsetOfChildren = !childSubset.empty();
335 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
336 {
337 if ( childIt->isActive() && ( !useSubsetOfChildren || childSubset.contains( childIt->childId() ) ) )
338 {
339 if ( childIt->algorithm() )
340 toExecute.insert( childIt->childId() );
341 else
342 broken.insert( childIt->childId() );
343 }
344 }
345
346 if ( !broken.empty() )
347 {
348 if ( modelFeedback )
349 {
350 modelFeedback->reportBrokenChildAlgorithms( broken );
351 }
353 QCoreApplication::translate( "QgsProcessingModelAlgorithm", "Cannot run model, the following algorithms are not available on this system: %1" ).arg( qgsSetJoin( broken, ", "_L1 ) )
354 );
355 }
356
357 QElapsedTimer totalTime;
358 totalTime.start();
359
360 QgsProcessingMultiStepFeedback childAlgorithmFeedback( toExecute.count(), feedback );
361 QgsExpressionContext baseContext = createExpressionContext( parameters, context );
362
363 QVariantMap &childInputs = context.modelResult().rawChildInputs();
364 QVariantMap &childResults = context.modelResult().rawChildOutputs();
365 QSet< QString > &executed = context.modelResult().executedChildIds();
366 QMap< QString, QgsProcessingModelChildAlgorithmResult > &contextChildResults = context.modelResult().childResults();
367
368 // start with initial configuration from the context's model configuration (allowing us to
369 // resume execution using a previous state)
371 {
372 childInputs = config->initialChildInputs();
373 childResults = config->initialChildOutputs();
374 executed = config->previouslyExecutedChildAlgorithms();
375 // discard the model config, this should only be used when running the top level model
376 context.setModelInitialRunConfig( nullptr );
377 }
378 if ( useSubsetOfChildren )
379 {
380 executed.subtract( childSubset );
381 }
382
383 QVariantMap finalResults;
384
385 bool executedAlg = true;
386 int previousHtmlLogLength = feedback ? feedback->htmlLog().length() : 0;
387 int countExecuted = 0;
388 while ( executedAlg && countExecuted < toExecute.count() )
389 {
390 executedAlg = false;
391 for ( const QString &childId : std::as_const( toExecute ) )
392 {
393 if ( feedback && feedback->isCanceled() )
394 break;
395
396 if ( executed.contains( childId ) )
397 {
398 continue;
399 }
400
401 bool canExecute = true;
402 const QSet< QString > dependencies = dependsOnChildAlgorithms( childId );
403 for ( const QString &dependency : dependencies )
404 {
405 if ( !executed.contains( dependency ) )
406 {
407 canExecute = false;
408 break;
409 }
410 }
411
412 if ( !canExecute )
413 {
414 continue;
415 }
416
417 executedAlg = true;
418
419 const QgsProcessingModelChildAlgorithm &child = mChildAlgorithms[childId];
420 std::unique_ptr< QgsProcessingAlgorithm > childAlg( child.algorithm()->create( child.configuration() ) );
421
422 bool skipGenericLogging = true;
423 switch ( context.logLevel() )
424 {
426 // at default log level we skip all the generic logs about prepare steps, step inputs and outputs
427 skipGenericLogging = true;
428 break;
430 // at verbose log level we include all the generic logs about prepare steps, step inputs and outputs
431 // UNLESS the algorithm specifically tells to skip these (eg raise warning steps and other special cases)
432 skipGenericLogging = childAlg->flags() & Qgis::ProcessingAlgorithmFlag::SkipGenericModelLogging;
433 break;
435 // at model debug log level we'll show all the generic logs for step preparation, inputs and outputs
436 // for every child algorithm
437 skipGenericLogging = false;
438 break;
439 }
440
441 childAlgorithmFeedback.resetFeatureSinkCounts();
442
443 if ( feedback && !skipGenericLogging )
444 {
445 feedback->pushDebugInfo( QObject::tr( "Prepare algorithm: %1" ).arg( childId ) );
446 }
447 if ( modelFeedback )
448 {
449 modelFeedback->reportPreparingChild( childId );
450 }
451
452 QgsExpressionContext expContext = baseContext;
453 expContext
454 << QgsExpressionContextUtils::processingAlgorithmScope( child.algorithm(), parameters, context )
455 << createExpressionContextScopeForChildAlgorithm( childId, context, parameters, childResults ).release();
456 context.setExpressionContext( expContext );
457
458 QString error;
459 QVariantMap childParams = parametersForChildAlgorithm( child, parameters, childResults, expContext, error, &context );
460 if ( !error.isEmpty() )
461 {
462 if ( modelFeedback )
463 {
464 modelFeedback->reportChildPreparationFailure( childId, error );
465 }
466 throw QgsProcessingException( error );
467 }
468
469 if ( feedback && !skipGenericLogging )
470 feedback->setProgressText( QObject::tr( "Running %1 [%2/%3]" ).arg( child.description() ).arg( executed.count() + 1 ).arg( toExecute.count() ) );
471
473
474 const QVariantMap thisChildParams = QgsProcessingUtils::removePointerValuesFromMap( childParams );
475 childInputs.insert( childId, thisChildParams );
476 childResult.setInputs( thisChildParams );
477
478 QStringList params;
479 for ( auto childParamIt = childParams.constBegin(); childParamIt != childParams.constEnd(); ++childParamIt )
480 {
481 params << u"%1: %2"_s.arg( childParamIt.key(), child.algorithm()->parameterDefinition( childParamIt.key() )->valueAsPythonString( childParamIt.value(), context ) );
482 }
483
484 if ( feedback && !skipGenericLogging )
485 {
486 feedback->pushInfo( QObject::tr( "Input Parameters:" ) );
487 feedback->pushCommandInfo( u"{ %1 }"_s.arg( params.join( ", "_L1 ) ) );
488 }
489
490 QElapsedTimer childTime;
491 childTime.start();
492
493 QVariantMap outerScopeChildInputs = childInputs;
494 QVariantMap outerScopePrevChildResults = childResults;
495 QSet< QString > outerScopeExecuted = executed;
496 QMap< QString, QgsProcessingModelChildAlgorithmResult > outerScopeContextChildResult = contextChildResults;
497 if ( dynamic_cast< QgsProcessingModelAlgorithm * >( childAlg.get() ) )
498 {
499 // don't allow results from outer models to "leak" into child models
500 childInputs.clear();
501 childResults.clear();
502 executed.clear();
503 contextChildResults.clear();
504 }
505
506 bool ok = false;
507
508 QThread *modelThread = QThread::currentThread();
509
510 auto prepareOnMainThread = [modelThread, &ok, &childAlg, &childParams, &context, &childAlgorithmFeedback] {
511 Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "QgsProcessingModelAlgorithm::processAlgorithm", "childAlg->prepare() must be run on the main thread" );
512 ok = childAlg->prepare( childParams, context, &childAlgorithmFeedback );
513 context.pushToThread( modelThread );
514 };
515
516 // Make sure we only run prepare steps on the main thread!
517 if ( modelThread == qApp->thread() )
518 ok = childAlg->prepare( childParams, context, &childAlgorithmFeedback );
519 else
520 {
521 context.pushToThread( qApp->thread() );
522// silence false positive leak warning
523#ifndef __clang_analyzer__
524 QMetaObject::invokeMethod( qApp, prepareOnMainThread, Qt::BlockingQueuedConnection );
525#endif
526 }
527
528 Q_ASSERT_X( QThread::currentThread() == context.thread(), "QgsProcessingModelAlgorithm::processAlgorithm", "context was not transferred back to model thread" );
529
530 if ( !ok )
531 {
532 const QString error = ( childAlg->flags() & Qgis::ProcessingAlgorithmFlag::CustomException ) ? QString() : QObject::tr( "Error encountered while running %1" ).arg( child.description() );
533 if ( modelFeedback )
534 {
535 modelFeedback->reportChildPreparationFailure( childId, error );
536 }
537 throw QgsProcessingException( error );
538 }
539
540 if ( modelFeedback )
541 {
542 modelFeedback->reportChildStarted( childId, childParams );
543 }
544
545 QVariantMap results;
546
547 bool runResult = false;
548 try
549 {
550 QgsScopedConnection childProgressConnection;
551 QgsScopedConnection childSourceLoadedConnection;
552 QgsScopedConnection childSinkCountChangedConnection;
553 if ( modelFeedback )
554 {
555 // these are scoped connections -- we only want them to exist for the duration that we're actually running THIS
556 // particular child algorithm
557 childProgressConnection = QObject::connect( &childAlgorithmFeedback, &QgsFeedback::progressChanged, &childAlgorithmFeedback, [&modelFeedback, &childId]( double progress ) {
558 modelFeedback->reportChildProgress( childId, progress );
559 } );
560 childSinkCountChangedConnection
561 = QObject::connect( &childAlgorithmFeedback, &QgsProcessingFeedback::sinkFeatureCountChanged, &childAlgorithmFeedback, [&modelFeedback, &childId]( const QString &sinkId, long long featureCount ) {
562 modelFeedback->reportChildSinkFeatureCountChanged( childId, sinkId, featureCount );
563 } );
564 // note -- this is INTENTIONALLY connected to feedback, not childAlgorithmFeedback!
565 childSourceLoadedConnection = QObject::connect( feedback, &QgsProcessingFeedback::sourceLoaded, feedback, [&modelFeedback, &childId]( const QString &parameterName, long long featureCount ) {
566 modelFeedback->reportChildSourceLoaded( childId, parameterName, featureCount );
567 } );
568 }
569
570 if ( ( childAlg->flags() & Qgis::ProcessingAlgorithmFlag::NoThreading ) && ( QThread::currentThread() != qApp->thread() ) )
571 {
572 // child algorithm run step must be called on main thread
573 bool exceptionFromMainThread = false;
574 auto runOnMainThread = [modelThread, &context, &childAlgorithmFeedback, &results, &childAlg, &childParams, &exceptionFromMainThread, &child, &error, &childResult] {
575 Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "QgsProcessingModelAlgorithm::processAlgorithm", "childAlg->runPrepared() must be run on the main thread" );
576 try
577 {
578 results = childAlg->runPrepared( childParams, context, &childAlgorithmFeedback );
579 }
580 catch ( QgsProcessingException &e )
581 {
582 error = ( childAlg->flags() & Qgis::ProcessingAlgorithmFlag::CustomException ) ? e.what() : QObject::tr( "Error encountered while running %1: %2" ).arg( child.description(), e.what() );
584 exceptionFromMainThread = true;
585 }
586 context.pushToThread( modelThread );
587 };
588
589 if ( feedback && !skipGenericLogging && modelThread != qApp->thread() )
590 feedback->pushWarning( QObject::tr( "Algorithm “%1” cannot be run in a background thread, switching to main thread for this step" ).arg( childAlg->displayName() ) );
591
592 context.pushToThread( qApp->thread() );
593// silence false positive leak warning
594#ifndef __clang_analyzer__
595 QMetaObject::invokeMethod( qApp, runOnMainThread, Qt::BlockingQueuedConnection );
596#endif
597 if ( !exceptionFromMainThread )
598 {
599 runResult = true;
601 }
602 }
603 else
604 {
605 // safe to run on model thread
606 results = childAlg->runPrepared( childParams, context, &childAlgorithmFeedback );
607 runResult = true;
609 }
610 }
611 catch ( QgsProcessingException &e )
612 {
613 error = ( childAlg->flags() & Qgis::ProcessingAlgorithmFlag::CustomException ) ? e.what() : QObject::tr( "Error encountered while running %1: %2" ).arg( child.description(), e.what() );
615 }
616
617 Q_ASSERT_X( QThread::currentThread() == context.thread(), "QgsProcessingModelAlgorithm::processAlgorithm", "context was not transferred back to model thread" );
618
619 QVariantMap ppRes;
620 auto postProcessOnMainThread = [modelThread, &ppRes, &childAlg, &context, &childAlgorithmFeedback, runResult] {
621 Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "QgsProcessingModelAlgorithm::processAlgorithm", "childAlg->postProcess() must be run on the main thread" );
622 ppRes = childAlg->postProcess( context, &childAlgorithmFeedback, runResult );
623 context.pushToThread( modelThread );
624 };
625
626 // Make sure we only run postProcess steps on the main thread!
627 if ( modelThread == qApp->thread() )
628 ppRes = childAlg->postProcess( context, &childAlgorithmFeedback, runResult );
629 else
630 {
631 context.pushToThread( qApp->thread() );
632// silence false positive leak warning
633#ifndef __clang_analyzer__
634 QMetaObject::invokeMethod( qApp, postProcessOnMainThread, Qt::BlockingQueuedConnection );
635#endif
636 }
637
638 Q_ASSERT_X( QThread::currentThread() == context.thread(), "QgsProcessingModelAlgorithm::processAlgorithm", "context was not transferred back to model thread" );
639
640 if ( !ppRes.isEmpty() )
641 results = ppRes;
642
643 if ( dynamic_cast< QgsProcessingModelAlgorithm * >( childAlg.get() ) )
644 {
645 childInputs = outerScopeChildInputs;
646 childResults = outerScopePrevChildResults;
647 executed = outerScopeExecuted;
648 contextChildResults = outerScopeContextChildResult;
649 }
650
651 childResults.insert( childId, results );
652 childResult.setOutputs( results );
653
654 if ( modelFeedback )
655 {
657 modelFeedback->reportChildExecutionFailure( childId, error );
659 {
660 modelFeedback->reportChildProgress( childId, 100 );
661 modelFeedback->reportChildExecutionSuccess( childId, results );
662 }
663 }
664
665 if ( runResult )
666 {
667 if ( feedback && !skipGenericLogging )
668 {
669 const QVariantMap displayOutputs = QgsProcessingUtils::removePointerValuesFromMap( results );
670 QStringList formattedOutputs;
671 for ( auto displayOutputIt = displayOutputs.constBegin(); displayOutputIt != displayOutputs.constEnd(); ++displayOutputIt )
672 {
673 formattedOutputs << u"%1: %2"_s.arg( displayOutputIt.key(), QgsProcessingUtils::variantToPythonLiteral( displayOutputIt.value() ) );
674 ;
675 }
676 feedback->pushInfo( QObject::tr( "Results:" ) );
677 feedback->pushCommandInfo( u"{ %1 }"_s.arg( formattedOutputs.join( ", "_L1 ) ) );
678 }
679
680 // look through child alg's outputs to determine whether any of these should be copied
681 // to the final model outputs
682 const QMap<QString, QgsProcessingModelOutput> outputs = child.modelOutputs();
683 for ( auto outputIt = outputs.constBegin(); outputIt != outputs.constEnd(); ++outputIt )
684 {
685 const int outputSortKey = mOutputOrder.indexOf( u"%1:%2"_s.arg( childId, outputIt->childOutputName() ) );
686 switch ( mInternalVersion )
687 {
688 case QgsProcessingModelAlgorithm::InternalVersion::Version1:
689 finalResults.insert( childId + ':' + outputIt->name(), results.value( outputIt->childOutputName() ) );
690 break;
691 case QgsProcessingModelAlgorithm::InternalVersion::Version2:
692 if ( const QgsProcessingParameterDefinition *modelParam = modelParameterFromChildIdAndOutputName( child.childId(), outputIt.key() ) )
693 {
694 finalResults.insert( modelParam->name(), results.value( outputIt->childOutputName() ) );
695 }
696 break;
697 }
698
699 const QString outputLayer = results.value( outputIt->childOutputName() ).toString();
700 if ( !outputLayer.isEmpty() && context.willLoadLayerOnCompletion( outputLayer ) )
701 {
702 QgsProcessingContext::LayerDetails &details = context.layerToLoadOnCompletionDetails( outputLayer );
703 details.groupName = mOutputGroup;
704 if ( outputSortKey > 0 )
705 details.layerSortKey = outputSortKey;
706 }
707 }
708
709 executed.insert( childId );
710
711 std::function< void( const QString &, const QString & )> pruneAlgorithmBranchRecursive;
712 pruneAlgorithmBranchRecursive = [&]( const QString &id, const QString &branch = QString() ) {
713 const QSet<QString> toPrune = dependentChildAlgorithms( id, branch );
714 for ( const QString &targetId : toPrune )
715 {
716 if ( executed.contains( targetId ) )
717 continue;
718
719 executed.insert( targetId );
720 if ( modelFeedback )
721 {
722 modelFeedback->reportChildPruned( targetId );
723 }
724 pruneAlgorithmBranchRecursive( targetId, branch );
725 }
726 };
727
728 // prune remaining algorithms if they are dependent on a branch from this child which didn't eventuate
729 const QgsProcessingOutputDefinitions outputDefs = childAlg->outputDefinitions();
730 for ( const QgsProcessingOutputDefinition *outputDef : outputDefs )
731 {
732 if ( outputDef->type() == QgsProcessingOutputConditionalBranch::typeName() && !results.value( outputDef->name() ).toBool() )
733 {
734 pruneAlgorithmBranchRecursive( childId, outputDef->name() );
735 }
736 }
737
739 {
740 // check if any dependent algorithms should be canceled based on the outputs of this algorithm run
741 // first find all direct dependencies of this algorithm by looking through all remaining child algorithms
742 for ( const QString &candidateId : std::as_const( toExecute ) )
743 {
744 if ( executed.contains( candidateId ) )
745 continue;
746
747 // a pending algorithm was found..., check it's parameter sources to see if it links to any of the current
748 // algorithm's outputs
749 const QgsProcessingModelChildAlgorithm &candidate = mChildAlgorithms[candidateId];
750 const QMap<QString, QgsProcessingModelChildParameterSources> candidateParams = candidate.parameterSources();
751 QMap<QString, QgsProcessingModelChildParameterSources>::const_iterator paramIt = candidateParams.constBegin();
752 bool pruned = false;
753 for ( ; paramIt != candidateParams.constEnd(); ++paramIt )
754 {
755 for ( const QgsProcessingModelChildParameterSource &source : paramIt.value() )
756 {
757 if ( source.source() == Qgis::ProcessingModelChildParameterSource::ChildOutput && source.outputChildId() == childId )
758 {
759 // ok, this one is dependent on the current alg. Did we get a value for it?
760 if ( !results.contains( source.outputName() ) )
761 {
762 // oh no, nothing returned for this parameter. Gotta trim the branch back!
763 pruned = true;
764 // skip the dependent alg..
765 executed.insert( candidateId );
766 if ( modelFeedback )
767 {
768 modelFeedback->reportChildPruned( candidateId );
769 }
770 //... and everything which depends on it
771 pruneAlgorithmBranchRecursive( candidateId, QString() );
772 break;
773 }
774 }
775 }
776 if ( pruned )
777 break;
778 }
779 }
780 }
781
782 childAlg.reset( nullptr );
783 countExecuted++;
784 childAlgorithmFeedback.setCurrentStep( countExecuted );
785 if ( feedback && !skipGenericLogging )
786 {
787 feedback->pushInfo( QObject::tr( "OK. Execution took %1 s (%n output(s)).", nullptr, results.count() ).arg( childTime.elapsed() / 1000.0 ) );
788 }
789 }
790
791 // trim out just the portion of the overall log which relates to this child
792 const QString thisAlgorithmHtmlLog = feedback ? feedback->htmlLog().mid( previousHtmlLogLength ) : QString();
793 previousHtmlLogLength = feedback ? feedback->htmlLog().length() : 0;
794
795 if ( !runResult )
796 {
797 const QString formattedException = u"<span style=\"color:red\">%1</span><br/>"_s.arg( error.toHtmlEscaped() ).replace( '\n', "<br>"_L1 );
798 const QString formattedRunTime
799 = u"<span style=\"color:red\">%1</span><br/>"_s.arg( QObject::tr( "Failed after %1 s." ).arg( childTime.elapsed() / 1000.0 ).toHtmlEscaped() ).replace( '\n', "<br>"_L1 );
800
801 childResult.setHtmlLog( thisAlgorithmHtmlLog + formattedException + formattedRunTime );
802 context.modelResult().childResults().insert( childId, childResult );
803
804 if ( modelFeedback )
805 {
806 modelFeedback->reportChildResult( childId, childResult );
807 }
808
809 throw QgsProcessingException( error );
810 }
811 else
812 {
813 childResult.setHtmlLog( thisAlgorithmHtmlLog );
814 context.modelResult().childResults().insert( childId, childResult );
815
816 if ( modelFeedback )
817 {
818 modelFeedback->reportChildResult( childId, childResult );
819 }
820 }
821 }
822
823 if ( feedback && feedback->isCanceled() )
824 break;
825 }
826 if ( feedback )
827 feedback->pushDebugInfo( QObject::tr( "Model processed OK. Executed %n algorithm(s) total in %1 s.", nullptr, countExecuted ).arg( static_cast< double >( totalTime.elapsed() ) / 1000.0 ) );
828
829 mResults = finalResults;
830 mResults.insert( u"CHILD_RESULTS"_s, childResults );
831 mResults.insert( u"CHILD_INPUTS"_s, childInputs );
832 return mResults;
833}
834
835QString QgsProcessingModelAlgorithm::sourceFilePath() const
836{
837 return mSourceFile;
838}
839
840void QgsProcessingModelAlgorithm::setSourceFilePath( const QString &sourceFile )
841{
842 mSourceFile = sourceFile;
843}
844
845bool QgsProcessingModelAlgorithm::modelNameMatchesFilePath() const
846{
847 if ( mSourceFile.isEmpty() )
848 return false;
849
850 const QFileInfo fi( mSourceFile );
851 return fi.completeBaseName().compare( mModelName, Qt::CaseInsensitive ) == 0;
852}
853
854QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::PythonOutputType outputType, const int indentSize ) const
855{
856 QStringList fileDocString;
857 fileDocString << u"\"\"\""_s;
858 fileDocString << u"Model exported as python."_s;
859 fileDocString << u"Name : %1"_s.arg( displayName() );
860 fileDocString << u"Group : %1"_s.arg( group() );
861 fileDocString << u"With QGIS : %1"_s.arg( Qgis::versionInt() );
862 fileDocString << u"\"\"\""_s;
863 fileDocString << QString();
864
865 QStringList lines;
866 QString indent = QString( ' ' ).repeated( indentSize );
867 QString currentIndent;
868
869 QMap< QString, QString> friendlyChildNames;
870 QMap< QString, QString> friendlyOutputNames;
871 auto uniqueSafeName = []( const QString &name, bool capitalize, const QMap< QString, QString > &friendlyNames ) -> QString {
872 const QString base = safeName( name, capitalize );
873 QString candidate = base;
874 int i = 1;
875 // iterate over friendlyNames value to find candidate
876 while ( std::find( friendlyNames.cbegin(), friendlyNames.cend(), candidate ) != friendlyNames.cend() )
877 {
878 i++;
879 candidate = u"%1_%2"_s.arg( base ).arg( i );
880 }
881 return candidate;
882 };
883
884 const QString algorithmClassName = safeName( name(), true );
885
886 QSet< QString > toExecute;
887 for ( auto childIt = mChildAlgorithms.constBegin(); childIt != mChildAlgorithms.constEnd(); ++childIt )
888 {
889 if ( childIt->isActive() && childIt->algorithm() )
890 {
891 toExecute.insert( childIt->childId() );
892 friendlyChildNames.insert( childIt->childId(), uniqueSafeName( childIt->description().isEmpty() ? childIt->childId() : childIt->description(), !childIt->description().isEmpty(), friendlyChildNames ) );
893 }
894 }
895 const int totalSteps = toExecute.count();
896
897 QStringList importLines; // not a set - we need regular ordering
898 switch ( outputType )
899 {
901 {
902 // add specific parameter type imports
903 const auto params = parameterDefinitions();
904 importLines.reserve( params.count() + 6 );
905 importLines << u"from typing import Any, Optional"_s;
906 importLines << QString();
907 importLines << u"from qgis.core import QgsProcessing"_s;
908 importLines << u"from qgis.core import QgsProcessingAlgorithm"_s;
909 importLines << u"from qgis.core import QgsProcessingContext"_s;
910 importLines << u"from qgis.core import QgsProcessingFeedback, QgsProcessingMultiStepFeedback"_s;
911
912 bool hasAdvancedParams = false;
913 for ( const QgsProcessingParameterDefinition *def : params )
914 {
915 if ( def->flags() & Qgis::ProcessingParameterFlag::Advanced )
916 hasAdvancedParams = true;
917
918 if ( const QgsProcessingParameterType *type = QgsApplication::processingRegistry()->parameterType( def->type() ) )
919 {
920 const QString importString = type->pythonImportString();
921 if ( !importString.isEmpty() && !importLines.contains( importString ) )
922 importLines << importString;
923 }
924 }
925
926 if ( hasAdvancedParams )
927 importLines << u"from qgis.core import QgsProcessingParameterDefinition"_s;
928
929 lines << u"from qgis import processing"_s;
930 lines << QString() << QString();
931
932 lines << u"class %1(QgsProcessingAlgorithm):"_s.arg( algorithmClassName );
933 lines << QString();
934
935 // initAlgorithm, parameter definitions
936 lines << indent + u"def initAlgorithm(self, config: Optional[dict[str, Any]] = None):"_s;
937 if ( params.empty() )
938 {
939 lines << indent + indent + u"pass"_s;
940 }
941 else
942 {
943 lines.reserve( lines.size() + params.size() );
944 for ( const QgsProcessingParameterDefinition *def : params )
945 {
946 std::unique_ptr< QgsProcessingParameterDefinition > defClone( def->clone() );
947
948 if ( defClone->isDestination() )
949 {
950 const QString uniqueChildName = defClone->metadata().value( u"_modelChildId"_s ).toString() + ':' + defClone->metadata().value( u"_modelChildOutputName"_s ).toString();
951 const QString friendlyName = !defClone->description().isEmpty() ? uniqueSafeName( defClone->description(), true, friendlyOutputNames ) : defClone->name();
952 friendlyOutputNames.insert( uniqueChildName, friendlyName );
953 defClone->setName( friendlyName );
954 }
955 else
956 {
957 if ( !mParameterComponents.value( defClone->name() ).comment()->description().isEmpty() )
958 {
959 const QStringList parts = mParameterComponents.value( defClone->name() ).comment()->description().split( u"\n"_s );
960 for ( const QString &part : parts )
961 {
962 lines << indent + indent + u"# %1"_s.arg( part );
963 }
964 }
965 }
966
967 if ( defClone->flags() & Qgis::ProcessingParameterFlag::Advanced )
968 {
969 lines << indent + indent + u"param = %1"_s.arg( defClone->asPythonString() );
970 lines << indent + indent + u"param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)"_s;
971 lines << indent + indent + u"self.addParameter(param)"_s;
972 }
973 else
974 {
975 lines << indent + indent + u"self.addParameter(%1)"_s.arg( defClone->asPythonString() );
976 }
977 }
978 }
979
980 lines << QString();
981 lines << indent + u"def processAlgorithm(self, parameters: dict[str, Any], context: QgsProcessingContext, model_feedback: QgsProcessingFeedback) -> dict[str, Any]:"_s;
982 currentIndent = indent + indent;
983
984 lines << currentIndent + u"# Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the"_s;
985 lines << currentIndent + u"# overall progress through the model"_s;
986 lines << currentIndent + u"feedback = QgsProcessingMultiStepFeedback(%1, model_feedback)"_s.arg( totalSteps );
987 break;
988 }
989#if 0
990 case Script:
991 {
992 QgsStringMap params;
993 QgsProcessingContext context;
994 QMap< QString, QgsProcessingModelParameter >::const_iterator paramIt = mParameterComponents.constBegin();
995 for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt )
996 {
997 QString name = paramIt.value().parameterName();
998 if ( parameterDefinition( name ) )
999 {
1000 // TODO - generic value to string method
1001 params.insert( name, parameterDefinition( name )->valueAsPythonString( parameterDefinition( name )->defaultValue(), context ) );
1002 }
1003 }
1004
1005 if ( !params.isEmpty() )
1006 {
1007 lines << u"parameters = {"_s;
1008 for ( auto it = params.constBegin(); it != params.constEnd(); ++it )
1009 {
1010 lines << u" '%1':%2,"_s.arg( it.key(), it.value() );
1011 }
1012 lines << u"}"_s
1013 << QString();
1014 }
1015
1016 lines << u"context = QgsProcessingContext()"_s
1017 << u"context.setProject(QgsProject.instance())"_s
1018 << u"feedback = QgsProcessingFeedback()"_s
1019 << QString();
1020
1021 break;
1022 }
1023#endif
1024 }
1025
1026 lines << currentIndent + u"results = {}"_s;
1027 lines << currentIndent + u"outputs = {}"_s;
1028 lines << QString();
1029
1030 QSet< QString > executed;
1031 bool executedAlg = true;
1032 int currentStep = 0;
1033 while ( executedAlg && executed.count() < toExecute.count() )
1034 {
1035 executedAlg = false;
1036 const auto constToExecute = toExecute;
1037 for ( const QString &childId : constToExecute )
1038 {
1039 if ( executed.contains( childId ) )
1040 continue;
1041
1042 bool canExecute = true;
1043 const auto constDependsOnChildAlgorithms = dependsOnChildAlgorithms( childId );
1044 for ( const QString &dependency : constDependsOnChildAlgorithms )
1045 {
1046 if ( !executed.contains( dependency ) )
1047 {
1048 canExecute = false;
1049 break;
1050 }
1051 }
1052
1053 if ( !canExecute )
1054 continue;
1055
1056 executedAlg = true;
1057
1058 const QgsProcessingModelChildAlgorithm &child = mChildAlgorithms[childId];
1059
1060 // fill in temporary outputs
1061 const QgsProcessingParameterDefinitions childDefs = child.algorithm()->parameterDefinitions();
1062 QgsStringMap childParams;
1063 for ( const QgsProcessingParameterDefinition *def : childDefs )
1064 {
1065 if ( def->isDestination() )
1066 {
1067 const QgsProcessingDestinationParameter *destParam = static_cast< const QgsProcessingDestinationParameter * >( def );
1068
1069 // is destination linked to one of the final outputs from this model?
1070 bool isFinalOutput = false;
1071 QMap<QString, QgsProcessingModelOutput> outputs = child.modelOutputs();
1072 QMap<QString, QgsProcessingModelOutput>::const_iterator outputIt = outputs.constBegin();
1073 for ( ; outputIt != outputs.constEnd(); ++outputIt )
1074 {
1075 if ( outputIt->childOutputName() == destParam->name() )
1076 {
1077 QString paramName = child.childId() + ':' + outputIt.key();
1078 paramName = friendlyOutputNames.value( paramName, paramName );
1079 childParams.insert( destParam->name(), u"parameters['%1']"_s.arg( paramName ) );
1080 isFinalOutput = true;
1081 break;
1082 }
1083 }
1084
1085 if ( !isFinalOutput )
1086 {
1087 // output is temporary
1088
1089 // check whether it's optional, and if so - is it required?
1090 bool required = true;
1092 {
1093 required = childOutputIsRequired( child.childId(), destParam->name() );
1094 }
1095
1096 // not optional, or required elsewhere in model
1097 if ( required )
1098 {
1099 childParams.insert( destParam->name(), u"QgsProcessing.TEMPORARY_OUTPUT"_s );
1100 }
1101 }
1102 }
1103 }
1104
1105 lines << child.asPythonCode( outputType, childParams, currentIndent.size(), indentSize, friendlyChildNames, friendlyOutputNames );
1106 currentStep++;
1107 if ( currentStep < totalSteps )
1108 {
1109 lines << QString();
1110 lines << currentIndent + u"feedback.setCurrentStep(%1)"_s.arg( currentStep );
1111 lines << currentIndent + u"if feedback.isCanceled():"_s;
1112 lines << currentIndent + indent + u"return {}"_s;
1113 lines << QString();
1114 }
1115 executed.insert( childId );
1116 }
1117 }
1118
1119 switch ( outputType )
1120 {
1122 lines << currentIndent + u"return results"_s;
1123 lines << QString();
1124
1125 // name, displayName
1126 lines << indent + u"def name(self) -> str:"_s;
1127 lines << indent + indent + u"return '%1'"_s.arg( mModelName );
1128 lines << QString();
1129 lines << indent + u"def displayName(self) -> str:"_s;
1130 lines << indent + indent + u"return '%1'"_s.arg( mModelName );
1131 lines << QString();
1132
1133 // group, groupId
1134 lines << indent + u"def group(self) -> str:"_s;
1135 lines << indent + indent + u"return '%1'"_s.arg( mModelGroup );
1136 lines << QString();
1137 lines << indent + u"def groupId(self) -> str:"_s;
1138 lines << indent + indent + u"return '%1'"_s.arg( mModelGroupId );
1139 lines << QString();
1140
1141 // help
1142 if ( !shortHelpString().isEmpty() )
1143 {
1144 lines << indent + u"def shortHelpString(self) -> str:"_s;
1145 lines << indent + indent + u"return \"\"\"%1\"\"\""_s.arg( shortHelpString() );
1146 lines << QString();
1147 }
1148 if ( !helpUrl().isEmpty() )
1149 {
1150 lines << indent + u"def helpUrl(self) -> str:"_s;
1151 lines << indent + indent + u"return '%1'"_s.arg( helpUrl() );
1152 lines << QString();
1153 }
1154
1155 // createInstance
1156 lines << indent + u"def createInstance(self):"_s;
1157 lines << indent + indent + u"return self.__class__()"_s;
1158
1159 // additional import lines
1160 static QMap< QString, QString > sAdditionalImports {
1161 { u"QgsCoordinateReferenceSystem"_s, u"from qgis.core import QgsCoordinateReferenceSystem"_s },
1162 { u"QgsExpression"_s, u"from qgis.core import QgsExpression"_s },
1163 { u"QgsRectangle"_s, u"from qgis.core import QgsRectangle"_s },
1164 { u"QgsReferencedRectangle"_s, u"from qgis.core import QgsReferencedRectangle"_s },
1165 { u"QgsPoint"_s, u"from qgis.core import QgsPoint"_s },
1166 { u"QgsReferencedPoint"_s, u"from qgis.core import QgsReferencedPoint"_s },
1167 { u"QgsProperty"_s, u"from qgis.core import QgsProperty"_s },
1168 { u"QgsRasterLayer"_s, u"from qgis.core import QgsRasterLayer"_s },
1169 { u"QgsMeshLayer"_s, u"from qgis.core import QgsMeshLayer"_s },
1170 { u"QgsVectorLayer"_s, u"from qgis.core import QgsVectorLayer"_s },
1171 { u"QgsMapLayer"_s, u"from qgis.core import QgsMapLayer"_s },
1172 { u"QgsProcessingFeatureSourceDefinition"_s, u"from qgis.core import QgsProcessingFeatureSourceDefinition"_s },
1173 { u"QgsPointXY"_s, u"from qgis.core import QgsPointXY"_s },
1174 { u"QgsReferencedPointXY"_s, u"from qgis.core import QgsReferencedPointXY"_s },
1175 { u"QgsGeometry"_s, u"from qgis.core import QgsGeometry"_s },
1176 { u"QgsProcessingOutputLayerDefinition"_s, u"from qgis.core import QgsProcessingOutputLayerDefinition"_s },
1177 { u"QColor"_s, u"from qgis.PyQt.QtGui import QColor"_s },
1178 { u"QDateTime"_s, u"from qgis.PyQt.QtCore import QDateTime"_s },
1179 { u"QDate"_s, u"from qgis.PyQt.QtCore import QDate"_s },
1180 { u"QTime"_s, u"from qgis.PyQt.QtCore import QTime"_s },
1181 };
1182
1183 for ( auto it = sAdditionalImports.constBegin(); it != sAdditionalImports.constEnd(); ++it )
1184 {
1185 if ( importLines.contains( it.value() ) )
1186 {
1187 // already got this import
1188 continue;
1189 }
1190
1191 bool found = false;
1192 for ( const QString &line : std::as_const( lines ) )
1193 {
1194 if ( line.contains( it.key() ) )
1195 {
1196 found = true;
1197 break;
1198 }
1199 }
1200 if ( found )
1201 {
1202 importLines << it.value();
1203 }
1204 }
1205
1206 lines = fileDocString + importLines + lines;
1207 break;
1208 }
1209
1210 lines << QString();
1211
1212 return lines;
1213}
1214
1215QMap<QString, QgsProcessingModelAlgorithm::VariableDefinition> QgsProcessingModelAlgorithm::variablesForChildAlgorithm(
1216 const QString &childId, QgsProcessingContext *context, const QVariantMap &modelParameters, const QVariantMap &results
1217) const
1218{
1219 QMap<QString, QgsProcessingModelAlgorithm::VariableDefinition> variables;
1220
1221 auto safeName = []( const QString &name ) -> QString {
1222 QString s = name;
1223 const thread_local QRegularExpression safeNameRe( u"[\\s'\"\\(\\):\\.]"_s );
1224 return s.replace( safeNameRe, u"_"_s );
1225 };
1226
1227 // "static"/single value sources
1228 QgsProcessingModelChildParameterSources sources = availableSourcesForChild(
1229 childId,
1230 QStringList()
1260 );
1261
1262 for ( const QgsProcessingModelChildParameterSource &source : std::as_const( sources ) )
1263 {
1264 QString name;
1265 QVariant value;
1266 QString description;
1267 switch ( source.source() )
1268 {
1270 {
1271 name = source.parameterName();
1272 value = modelParameters.value( source.parameterName() );
1273 description = parameterDefinition( source.parameterName() )->description();
1274 break;
1275 }
1277 {
1278 const QgsProcessingModelChildAlgorithm &child = mChildAlgorithms.value( source.outputChildId() );
1279 name = u"%1_%2"_s.arg( child.description().isEmpty() ? source.outputChildId() : child.description(), source.outputName() );
1280 if ( const QgsProcessingAlgorithm *alg = child.algorithm() )
1281 {
1282 description = QObject::tr( "Output '%1' from algorithm '%2'" ).arg( alg->outputDefinition( source.outputName() )->description(), child.description() );
1283 }
1284 value = results.value( source.outputChildId() ).toMap().value( source.outputName() );
1285 break;
1286 }
1287
1292 continue;
1293 }
1294 variables.insert( safeName( name ), VariableDefinition( value, source, description ) );
1295 }
1296
1297 // layer sources
1298 sources = availableSourcesForChild(
1299 childId,
1302 );
1303
1304 for ( const QgsProcessingModelChildParameterSource &source : std::as_const( sources ) )
1305 {
1306 QString name;
1307 QVariant value;
1308 QString description;
1309
1310 switch ( source.source() )
1311 {
1313 {
1314 name = source.parameterName();
1315 value = modelParameters.value( source.parameterName() );
1316 description = parameterDefinition( source.parameterName() )->description();
1317 break;
1318 }
1320 {
1321 const QgsProcessingModelChildAlgorithm &child = mChildAlgorithms.value( source.outputChildId() );
1322 name = u"%1_%2"_s.arg( child.description().isEmpty() ? source.outputChildId() : child.description(), source.outputName() );
1323 value = results.value( source.outputChildId() ).toMap().value( source.outputName() );
1324 if ( const QgsProcessingAlgorithm *alg = child.algorithm() )
1325 {
1326 description = QObject::tr( "Output '%1' from algorithm '%2'" ).arg( alg->outputDefinition( source.outputName() )->description(), child.description() );
1327 }
1328 break;
1329 }
1330
1335 continue;
1336 }
1337
1338 if ( value.userType() == qMetaTypeId<QgsProcessingOutputLayerDefinition>() )
1339 {
1340 QgsProcessingOutputLayerDefinition fromVar = qvariant_cast<QgsProcessingOutputLayerDefinition>( value );
1341 value = fromVar.sink;
1342 if ( value.userType() == qMetaTypeId<QgsProperty>() && context )
1343 {
1344 value = value.value< QgsProperty >().valueAsString( context->expressionContext() );
1345 }
1346 }
1347 QgsMapLayer *layer = nullptr;
1348 if ( context )
1349 {
1350 layer = qobject_cast< QgsMapLayer * >( qvariant_cast<QObject *>( value ) );
1351 if ( !layer )
1352 layer = QgsProcessingUtils::mapLayerFromString( value.toString(), *context );
1353 }
1354
1355 variables.insert( safeName( name ), VariableDefinition( layer ? QVariant::fromValue( QgsWeakMapLayerPointer( layer ) ) : QVariant(), source, description ) );
1356 variables.insert( safeName( u"%1_minx"_s.arg( name ) ), VariableDefinition( layer ? layer->extent().xMinimum() : QVariant(), source, QObject::tr( "Minimum X of %1" ).arg( description ) ) );
1357 variables.insert( safeName( u"%1_miny"_s.arg( name ) ), VariableDefinition( layer ? layer->extent().yMinimum() : QVariant(), source, QObject::tr( "Minimum Y of %1" ).arg( description ) ) );
1358 variables.insert( safeName( u"%1_maxx"_s.arg( name ) ), VariableDefinition( layer ? layer->extent().xMaximum() : QVariant(), source, QObject::tr( "Maximum X of %1" ).arg( description ) ) );
1359 variables.insert( safeName( u"%1_maxy"_s.arg( name ) ), VariableDefinition( layer ? layer->extent().yMaximum() : QVariant(), source, QObject::tr( "Maximum Y of %1" ).arg( description ) ) );
1360 }
1361
1362 sources = availableSourcesForChild( childId, QStringList() << QgsProcessingParameterFeatureSource::typeName() );
1363 for ( const QgsProcessingModelChildParameterSource &source : std::as_const( sources ) )
1364 {
1365 QString name;
1366 QVariant value;
1367 QString description;
1368
1369 switch ( source.source() )
1370 {
1372 {
1373 name = source.parameterName();
1374 value = modelParameters.value( source.parameterName() );
1375 description = parameterDefinition( source.parameterName() )->description();
1376 break;
1377 }
1379 {
1380 const QgsProcessingModelChildAlgorithm &child = mChildAlgorithms.value( source.outputChildId() );
1381 name = u"%1_%2"_s.arg( child.description().isEmpty() ? source.outputChildId() : child.description(), source.outputName() );
1382 value = results.value( source.outputChildId() ).toMap().value( source.outputName() );
1383 if ( const QgsProcessingAlgorithm *alg = child.algorithm() )
1384 {
1385 description = QObject::tr( "Output '%1' from algorithm '%2'" ).arg( alg->outputDefinition( source.outputName() )->description(), child.description() );
1386 }
1387 break;
1388 }
1389
1394 continue;
1395 }
1396
1397 QgsFeatureSource *featureSource = nullptr;
1398 if ( value.userType() == qMetaTypeId<QgsProcessingFeatureSourceDefinition>() )
1399 {
1400 QgsProcessingFeatureSourceDefinition fromVar = qvariant_cast<QgsProcessingFeatureSourceDefinition>( value );
1401 value = fromVar.source;
1402 }
1403 else if ( value.userType() == qMetaTypeId<QgsProcessingOutputLayerDefinition>() )
1404 {
1405 QgsProcessingOutputLayerDefinition fromVar = qvariant_cast<QgsProcessingOutputLayerDefinition>( value );
1406 value = fromVar.sink;
1407 if ( context && value.userType() == qMetaTypeId<QgsProperty>() )
1408 {
1409 value = value.value< QgsProperty >().valueAsString( context->expressionContext() );
1410 }
1411 }
1412 if ( QgsVectorLayer *layer = qobject_cast< QgsVectorLayer * >( qvariant_cast<QObject *>( value ) ) )
1413 {
1414 featureSource = layer;
1415 }
1416 if ( context && !featureSource )
1417 {
1418 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( value.toString(), *context, true, QgsProcessingUtils::LayerHint::Vector ) ) )
1419 featureSource = vl;
1420 }
1421
1422 variables.insert( safeName( name ), VariableDefinition( value, source, description ) );
1423 variables.insert( safeName( u"%1_minx"_s.arg( name ) ), VariableDefinition( featureSource ? featureSource->sourceExtent().xMinimum() : QVariant(), source, QObject::tr( "Minimum X of %1" ).arg( description ) ) );
1424 variables.insert( safeName( u"%1_miny"_s.arg( name ) ), VariableDefinition( featureSource ? featureSource->sourceExtent().yMinimum() : QVariant(), source, QObject::tr( "Minimum Y of %1" ).arg( description ) ) );
1425 variables.insert( safeName( u"%1_maxx"_s.arg( name ) ), VariableDefinition( featureSource ? featureSource->sourceExtent().xMaximum() : QVariant(), source, QObject::tr( "Maximum X of %1" ).arg( description ) ) );
1426 variables.insert( safeName( u"%1_maxy"_s.arg( name ) ), VariableDefinition( featureSource ? featureSource->sourceExtent().yMaximum() : QVariant(), source, QObject::tr( "Maximum Y of %1" ).arg( description ) ) );
1427 }
1428
1429 return variables;
1430}
1431
1432std::unique_ptr<QgsExpressionContextScope> QgsProcessingModelAlgorithm::createExpressionContextScopeForChildAlgorithm(
1433 const QString &childId, QgsProcessingContext &context, const QVariantMap &modelParameters, const QVariantMap &results
1434) const
1435{
1436 auto scope = std::make_unique<QgsExpressionContextScope>( u"algorithm_inputs"_s );
1437 QMap< QString, QgsProcessingModelAlgorithm::VariableDefinition> variables = variablesForChildAlgorithm( childId, &context, modelParameters, results );
1438 QMap< QString, QgsProcessingModelAlgorithm::VariableDefinition>::const_iterator varIt = variables.constBegin();
1439 for ( ; varIt != variables.constEnd(); ++varIt )
1440 {
1441 scope->addVariable( QgsExpressionContextScope::StaticVariable( varIt.key(), varIt->value, true, false, varIt->description ) );
1442 }
1443 return scope;
1444}
1445
1446QgsProcessingModelChildParameterSources QgsProcessingModelAlgorithm::availableSourcesForChild( const QString &childId, const QgsProcessingParameterDefinition *param ) const
1447{
1449 if ( !paramType )
1450 return QgsProcessingModelChildParameterSources();
1451 return availableSourcesForChild( childId, paramType->acceptedParameterTypes(), paramType->acceptedOutputTypes(), paramType->acceptedDataTypes( param ) );
1452}
1453
1454QgsProcessingModelChildParameterSources QgsProcessingModelAlgorithm::availableSourcesForChild(
1455 const QString &childId, const QStringList &parameterTypes, const QStringList &outputTypes, const QList<int> &dataTypes
1456) const
1457{
1458 QgsProcessingModelChildParameterSources sources;
1459
1460 // first look through model parameters
1461 QMap< QString, QgsProcessingModelParameter >::const_iterator paramIt = mParameterComponents.constBegin();
1462 for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt )
1463 {
1464 const QgsProcessingParameterDefinition *def = parameterDefinition( paramIt->parameterName() );
1465 if ( !def )
1466 continue;
1467
1468 if ( parameterTypes.contains( def->type() ) )
1469 {
1470 if ( !dataTypes.isEmpty() )
1471 {
1473 {
1474 const QgsProcessingParameterField *fieldDef = static_cast< const QgsProcessingParameterField * >( def );
1475 if ( !( dataTypes.contains( static_cast< int >( fieldDef->dataType() ) ) || fieldDef->dataType() == Qgis::ProcessingFieldParameterDataType::Any ) )
1476 {
1477 continue;
1478 }
1479 }
1481 {
1482 const QgsProcessingParameterLimitedDataTypes *sourceDef = dynamic_cast< const QgsProcessingParameterLimitedDataTypes *>( def );
1483 if ( !sourceDef )
1484 continue;
1485
1486 bool ok = sourceDef->dataTypes().isEmpty();
1487 const auto constDataTypes = sourceDef->dataTypes();
1488 for ( int type : constDataTypes )
1489 {
1490 if ( dataTypes.contains( type )
1491 || type == static_cast< int >( Qgis::ProcessingSourceType::MapLayer )
1492 || type == static_cast< int >( Qgis::ProcessingSourceType::Vector )
1493 || type == static_cast< int >( Qgis::ProcessingSourceType::VectorAnyGeometry ) )
1494 {
1495 ok = true;
1496 break;
1497 }
1498 }
1499 if ( dataTypes.contains( static_cast< int >( Qgis::ProcessingSourceType::MapLayer ) )
1500 || dataTypes.contains( static_cast< int >( Qgis::ProcessingSourceType::Vector ) )
1501 || dataTypes.contains( static_cast< int >( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) )
1502 ok = true;
1503
1504 if ( !ok )
1505 continue;
1506 }
1507 }
1508 sources << QgsProcessingModelChildParameterSource::fromModelParameter( paramIt->parameterName() );
1509 }
1510 }
1511
1512 QSet< QString > dependents;
1513 if ( !childId.isEmpty() )
1514 {
1515 dependents = dependentChildAlgorithms( childId );
1516 dependents << childId;
1517 }
1518
1519 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
1520 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
1521 {
1522 if ( dependents.contains( childIt->childId() ) )
1523 continue;
1524
1525 const QgsProcessingAlgorithm *alg = childIt->algorithm();
1526 if ( !alg )
1527 continue;
1528
1529 const auto constOutputDefinitions = alg->outputDefinitions();
1530 for ( const QgsProcessingOutputDefinition *out : constOutputDefinitions )
1531 {
1532 if ( outputTypes.contains( out->type() ) )
1533 {
1534 if ( !dataTypes.isEmpty() )
1535 {
1536 if ( out->type() == QgsProcessingOutputVectorLayer::typeName() )
1537 {
1538 const QgsProcessingOutputVectorLayer *vectorOut = static_cast< const QgsProcessingOutputVectorLayer *>( out );
1539
1540 if ( !vectorOutputIsCompatibleType( dataTypes, vectorOut->dataType() ) )
1541 {
1542 //unacceptable output
1543 continue;
1544 }
1545 }
1546 }
1547 sources << QgsProcessingModelChildParameterSource::fromChildOutput( childIt->childId(), out->name() );
1548 }
1549 }
1550 }
1551
1552 return sources;
1553}
1554
1555QVariantMap QgsProcessingModelAlgorithm::helpContent() const
1556{
1557 return mHelpContent;
1558}
1559
1560void QgsProcessingModelAlgorithm::setHelpContent( const QVariantMap &helpContent )
1561{
1562 mHelpContent = helpContent;
1563}
1564
1565void QgsProcessingModelAlgorithm::setName( const QString &name )
1566{
1567 mModelName = name;
1568}
1569
1570void QgsProcessingModelAlgorithm::setGroup( const QString &group )
1571{
1572 mModelGroup = group;
1573}
1574
1575bool QgsProcessingModelAlgorithm::validate( QStringList &issues ) const
1576{
1577 issues.clear();
1578 bool res = true;
1579
1580 if ( mChildAlgorithms.empty() )
1581 {
1582 res = false;
1583 issues << QObject::tr( "Model does not contain any algorithms" );
1584 }
1585
1586 for ( auto it = mChildAlgorithms.constBegin(); it != mChildAlgorithms.constEnd(); ++it )
1587 {
1588 QStringList childIssues;
1589 res = validateChildAlgorithm( it->childId(), childIssues ) && res;
1590
1591 for ( const QString &issue : std::as_const( childIssues ) )
1592 {
1593 issues << u"<b>%1</b>: %2"_s.arg( it->description(), issue );
1594 }
1595 }
1596 return res;
1597}
1598
1599QMap<QString, QgsProcessingModelChildAlgorithm> QgsProcessingModelAlgorithm::childAlgorithms() const
1600{
1601 return mChildAlgorithms;
1602}
1603
1604void QgsProcessingModelAlgorithm::setParameterComponents( const QMap<QString, QgsProcessingModelParameter> &parameterComponents )
1605{
1606 mParameterComponents = parameterComponents;
1607}
1608
1609void QgsProcessingModelAlgorithm::setParameterComponent( const QgsProcessingModelParameter &component )
1610{
1611 mParameterComponents.insert( component.parameterName(), component );
1612}
1613
1614QgsProcessingModelParameter &QgsProcessingModelAlgorithm::parameterComponent( const QString &name )
1615{
1616 if ( !mParameterComponents.contains( name ) )
1617 {
1618 QgsProcessingModelParameter &component = mParameterComponents[name];
1619 component.setParameterName( name );
1620 return component;
1621 }
1622 return mParameterComponents[name];
1623}
1624
1625QList< QgsProcessingModelParameter > QgsProcessingModelAlgorithm::orderedParameters() const
1626{
1627 QList< QgsProcessingModelParameter > res;
1628 QSet< QString > found;
1629 for ( const QString &parameter : mParameterOrder )
1630 {
1631 if ( mParameterComponents.contains( parameter ) )
1632 {
1633 res << mParameterComponents.value( parameter );
1634 found << parameter;
1635 }
1636 }
1637
1638 // add any missing ones to end of list
1639 for ( auto it = mParameterComponents.constBegin(); it != mParameterComponents.constEnd(); ++it )
1640 {
1641 if ( !found.contains( it.key() ) )
1642 {
1643 res << it.value();
1644 }
1645 }
1646 return res;
1647}
1648
1649void QgsProcessingModelAlgorithm::setParameterOrder( const QStringList &order )
1650{
1651 mParameterOrder = order;
1652}
1653
1654QList<QgsProcessingModelOutput> QgsProcessingModelAlgorithm::orderedOutputs() const
1655{
1656 QList< QgsProcessingModelOutput > res;
1657 QSet< QString > found;
1658
1659 for ( const QString &output : mOutputOrder )
1660 {
1661 bool foundOutput = false;
1662 for ( auto it = mChildAlgorithms.constBegin(); it != mChildAlgorithms.constEnd(); ++it )
1663 {
1664 const QMap<QString, QgsProcessingModelOutput> outputs = it.value().modelOutputs();
1665 for ( auto outputIt = outputs.constBegin(); outputIt != outputs.constEnd(); ++outputIt )
1666 {
1667 if ( output == u"%1:%2"_s.arg( outputIt->childId(), outputIt->childOutputName() ) )
1668 {
1669 res << outputIt.value();
1670 foundOutput = true;
1671 found.insert( u"%1:%2"_s.arg( outputIt->childId(), outputIt->childOutputName() ) );
1672 }
1673 }
1674 if ( foundOutput )
1675 break;
1676 }
1677 }
1678
1679 // add any missing ones to end of list
1680 for ( auto it = mChildAlgorithms.constBegin(); it != mChildAlgorithms.constEnd(); ++it )
1681 {
1682 const QMap<QString, QgsProcessingModelOutput> outputs = it.value().modelOutputs();
1683 for ( auto outputIt = outputs.constBegin(); outputIt != outputs.constEnd(); ++outputIt )
1684 {
1685 if ( !found.contains( u"%1:%2"_s.arg( outputIt->childId(), outputIt->childOutputName() ) ) )
1686 {
1687 res << outputIt.value();
1688 }
1689 }
1690 }
1691
1692 return res;
1693}
1694
1695void QgsProcessingModelAlgorithm::setOutputOrder( const QStringList &order )
1696{
1697 mOutputOrder = order;
1698}
1699
1700QString QgsProcessingModelAlgorithm::outputGroup() const
1701{
1702 return mOutputGroup;
1703}
1704
1705void QgsProcessingModelAlgorithm::setOutputGroup( const QString &group )
1706{
1707 mOutputGroup = group;
1708}
1709
1710void QgsProcessingModelAlgorithm::updateDestinationParameters()
1711{
1712 //delete existing destination parameters
1713 QMutableListIterator<const QgsProcessingParameterDefinition *> it( mParameters );
1714 while ( it.hasNext() )
1715 {
1716 const QgsProcessingParameterDefinition *def = it.next();
1717 if ( def->isDestination() )
1718 {
1719 delete def;
1720 it.remove();
1721 }
1722 }
1723 // also delete outputs
1724 qDeleteAll( mOutputs );
1725 mOutputs.clear();
1726
1727 // rebuild
1728 QSet< QString > usedFriendlyNames;
1729 auto uniqueSafeName = [&usedFriendlyNames]( const QString &name ) -> QString {
1730 const QString base = safeName( name, false );
1731 QString candidate = base;
1732 int i = 1;
1733 while ( usedFriendlyNames.contains( candidate ) )
1734 {
1735 i++;
1736 candidate = u"%1_%2"_s.arg( base ).arg( i );
1737 }
1738 usedFriendlyNames.insert( candidate );
1739 return candidate;
1740 };
1741
1742 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
1743 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
1744 {
1745 QMap<QString, QgsProcessingModelOutput> outputs = childIt->modelOutputs();
1746 QMap<QString, QgsProcessingModelOutput>::const_iterator outputIt = outputs.constBegin();
1747 for ( ; outputIt != outputs.constEnd(); ++outputIt )
1748 {
1749 if ( !childIt->isActive() || !childIt->algorithm() )
1750 continue;
1751
1752 // child algorithm has a destination parameter set, copy it to the model
1753 const QgsProcessingParameterDefinition *source = childIt->algorithm()->parameterDefinition( outputIt->childOutputName() );
1754 if ( !source )
1755 continue;
1756
1757 std::unique_ptr< QgsProcessingParameterDefinition > param( source->clone() );
1758 // Even if an output was hidden in a child algorithm, we want to show it here for the final
1759 // outputs.
1760 param->setFlags( param->flags() & ~static_cast< int >( Qgis::ProcessingParameterFlag::Hidden ) );
1761 if ( outputIt->isMandatory() )
1762 param->setFlags( param->flags() & ~static_cast< int >( Qgis::ProcessingParameterFlag::Optional ) );
1763 if ( mInternalVersion != InternalVersion::Version1 && !outputIt->description().isEmpty() )
1764 {
1765 QString friendlyName = uniqueSafeName( outputIt->description() );
1766 param->setName( friendlyName );
1767 }
1768 else
1769 {
1770 param->setName( outputIt->childId() + ':' + outputIt->name() );
1771 }
1772 // add some metadata so we can easily link this parameter back to the child source
1773 param->metadata().insert( u"_modelChildId"_s, outputIt->childId() );
1774 param->metadata().insert( u"_modelChildOutputName"_s, outputIt->name() );
1775 param->metadata().insert( u"_modelChildProvider"_s, childIt->algorithm()->provider() ? childIt->algorithm()->provider()->id() : QString() );
1776
1777 param->setDescription( outputIt->description() );
1778 param->setDefaultValue( outputIt->defaultValue() );
1779
1780 QgsProcessingDestinationParameter *newDestParam = dynamic_cast< QgsProcessingDestinationParameter * >( param.get() );
1781 if ( addParameter( param.release() ) && newDestParam )
1782 {
1783 if ( QgsProcessingProvider *provider = childIt->algorithm()->provider() )
1784 {
1785 // we need to copy the constraints given by the provider which creates this output across
1786 // and replace those which have been set to match the model provider's constraints
1787 newDestParam->setSupportsNonFileBasedOutput( provider->supportsNonFileBasedOutput() );
1788 newDestParam->mOriginalProvider = provider;
1789 }
1790 }
1791 }
1792 }
1793}
1794
1795void QgsProcessingModelAlgorithm::addGroupBox( const QgsProcessingModelGroupBox &groupBox )
1796{
1797 mGroupBoxes.insert( groupBox.uuid(), groupBox );
1798}
1799
1800QList<QgsProcessingModelGroupBox> QgsProcessingModelAlgorithm::groupBoxes() const
1801{
1802 return mGroupBoxes.values();
1803}
1804
1805void QgsProcessingModelAlgorithm::removeGroupBox( const QString &uuid )
1806{
1807 mGroupBoxes.remove( uuid );
1808}
1809
1810QVariant QgsProcessingModelAlgorithm::toVariant() const
1811{
1812 QVariantMap map;
1813 map.insert( u"model_name"_s, mModelName );
1814 map.insert( u"model_group"_s, mModelGroup );
1815 map.insert( u"help"_s, mHelpContent );
1816 map.insert( u"internal_version"_s, qgsEnumValueToKey( mInternalVersion ) );
1817 map.insert( u"qgis_version"_s, mModelQgisVersion );
1818
1819 QVariantMap childMap;
1820 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
1821 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
1822 {
1823 childMap.insert( childIt.key(), childIt.value().toVariant() );
1824 }
1825 map.insert( u"children"_s, childMap );
1826
1827 QVariantMap paramMap;
1828 QMap< QString, QgsProcessingModelParameter >::const_iterator paramIt = mParameterComponents.constBegin();
1829 for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt )
1830 {
1831 paramMap.insert( paramIt.key(), paramIt.value().toVariant() );
1832 }
1833 map.insert( u"parameters"_s, paramMap );
1834
1835 QVariantMap paramDefMap;
1836 for ( const QgsProcessingParameterDefinition *def : mParameters )
1837 {
1838 paramDefMap.insert( def->name(), def->toVariantMap() );
1839 }
1840 map.insert( u"parameterDefinitions"_s, paramDefMap );
1841
1842 QVariantList groupBoxDefs;
1843 for ( auto it = mGroupBoxes.constBegin(); it != mGroupBoxes.constEnd(); ++it )
1844 {
1845 groupBoxDefs.append( it.value().toVariant() );
1846 }
1847 map.insert( u"groupBoxes"_s, groupBoxDefs );
1848
1849 map.insert( u"modelVariables"_s, mVariables );
1850
1851 map.insert( u"designerParameterValues"_s, mDesignerParameterValues );
1852
1853 map.insert( u"parameterOrder"_s, mParameterOrder );
1854 map.insert( u"outputOrder"_s, mOutputOrder );
1855 map.insert( u"outputGroup"_s, mOutputGroup );
1856
1857 return map;
1858}
1859
1860bool QgsProcessingModelAlgorithm::loadVariant( const QVariant &model )
1861{
1862 QVariantMap map = model.toMap();
1863
1864 mModelName = map.value( u"model_name"_s ).toString();
1865 mModelGroup = map.value( u"model_group"_s ).toString();
1866 mModelGroupId = map.value( u"model_group"_s ).toString();
1867 mModelQgisVersion = map.value( u"qgis_version"_s, QString() ).toString();
1868
1869 if ( mModelQgisVersion == QString() )
1870 {
1871 mModelQgisVersion = QgsProjectVersion( 0, 0, 0 ).text();
1872 }
1873
1874 mHelpContent = map.value( u"help"_s ).toMap();
1875
1876 mInternalVersion = qgsEnumKeyToValue( map.value( u"internal_version"_s ).toString(), InternalVersion::Version1 );
1877
1878 mVariables = map.value( u"modelVariables"_s ).toMap();
1879 mDesignerParameterValues = map.value( u"designerParameterValues"_s ).toMap();
1880
1881 mParameterOrder = map.value( u"parameterOrder"_s ).toStringList();
1882 mOutputOrder = map.value( u"outputOrder"_s ).toStringList();
1883 mOutputGroup = map.value( u"outputGroup"_s ).toString();
1884
1885 mChildAlgorithms.clear();
1886 QVariantMap childMap = map.value( u"children"_s ).toMap();
1887 QVariantMap::const_iterator childIt = childMap.constBegin();
1888 for ( ; childIt != childMap.constEnd(); ++childIt )
1889 {
1890 QgsProcessingModelChildAlgorithm child;
1891 // we be lenient here - even if we couldn't load a parameter, don't interrupt the model loading
1892 // otherwise models may become unusable (e.g. due to removed plugins providing algs/parameters)
1893 // with no way for users to repair them
1894 if ( !child.loadVariant( childIt.value() ) )
1895 continue;
1896
1897 mChildAlgorithms.insert( child.childId(), child );
1898 }
1899
1900 mParameterComponents.clear();
1901 QVariantMap paramMap = map.value( u"parameters"_s ).toMap();
1902 QVariantMap::const_iterator paramIt = paramMap.constBegin();
1903 for ( ; paramIt != paramMap.constEnd(); ++paramIt )
1904 {
1905 QgsProcessingModelParameter param;
1906 if ( !param.loadVariant( paramIt.value().toMap() ) )
1907 return false;
1908
1909 mParameterComponents.insert( param.parameterName(), param );
1910 }
1911
1912 qDeleteAll( mParameters );
1913 mParameters.clear();
1914 QVariantMap paramDefMap = map.value( u"parameterDefinitions"_s ).toMap();
1915
1916 auto addParam = [this]( const QVariant &value ) {
1917 std::unique_ptr< QgsProcessingParameterDefinition > param( QgsProcessingParameters::parameterFromVariantMap( value.toMap() ) );
1918 // we be lenient here - even if we couldn't load a parameter, don't interrupt the model loading
1919 // otherwise models may become unusable (e.g. due to removed plugins providing algs/parameters)
1920 // with no way for users to repair them
1921 if ( param )
1922 {
1923 if ( param->name() == "VERBOSE_LOG"_L1 )
1924 return; // internal parameter -- some versions of QGIS incorrectly stored this in the model definition file
1925
1926 // set parameter help from help content
1927 param->setHelp( mHelpContent.value( param->name() ).toString() );
1928
1929 // add parameter
1930 addParameter( param.release() );
1931 }
1932 else
1933 {
1934 QVariantMap map = value.toMap();
1935 QString type = map.value( u"parameter_type"_s ).toString();
1936 QString name = map.value( u"name"_s ).toString();
1937
1938 QgsMessageLog::logMessage( QCoreApplication::translate( "Processing", "Could not load parameter %1 of type %2." ).arg( name, type ), QCoreApplication::translate( "Processing", "Processing" ) );
1939 }
1940 };
1941
1942 QSet< QString > loadedParams;
1943 // first add parameters respecting mParameterOrder
1944 for ( const QString &name : std::as_const( mParameterOrder ) )
1945 {
1946 if ( paramDefMap.contains( name ) )
1947 {
1948 addParam( paramDefMap.value( name ) );
1949 loadedParams << name;
1950 }
1951 }
1952 // then load any remaining parameters
1953 QVariantMap::const_iterator paramDefIt = paramDefMap.constBegin();
1954 for ( ; paramDefIt != paramDefMap.constEnd(); ++paramDefIt )
1955 {
1956 if ( !loadedParams.contains( paramDefIt.key() ) )
1957 addParam( paramDefIt.value() );
1958 }
1959
1960 mGroupBoxes.clear();
1961 const QVariantList groupBoxList = map.value( u"groupBoxes"_s ).toList();
1962 for ( const QVariant &groupBoxDef : groupBoxList )
1963 {
1964 QgsProcessingModelGroupBox groupBox;
1965 groupBox.loadVariant( groupBoxDef.toMap() );
1966 mGroupBoxes.insert( groupBox.uuid(), groupBox );
1967 }
1968
1969 updateDestinationParameters();
1970
1971 return true;
1972}
1973
1974bool QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( const QList<int> &acceptableDataTypes, Qgis::ProcessingSourceType outputType )
1975{
1976 // This method is intended to be "permissive" rather than "restrictive".
1977 // I.e. we only reject outputs which we know can NEVER be acceptable, but
1978 // if there's doubt then we default to returning true.
1979 return (
1980 acceptableDataTypes.empty()
1981 || acceptableDataTypes.contains( static_cast< int >( outputType ) )
1983 || outputType == Qgis::ProcessingSourceType::Vector
1985 || acceptableDataTypes.contains( static_cast< int >( Qgis::ProcessingSourceType::Vector ) )
1986 || acceptableDataTypes.contains( static_cast< int >( Qgis::ProcessingSourceType::MapLayer ) )
1987 || ( acceptableDataTypes.contains( static_cast< int >( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) && ( outputType == Qgis::ProcessingSourceType::VectorPoint || outputType == Qgis::ProcessingSourceType::VectorLine || outputType == Qgis::ProcessingSourceType::VectorPolygon ) )
1988 );
1989}
1990
1991void QgsProcessingModelAlgorithm::reattachAlgorithms() const
1992{
1993 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
1994 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
1995 {
1996 if ( !childIt->algorithm() )
1997 childIt->reattach();
1998 }
1999}
2000
2001bool QgsProcessingModelAlgorithm::toFile( const QString &path ) const
2002{
2003 QDomDocument doc = QDomDocument( u"model"_s );
2004 QVariantMap map = toVariant().toMap();
2005 map.insert( u"qgis_version"_s, Qgis::version() );
2006 QDomElement elem = QgsXmlUtils::writeVariant( map, doc );
2007 doc.appendChild( elem );
2008
2009 QFile file( path );
2010 if ( file.open( QFile::WriteOnly | QFile::Truncate ) )
2011 {
2012 QTextStream stream( &file );
2013 doc.save( stream, 2 );
2014 file.close();
2015 return true;
2016 }
2017 return false;
2018}
2019
2020bool QgsProcessingModelAlgorithm::fromFile( const QString &path )
2021{
2022 QDomDocument doc;
2023
2024 QFile file( path );
2025 if ( file.open( QFile::ReadOnly ) )
2026 {
2027 if ( !doc.setContent( &file ) )
2028 return false;
2029
2030 file.close();
2031 }
2032 else
2033 {
2034 return false;
2035 }
2036
2037 QVariant props = QgsXmlUtils::readVariant( doc.firstChildElement() );
2038 return loadVariant( props );
2039}
2040
2041void QgsProcessingModelAlgorithm::setChildAlgorithms( const QMap<QString, QgsProcessingModelChildAlgorithm> &childAlgorithms )
2042{
2043 mChildAlgorithms = childAlgorithms;
2044 updateDestinationParameters();
2045}
2046
2047void QgsProcessingModelAlgorithm::setChildAlgorithm( const QgsProcessingModelChildAlgorithm &algorithm )
2048{
2049 mChildAlgorithms.insert( algorithm.childId(), algorithm );
2050 updateDestinationParameters();
2051}
2052
2053QString QgsProcessingModelAlgorithm::addChildAlgorithm( QgsProcessingModelChildAlgorithm &algorithm )
2054{
2055 if ( algorithm.childId().isEmpty() || mChildAlgorithms.contains( algorithm.childId() ) )
2056 algorithm.generateChildId( *this );
2057
2058 mChildAlgorithms.insert( algorithm.childId(), algorithm );
2059 updateDestinationParameters();
2060 return algorithm.childId();
2061}
2062
2063QgsProcessingModelChildAlgorithm &QgsProcessingModelAlgorithm::childAlgorithm( const QString &childId )
2064{
2065 return mChildAlgorithms[childId];
2066}
2067
2068bool QgsProcessingModelAlgorithm::removeChildAlgorithm( const QString &id )
2069{
2070 if ( !dependentChildAlgorithms( id ).isEmpty() )
2071 return false;
2072
2073 mChildAlgorithms.remove( id );
2074 updateDestinationParameters();
2075 return true;
2076}
2077
2078void QgsProcessingModelAlgorithm::deactivateChildAlgorithm( const QString &id )
2079{
2080 const auto constDependentChildAlgorithms = dependentChildAlgorithms( id );
2081 for ( const QString &child : constDependentChildAlgorithms )
2082 {
2083 childAlgorithm( child ).setActive( false );
2084 }
2085 childAlgorithm( id ).setActive( false );
2086 updateDestinationParameters();
2087}
2088
2089bool QgsProcessingModelAlgorithm::activateChildAlgorithm( const QString &id )
2090{
2091 const auto constDependsOnChildAlgorithms = dependsOnChildAlgorithms( id );
2092 for ( const QString &child : constDependsOnChildAlgorithms )
2093 {
2094 if ( !childAlgorithm( child ).isActive() )
2095 return false;
2096 }
2097 childAlgorithm( id ).setActive( true );
2098 updateDestinationParameters();
2099 return true;
2100}
2101
2102void QgsProcessingModelAlgorithm::addModelParameter( QgsProcessingParameterDefinition *definition, const QgsProcessingModelParameter &component )
2103{
2104 if ( addParameter( definition ) )
2105 mParameterComponents.insert( definition->name(), component );
2106}
2107
2108void QgsProcessingModelAlgorithm::updateModelParameter( QgsProcessingParameterDefinition *definition )
2109{
2110 removeParameter( definition->name() );
2111 addParameter( definition );
2112}
2113
2114void QgsProcessingModelAlgorithm::removeModelParameter( const QString &name )
2115{
2116 removeParameter( name );
2117 mParameterComponents.remove( name );
2118}
2119
2120void QgsProcessingModelAlgorithm::changeParameterName( const QString &oldName, const QString &newName )
2121{
2122 QgsProcessingContext context;
2123 QgsExpressionContext expressionContext = createExpressionContext( QVariantMap(), context );
2124
2125 auto replaceExpressionVariable = [oldName, newName, &expressionContext]( const QString &expressionString ) -> std::tuple< bool, QString > {
2126 QgsExpression expression( expressionString );
2127 expression.prepare( &expressionContext );
2128 QSet<QString> variables = expression.referencedVariables();
2129 if ( variables.contains( oldName ) )
2130 {
2131 QString newExpression = expressionString;
2132 newExpression.replace( u"@%1"_s.arg( oldName ), u"@%2"_s.arg( newName ) );
2133 return { true, newExpression };
2134 }
2135 return { false, QString() };
2136 };
2137
2138 QMap< QString, QgsProcessingModelChildAlgorithm >::iterator childIt = mChildAlgorithms.begin();
2139 for ( ; childIt != mChildAlgorithms.end(); ++childIt )
2140 {
2141 bool changed = false;
2142 QMap<QString, QgsProcessingModelChildParameterSources> childParams = childIt->parameterSources();
2143 QMap<QString, QgsProcessingModelChildParameterSources>::iterator paramIt = childParams.begin();
2144 for ( ; paramIt != childParams.end(); ++paramIt )
2145 {
2146 QList< QgsProcessingModelChildParameterSource > &value = paramIt.value();
2147 for ( auto valueIt = value.begin(); valueIt != value.end(); ++valueIt )
2148 {
2149 switch ( valueIt->source() )
2150 {
2152 {
2153 if ( valueIt->parameterName() == oldName )
2154 {
2155 valueIt->setParameterName( newName );
2156 changed = true;
2157 }
2158 break;
2159 }
2160
2162 {
2163 bool updatedExpression = false;
2164 QString newExpression;
2165 std::tie( updatedExpression, newExpression ) = replaceExpressionVariable( valueIt->expression() );
2166 if ( updatedExpression )
2167 {
2168 valueIt->setExpression( newExpression );
2169 changed = true;
2170 }
2171 break;
2172 }
2173
2175 {
2176 if ( valueIt->staticValue().userType() == qMetaTypeId<QgsProperty>() )
2177 {
2178 QgsProperty property = valueIt->staticValue().value< QgsProperty >();
2179 if ( property.propertyType() == Qgis::PropertyType::Expression )
2180 {
2181 bool updatedExpression = false;
2182 QString newExpression;
2183 std::tie( updatedExpression, newExpression ) = replaceExpressionVariable( property.expressionString() );
2184 if ( updatedExpression )
2185 {
2186 property.setExpressionString( newExpression );
2187 valueIt->setStaticValue( property );
2188 changed = true;
2189 }
2190 }
2191 }
2192 break;
2193 }
2194
2198 break;
2199 }
2200 }
2201 }
2202 if ( changed )
2203 childIt->setParameterSources( childParams );
2204 }
2205}
2206
2207bool QgsProcessingModelAlgorithm::childAlgorithmsDependOnParameter( const QString &name ) const
2208{
2209 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
2210 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
2211 {
2212 // check whether child requires this parameter
2213 QMap<QString, QgsProcessingModelChildParameterSources> childParams = childIt->parameterSources();
2214 QMap<QString, QgsProcessingModelChildParameterSources>::const_iterator paramIt = childParams.constBegin();
2215 for ( ; paramIt != childParams.constEnd(); ++paramIt )
2216 {
2217 const auto constValue = paramIt.value();
2218 for ( const QgsProcessingModelChildParameterSource &source : constValue )
2219 {
2220 if ( source.source() == Qgis::ProcessingModelChildParameterSource::ModelParameter && source.parameterName() == name )
2221 {
2222 return true;
2223 }
2224 }
2225 }
2226 }
2227 return false;
2228}
2229
2230bool QgsProcessingModelAlgorithm::otherParametersDependOnParameter( const QString &name ) const
2231{
2232 const auto constMParameters = mParameters;
2233 for ( const QgsProcessingParameterDefinition *def : constMParameters )
2234 {
2235 if ( def->name() == name )
2236 continue;
2237
2238 if ( def->dependsOnOtherParameters().contains( name ) )
2239 return true;
2240 }
2241 return false;
2242}
2243
2244QMap<QString, QgsProcessingModelParameter> QgsProcessingModelAlgorithm::parameterComponents() const
2245{
2246 return mParameterComponents;
2247}
2248
2249void QgsProcessingModelAlgorithm::dependentChildAlgorithmsRecursive( const QString &childId, QSet<QString> &depends, const QString &branch ) const
2250{
2251 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
2252 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
2253 {
2254 if ( depends.contains( childIt->childId() ) )
2255 continue;
2256
2257 // does alg have a direct dependency on this child?
2258 const QList< QgsProcessingModelChildDependency > constDependencies = childIt->dependencies();
2259 bool hasDependency = false;
2260 for ( const QgsProcessingModelChildDependency &dep : constDependencies )
2261 {
2262 if ( dep.childId == childId && ( branch.isEmpty() || dep.conditionalBranch == branch ) )
2263 {
2264 hasDependency = true;
2265 break;
2266 }
2267 }
2268
2269 if ( hasDependency )
2270 {
2271 depends.insert( childIt->childId() );
2272 dependentChildAlgorithmsRecursive( childIt->childId(), depends, branch );
2273 continue;
2274 }
2275
2276 // check whether child requires any outputs from the target alg
2277 QMap<QString, QgsProcessingModelChildParameterSources> childParams = childIt->parameterSources();
2278 QMap<QString, QgsProcessingModelChildParameterSources>::const_iterator paramIt = childParams.constBegin();
2279 for ( ; paramIt != childParams.constEnd(); ++paramIt )
2280 {
2281 const auto constValue = paramIt.value();
2282 for ( const QgsProcessingModelChildParameterSource &source : constValue )
2283 {
2284 if ( source.source() == Qgis::ProcessingModelChildParameterSource::ChildOutput && source.outputChildId() == childId )
2285 {
2286 depends.insert( childIt->childId() );
2287 dependentChildAlgorithmsRecursive( childIt->childId(), depends, branch );
2288 break;
2289 }
2290 }
2291 }
2292 }
2293}
2294
2295QSet<QString> QgsProcessingModelAlgorithm::dependentChildAlgorithms( const QString &childId, const QString &conditionalBranch ) const
2296{
2297 QSet< QString > algs;
2298
2299 // temporarily insert the target child algorithm to avoid
2300 // unnecessarily recursion though it
2301 algs.insert( childId );
2302
2303 dependentChildAlgorithmsRecursive( childId, algs, conditionalBranch );
2304
2305 // remove temporary target alg
2306 algs.remove( childId );
2307
2308 return algs;
2309}
2310
2311
2312void QgsProcessingModelAlgorithm::dependsOnChildAlgorithmsRecursive( const QString &childId, QSet< QString > &depends ) const
2313{
2314 const QgsProcessingModelChildAlgorithm &alg = mChildAlgorithms.value( childId );
2315
2316 // add direct dependencies
2317 const QList< QgsProcessingModelChildDependency > constDependencies = alg.dependencies();
2318 for ( const QgsProcessingModelChildDependency &val : constDependencies )
2319 {
2320 if ( !depends.contains( val.childId ) )
2321 {
2322 depends.insert( val.childId );
2323 dependsOnChildAlgorithmsRecursive( val.childId, depends );
2324 }
2325 }
2326
2327 // check through parameter dependencies
2328 QMap<QString, QgsProcessingModelChildParameterSources> childParams = alg.parameterSources();
2329 QMap<QString, QgsProcessingModelChildParameterSources>::const_iterator paramIt = childParams.constBegin();
2330 for ( ; paramIt != childParams.constEnd(); ++paramIt )
2331 {
2332 const auto constValue = paramIt.value();
2333 for ( const QgsProcessingModelChildParameterSource &source : constValue )
2334 {
2335 switch ( source.source() )
2336 {
2338 if ( !depends.contains( source.outputChildId() ) )
2339 {
2340 depends.insert( source.outputChildId() );
2341 dependsOnChildAlgorithmsRecursive( source.outputChildId(), depends );
2342 }
2343 break;
2344
2346 {
2347 const QgsExpression exp( source.expression() );
2348 const QSet<QString> vars = exp.referencedVariables();
2349 if ( vars.empty() )
2350 break;
2351
2352 // find the source of referenced variables and check if it's another child algorithm
2353 const QMap<QString, QgsProcessingModelAlgorithm::VariableDefinition> availableVariables = variablesForChildAlgorithm( childId );
2354 for ( auto childVarIt = availableVariables.constBegin(); childVarIt != availableVariables.constEnd(); ++childVarIt )
2355 {
2356 // we're only looking here for variables coming from other child algorithm outputs
2357 if ( childVarIt->source.source() != Qgis::ProcessingModelChildParameterSource::ChildOutput )
2358 continue;
2359
2360 if ( !vars.contains( childVarIt.key() ) || depends.contains( childVarIt->source.outputChildId() ) )
2361 continue;
2362
2363 // this variable is required for the child's expression, so the corresponding algorithm must be run first
2364 depends.insert( childVarIt->source.outputChildId() );
2365 dependsOnChildAlgorithmsRecursive( childVarIt->source.outputChildId(), depends );
2366 }
2367 break;
2368 }
2369
2374 break;
2375 }
2376 }
2377 }
2378}
2379
2380QSet< QString > QgsProcessingModelAlgorithm::dependsOnChildAlgorithms( const QString &childId ) const
2381{
2382 QSet< QString > algs;
2383
2384 // temporarily insert the target child algorithm to avoid
2385 // unnecessarily recursion though it
2386 algs.insert( childId );
2387
2388 dependsOnChildAlgorithmsRecursive( childId, algs );
2389
2390 // remove temporary target alg
2391 algs.remove( childId );
2392
2393 return algs;
2394}
2395
2396QList<QgsProcessingModelChildDependency> QgsProcessingModelAlgorithm::availableDependenciesForChildAlgorithm( const QString &childId ) const
2397{
2398 QSet< QString > dependent;
2399 if ( !childId.isEmpty() )
2400 {
2401 dependent.unite( dependentChildAlgorithms( childId ) );
2402 dependent.insert( childId );
2403 }
2404
2405 QList<QgsProcessingModelChildDependency> res;
2406 for ( auto it = mChildAlgorithms.constBegin(); it != mChildAlgorithms.constEnd(); ++it )
2407 {
2408 if ( !dependent.contains( it->childId() ) )
2409 {
2410 // check first if algorithm provides output branches
2411 bool hasBranches = false;
2412 if ( it->algorithm() )
2413 {
2414 const QgsProcessingOutputDefinitions defs = it->algorithm()->outputDefinitions();
2415 for ( const QgsProcessingOutputDefinition *def : defs )
2416 {
2418 {
2419 hasBranches = true;
2420 QgsProcessingModelChildDependency alg;
2421 alg.childId = it->childId();
2422 alg.conditionalBranch = def->name();
2423 res << alg;
2424 }
2425 }
2426 }
2427
2428 if ( !hasBranches )
2429 {
2430 QgsProcessingModelChildDependency alg;
2431 alg.childId = it->childId();
2432 res << alg;
2433 }
2434 }
2435 }
2436 return res;
2437}
2438
2439bool QgsProcessingModelAlgorithm::validateChildAlgorithm( const QString &childId, QStringList &issues ) const
2440{
2441 issues.clear();
2442 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constFind( childId );
2443 if ( childIt != mChildAlgorithms.constEnd() )
2444 {
2445 if ( !childIt->algorithm() )
2446 {
2447 issues << QObject::tr( "Algorithm is not available: <i>%1</i>" ).arg( childIt->algorithmId() );
2448 return false;
2449 }
2450 bool res = true;
2451
2452 // loop through child algorithm parameters and check that they are all valid
2453 const QgsProcessingParameterDefinitions defs = childIt->algorithm()->parameterDefinitions();
2454 for ( const QgsProcessingParameterDefinition *def : defs )
2455 {
2456 if ( childIt->parameterSources().contains( def->name() ) )
2457 {
2458 // is the value acceptable?
2459 const QList< QgsProcessingModelChildParameterSource > sources = childIt->parameterSources().value( def->name() );
2460 for ( const QgsProcessingModelChildParameterSource &source : sources )
2461 {
2462 switch ( source.source() )
2463 {
2465 if ( !def->checkValueIsAcceptable( source.staticValue() ) )
2466 {
2467 res = false;
2468 issues << QObject::tr( "Value for <i>%1</i> is not acceptable for this parameter" ).arg( def->name() );
2469 }
2470 break;
2471
2473 if ( !parameterComponents().contains( source.parameterName() ) )
2474 {
2475 res = false;
2476 issues << QObject::tr( "Model input <i>%1</i> used for parameter <i>%2</i> does not exist" ).arg( source.parameterName(), def->name() );
2477 }
2478 break;
2479
2481 if ( !childAlgorithms().contains( source.outputChildId() ) )
2482 {
2483 res = false;
2484 issues << QObject::tr( "Child algorithm <i>%1</i> used for parameter <i>%2</i> does not exist" ).arg( source.outputChildId(), def->name() );
2485 }
2486 break;
2487
2491 break;
2492 }
2493 }
2494 }
2495 else
2496 {
2497 // not specified. Is it optional?
2498
2499 // ignore destination parameters -- they shouldn't ever be mandatory
2500 if ( def->isDestination() )
2501 continue;
2502
2503 if ( !def->checkValueIsAcceptable( QVariant() ) )
2504 {
2505 res = false;
2506 issues << QObject::tr( "Parameter <i>%1</i> is mandatory" ).arg( def->name() );
2507 }
2508 }
2509 }
2510
2511 return res;
2512 }
2513 else
2514 {
2515 issues << QObject::tr( "Invalid child ID: <i>%1</i>" ).arg( childId );
2516 return false;
2517 }
2518}
2519
2520bool QgsProcessingModelAlgorithm::canExecute( QString *errorMessage ) const
2521{
2522 reattachAlgorithms();
2523 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
2524 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
2525 {
2526 if ( !childIt->algorithm() )
2527 {
2528 if ( errorMessage )
2529 {
2530 *errorMessage = QObject::tr( "The model you are trying to run contains an algorithm that is not available: <i>%1</i>" ).arg( childIt->algorithmId() );
2531 }
2532 return false;
2533 }
2534 }
2535 return true;
2536}
2537
2538QString QgsProcessingModelAlgorithm::asPythonCommand( const QVariantMap &parameters, QgsProcessingContext &context ) const
2539{
2540 if ( mSourceFile.isEmpty() )
2541 return QString(); // temporary model - can't run as python command
2542
2543 return QgsProcessingAlgorithm::asPythonCommand( parameters, context );
2544}
2545
2546QgsExpressionContext QgsProcessingModelAlgorithm::createExpressionContext( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeatureSource *source ) const
2547{
2548 QgsExpressionContext res = QgsProcessingAlgorithm::createExpressionContext( parameters, context, source );
2549 res << QgsExpressionContextUtils::processingModelAlgorithmScope( this, parameters, context );
2550 return res;
2551}
2552
2553QgsProcessingAlgorithm *QgsProcessingModelAlgorithm::createInstance() const
2554{
2555 QgsProcessingModelAlgorithm *alg = new QgsProcessingModelAlgorithm();
2556 alg->loadVariant( toVariant() );
2557 alg->setProvider( provider() );
2558 alg->setSourceFilePath( sourceFilePath() );
2559 return alg;
2560}
2561
2562QString QgsProcessingModelAlgorithm::safeName( const QString &name, bool capitalize )
2563{
2564 QString n = name.toLower().trimmed();
2565 const thread_local QRegularExpression rx( u"[^\\sa-z_A-Z0-9]"_s );
2566 n.replace( rx, QString() );
2567 const thread_local QRegularExpression rx2( u"^\\d*"_s ); // name can't start in a digit
2568 n.replace( rx2, QString() );
2569 if ( !capitalize )
2570 n = n.replace( ' ', '_' );
2572}
2573
2574QVariantMap QgsProcessingModelAlgorithm::variables() const
2575{
2576 return mVariables;
2577}
2578
2579void QgsProcessingModelAlgorithm::setVariables( const QVariantMap &variables )
2580{
2581 mVariables = variables;
2582}
2583
2584QVariantMap QgsProcessingModelAlgorithm::designerParameterValues() const
2585{
2586 return mDesignerParameterValues;
2587}
2588
ProcessingSourceType
Processing data source types.
Definition qgis.h:3769
@ Vector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition qgis.h:3777
@ MapLayer
Any map layer type (raster, vector, mesh, point cloud, annotation or plugin layer).
Definition qgis.h:3770
@ VectorAnyGeometry
Any vector layer with geometry.
Definition qgis.h:3771
@ VectorPoint
Vector point layers.
Definition qgis.h:3772
@ VectorPolygon
Vector polygon layers.
Definition qgis.h:3774
@ VectorLine
Vector line layers.
Definition qgis.h:3773
@ Success
Child was successfully executed.
Definition qgis.h:4128
@ Failed
Child encountered an error while executing.
Definition qgis.h:4129
static QString version()
Version string.
Definition qgis.cpp:682
@ Expression
Expression based property.
Definition qgis.h:747
@ UpperCamelCase
Convert the string to upper camel case. Note that this method does not unaccent characters.
Definition qgis.h:3634
QFlags< ProcessingAlgorithmFlag > ProcessingAlgorithmFlags
Flags indicating how and when an algorithm operates and should be exposed to users.
Definition qgis.h:3848
static int versionInt()
Version number used for comparing versions using the "Check QGIS Version" function.
Definition qgis.cpp:687
@ ExpressionText
Parameter value is taken from a text with expressions, evaluated just before the algorithm runs.
Definition qgis.h:4115
@ ModelOutput
Parameter value is linked to an output parameter for the model.
Definition qgis.h:4116
@ ChildOutput
Parameter value is taken from an output generated by a child algorithm.
Definition qgis.h:4112
@ ModelParameter
Parameter value is taken from a parent model parameter.
Definition qgis.h:4111
@ StaticValue
Parameter value is a static value.
Definition qgis.h:4113
@ Expression
Parameter value is taken from an expression, evaluated just before the algorithm runs.
Definition qgis.h:4114
@ SkipGenericModelLogging
When running as part of a model, the generic algorithm setup and results logging should be skipped.
Definition qgis.h:3833
@ CustomException
Algorithm raises custom exception notices, don't use the standard ones.
Definition qgis.h:3831
@ NoThreading
Algorithm is not thread safe and cannot be run in a background thread, e.g. for algorithms which mani...
Definition qgis.h:3827
@ PruneModelBranchesBasedOnAlgorithmResults
Algorithm results will cause remaining model branches to be pruned based on the results of running th...
Definition qgis.h:3832
@ SecurityRisk
The algorithm represents a potential security risk if executed with untrusted inputs.
Definition qgis.h:3836
@ Hidden
Parameter is hidden and should not be shown to users.
Definition qgis.h:4027
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:4026
@ Optional
Parameter is optional.
Definition qgis.h:4028
@ DefaultLevel
Default logging level.
Definition qgis.h:3896
@ Verbose
Verbose logging.
Definition qgis.h:3897
@ ModelDebug
Model debug level logging. Includes verbose logging and other outputs useful for debugging models.
Definition qgis.h:3898
static QgsProcessingRegistry * processingRegistry()
Returns the application's processing registry, used for managing processing providers,...
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
QString what() const
static QgsExpressionContextScope * processingModelAlgorithmScope(const QgsProcessingModelAlgorithm *model, const QVariantMap &parameters, QgsProcessingContext &context)
Creates a new scope which contains variables and functions relating to a processing model algorithm,...
static QgsExpressionContextScope * processingAlgorithmScope(const QgsProcessingAlgorithm *algorithm, const QVariantMap &parameters, QgsProcessingContext &context)
Creates a new scope which contains variables and functions relating to a processing algorithm,...
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Handles parsing and evaluation of expressions (formerly called "search strings").
static QString replaceExpressionText(const QString &action, const QgsExpressionContext *context, const QgsDistanceArea *distanceArea=nullptr)
This function replaces each expression between [% and %] in the string with the result of its evaluat...
An interface for objects which provide features via a getFeatures method.
virtual QgsRectangle sourceExtent() const
Returns the extent of all geometries from the source.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void progressChanged(double progress)
Emitted when the feedback object reports a progress change.
Base class for all map layer types.
Definition qgsmaplayer.h:83
virtual Q_INVOKABLE QgsRectangle extent() const
Returns the extent of the layer.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
Abstract base class for processing algorithms.
QgsProcessingOutputDefinitions outputDefinitions() const
Returns an ordered list of output definitions utilized by the algorithm.
virtual Qgis::ProcessingAlgorithmFlags flags() const
Returns the flags indicating how and when the algorithm operates and should be exposed to users.
virtual QgsExpressionContext createExpressionContext(const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeatureSource *source=nullptr) const
Creates an expression context relating to the algorithm.
const QgsProcessingParameterDefinition * parameterDefinition(const QString &name) const
Returns a matching parameter by name.
virtual QString asPythonCommand(const QVariantMap &parameters, QgsProcessingContext &context) const
Returns a Python command string which can be executed to run the algorithm using the specified parame...
QgsProcessingProvider * provider() const
Returns the provider to which this algorithm belongs.
Details for layers to load into projects.
int layerSortKey
Optional sorting key for sorting output layers when loading them into a project.
QString groupName
Optional name for a layer tree group under which to place the layer when loading it into a project.
Contains information about the context in which a processing algorithm is executed.
QgsExpressionContext & expressionContext()
Returns the expression context.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
QgsProcessingModelResult modelResult() const
Returns the model results, populated when the context is used to run a model algorithm.
QgsProcessingModelInitialRunConfig * modelInitialRunConfig()
Returns a reference to the model initial run configuration, used to run a model algorithm.
Qgis::ProcessingLogLevel logLevel() const
Returns the logging level for algorithms to use when pushing feedback messages to users.
void setModelInitialRunConfig(std::unique_ptr< QgsProcessingModelInitialRunConfig > config)
Sets the model initial run configuration, used to run a model algorithm.
Base class for all parameter definitions which represent file or layer destinations,...
virtual QString generateTemporaryDestination(const QgsProcessingContext *context=nullptr) const
Generates a temporary destination value for this parameter.
void setSupportsNonFileBasedOutput(bool supportsNonFileBasedOutput)
Sets whether the destination parameter supports non filed-based outputs, such as memory layers or dir...
Custom exception class for processing related exceptions.
Encapsulates settings relating to a feature source input to a processing algorithm.
QgsFeatureSource subclass which proxies methods to an underlying QgsFeatureSource,...
Base class for providing feedback from a processing algorithm.
virtual void pushCommandInfo(const QString &info)
Pushes an informational message containing a command from the algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
virtual void pushWarning(const QString &warning)
Pushes a warning informational message from the algorithm.
void sinkFeatureCountChanged(const QString &output, long long featureCount)
Emitted when the count of features pushed to a sink has changed.
virtual QString htmlLog() const
Returns the HTML formatted contents of the log, which contains all messages pushed to the feedback ob...
void sourceLoaded(const QString &parameterName, long long featureCount)
Emitted when a feature source was retrieved for the specified algorithm input parameter.
virtual void pushDebugInfo(const QString &info)
Pushes an informational message containing debugging helpers from the algorithm.
virtual void setProgressText(const QString &text)
Sets a progress report text string.
Encapsulates the results of running a child algorithm within a model.
void setOutputs(const QVariantMap &outputs)
Sets the outputs generated by child algorithm.
void setExecutionStatus(Qgis::ProcessingModelChildAlgorithmExecutionStatus status)
Sets the status of executing the child algorithm.
Qgis::ProcessingModelChildAlgorithmExecutionStatus executionStatus() const
Returns the status of executing the child algorithm.
void setInputs(const QVariantMap &inputs)
Sets the inputs used for the child algorithm.
void setHtmlLog(const QString &log)
Sets the HTML formatted contents of logged messages which occurred while running the child.
A Processing feedback class with extra signals and properties specific to feedback from Processing mo...
void reportChildExecutionFailure(const QString &childId, const QString &error)
Report an error which occurred while executing a child algorithm.
void reportChildResult(const QString &childId, const QgsProcessingModelChildAlgorithmResult &result)
Reports the result of the execution of a child algorithm.
void reportBrokenChildAlgorithms(const QSet< QString > &childIds)
Report a set of child algorithms as broken (e.g.
void reportChildSinkFeatureCountChanged(const QString &childId, const QString &childOutput, long long featureCount)
Reports that the count of features pushed to a child algorithm's sink has changed.
void reportChildPreparationFailure(const QString &childId, const QString &error)
Report an error which occurred while preparing a child algorithm.
void reportPreparingChild(const QString &childId)
Report a child algorithm as undergoing the preparation step.
void reportChildExecutionSuccess(const QString &childId, const QVariantMap &childResults)
Report that a child algorithm successfully executed.
void reportChildProgress(const QString &childId, double progress)
Reports the progress of a running child algorithm.
void reportChildStarted(const QString &childId, const QVariantMap &childParameters)
Report a child algorithm as started execution.
void reportChildPruned(const QString &childId)
Report that a child algorithm was pruned from the pending children (i.e.
void reportChildSourceLoaded(const QString &childId, const QString &parameterName, long long featureCount)
Reports that a feature source was retrieved for the specified child algorithm input parameter.
Configuration settings which control how a Processing model is executed.
QSet< QString > childAlgorithmSubset() const
Returns the subset of child algorithms to run (by child ID).
QVariantMap & rawChildOutputs()
Returns a reference to the map of raw child algorithm outputs.
QVariantMap & rawChildInputs()
Returns a reference to the map of raw child algorithm inputs.
QSet< QString > & executedChildIds()
Returns a reference to the set of child algorithm IDs which were executed during the model execution.
QMap< QString, QgsProcessingModelChildAlgorithmResult > childResults() const
Returns the map of child algorithm results.
Processing feedback object for multi-step operations.
static QString typeName()
Returns the type name for the output class.
static QString typeName()
Returns the type name for the output class.
Base class for the definition of processing outputs.
Encapsulates settings relating to a feature sink or output raster layer for a processing algorithm.
QgsProperty sink
Sink/layer definition.
QString destinationName
Name to use for sink if it's to be loaded into a destination project.
static QString typeName()
Returns the type name for the output class.
static QString typeName()
Returns the type name for the output class.
static QString typeName()
Returns the type name for the output class.
static QString typeName()
Returns the type name for the output class.
static QString typeName()
Returns the type name for the output class.
A vector layer output for processing algorithms.
Qgis::ProcessingSourceType dataType() const
Returns the layer type for the output layer.
static QString typeName()
Returns the type name for the output class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
Base class for the definition of processing parameters.
void setDefaultValue(const QVariant &value)
Sets the default value for the parameter.
QgsProcessingAlgorithm * algorithm() const
Returns a pointer to the algorithm which owns this parameter.
void setFlags(Qgis::ProcessingParameterFlags flags)
Sets the flags associated with the parameter.
QVariantMap metadata() const
Returns the parameter's freeform metadata.
virtual bool isDestination() const
Returns true if this parameter represents a file or layer destination, e.g.
void setDescription(const QString &description)
Sets the description for the parameter.
void setName(const QString &name)
Sets the name of the parameter.
virtual QgsProcessingParameterDefinition * clone() const =0
Creates a clone of the parameter definition.
virtual QString type() const =0
Unique parameter type name.
virtual QVariantMap toVariantMap() const
Saves this parameter to a QVariantMap.
QString name() const
Returns the name of the parameter.
virtual QStringList dependsOnOtherParameters() const
Returns a list of other parameter names on which this parameter is dependent (e.g.
Qgis::ProcessingParameterFlags flags() const
Returns any flags associated with the parameter.
virtual bool checkValueIsAcceptable(const QVariant &input, QgsProcessingContext *context=nullptr) const
Checks whether the specified input value is acceptable for the parameter.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
A vector layer or feature source field parameter for processing algorithms.
Qgis::ProcessingFieldParameterDataType dataType() const
Returns the acceptable data type for the field.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
Can be inherited by parameters which require limits to their acceptable data types.
QList< int > dataTypes() const
Returns the geometry types for sources acceptable by the parameter.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
Makes metadata of processing parameters available.
virtual QStringList acceptedOutputTypes() const =0
Returns a list of compatible Processing output types for inputs for this parameter type.
virtual QList< int > acceptedDataTypes(const QgsProcessingParameterDefinition *parameter) const
Returns a list of compatible Processing data types for inputs for this parameter type for the specifi...
virtual QStringList acceptedParameterTypes() const =0
Returns a list of compatible Processing parameter types for inputs for this parameter type.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QgsProcessingParameterDefinition * parameterFromVariantMap(const QVariantMap &map)
Creates a new QgsProcessingParameterDefinition using the configuration from a supplied variant map.
Abstract base class for processing providers.
const QgsProcessingAlgorithm * algorithm(const QString &name) const
Returns the matching algorithm by name, or nullptr if no matching algorithm is contained by this prov...
QgsProcessingParameterType * parameterType(const QString &id) const
Returns the parameter type registered for id.
static QString formatHelpMapAsHtml(const QVariantMap &map, const QgsProcessingAlgorithm *algorithm)
Returns a HTML formatted version of the help text encoded in a variant map for a specified algorithm.
static QString variantToPythonLiteral(const QVariant &value)
Converts a variant to a Python literal.
static QVariantMap removePointerValuesFromMap(const QVariantMap &map)
Removes any raw pointer values from an input map, replacing them with appropriate string values where...
static QgsMapLayer * mapLayerFromString(const QString &string, QgsProcessingContext &context, bool allowLoadingNewLayers=true, QgsProcessingUtils::LayerHint typeHint=QgsProcessingUtils::LayerHint::UnknownType, QgsProcessing::LayerOptionsFlags flags=QgsProcessing::LayerOptionsFlags())
Interprets a string as a map layer within the supplied context.
PythonOutputType
Available Python output types.
@ PythonQgsProcessingAlgorithmSubclass
Full Python QgsProcessingAlgorithm subclass.
Describes the version of a project.
QString text() const
Returns a string representation of the version.
A store for object properties.
QVariant staticValue() const
Returns the current static value for the property.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
Keeps a reference to a Qt connection (a QMetaObject::Connection) and disconnects it whenever this obj...
static QString capitalize(const QString &string, Qgis::Capitalization capitalization)
Converts a string by applying capitalization rules to the string.
Represents a vector layer which manages a vector based dataset.
static QDomElement writeVariant(const QVariant &value, QDomDocument &doc)
Write a QVariant to a QDomElement.
static QVariant readVariant(const QDomElement &element)
Read a QVariant from a QDomElement.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into allowing algorithms to be written in pure substantial changes are required in order to port existing x Processing algorithms for QGIS x The most significant changes are outlined not GeoAlgorithm For algorithms which operate on features one by consider subclassing the QgsProcessingFeatureBasedAlgorithm class This class allows much of the boilerplate code for looping over features from a vector layer to be bypassed and instead requires implementation of a processFeature method Ensure that your algorithm(or algorithm 's parent class) implements the new pure virtual createInstance(self) call
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition qgis.h:8064
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:8407
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:8045
QString qgsSetJoin(const QSet< T > &set, const QString &separator)
Joins all the set values into a single string with each element separated by the given separator.
Definition qgis.h:7912
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:8406
QMap< QString, QString > QgsStringMap
Definition qgis.h:8378
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.
QList< const QgsProcessingOutputDefinition * > QgsProcessingOutputDefinitions
List of processing parameters.
QList< const QgsProcessingParameterDefinition * > QgsProcessingParameterDefinitions
List of processing parameters.
Single variable definition for use within a QgsExpressionContextScope.