QGIS API Documentation 4.3.0-Master (0ff9723465c)
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
1818 QVariantMap childMap;
1819 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
1820 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
1821 {
1822 childMap.insert( childIt.key(), childIt.value().toVariant() );
1823 }
1824 map.insert( u"children"_s, childMap );
1825
1826 QVariantMap paramMap;
1827 QMap< QString, QgsProcessingModelParameter >::const_iterator paramIt = mParameterComponents.constBegin();
1828 for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt )
1829 {
1830 paramMap.insert( paramIt.key(), paramIt.value().toVariant() );
1831 }
1832 map.insert( u"parameters"_s, paramMap );
1833
1834 QVariantMap paramDefMap;
1835 for ( const QgsProcessingParameterDefinition *def : mParameters )
1836 {
1837 paramDefMap.insert( def->name(), def->toVariantMap() );
1838 }
1839 map.insert( u"parameterDefinitions"_s, paramDefMap );
1840
1841 QVariantList groupBoxDefs;
1842 for ( auto it = mGroupBoxes.constBegin(); it != mGroupBoxes.constEnd(); ++it )
1843 {
1844 groupBoxDefs.append( it.value().toVariant() );
1845 }
1846 map.insert( u"groupBoxes"_s, groupBoxDefs );
1847
1848 map.insert( u"modelVariables"_s, mVariables );
1849
1850 map.insert( u"designerParameterValues"_s, mDesignerParameterValues );
1851
1852 map.insert( u"parameterOrder"_s, mParameterOrder );
1853 map.insert( u"outputOrder"_s, mOutputOrder );
1854 map.insert( u"outputGroup"_s, mOutputGroup );
1855
1856 return map;
1857}
1858
1859bool QgsProcessingModelAlgorithm::loadVariant( const QVariant &model )
1860{
1861 QVariantMap map = model.toMap();
1862
1863 mModelName = map.value( u"model_name"_s ).toString();
1864 mModelGroup = map.value( u"model_group"_s ).toString();
1865 mModelGroupId = map.value( u"model_group"_s ).toString();
1866 mHelpContent = map.value( u"help"_s ).toMap();
1867
1868 mInternalVersion = qgsEnumKeyToValue( map.value( u"internal_version"_s ).toString(), InternalVersion::Version1 );
1869
1870 mVariables = map.value( u"modelVariables"_s ).toMap();
1871 mDesignerParameterValues = map.value( u"designerParameterValues"_s ).toMap();
1872
1873 mParameterOrder = map.value( u"parameterOrder"_s ).toStringList();
1874 mOutputOrder = map.value( u"outputOrder"_s ).toStringList();
1875 mOutputGroup = map.value( u"outputGroup"_s ).toString();
1876
1877 mChildAlgorithms.clear();
1878 QVariantMap childMap = map.value( u"children"_s ).toMap();
1879 QVariantMap::const_iterator childIt = childMap.constBegin();
1880 for ( ; childIt != childMap.constEnd(); ++childIt )
1881 {
1882 QgsProcessingModelChildAlgorithm child;
1883 // we be lenient here - even if we couldn't load a parameter, don't interrupt the model loading
1884 // otherwise models may become unusable (e.g. due to removed plugins providing algs/parameters)
1885 // with no way for users to repair them
1886 if ( !child.loadVariant( childIt.value() ) )
1887 continue;
1888
1889 mChildAlgorithms.insert( child.childId(), child );
1890 }
1891
1892 mParameterComponents.clear();
1893 QVariantMap paramMap = map.value( u"parameters"_s ).toMap();
1894 QVariantMap::const_iterator paramIt = paramMap.constBegin();
1895 for ( ; paramIt != paramMap.constEnd(); ++paramIt )
1896 {
1897 QgsProcessingModelParameter param;
1898 if ( !param.loadVariant( paramIt.value().toMap() ) )
1899 return false;
1900
1901 mParameterComponents.insert( param.parameterName(), param );
1902 }
1903
1904 qDeleteAll( mParameters );
1905 mParameters.clear();
1906 QVariantMap paramDefMap = map.value( u"parameterDefinitions"_s ).toMap();
1907
1908 auto addParam = [this]( const QVariant &value ) {
1909 std::unique_ptr< QgsProcessingParameterDefinition > param( QgsProcessingParameters::parameterFromVariantMap( value.toMap() ) );
1910 // we be lenient here - even if we couldn't load a parameter, don't interrupt the model loading
1911 // otherwise models may become unusable (e.g. due to removed plugins providing algs/parameters)
1912 // with no way for users to repair them
1913 if ( param )
1914 {
1915 if ( param->name() == "VERBOSE_LOG"_L1 )
1916 return; // internal parameter -- some versions of QGIS incorrectly stored this in the model definition file
1917
1918 // set parameter help from help content
1919 param->setHelp( mHelpContent.value( param->name() ).toString() );
1920
1921 // add parameter
1922 addParameter( param.release() );
1923 }
1924 else
1925 {
1926 QVariantMap map = value.toMap();
1927 QString type = map.value( u"parameter_type"_s ).toString();
1928 QString name = map.value( u"name"_s ).toString();
1929
1930 QgsMessageLog::logMessage( QCoreApplication::translate( "Processing", "Could not load parameter %1 of type %2." ).arg( name, type ), QCoreApplication::translate( "Processing", "Processing" ) );
1931 }
1932 };
1933
1934 QSet< QString > loadedParams;
1935 // first add parameters respecting mParameterOrder
1936 for ( const QString &name : std::as_const( mParameterOrder ) )
1937 {
1938 if ( paramDefMap.contains( name ) )
1939 {
1940 addParam( paramDefMap.value( name ) );
1941 loadedParams << name;
1942 }
1943 }
1944 // then load any remaining parameters
1945 QVariantMap::const_iterator paramDefIt = paramDefMap.constBegin();
1946 for ( ; paramDefIt != paramDefMap.constEnd(); ++paramDefIt )
1947 {
1948 if ( !loadedParams.contains( paramDefIt.key() ) )
1949 addParam( paramDefIt.value() );
1950 }
1951
1952 mGroupBoxes.clear();
1953 const QVariantList groupBoxList = map.value( u"groupBoxes"_s ).toList();
1954 for ( const QVariant &groupBoxDef : groupBoxList )
1955 {
1956 QgsProcessingModelGroupBox groupBox;
1957 groupBox.loadVariant( groupBoxDef.toMap() );
1958 mGroupBoxes.insert( groupBox.uuid(), groupBox );
1959 }
1960
1961 updateDestinationParameters();
1962
1963 return true;
1964}
1965
1966bool QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( const QList<int> &acceptableDataTypes, Qgis::ProcessingSourceType outputType )
1967{
1968 // This method is intended to be "permissive" rather than "restrictive".
1969 // I.e. we only reject outputs which we know can NEVER be acceptable, but
1970 // if there's doubt then we default to returning true.
1971 return (
1972 acceptableDataTypes.empty()
1973 || acceptableDataTypes.contains( static_cast< int >( outputType ) )
1975 || outputType == Qgis::ProcessingSourceType::Vector
1977 || acceptableDataTypes.contains( static_cast< int >( Qgis::ProcessingSourceType::Vector ) )
1978 || acceptableDataTypes.contains( static_cast< int >( Qgis::ProcessingSourceType::MapLayer ) )
1979 || ( acceptableDataTypes.contains( static_cast< int >( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) && ( outputType == Qgis::ProcessingSourceType::VectorPoint || outputType == Qgis::ProcessingSourceType::VectorLine || outputType == Qgis::ProcessingSourceType::VectorPolygon ) )
1980 );
1981}
1982
1983void QgsProcessingModelAlgorithm::reattachAlgorithms() const
1984{
1985 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
1986 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
1987 {
1988 if ( !childIt->algorithm() )
1989 childIt->reattach();
1990 }
1991}
1992
1993bool QgsProcessingModelAlgorithm::toFile( const QString &path ) const
1994{
1995 QDomDocument doc = QDomDocument( u"model"_s );
1996 QDomElement elem = QgsXmlUtils::writeVariant( toVariant(), doc );
1997 doc.appendChild( elem );
1998
1999 QFile file( path );
2000 if ( file.open( QFile::WriteOnly | QFile::Truncate ) )
2001 {
2002 QTextStream stream( &file );
2003 doc.save( stream, 2 );
2004 file.close();
2005 return true;
2006 }
2007 return false;
2008}
2009
2010bool QgsProcessingModelAlgorithm::fromFile( const QString &path )
2011{
2012 QDomDocument doc;
2013
2014 QFile file( path );
2015 if ( file.open( QFile::ReadOnly ) )
2016 {
2017 if ( !doc.setContent( &file ) )
2018 return false;
2019
2020 file.close();
2021 }
2022 else
2023 {
2024 return false;
2025 }
2026
2027 QVariant props = QgsXmlUtils::readVariant( doc.firstChildElement() );
2028 return loadVariant( props );
2029}
2030
2031void QgsProcessingModelAlgorithm::setChildAlgorithms( const QMap<QString, QgsProcessingModelChildAlgorithm> &childAlgorithms )
2032{
2033 mChildAlgorithms = childAlgorithms;
2034 updateDestinationParameters();
2035}
2036
2037void QgsProcessingModelAlgorithm::setChildAlgorithm( const QgsProcessingModelChildAlgorithm &algorithm )
2038{
2039 mChildAlgorithms.insert( algorithm.childId(), algorithm );
2040 updateDestinationParameters();
2041}
2042
2043QString QgsProcessingModelAlgorithm::addChildAlgorithm( QgsProcessingModelChildAlgorithm &algorithm )
2044{
2045 if ( algorithm.childId().isEmpty() || mChildAlgorithms.contains( algorithm.childId() ) )
2046 algorithm.generateChildId( *this );
2047
2048 mChildAlgorithms.insert( algorithm.childId(), algorithm );
2049 updateDestinationParameters();
2050 return algorithm.childId();
2051}
2052
2053QgsProcessingModelChildAlgorithm &QgsProcessingModelAlgorithm::childAlgorithm( const QString &childId )
2054{
2055 return mChildAlgorithms[childId];
2056}
2057
2058bool QgsProcessingModelAlgorithm::removeChildAlgorithm( const QString &id )
2059{
2060 if ( !dependentChildAlgorithms( id ).isEmpty() )
2061 return false;
2062
2063 mChildAlgorithms.remove( id );
2064 updateDestinationParameters();
2065 return true;
2066}
2067
2068void QgsProcessingModelAlgorithm::deactivateChildAlgorithm( const QString &id )
2069{
2070 const auto constDependentChildAlgorithms = dependentChildAlgorithms( id );
2071 for ( const QString &child : constDependentChildAlgorithms )
2072 {
2073 childAlgorithm( child ).setActive( false );
2074 }
2075 childAlgorithm( id ).setActive( false );
2076 updateDestinationParameters();
2077}
2078
2079bool QgsProcessingModelAlgorithm::activateChildAlgorithm( const QString &id )
2080{
2081 const auto constDependsOnChildAlgorithms = dependsOnChildAlgorithms( id );
2082 for ( const QString &child : constDependsOnChildAlgorithms )
2083 {
2084 if ( !childAlgorithm( child ).isActive() )
2085 return false;
2086 }
2087 childAlgorithm( id ).setActive( true );
2088 updateDestinationParameters();
2089 return true;
2090}
2091
2092void QgsProcessingModelAlgorithm::addModelParameter( QgsProcessingParameterDefinition *definition, const QgsProcessingModelParameter &component )
2093{
2094 if ( addParameter( definition ) )
2095 mParameterComponents.insert( definition->name(), component );
2096}
2097
2098void QgsProcessingModelAlgorithm::updateModelParameter( QgsProcessingParameterDefinition *definition )
2099{
2100 removeParameter( definition->name() );
2101 addParameter( definition );
2102}
2103
2104void QgsProcessingModelAlgorithm::removeModelParameter( const QString &name )
2105{
2106 removeParameter( name );
2107 mParameterComponents.remove( name );
2108}
2109
2110void QgsProcessingModelAlgorithm::changeParameterName( const QString &oldName, const QString &newName )
2111{
2112 QgsProcessingContext context;
2113 QgsExpressionContext expressionContext = createExpressionContext( QVariantMap(), context );
2114
2115 auto replaceExpressionVariable = [oldName, newName, &expressionContext]( const QString &expressionString ) -> std::tuple< bool, QString > {
2116 QgsExpression expression( expressionString );
2117 expression.prepare( &expressionContext );
2118 QSet<QString> variables = expression.referencedVariables();
2119 if ( variables.contains( oldName ) )
2120 {
2121 QString newExpression = expressionString;
2122 newExpression.replace( u"@%1"_s.arg( oldName ), u"@%2"_s.arg( newName ) );
2123 return { true, newExpression };
2124 }
2125 return { false, QString() };
2126 };
2127
2128 QMap< QString, QgsProcessingModelChildAlgorithm >::iterator childIt = mChildAlgorithms.begin();
2129 for ( ; childIt != mChildAlgorithms.end(); ++childIt )
2130 {
2131 bool changed = false;
2132 QMap<QString, QgsProcessingModelChildParameterSources> childParams = childIt->parameterSources();
2133 QMap<QString, QgsProcessingModelChildParameterSources>::iterator paramIt = childParams.begin();
2134 for ( ; paramIt != childParams.end(); ++paramIt )
2135 {
2136 QList< QgsProcessingModelChildParameterSource > &value = paramIt.value();
2137 for ( auto valueIt = value.begin(); valueIt != value.end(); ++valueIt )
2138 {
2139 switch ( valueIt->source() )
2140 {
2142 {
2143 if ( valueIt->parameterName() == oldName )
2144 {
2145 valueIt->setParameterName( newName );
2146 changed = true;
2147 }
2148 break;
2149 }
2150
2152 {
2153 bool updatedExpression = false;
2154 QString newExpression;
2155 std::tie( updatedExpression, newExpression ) = replaceExpressionVariable( valueIt->expression() );
2156 if ( updatedExpression )
2157 {
2158 valueIt->setExpression( newExpression );
2159 changed = true;
2160 }
2161 break;
2162 }
2163
2165 {
2166 if ( valueIt->staticValue().userType() == qMetaTypeId<QgsProperty>() )
2167 {
2168 QgsProperty property = valueIt->staticValue().value< QgsProperty >();
2169 if ( property.propertyType() == Qgis::PropertyType::Expression )
2170 {
2171 bool updatedExpression = false;
2172 QString newExpression;
2173 std::tie( updatedExpression, newExpression ) = replaceExpressionVariable( property.expressionString() );
2174 if ( updatedExpression )
2175 {
2176 property.setExpressionString( newExpression );
2177 valueIt->setStaticValue( property );
2178 changed = true;
2179 }
2180 }
2181 }
2182 break;
2183 }
2184
2188 break;
2189 }
2190 }
2191 }
2192 if ( changed )
2193 childIt->setParameterSources( childParams );
2194 }
2195}
2196
2197bool QgsProcessingModelAlgorithm::childAlgorithmsDependOnParameter( const QString &name ) const
2198{
2199 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
2200 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
2201 {
2202 // check whether child requires this parameter
2203 QMap<QString, QgsProcessingModelChildParameterSources> childParams = childIt->parameterSources();
2204 QMap<QString, QgsProcessingModelChildParameterSources>::const_iterator paramIt = childParams.constBegin();
2205 for ( ; paramIt != childParams.constEnd(); ++paramIt )
2206 {
2207 const auto constValue = paramIt.value();
2208 for ( const QgsProcessingModelChildParameterSource &source : constValue )
2209 {
2210 if ( source.source() == Qgis::ProcessingModelChildParameterSource::ModelParameter && source.parameterName() == name )
2211 {
2212 return true;
2213 }
2214 }
2215 }
2216 }
2217 return false;
2218}
2219
2220bool QgsProcessingModelAlgorithm::otherParametersDependOnParameter( const QString &name ) const
2221{
2222 const auto constMParameters = mParameters;
2223 for ( const QgsProcessingParameterDefinition *def : constMParameters )
2224 {
2225 if ( def->name() == name )
2226 continue;
2227
2228 if ( def->dependsOnOtherParameters().contains( name ) )
2229 return true;
2230 }
2231 return false;
2232}
2233
2234QMap<QString, QgsProcessingModelParameter> QgsProcessingModelAlgorithm::parameterComponents() const
2235{
2236 return mParameterComponents;
2237}
2238
2239void QgsProcessingModelAlgorithm::dependentChildAlgorithmsRecursive( const QString &childId, QSet<QString> &depends, const QString &branch ) const
2240{
2241 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
2242 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
2243 {
2244 if ( depends.contains( childIt->childId() ) )
2245 continue;
2246
2247 // does alg have a direct dependency on this child?
2248 const QList< QgsProcessingModelChildDependency > constDependencies = childIt->dependencies();
2249 bool hasDependency = false;
2250 for ( const QgsProcessingModelChildDependency &dep : constDependencies )
2251 {
2252 if ( dep.childId == childId && ( branch.isEmpty() || dep.conditionalBranch == branch ) )
2253 {
2254 hasDependency = true;
2255 break;
2256 }
2257 }
2258
2259 if ( hasDependency )
2260 {
2261 depends.insert( childIt->childId() );
2262 dependentChildAlgorithmsRecursive( childIt->childId(), depends, branch );
2263 continue;
2264 }
2265
2266 // check whether child requires any outputs from the target alg
2267 QMap<QString, QgsProcessingModelChildParameterSources> childParams = childIt->parameterSources();
2268 QMap<QString, QgsProcessingModelChildParameterSources>::const_iterator paramIt = childParams.constBegin();
2269 for ( ; paramIt != childParams.constEnd(); ++paramIt )
2270 {
2271 const auto constValue = paramIt.value();
2272 for ( const QgsProcessingModelChildParameterSource &source : constValue )
2273 {
2274 if ( source.source() == Qgis::ProcessingModelChildParameterSource::ChildOutput && source.outputChildId() == childId )
2275 {
2276 depends.insert( childIt->childId() );
2277 dependentChildAlgorithmsRecursive( childIt->childId(), depends, branch );
2278 break;
2279 }
2280 }
2281 }
2282 }
2283}
2284
2285QSet<QString> QgsProcessingModelAlgorithm::dependentChildAlgorithms( const QString &childId, const QString &conditionalBranch ) const
2286{
2287 QSet< QString > algs;
2288
2289 // temporarily insert the target child algorithm to avoid
2290 // unnecessarily recursion though it
2291 algs.insert( childId );
2292
2293 dependentChildAlgorithmsRecursive( childId, algs, conditionalBranch );
2294
2295 // remove temporary target alg
2296 algs.remove( childId );
2297
2298 return algs;
2299}
2300
2301
2302void QgsProcessingModelAlgorithm::dependsOnChildAlgorithmsRecursive( const QString &childId, QSet< QString > &depends ) const
2303{
2304 const QgsProcessingModelChildAlgorithm &alg = mChildAlgorithms.value( childId );
2305
2306 // add direct dependencies
2307 const QList< QgsProcessingModelChildDependency > constDependencies = alg.dependencies();
2308 for ( const QgsProcessingModelChildDependency &val : constDependencies )
2309 {
2310 if ( !depends.contains( val.childId ) )
2311 {
2312 depends.insert( val.childId );
2313 dependsOnChildAlgorithmsRecursive( val.childId, depends );
2314 }
2315 }
2316
2317 // check through parameter dependencies
2318 QMap<QString, QgsProcessingModelChildParameterSources> childParams = alg.parameterSources();
2319 QMap<QString, QgsProcessingModelChildParameterSources>::const_iterator paramIt = childParams.constBegin();
2320 for ( ; paramIt != childParams.constEnd(); ++paramIt )
2321 {
2322 const auto constValue = paramIt.value();
2323 for ( const QgsProcessingModelChildParameterSource &source : constValue )
2324 {
2325 switch ( source.source() )
2326 {
2328 if ( !depends.contains( source.outputChildId() ) )
2329 {
2330 depends.insert( source.outputChildId() );
2331 dependsOnChildAlgorithmsRecursive( source.outputChildId(), depends );
2332 }
2333 break;
2334
2336 {
2337 const QgsExpression exp( source.expression() );
2338 const QSet<QString> vars = exp.referencedVariables();
2339 if ( vars.empty() )
2340 break;
2341
2342 // find the source of referenced variables and check if it's another child algorithm
2343 const QMap<QString, QgsProcessingModelAlgorithm::VariableDefinition> availableVariables = variablesForChildAlgorithm( childId );
2344 for ( auto childVarIt = availableVariables.constBegin(); childVarIt != availableVariables.constEnd(); ++childVarIt )
2345 {
2346 // we're only looking here for variables coming from other child algorithm outputs
2347 if ( childVarIt->source.source() != Qgis::ProcessingModelChildParameterSource::ChildOutput )
2348 continue;
2349
2350 if ( !vars.contains( childVarIt.key() ) || depends.contains( childVarIt->source.outputChildId() ) )
2351 continue;
2352
2353 // this variable is required for the child's expression, so the corresponding algorithm must be run first
2354 depends.insert( childVarIt->source.outputChildId() );
2355 dependsOnChildAlgorithmsRecursive( childVarIt->source.outputChildId(), depends );
2356 }
2357 break;
2358 }
2359
2364 break;
2365 }
2366 }
2367 }
2368}
2369
2370QSet< QString > QgsProcessingModelAlgorithm::dependsOnChildAlgorithms( const QString &childId ) const
2371{
2372 QSet< QString > algs;
2373
2374 // temporarily insert the target child algorithm to avoid
2375 // unnecessarily recursion though it
2376 algs.insert( childId );
2377
2378 dependsOnChildAlgorithmsRecursive( childId, algs );
2379
2380 // remove temporary target alg
2381 algs.remove( childId );
2382
2383 return algs;
2384}
2385
2386QList<QgsProcessingModelChildDependency> QgsProcessingModelAlgorithm::availableDependenciesForChildAlgorithm( const QString &childId ) const
2387{
2388 QSet< QString > dependent;
2389 if ( !childId.isEmpty() )
2390 {
2391 dependent.unite( dependentChildAlgorithms( childId ) );
2392 dependent.insert( childId );
2393 }
2394
2395 QList<QgsProcessingModelChildDependency> res;
2396 for ( auto it = mChildAlgorithms.constBegin(); it != mChildAlgorithms.constEnd(); ++it )
2397 {
2398 if ( !dependent.contains( it->childId() ) )
2399 {
2400 // check first if algorithm provides output branches
2401 bool hasBranches = false;
2402 if ( it->algorithm() )
2403 {
2404 const QgsProcessingOutputDefinitions defs = it->algorithm()->outputDefinitions();
2405 for ( const QgsProcessingOutputDefinition *def : defs )
2406 {
2408 {
2409 hasBranches = true;
2410 QgsProcessingModelChildDependency alg;
2411 alg.childId = it->childId();
2412 alg.conditionalBranch = def->name();
2413 res << alg;
2414 }
2415 }
2416 }
2417
2418 if ( !hasBranches )
2419 {
2420 QgsProcessingModelChildDependency alg;
2421 alg.childId = it->childId();
2422 res << alg;
2423 }
2424 }
2425 }
2426 return res;
2427}
2428
2429bool QgsProcessingModelAlgorithm::validateChildAlgorithm( const QString &childId, QStringList &issues ) const
2430{
2431 issues.clear();
2432 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constFind( childId );
2433 if ( childIt != mChildAlgorithms.constEnd() )
2434 {
2435 if ( !childIt->algorithm() )
2436 {
2437 issues << QObject::tr( "Algorithm is not available: <i>%1</i>" ).arg( childIt->algorithmId() );
2438 return false;
2439 }
2440 bool res = true;
2441
2442 // loop through child algorithm parameters and check that they are all valid
2443 const QgsProcessingParameterDefinitions defs = childIt->algorithm()->parameterDefinitions();
2444 for ( const QgsProcessingParameterDefinition *def : defs )
2445 {
2446 if ( childIt->parameterSources().contains( def->name() ) )
2447 {
2448 // is the value acceptable?
2449 const QList< QgsProcessingModelChildParameterSource > sources = childIt->parameterSources().value( def->name() );
2450 for ( const QgsProcessingModelChildParameterSource &source : sources )
2451 {
2452 switch ( source.source() )
2453 {
2455 if ( !def->checkValueIsAcceptable( source.staticValue() ) )
2456 {
2457 res = false;
2458 issues << QObject::tr( "Value for <i>%1</i> is not acceptable for this parameter" ).arg( def->name() );
2459 }
2460 break;
2461
2463 if ( !parameterComponents().contains( source.parameterName() ) )
2464 {
2465 res = false;
2466 issues << QObject::tr( "Model input <i>%1</i> used for parameter <i>%2</i> does not exist" ).arg( source.parameterName(), def->name() );
2467 }
2468 break;
2469
2471 if ( !childAlgorithms().contains( source.outputChildId() ) )
2472 {
2473 res = false;
2474 issues << QObject::tr( "Child algorithm <i>%1</i> used for parameter <i>%2</i> does not exist" ).arg( source.outputChildId(), def->name() );
2475 }
2476 break;
2477
2481 break;
2482 }
2483 }
2484 }
2485 else
2486 {
2487 // not specified. Is it optional?
2488
2489 // ignore destination parameters -- they shouldn't ever be mandatory
2490 if ( def->isDestination() )
2491 continue;
2492
2493 if ( !def->checkValueIsAcceptable( QVariant() ) )
2494 {
2495 res = false;
2496 issues << QObject::tr( "Parameter <i>%1</i> is mandatory" ).arg( def->name() );
2497 }
2498 }
2499 }
2500
2501 return res;
2502 }
2503 else
2504 {
2505 issues << QObject::tr( "Invalid child ID: <i>%1</i>" ).arg( childId );
2506 return false;
2507 }
2508}
2509
2510bool QgsProcessingModelAlgorithm::canExecute( QString *errorMessage ) const
2511{
2512 reattachAlgorithms();
2513 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
2514 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
2515 {
2516 if ( !childIt->algorithm() )
2517 {
2518 if ( errorMessage )
2519 {
2520 *errorMessage = QObject::tr( "The model you are trying to run contains an algorithm that is not available: <i>%1</i>" ).arg( childIt->algorithmId() );
2521 }
2522 return false;
2523 }
2524 }
2525 return true;
2526}
2527
2528QString QgsProcessingModelAlgorithm::asPythonCommand( const QVariantMap &parameters, QgsProcessingContext &context ) const
2529{
2530 if ( mSourceFile.isEmpty() )
2531 return QString(); // temporary model - can't run as python command
2532
2533 return QgsProcessingAlgorithm::asPythonCommand( parameters, context );
2534}
2535
2536QgsExpressionContext QgsProcessingModelAlgorithm::createExpressionContext( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeatureSource *source ) const
2537{
2538 QgsExpressionContext res = QgsProcessingAlgorithm::createExpressionContext( parameters, context, source );
2539 res << QgsExpressionContextUtils::processingModelAlgorithmScope( this, parameters, context );
2540 return res;
2541}
2542
2543QgsProcessingAlgorithm *QgsProcessingModelAlgorithm::createInstance() const
2544{
2545 QgsProcessingModelAlgorithm *alg = new QgsProcessingModelAlgorithm();
2546 alg->loadVariant( toVariant() );
2547 alg->setProvider( provider() );
2548 alg->setSourceFilePath( sourceFilePath() );
2549 return alg;
2550}
2551
2552QString QgsProcessingModelAlgorithm::safeName( const QString &name, bool capitalize )
2553{
2554 QString n = name.toLower().trimmed();
2555 const thread_local QRegularExpression rx( u"[^\\sa-z_A-Z0-9]"_s );
2556 n.replace( rx, QString() );
2557 const thread_local QRegularExpression rx2( u"^\\d*"_s ); // name can't start in a digit
2558 n.replace( rx2, QString() );
2559 if ( !capitalize )
2560 n = n.replace( ' ', '_' );
2562}
2563
2564QVariantMap QgsProcessingModelAlgorithm::variables() const
2565{
2566 return mVariables;
2567}
2568
2569void QgsProcessingModelAlgorithm::setVariables( const QVariantMap &variables )
2570{
2571 mVariables = variables;
2572}
2573
2574QVariantMap QgsProcessingModelAlgorithm::designerParameterValues() const
2575{
2576 return mDesignerParameterValues;
2577}
2578
ProcessingSourceType
Processing data source types.
Definition qgis.h:3749
@ Vector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition qgis.h:3757
@ MapLayer
Any map layer type (raster, vector, mesh, point cloud, annotation or plugin layer).
Definition qgis.h:3750
@ VectorAnyGeometry
Any vector layer with geometry.
Definition qgis.h:3751
@ VectorPoint
Vector point layers.
Definition qgis.h:3752
@ VectorPolygon
Vector polygon layers.
Definition qgis.h:3754
@ VectorLine
Vector line layers.
Definition qgis.h:3753
@ Success
Child was successfully executed.
Definition qgis.h:4108
@ Failed
Child encountered an error while executing.
Definition qgis.h:4109
@ Expression
Expression based property.
Definition qgis.h:727
@ UpperCamelCase
Convert the string to upper camel case. Note that this method does not unaccent characters.
Definition qgis.h:3614
QFlags< ProcessingAlgorithmFlag > ProcessingAlgorithmFlags
Flags indicating how and when an algorithm operates and should be exposed to users.
Definition qgis.h:3828
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:4095
@ ModelOutput
Parameter value is linked to an output parameter for the model.
Definition qgis.h:4096
@ ChildOutput
Parameter value is taken from an output generated by a child algorithm.
Definition qgis.h:4092
@ ModelParameter
Parameter value is taken from a parent model parameter.
Definition qgis.h:4091
@ StaticValue
Parameter value is a static value.
Definition qgis.h:4093
@ Expression
Parameter value is taken from an expression, evaluated just before the algorithm runs.
Definition qgis.h:4094
@ SkipGenericModelLogging
When running as part of a model, the generic algorithm setup and results logging should be skipped.
Definition qgis.h:3813
@ CustomException
Algorithm raises custom exception notices, don't use the standard ones.
Definition qgis.h:3811
@ NoThreading
Algorithm is not thread safe and cannot be run in a background thread, e.g. for algorithms which mani...
Definition qgis.h:3807
@ PruneModelBranchesBasedOnAlgorithmResults
Algorithm results will cause remaining model branches to be pruned based on the results of running th...
Definition qgis.h:3812
@ SecurityRisk
The algorithm represents a potential security risk if executed with untrusted inputs.
Definition qgis.h:3816
@ Hidden
Parameter is hidden and should not be shown to users.
Definition qgis.h:4007
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:4006
@ Optional
Parameter is optional.
Definition qgis.h:4008
@ DefaultLevel
Default logging level.
Definition qgis.h:3876
@ Verbose
Verbose logging.
Definition qgis.h:3877
@ ModelDebug
Model debug level logging. Includes verbose logging and other outputs useful for debugging models.
Definition qgis.h:3878
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.
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:7986
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:8329
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7967
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:7834
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:8328
QMap< QString, QString > QgsStringMap
Definition qgis.h:8300
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.