QGIS API Documentation 4.3.0-Master (1930e81c918)
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 << QgsExpressionContextUtils::processingAlgorithmScope( child.algorithm(), parameters, context ) << createExpressionContextScopeForChildAlgorithm( childId, context, parameters, childResults );
454 context.setExpressionContext( expContext );
455
456 QString error;
457 QVariantMap childParams = parametersForChildAlgorithm( child, parameters, childResults, expContext, error, &context );
458 if ( !error.isEmpty() )
459 {
460 if ( modelFeedback )
461 {
462 modelFeedback->reportChildPreparationFailure( childId, error );
463 }
464 throw QgsProcessingException( error );
465 }
466
467 if ( feedback && !skipGenericLogging )
468 feedback->setProgressText( QObject::tr( "Running %1 [%2/%3]" ).arg( child.description() ).arg( executed.count() + 1 ).arg( toExecute.count() ) );
469
471
472 const QVariantMap thisChildParams = QgsProcessingUtils::removePointerValuesFromMap( childParams );
473 childInputs.insert( childId, thisChildParams );
474 childResult.setInputs( thisChildParams );
475
476 QStringList params;
477 for ( auto childParamIt = childParams.constBegin(); childParamIt != childParams.constEnd(); ++childParamIt )
478 {
479 params << u"%1: %2"_s.arg( childParamIt.key(), child.algorithm()->parameterDefinition( childParamIt.key() )->valueAsPythonString( childParamIt.value(), context ) );
480 }
481
482 if ( feedback && !skipGenericLogging )
483 {
484 feedback->pushInfo( QObject::tr( "Input Parameters:" ) );
485 feedback->pushCommandInfo( u"{ %1 }"_s.arg( params.join( ", "_L1 ) ) );
486 }
487
488 QElapsedTimer childTime;
489 childTime.start();
490
491 QVariantMap outerScopeChildInputs = childInputs;
492 QVariantMap outerScopePrevChildResults = childResults;
493 QSet< QString > outerScopeExecuted = executed;
494 QMap< QString, QgsProcessingModelChildAlgorithmResult > outerScopeContextChildResult = contextChildResults;
495 if ( dynamic_cast< QgsProcessingModelAlgorithm * >( childAlg.get() ) )
496 {
497 // don't allow results from outer models to "leak" into child models
498 childInputs.clear();
499 childResults.clear();
500 executed.clear();
501 contextChildResults.clear();
502 }
503
504 bool ok = false;
505
506 QThread *modelThread = QThread::currentThread();
507
508 auto prepareOnMainThread = [modelThread, &ok, &childAlg, &childParams, &context, &childAlgorithmFeedback] {
509 Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "QgsProcessingModelAlgorithm::processAlgorithm", "childAlg->prepare() must be run on the main thread" );
510 ok = childAlg->prepare( childParams, context, &childAlgorithmFeedback );
511 context.pushToThread( modelThread );
512 };
513
514 // Make sure we only run prepare steps on the main thread!
515 if ( modelThread == qApp->thread() )
516 ok = childAlg->prepare( childParams, context, &childAlgorithmFeedback );
517 else
518 {
519 context.pushToThread( qApp->thread() );
520// silence false positive leak warning
521#ifndef __clang_analyzer__
522 QMetaObject::invokeMethod( qApp, prepareOnMainThread, Qt::BlockingQueuedConnection );
523#endif
524 }
525
526 Q_ASSERT_X( QThread::currentThread() == context.thread(), "QgsProcessingModelAlgorithm::processAlgorithm", "context was not transferred back to model thread" );
527
528 if ( !ok )
529 {
530 const QString error = ( childAlg->flags() & Qgis::ProcessingAlgorithmFlag::CustomException ) ? QString() : QObject::tr( "Error encountered while running %1" ).arg( child.description() );
531 if ( modelFeedback )
532 {
533 modelFeedback->reportChildPreparationFailure( childId, error );
534 }
535 throw QgsProcessingException( error );
536 }
537
538 if ( modelFeedback )
539 {
540 modelFeedback->reportChildStarted( childId, childParams );
541 }
542
543 QVariantMap results;
544
545 bool runResult = false;
546 try
547 {
548 QgsScopedConnection childProgressConnection;
549 QgsScopedConnection childSourceLoadedConnection;
550 QgsScopedConnection childSinkCountChangedConnection;
551 if ( modelFeedback )
552 {
553 // these are scoped connections -- we only want them to exist for the duration that we're actually running THIS
554 // particular child algorithm
555 childProgressConnection = QObject::connect( &childAlgorithmFeedback, &QgsFeedback::progressChanged, &childAlgorithmFeedback, [&modelFeedback, &childId]( double progress ) {
556 modelFeedback->reportChildProgress( childId, progress );
557 } );
558 childSinkCountChangedConnection
559 = QObject::connect( &childAlgorithmFeedback, &QgsProcessingFeedback::sinkFeatureCountChanged, &childAlgorithmFeedback, [&modelFeedback, &childId]( const QString &sinkId, long long featureCount ) {
560 modelFeedback->reportChildSinkFeatureCountChanged( childId, sinkId, featureCount );
561 } );
562 // note -- this is INTENTIONALLY connected to feedback, not childAlgorithmFeedback!
563 childSourceLoadedConnection = QObject::connect( feedback, &QgsProcessingFeedback::sourceLoaded, feedback, [&modelFeedback, &childId]( const QString &parameterName, long long featureCount ) {
564 modelFeedback->reportChildSourceLoaded( childId, parameterName, featureCount );
565 } );
566 }
567
568 if ( ( childAlg->flags() & Qgis::ProcessingAlgorithmFlag::NoThreading ) && ( QThread::currentThread() != qApp->thread() ) )
569 {
570 // child algorithm run step must be called on main thread
571 bool exceptionFromMainThread = false;
572 auto runOnMainThread = [modelThread, &context, &childAlgorithmFeedback, &results, &childAlg, &childParams, &exceptionFromMainThread, &child, &error, &childResult] {
573 Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "QgsProcessingModelAlgorithm::processAlgorithm", "childAlg->runPrepared() must be run on the main thread" );
574 try
575 {
576 results = childAlg->runPrepared( childParams, context, &childAlgorithmFeedback );
577 }
578 catch ( QgsProcessingException &e )
579 {
580 error = ( childAlg->flags() & Qgis::ProcessingAlgorithmFlag::CustomException ) ? e.what() : QObject::tr( "Error encountered while running %1: %2" ).arg( child.description(), e.what() );
582 exceptionFromMainThread = true;
583 }
584 context.pushToThread( modelThread );
585 };
586
587 if ( feedback && !skipGenericLogging && modelThread != qApp->thread() )
588 feedback->pushWarning( QObject::tr( "Algorithm “%1” cannot be run in a background thread, switching to main thread for this step" ).arg( childAlg->displayName() ) );
589
590 context.pushToThread( qApp->thread() );
591// silence false positive leak warning
592#ifndef __clang_analyzer__
593 QMetaObject::invokeMethod( qApp, runOnMainThread, Qt::BlockingQueuedConnection );
594#endif
595 if ( !exceptionFromMainThread )
596 {
597 runResult = true;
599 }
600 }
601 else
602 {
603 // safe to run on model thread
604 results = childAlg->runPrepared( childParams, context, &childAlgorithmFeedback );
605 runResult = true;
607 }
608 }
609 catch ( QgsProcessingException &e )
610 {
611 error = ( childAlg->flags() & Qgis::ProcessingAlgorithmFlag::CustomException ) ? e.what() : QObject::tr( "Error encountered while running %1: %2" ).arg( child.description(), e.what() );
613 }
614
615 Q_ASSERT_X( QThread::currentThread() == context.thread(), "QgsProcessingModelAlgorithm::processAlgorithm", "context was not transferred back to model thread" );
616
617 QVariantMap ppRes;
618 auto postProcessOnMainThread = [modelThread, &ppRes, &childAlg, &context, &childAlgorithmFeedback, runResult] {
619 Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "QgsProcessingModelAlgorithm::processAlgorithm", "childAlg->postProcess() must be run on the main thread" );
620 ppRes = childAlg->postProcess( context, &childAlgorithmFeedback, runResult );
621 context.pushToThread( modelThread );
622 };
623
624 // Make sure we only run postProcess steps on the main thread!
625 if ( modelThread == qApp->thread() )
626 ppRes = childAlg->postProcess( context, &childAlgorithmFeedback, runResult );
627 else
628 {
629 context.pushToThread( qApp->thread() );
630// silence false positive leak warning
631#ifndef __clang_analyzer__
632 QMetaObject::invokeMethod( qApp, postProcessOnMainThread, Qt::BlockingQueuedConnection );
633#endif
634 }
635
636 Q_ASSERT_X( QThread::currentThread() == context.thread(), "QgsProcessingModelAlgorithm::processAlgorithm", "context was not transferred back to model thread" );
637
638 if ( !ppRes.isEmpty() )
639 results = ppRes;
640
641 if ( dynamic_cast< QgsProcessingModelAlgorithm * >( childAlg.get() ) )
642 {
643 childInputs = outerScopeChildInputs;
644 childResults = outerScopePrevChildResults;
645 executed = outerScopeExecuted;
646 contextChildResults = outerScopeContextChildResult;
647 }
648
649 childResults.insert( childId, results );
650 childResult.setOutputs( results );
651
652 if ( modelFeedback )
653 {
655 modelFeedback->reportChildExecutionFailure( childId, error );
657 {
658 modelFeedback->reportChildProgress( childId, 100 );
659 modelFeedback->reportChildExecutionSuccess( childId, results );
660 }
661 }
662
663 if ( runResult )
664 {
665 if ( feedback && !skipGenericLogging )
666 {
667 const QVariantMap displayOutputs = QgsProcessingUtils::removePointerValuesFromMap( results );
668 QStringList formattedOutputs;
669 for ( auto displayOutputIt = displayOutputs.constBegin(); displayOutputIt != displayOutputs.constEnd(); ++displayOutputIt )
670 {
671 formattedOutputs << u"%1: %2"_s.arg( displayOutputIt.key(), QgsProcessingUtils::variantToPythonLiteral( displayOutputIt.value() ) );
672 ;
673 }
674 feedback->pushInfo( QObject::tr( "Results:" ) );
675 feedback->pushCommandInfo( u"{ %1 }"_s.arg( formattedOutputs.join( ", "_L1 ) ) );
676 }
677
678 // look through child alg's outputs to determine whether any of these should be copied
679 // to the final model outputs
680 const QMap<QString, QgsProcessingModelOutput> outputs = child.modelOutputs();
681 for ( auto outputIt = outputs.constBegin(); outputIt != outputs.constEnd(); ++outputIt )
682 {
683 const int outputSortKey = mOutputOrder.indexOf( u"%1:%2"_s.arg( childId, outputIt->childOutputName() ) );
684 switch ( mInternalVersion )
685 {
686 case QgsProcessingModelAlgorithm::InternalVersion::Version1:
687 finalResults.insert( childId + ':' + outputIt->name(), results.value( outputIt->childOutputName() ) );
688 break;
689 case QgsProcessingModelAlgorithm::InternalVersion::Version2:
690 if ( const QgsProcessingParameterDefinition *modelParam = modelParameterFromChildIdAndOutputName( child.childId(), outputIt.key() ) )
691 {
692 finalResults.insert( modelParam->name(), results.value( outputIt->childOutputName() ) );
693 }
694 break;
695 }
696
697 const QString outputLayer = results.value( outputIt->childOutputName() ).toString();
698 if ( !outputLayer.isEmpty() && context.willLoadLayerOnCompletion( outputLayer ) )
699 {
700 QgsProcessingContext::LayerDetails &details = context.layerToLoadOnCompletionDetails( outputLayer );
701 details.groupName = mOutputGroup;
702 if ( outputSortKey > 0 )
703 details.layerSortKey = outputSortKey;
704 }
705 }
706
707 executed.insert( childId );
708
709 std::function< void( const QString &, const QString & )> pruneAlgorithmBranchRecursive;
710 pruneAlgorithmBranchRecursive = [&]( const QString &id, const QString &branch = QString() ) {
711 const QSet<QString> toPrune = dependentChildAlgorithms( id, branch );
712 for ( const QString &targetId : toPrune )
713 {
714 if ( executed.contains( targetId ) )
715 continue;
716
717 executed.insert( targetId );
718 if ( modelFeedback )
719 {
720 modelFeedback->reportChildPruned( targetId );
721 }
722 pruneAlgorithmBranchRecursive( targetId, branch );
723 }
724 };
725
726 // prune remaining algorithms if they are dependent on a branch from this child which didn't eventuate
727 const QgsProcessingOutputDefinitions outputDefs = childAlg->outputDefinitions();
728 for ( const QgsProcessingOutputDefinition *outputDef : outputDefs )
729 {
730 if ( outputDef->type() == QgsProcessingOutputConditionalBranch::typeName() && !results.value( outputDef->name() ).toBool() )
731 {
732 pruneAlgorithmBranchRecursive( childId, outputDef->name() );
733 }
734 }
735
737 {
738 // check if any dependent algorithms should be canceled based on the outputs of this algorithm run
739 // first find all direct dependencies of this algorithm by looking through all remaining child algorithms
740 for ( const QString &candidateId : std::as_const( toExecute ) )
741 {
742 if ( executed.contains( candidateId ) )
743 continue;
744
745 // a pending algorithm was found..., check it's parameter sources to see if it links to any of the current
746 // algorithm's outputs
747 const QgsProcessingModelChildAlgorithm &candidate = mChildAlgorithms[candidateId];
748 const QMap<QString, QgsProcessingModelChildParameterSources> candidateParams = candidate.parameterSources();
749 QMap<QString, QgsProcessingModelChildParameterSources>::const_iterator paramIt = candidateParams.constBegin();
750 bool pruned = false;
751 for ( ; paramIt != candidateParams.constEnd(); ++paramIt )
752 {
753 for ( const QgsProcessingModelChildParameterSource &source : paramIt.value() )
754 {
755 if ( source.source() == Qgis::ProcessingModelChildParameterSource::ChildOutput && source.outputChildId() == childId )
756 {
757 // ok, this one is dependent on the current alg. Did we get a value for it?
758 if ( !results.contains( source.outputName() ) )
759 {
760 // oh no, nothing returned for this parameter. Gotta trim the branch back!
761 pruned = true;
762 // skip the dependent alg..
763 executed.insert( candidateId );
764 if ( modelFeedback )
765 {
766 modelFeedback->reportChildPruned( candidateId );
767 }
768 //... and everything which depends on it
769 pruneAlgorithmBranchRecursive( candidateId, QString() );
770 break;
771 }
772 }
773 }
774 if ( pruned )
775 break;
776 }
777 }
778 }
779
780 childAlg.reset( nullptr );
781 countExecuted++;
782 childAlgorithmFeedback.setCurrentStep( countExecuted );
783 if ( feedback && !skipGenericLogging )
784 {
785 feedback->pushInfo( QObject::tr( "OK. Execution took %1 s (%n output(s)).", nullptr, results.count() ).arg( childTime.elapsed() / 1000.0 ) );
786 }
787 }
788
789 // trim out just the portion of the overall log which relates to this child
790 const QString thisAlgorithmHtmlLog = feedback ? feedback->htmlLog().mid( previousHtmlLogLength ) : QString();
791 previousHtmlLogLength = feedback ? feedback->htmlLog().length() : 0;
792
793 if ( !runResult )
794 {
795 const QString formattedException = u"<span style=\"color:red\">%1</span><br/>"_s.arg( error.toHtmlEscaped() ).replace( '\n', "<br>"_L1 );
796 const QString formattedRunTime
797 = 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 );
798
799 childResult.setHtmlLog( thisAlgorithmHtmlLog + formattedException + formattedRunTime );
800 context.modelResult().childResults().insert( childId, childResult );
801
802 if ( modelFeedback )
803 {
804 modelFeedback->reportChildResult( childId, childResult );
805 }
806
807 throw QgsProcessingException( error );
808 }
809 else
810 {
811 childResult.setHtmlLog( thisAlgorithmHtmlLog );
812 context.modelResult().childResults().insert( childId, childResult );
813
814 if ( modelFeedback )
815 {
816 modelFeedback->reportChildResult( childId, childResult );
817 }
818 }
819 }
820
821 if ( feedback && feedback->isCanceled() )
822 break;
823 }
824 if ( feedback )
825 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 ) );
826
827 mResults = finalResults;
828 mResults.insert( u"CHILD_RESULTS"_s, childResults );
829 mResults.insert( u"CHILD_INPUTS"_s, childInputs );
830 return mResults;
831}
832
833QString QgsProcessingModelAlgorithm::sourceFilePath() const
834{
835 return mSourceFile;
836}
837
838void QgsProcessingModelAlgorithm::setSourceFilePath( const QString &sourceFile )
839{
840 mSourceFile = sourceFile;
841}
842
843bool QgsProcessingModelAlgorithm::modelNameMatchesFilePath() const
844{
845 if ( mSourceFile.isEmpty() )
846 return false;
847
848 const QFileInfo fi( mSourceFile );
849 return fi.completeBaseName().compare( mModelName, Qt::CaseInsensitive ) == 0;
850}
851
852QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::PythonOutputType outputType, const int indentSize ) const
853{
854 QStringList fileDocString;
855 fileDocString << u"\"\"\""_s;
856 fileDocString << u"Model exported as python."_s;
857 fileDocString << u"Name : %1"_s.arg( displayName() );
858 fileDocString << u"Group : %1"_s.arg( group() );
859 fileDocString << u"With QGIS : %1"_s.arg( Qgis::versionInt() );
860 fileDocString << u"\"\"\""_s;
861 fileDocString << QString();
862
863 QStringList lines;
864 QString indent = QString( ' ' ).repeated( indentSize );
865 QString currentIndent;
866
867 QMap< QString, QString> friendlyChildNames;
868 QMap< QString, QString> friendlyOutputNames;
869 auto uniqueSafeName = []( const QString &name, bool capitalize, const QMap< QString, QString > &friendlyNames ) -> QString {
870 const QString base = safeName( name, capitalize );
871 QString candidate = base;
872 int i = 1;
873 // iterate over friendlyNames value to find candidate
874 while ( std::find( friendlyNames.cbegin(), friendlyNames.cend(), candidate ) != friendlyNames.cend() )
875 {
876 i++;
877 candidate = u"%1_%2"_s.arg( base ).arg( i );
878 }
879 return candidate;
880 };
881
882 const QString algorithmClassName = safeName( name(), true );
883
884 QSet< QString > toExecute;
885 for ( auto childIt = mChildAlgorithms.constBegin(); childIt != mChildAlgorithms.constEnd(); ++childIt )
886 {
887 if ( childIt->isActive() && childIt->algorithm() )
888 {
889 toExecute.insert( childIt->childId() );
890 friendlyChildNames.insert( childIt->childId(), uniqueSafeName( childIt->description().isEmpty() ? childIt->childId() : childIt->description(), !childIt->description().isEmpty(), friendlyChildNames ) );
891 }
892 }
893 const int totalSteps = toExecute.count();
894
895 QStringList importLines; // not a set - we need regular ordering
896 switch ( outputType )
897 {
899 {
900 // add specific parameter type imports
901 const auto params = parameterDefinitions();
902 importLines.reserve( params.count() + 6 );
903 importLines << u"from typing import Any, Optional"_s;
904 importLines << QString();
905 importLines << u"from qgis.core import QgsProcessing"_s;
906 importLines << u"from qgis.core import QgsProcessingAlgorithm"_s;
907 importLines << u"from qgis.core import QgsProcessingContext"_s;
908 importLines << u"from qgis.core import QgsProcessingFeedback, QgsProcessingMultiStepFeedback"_s;
909
910 bool hasAdvancedParams = false;
911 for ( const QgsProcessingParameterDefinition *def : params )
912 {
913 if ( def->flags() & Qgis::ProcessingParameterFlag::Advanced )
914 hasAdvancedParams = true;
915
916 if ( const QgsProcessingParameterType *type = QgsApplication::processingRegistry()->parameterType( def->type() ) )
917 {
918 const QString importString = type->pythonImportString();
919 if ( !importString.isEmpty() && !importLines.contains( importString ) )
920 importLines << importString;
921 }
922 }
923
924 if ( hasAdvancedParams )
925 importLines << u"from qgis.core import QgsProcessingParameterDefinition"_s;
926
927 lines << u"from qgis import processing"_s;
928 lines << QString() << QString();
929
930 lines << u"class %1(QgsProcessingAlgorithm):"_s.arg( algorithmClassName );
931 lines << QString();
932
933 // initAlgorithm, parameter definitions
934 lines << indent + u"def initAlgorithm(self, config: Optional[dict[str, Any]] = None):"_s;
935 if ( params.empty() )
936 {
937 lines << indent + indent + u"pass"_s;
938 }
939 else
940 {
941 lines.reserve( lines.size() + params.size() );
942 for ( const QgsProcessingParameterDefinition *def : params )
943 {
944 std::unique_ptr< QgsProcessingParameterDefinition > defClone( def->clone() );
945
946 if ( defClone->isDestination() )
947 {
948 const QString uniqueChildName = defClone->metadata().value( u"_modelChildId"_s ).toString() + ':' + defClone->metadata().value( u"_modelChildOutputName"_s ).toString();
949 const QString friendlyName = !defClone->description().isEmpty() ? uniqueSafeName( defClone->description(), true, friendlyOutputNames ) : defClone->name();
950 friendlyOutputNames.insert( uniqueChildName, friendlyName );
951 defClone->setName( friendlyName );
952 }
953 else
954 {
955 if ( !mParameterComponents.value( defClone->name() ).comment()->description().isEmpty() )
956 {
957 const QStringList parts = mParameterComponents.value( defClone->name() ).comment()->description().split( u"\n"_s );
958 for ( const QString &part : parts )
959 {
960 lines << indent + indent + u"# %1"_s.arg( part );
961 }
962 }
963 }
964
965 if ( defClone->flags() & Qgis::ProcessingParameterFlag::Advanced )
966 {
967 lines << indent + indent + u"param = %1"_s.arg( defClone->asPythonString() );
968 lines << indent + indent + u"param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)"_s;
969 lines << indent + indent + u"self.addParameter(param)"_s;
970 }
971 else
972 {
973 lines << indent + indent + u"self.addParameter(%1)"_s.arg( defClone->asPythonString() );
974 }
975 }
976 }
977
978 lines << QString();
979 lines << indent + u"def processAlgorithm(self, parameters: dict[str, Any], context: QgsProcessingContext, model_feedback: QgsProcessingFeedback) -> dict[str, Any]:"_s;
980 currentIndent = indent + indent;
981
982 lines << currentIndent + u"# Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the"_s;
983 lines << currentIndent + u"# overall progress through the model"_s;
984 lines << currentIndent + u"feedback = QgsProcessingMultiStepFeedback(%1, model_feedback)"_s.arg( totalSteps );
985 break;
986 }
987#if 0
988 case Script:
989 {
990 QgsStringMap params;
991 QgsProcessingContext context;
992 QMap< QString, QgsProcessingModelParameter >::const_iterator paramIt = mParameterComponents.constBegin();
993 for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt )
994 {
995 QString name = paramIt.value().parameterName();
996 if ( parameterDefinition( name ) )
997 {
998 // TODO - generic value to string method
999 params.insert( name, parameterDefinition( name )->valueAsPythonString( parameterDefinition( name )->defaultValue(), context ) );
1000 }
1001 }
1002
1003 if ( !params.isEmpty() )
1004 {
1005 lines << u"parameters = {"_s;
1006 for ( auto it = params.constBegin(); it != params.constEnd(); ++it )
1007 {
1008 lines << u" '%1':%2,"_s.arg( it.key(), it.value() );
1009 }
1010 lines << u"}"_s
1011 << QString();
1012 }
1013
1014 lines << u"context = QgsProcessingContext()"_s
1015 << u"context.setProject(QgsProject.instance())"_s
1016 << u"feedback = QgsProcessingFeedback()"_s
1017 << QString();
1018
1019 break;
1020 }
1021#endif
1022 }
1023
1024 lines << currentIndent + u"results = {}"_s;
1025 lines << currentIndent + u"outputs = {}"_s;
1026 lines << QString();
1027
1028 QSet< QString > executed;
1029 bool executedAlg = true;
1030 int currentStep = 0;
1031 while ( executedAlg && executed.count() < toExecute.count() )
1032 {
1033 executedAlg = false;
1034 const auto constToExecute = toExecute;
1035 for ( const QString &childId : constToExecute )
1036 {
1037 if ( executed.contains( childId ) )
1038 continue;
1039
1040 bool canExecute = true;
1041 const auto constDependsOnChildAlgorithms = dependsOnChildAlgorithms( childId );
1042 for ( const QString &dependency : constDependsOnChildAlgorithms )
1043 {
1044 if ( !executed.contains( dependency ) )
1045 {
1046 canExecute = false;
1047 break;
1048 }
1049 }
1050
1051 if ( !canExecute )
1052 continue;
1053
1054 executedAlg = true;
1055
1056 const QgsProcessingModelChildAlgorithm &child = mChildAlgorithms[childId];
1057
1058 // fill in temporary outputs
1059 const QgsProcessingParameterDefinitions childDefs = child.algorithm()->parameterDefinitions();
1060 QgsStringMap childParams;
1061 for ( const QgsProcessingParameterDefinition *def : childDefs )
1062 {
1063 if ( def->isDestination() )
1064 {
1065 const QgsProcessingDestinationParameter *destParam = static_cast< const QgsProcessingDestinationParameter * >( def );
1066
1067 // is destination linked to one of the final outputs from this model?
1068 bool isFinalOutput = false;
1069 QMap<QString, QgsProcessingModelOutput> outputs = child.modelOutputs();
1070 QMap<QString, QgsProcessingModelOutput>::const_iterator outputIt = outputs.constBegin();
1071 for ( ; outputIt != outputs.constEnd(); ++outputIt )
1072 {
1073 if ( outputIt->childOutputName() == destParam->name() )
1074 {
1075 QString paramName = child.childId() + ':' + outputIt.key();
1076 paramName = friendlyOutputNames.value( paramName, paramName );
1077 childParams.insert( destParam->name(), u"parameters['%1']"_s.arg( paramName ) );
1078 isFinalOutput = true;
1079 break;
1080 }
1081 }
1082
1083 if ( !isFinalOutput )
1084 {
1085 // output is temporary
1086
1087 // check whether it's optional, and if so - is it required?
1088 bool required = true;
1090 {
1091 required = childOutputIsRequired( child.childId(), destParam->name() );
1092 }
1093
1094 // not optional, or required elsewhere in model
1095 if ( required )
1096 {
1097 childParams.insert( destParam->name(), u"QgsProcessing.TEMPORARY_OUTPUT"_s );
1098 }
1099 }
1100 }
1101 }
1102
1103 lines << child.asPythonCode( outputType, childParams, currentIndent.size(), indentSize, friendlyChildNames, friendlyOutputNames );
1104 currentStep++;
1105 if ( currentStep < totalSteps )
1106 {
1107 lines << QString();
1108 lines << currentIndent + u"feedback.setCurrentStep(%1)"_s.arg( currentStep );
1109 lines << currentIndent + u"if feedback.isCanceled():"_s;
1110 lines << currentIndent + indent + u"return {}"_s;
1111 lines << QString();
1112 }
1113 executed.insert( childId );
1114 }
1115 }
1116
1117 switch ( outputType )
1118 {
1120 lines << currentIndent + u"return results"_s;
1121 lines << QString();
1122
1123 // name, displayName
1124 lines << indent + u"def name(self) -> str:"_s;
1125 lines << indent + indent + u"return '%1'"_s.arg( mModelName );
1126 lines << QString();
1127 lines << indent + u"def displayName(self) -> str:"_s;
1128 lines << indent + indent + u"return '%1'"_s.arg( mModelName );
1129 lines << QString();
1130
1131 // group, groupId
1132 lines << indent + u"def group(self) -> str:"_s;
1133 lines << indent + indent + u"return '%1'"_s.arg( mModelGroup );
1134 lines << QString();
1135 lines << indent + u"def groupId(self) -> str:"_s;
1136 lines << indent + indent + u"return '%1'"_s.arg( mModelGroupId );
1137 lines << QString();
1138
1139 // help
1140 if ( !shortHelpString().isEmpty() )
1141 {
1142 lines << indent + u"def shortHelpString(self) -> str:"_s;
1143 lines << indent + indent + u"return \"\"\"%1\"\"\""_s.arg( shortHelpString() );
1144 lines << QString();
1145 }
1146 if ( !helpUrl().isEmpty() )
1147 {
1148 lines << indent + u"def helpUrl(self) -> str:"_s;
1149 lines << indent + indent + u"return '%1'"_s.arg( helpUrl() );
1150 lines << QString();
1151 }
1152
1153 // createInstance
1154 lines << indent + u"def createInstance(self):"_s;
1155 lines << indent + indent + u"return self.__class__()"_s;
1156
1157 // additional import lines
1158 static QMap< QString, QString > sAdditionalImports {
1159 { u"QgsCoordinateReferenceSystem"_s, u"from qgis.core import QgsCoordinateReferenceSystem"_s },
1160 { u"QgsExpression"_s, u"from qgis.core import QgsExpression"_s },
1161 { u"QgsRectangle"_s, u"from qgis.core import QgsRectangle"_s },
1162 { u"QgsReferencedRectangle"_s, u"from qgis.core import QgsReferencedRectangle"_s },
1163 { u"QgsPoint"_s, u"from qgis.core import QgsPoint"_s },
1164 { u"QgsReferencedPoint"_s, u"from qgis.core import QgsReferencedPoint"_s },
1165 { u"QgsProperty"_s, u"from qgis.core import QgsProperty"_s },
1166 { u"QgsRasterLayer"_s, u"from qgis.core import QgsRasterLayer"_s },
1167 { u"QgsMeshLayer"_s, u"from qgis.core import QgsMeshLayer"_s },
1168 { u"QgsVectorLayer"_s, u"from qgis.core import QgsVectorLayer"_s },
1169 { u"QgsMapLayer"_s, u"from qgis.core import QgsMapLayer"_s },
1170 { u"QgsProcessingFeatureSourceDefinition"_s, u"from qgis.core import QgsProcessingFeatureSourceDefinition"_s },
1171 { u"QgsPointXY"_s, u"from qgis.core import QgsPointXY"_s },
1172 { u"QgsReferencedPointXY"_s, u"from qgis.core import QgsReferencedPointXY"_s },
1173 { u"QgsGeometry"_s, u"from qgis.core import QgsGeometry"_s },
1174 { u"QgsProcessingOutputLayerDefinition"_s, u"from qgis.core import QgsProcessingOutputLayerDefinition"_s },
1175 { u"QColor"_s, u"from qgis.PyQt.QtGui import QColor"_s },
1176 { u"QDateTime"_s, u"from qgis.PyQt.QtCore import QDateTime"_s },
1177 { u"QDate"_s, u"from qgis.PyQt.QtCore import QDate"_s },
1178 { u"QTime"_s, u"from qgis.PyQt.QtCore import QTime"_s },
1179 };
1180
1181 for ( auto it = sAdditionalImports.constBegin(); it != sAdditionalImports.constEnd(); ++it )
1182 {
1183 if ( importLines.contains( it.value() ) )
1184 {
1185 // already got this import
1186 continue;
1187 }
1188
1189 bool found = false;
1190 for ( const QString &line : std::as_const( lines ) )
1191 {
1192 if ( line.contains( it.key() ) )
1193 {
1194 found = true;
1195 break;
1196 }
1197 }
1198 if ( found )
1199 {
1200 importLines << it.value();
1201 }
1202 }
1203
1204 lines = fileDocString + importLines + lines;
1205 break;
1206 }
1207
1208 lines << QString();
1209
1210 return lines;
1211}
1212
1213QMap<QString, QgsProcessingModelAlgorithm::VariableDefinition> QgsProcessingModelAlgorithm::variablesForChildAlgorithm(
1214 const QString &childId, QgsProcessingContext *context, const QVariantMap &modelParameters, const QVariantMap &results
1215) const
1216{
1217 QMap<QString, QgsProcessingModelAlgorithm::VariableDefinition> variables;
1218
1219 auto safeName = []( const QString &name ) -> QString {
1220 QString s = name;
1221 const thread_local QRegularExpression safeNameRe( u"[\\s'\"\\(\\):\\.]"_s );
1222 return s.replace( safeNameRe, u"_"_s );
1223 };
1224
1225 // "static"/single value sources
1226 QgsProcessingModelChildParameterSources sources = availableSourcesForChild(
1227 childId,
1228 QStringList()
1258 );
1259
1260 for ( const QgsProcessingModelChildParameterSource &source : std::as_const( sources ) )
1261 {
1262 QString name;
1263 QVariant value;
1264 QString description;
1265 switch ( source.source() )
1266 {
1268 {
1269 name = source.parameterName();
1270 value = modelParameters.value( source.parameterName() );
1271 description = parameterDefinition( source.parameterName() )->description();
1272 break;
1273 }
1275 {
1276 const QgsProcessingModelChildAlgorithm &child = mChildAlgorithms.value( source.outputChildId() );
1277 name = u"%1_%2"_s.arg( child.description().isEmpty() ? source.outputChildId() : child.description(), source.outputName() );
1278 if ( const QgsProcessingAlgorithm *alg = child.algorithm() )
1279 {
1280 description = QObject::tr( "Output '%1' from algorithm '%2'" ).arg( alg->outputDefinition( source.outputName() )->description(), child.description() );
1281 }
1282 value = results.value( source.outputChildId() ).toMap().value( source.outputName() );
1283 break;
1284 }
1285
1290 continue;
1291 }
1292 variables.insert( safeName( name ), VariableDefinition( value, source, description ) );
1293 }
1294
1295 // layer sources
1296 sources = availableSourcesForChild(
1297 childId,
1300 );
1301
1302 for ( const QgsProcessingModelChildParameterSource &source : std::as_const( sources ) )
1303 {
1304 QString name;
1305 QVariant value;
1306 QString description;
1307
1308 switch ( source.source() )
1309 {
1311 {
1312 name = source.parameterName();
1313 value = modelParameters.value( source.parameterName() );
1314 description = parameterDefinition( source.parameterName() )->description();
1315 break;
1316 }
1318 {
1319 const QgsProcessingModelChildAlgorithm &child = mChildAlgorithms.value( source.outputChildId() );
1320 name = u"%1_%2"_s.arg( child.description().isEmpty() ? source.outputChildId() : child.description(), source.outputName() );
1321 value = results.value( source.outputChildId() ).toMap().value( source.outputName() );
1322 if ( const QgsProcessingAlgorithm *alg = child.algorithm() )
1323 {
1324 description = QObject::tr( "Output '%1' from algorithm '%2'" ).arg( alg->outputDefinition( source.outputName() )->description(), child.description() );
1325 }
1326 break;
1327 }
1328
1333 continue;
1334 }
1335
1336 if ( value.userType() == qMetaTypeId<QgsProcessingOutputLayerDefinition>() )
1337 {
1338 QgsProcessingOutputLayerDefinition fromVar = qvariant_cast<QgsProcessingOutputLayerDefinition>( value );
1339 value = fromVar.sink;
1340 if ( value.userType() == qMetaTypeId<QgsProperty>() && context )
1341 {
1342 value = value.value< QgsProperty >().valueAsString( context->expressionContext() );
1343 }
1344 }
1345 QgsMapLayer *layer = nullptr;
1346 if ( context )
1347 {
1348 layer = qobject_cast< QgsMapLayer * >( qvariant_cast<QObject *>( value ) );
1349 if ( !layer )
1350 layer = QgsProcessingUtils::mapLayerFromString( value.toString(), *context );
1351 }
1352
1353 variables.insert( safeName( name ), VariableDefinition( layer ? QVariant::fromValue( QgsWeakMapLayerPointer( layer ) ) : QVariant(), source, description ) );
1354 variables.insert( safeName( u"%1_minx"_s.arg( name ) ), VariableDefinition( layer ? layer->extent().xMinimum() : QVariant(), source, QObject::tr( "Minimum X of %1" ).arg( description ) ) );
1355 variables.insert( safeName( u"%1_miny"_s.arg( name ) ), VariableDefinition( layer ? layer->extent().yMinimum() : QVariant(), source, QObject::tr( "Minimum Y of %1" ).arg( description ) ) );
1356 variables.insert( safeName( u"%1_maxx"_s.arg( name ) ), VariableDefinition( layer ? layer->extent().xMaximum() : QVariant(), source, QObject::tr( "Maximum X of %1" ).arg( description ) ) );
1357 variables.insert( safeName( u"%1_maxy"_s.arg( name ) ), VariableDefinition( layer ? layer->extent().yMaximum() : QVariant(), source, QObject::tr( "Maximum Y of %1" ).arg( description ) ) );
1358 }
1359
1360 sources = availableSourcesForChild( childId, QStringList() << QgsProcessingParameterFeatureSource::typeName() );
1361 for ( const QgsProcessingModelChildParameterSource &source : std::as_const( sources ) )
1362 {
1363 QString name;
1364 QVariant value;
1365 QString description;
1366
1367 switch ( source.source() )
1368 {
1370 {
1371 name = source.parameterName();
1372 value = modelParameters.value( source.parameterName() );
1373 description = parameterDefinition( source.parameterName() )->description();
1374 break;
1375 }
1377 {
1378 const QgsProcessingModelChildAlgorithm &child = mChildAlgorithms.value( source.outputChildId() );
1379 name = u"%1_%2"_s.arg( child.description().isEmpty() ? source.outputChildId() : child.description(), source.outputName() );
1380 value = results.value( source.outputChildId() ).toMap().value( source.outputName() );
1381 if ( const QgsProcessingAlgorithm *alg = child.algorithm() )
1382 {
1383 description = QObject::tr( "Output '%1' from algorithm '%2'" ).arg( alg->outputDefinition( source.outputName() )->description(), child.description() );
1384 }
1385 break;
1386 }
1387
1392 continue;
1393 }
1394
1395 QgsFeatureSource *featureSource = nullptr;
1396 if ( value.userType() == qMetaTypeId<QgsProcessingFeatureSourceDefinition>() )
1397 {
1398 QgsProcessingFeatureSourceDefinition fromVar = qvariant_cast<QgsProcessingFeatureSourceDefinition>( value );
1399 value = fromVar.source;
1400 }
1401 else if ( value.userType() == qMetaTypeId<QgsProcessingOutputLayerDefinition>() )
1402 {
1403 QgsProcessingOutputLayerDefinition fromVar = qvariant_cast<QgsProcessingOutputLayerDefinition>( value );
1404 value = fromVar.sink;
1405 if ( context && value.userType() == qMetaTypeId<QgsProperty>() )
1406 {
1407 value = value.value< QgsProperty >().valueAsString( context->expressionContext() );
1408 }
1409 }
1410 if ( QgsVectorLayer *layer = qobject_cast< QgsVectorLayer * >( qvariant_cast<QObject *>( value ) ) )
1411 {
1412 featureSource = layer;
1413 }
1414 if ( context && !featureSource )
1415 {
1416 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( value.toString(), *context, true, QgsProcessingUtils::LayerHint::Vector ) ) )
1417 featureSource = vl;
1418 }
1419
1420 variables.insert( safeName( name ), VariableDefinition( value, source, description ) );
1421 variables.insert( safeName( u"%1_minx"_s.arg( name ) ), VariableDefinition( featureSource ? featureSource->sourceExtent().xMinimum() : QVariant(), source, QObject::tr( "Minimum X of %1" ).arg( description ) ) );
1422 variables.insert( safeName( u"%1_miny"_s.arg( name ) ), VariableDefinition( featureSource ? featureSource->sourceExtent().yMinimum() : QVariant(), source, QObject::tr( "Minimum Y of %1" ).arg( description ) ) );
1423 variables.insert( safeName( u"%1_maxx"_s.arg( name ) ), VariableDefinition( featureSource ? featureSource->sourceExtent().xMaximum() : QVariant(), source, QObject::tr( "Maximum X of %1" ).arg( description ) ) );
1424 variables.insert( safeName( u"%1_maxy"_s.arg( name ) ), VariableDefinition( featureSource ? featureSource->sourceExtent().yMaximum() : QVariant(), source, QObject::tr( "Maximum Y of %1" ).arg( description ) ) );
1425 }
1426
1427 return variables;
1428}
1429
1430QgsExpressionContextScope *QgsProcessingModelAlgorithm::createExpressionContextScopeForChildAlgorithm(
1431 const QString &childId, QgsProcessingContext &context, const QVariantMap &modelParameters, const QVariantMap &results
1432) const
1433{
1434 auto scope = std::make_unique<QgsExpressionContextScope>( u"algorithm_inputs"_s );
1435 QMap< QString, QgsProcessingModelAlgorithm::VariableDefinition> variables = variablesForChildAlgorithm( childId, &context, modelParameters, results );
1436 QMap< QString, QgsProcessingModelAlgorithm::VariableDefinition>::const_iterator varIt = variables.constBegin();
1437 for ( ; varIt != variables.constEnd(); ++varIt )
1438 {
1439 scope->addVariable( QgsExpressionContextScope::StaticVariable( varIt.key(), varIt->value, true, false, varIt->description ) );
1440 }
1441 return scope.release();
1442}
1443
1444QgsProcessingModelChildParameterSources QgsProcessingModelAlgorithm::availableSourcesForChild( const QString &childId, const QgsProcessingParameterDefinition *param ) const
1445{
1447 if ( !paramType )
1448 return QgsProcessingModelChildParameterSources();
1449 return availableSourcesForChild( childId, paramType->acceptedParameterTypes(), paramType->acceptedOutputTypes(), paramType->acceptedDataTypes( param ) );
1450}
1451
1452QgsProcessingModelChildParameterSources QgsProcessingModelAlgorithm::availableSourcesForChild(
1453 const QString &childId, const QStringList &parameterTypes, const QStringList &outputTypes, const QList<int> &dataTypes
1454) const
1455{
1456 QgsProcessingModelChildParameterSources sources;
1457
1458 // first look through model parameters
1459 QMap< QString, QgsProcessingModelParameter >::const_iterator paramIt = mParameterComponents.constBegin();
1460 for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt )
1461 {
1462 const QgsProcessingParameterDefinition *def = parameterDefinition( paramIt->parameterName() );
1463 if ( !def )
1464 continue;
1465
1466 if ( parameterTypes.contains( def->type() ) )
1467 {
1468 if ( !dataTypes.isEmpty() )
1469 {
1471 {
1472 const QgsProcessingParameterField *fieldDef = static_cast< const QgsProcessingParameterField * >( def );
1473 if ( !( dataTypes.contains( static_cast< int >( fieldDef->dataType() ) ) || fieldDef->dataType() == Qgis::ProcessingFieldParameterDataType::Any ) )
1474 {
1475 continue;
1476 }
1477 }
1479 {
1480 const QgsProcessingParameterLimitedDataTypes *sourceDef = dynamic_cast< const QgsProcessingParameterLimitedDataTypes *>( def );
1481 if ( !sourceDef )
1482 continue;
1483
1484 bool ok = sourceDef->dataTypes().isEmpty();
1485 const auto constDataTypes = sourceDef->dataTypes();
1486 for ( int type : constDataTypes )
1487 {
1488 if ( dataTypes.contains( type )
1489 || type == static_cast< int >( Qgis::ProcessingSourceType::MapLayer )
1490 || type == static_cast< int >( Qgis::ProcessingSourceType::Vector )
1491 || type == static_cast< int >( Qgis::ProcessingSourceType::VectorAnyGeometry ) )
1492 {
1493 ok = true;
1494 break;
1495 }
1496 }
1497 if ( dataTypes.contains( static_cast< int >( Qgis::ProcessingSourceType::MapLayer ) )
1498 || dataTypes.contains( static_cast< int >( Qgis::ProcessingSourceType::Vector ) )
1499 || dataTypes.contains( static_cast< int >( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) )
1500 ok = true;
1501
1502 if ( !ok )
1503 continue;
1504 }
1505 }
1506 sources << QgsProcessingModelChildParameterSource::fromModelParameter( paramIt->parameterName() );
1507 }
1508 }
1509
1510 QSet< QString > dependents;
1511 if ( !childId.isEmpty() )
1512 {
1513 dependents = dependentChildAlgorithms( childId );
1514 dependents << childId;
1515 }
1516
1517 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
1518 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
1519 {
1520 if ( dependents.contains( childIt->childId() ) )
1521 continue;
1522
1523 const QgsProcessingAlgorithm *alg = childIt->algorithm();
1524 if ( !alg )
1525 continue;
1526
1527 const auto constOutputDefinitions = alg->outputDefinitions();
1528 for ( const QgsProcessingOutputDefinition *out : constOutputDefinitions )
1529 {
1530 if ( outputTypes.contains( out->type() ) )
1531 {
1532 if ( !dataTypes.isEmpty() )
1533 {
1534 if ( out->type() == QgsProcessingOutputVectorLayer::typeName() )
1535 {
1536 const QgsProcessingOutputVectorLayer *vectorOut = static_cast< const QgsProcessingOutputVectorLayer *>( out );
1537
1538 if ( !vectorOutputIsCompatibleType( dataTypes, vectorOut->dataType() ) )
1539 {
1540 //unacceptable output
1541 continue;
1542 }
1543 }
1544 }
1545 sources << QgsProcessingModelChildParameterSource::fromChildOutput( childIt->childId(), out->name() );
1546 }
1547 }
1548 }
1549
1550 return sources;
1551}
1552
1553QVariantMap QgsProcessingModelAlgorithm::helpContent() const
1554{
1555 return mHelpContent;
1556}
1557
1558void QgsProcessingModelAlgorithm::setHelpContent( const QVariantMap &helpContent )
1559{
1560 mHelpContent = helpContent;
1561}
1562
1563void QgsProcessingModelAlgorithm::setName( const QString &name )
1564{
1565 mModelName = name;
1566}
1567
1568void QgsProcessingModelAlgorithm::setGroup( const QString &group )
1569{
1570 mModelGroup = group;
1571}
1572
1573bool QgsProcessingModelAlgorithm::validate( QStringList &issues ) const
1574{
1575 issues.clear();
1576 bool res = true;
1577
1578 if ( mChildAlgorithms.empty() )
1579 {
1580 res = false;
1581 issues << QObject::tr( "Model does not contain any algorithms" );
1582 }
1583
1584 for ( auto it = mChildAlgorithms.constBegin(); it != mChildAlgorithms.constEnd(); ++it )
1585 {
1586 QStringList childIssues;
1587 res = validateChildAlgorithm( it->childId(), childIssues ) && res;
1588
1589 for ( const QString &issue : std::as_const( childIssues ) )
1590 {
1591 issues << u"<b>%1</b>: %2"_s.arg( it->description(), issue );
1592 }
1593 }
1594 return res;
1595}
1596
1597QMap<QString, QgsProcessingModelChildAlgorithm> QgsProcessingModelAlgorithm::childAlgorithms() const
1598{
1599 return mChildAlgorithms;
1600}
1601
1602void QgsProcessingModelAlgorithm::setParameterComponents( const QMap<QString, QgsProcessingModelParameter> &parameterComponents )
1603{
1604 mParameterComponents = parameterComponents;
1605}
1606
1607void QgsProcessingModelAlgorithm::setParameterComponent( const QgsProcessingModelParameter &component )
1608{
1609 mParameterComponents.insert( component.parameterName(), component );
1610}
1611
1612QgsProcessingModelParameter &QgsProcessingModelAlgorithm::parameterComponent( const QString &name )
1613{
1614 if ( !mParameterComponents.contains( name ) )
1615 {
1616 QgsProcessingModelParameter &component = mParameterComponents[name];
1617 component.setParameterName( name );
1618 return component;
1619 }
1620 return mParameterComponents[name];
1621}
1622
1623QList< QgsProcessingModelParameter > QgsProcessingModelAlgorithm::orderedParameters() const
1624{
1625 QList< QgsProcessingModelParameter > res;
1626 QSet< QString > found;
1627 for ( const QString &parameter : mParameterOrder )
1628 {
1629 if ( mParameterComponents.contains( parameter ) )
1630 {
1631 res << mParameterComponents.value( parameter );
1632 found << parameter;
1633 }
1634 }
1635
1636 // add any missing ones to end of list
1637 for ( auto it = mParameterComponents.constBegin(); it != mParameterComponents.constEnd(); ++it )
1638 {
1639 if ( !found.contains( it.key() ) )
1640 {
1641 res << it.value();
1642 }
1643 }
1644 return res;
1645}
1646
1647void QgsProcessingModelAlgorithm::setParameterOrder( const QStringList &order )
1648{
1649 mParameterOrder = order;
1650}
1651
1652QList<QgsProcessingModelOutput> QgsProcessingModelAlgorithm::orderedOutputs() const
1653{
1654 QList< QgsProcessingModelOutput > res;
1655 QSet< QString > found;
1656
1657 for ( const QString &output : mOutputOrder )
1658 {
1659 bool foundOutput = false;
1660 for ( auto it = mChildAlgorithms.constBegin(); it != mChildAlgorithms.constEnd(); ++it )
1661 {
1662 const QMap<QString, QgsProcessingModelOutput> outputs = it.value().modelOutputs();
1663 for ( auto outputIt = outputs.constBegin(); outputIt != outputs.constEnd(); ++outputIt )
1664 {
1665 if ( output == u"%1:%2"_s.arg( outputIt->childId(), outputIt->childOutputName() ) )
1666 {
1667 res << outputIt.value();
1668 foundOutput = true;
1669 found.insert( u"%1:%2"_s.arg( outputIt->childId(), outputIt->childOutputName() ) );
1670 }
1671 }
1672 if ( foundOutput )
1673 break;
1674 }
1675 }
1676
1677 // add any missing ones to end of list
1678 for ( auto it = mChildAlgorithms.constBegin(); it != mChildAlgorithms.constEnd(); ++it )
1679 {
1680 const QMap<QString, QgsProcessingModelOutput> outputs = it.value().modelOutputs();
1681 for ( auto outputIt = outputs.constBegin(); outputIt != outputs.constEnd(); ++outputIt )
1682 {
1683 if ( !found.contains( u"%1:%2"_s.arg( outputIt->childId(), outputIt->childOutputName() ) ) )
1684 {
1685 res << outputIt.value();
1686 }
1687 }
1688 }
1689
1690 return res;
1691}
1692
1693void QgsProcessingModelAlgorithm::setOutputOrder( const QStringList &order )
1694{
1695 mOutputOrder = order;
1696}
1697
1698QString QgsProcessingModelAlgorithm::outputGroup() const
1699{
1700 return mOutputGroup;
1701}
1702
1703void QgsProcessingModelAlgorithm::setOutputGroup( const QString &group )
1704{
1705 mOutputGroup = group;
1706}
1707
1708void QgsProcessingModelAlgorithm::updateDestinationParameters()
1709{
1710 //delete existing destination parameters
1711 QMutableListIterator<const QgsProcessingParameterDefinition *> it( mParameters );
1712 while ( it.hasNext() )
1713 {
1714 const QgsProcessingParameterDefinition *def = it.next();
1715 if ( def->isDestination() )
1716 {
1717 delete def;
1718 it.remove();
1719 }
1720 }
1721 // also delete outputs
1722 qDeleteAll( mOutputs );
1723 mOutputs.clear();
1724
1725 // rebuild
1726 QSet< QString > usedFriendlyNames;
1727 auto uniqueSafeName = [&usedFriendlyNames]( const QString &name ) -> QString {
1728 const QString base = safeName( name, false );
1729 QString candidate = base;
1730 int i = 1;
1731 while ( usedFriendlyNames.contains( candidate ) )
1732 {
1733 i++;
1734 candidate = u"%1_%2"_s.arg( base ).arg( i );
1735 }
1736 usedFriendlyNames.insert( candidate );
1737 return candidate;
1738 };
1739
1740 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
1741 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
1742 {
1743 QMap<QString, QgsProcessingModelOutput> outputs = childIt->modelOutputs();
1744 QMap<QString, QgsProcessingModelOutput>::const_iterator outputIt = outputs.constBegin();
1745 for ( ; outputIt != outputs.constEnd(); ++outputIt )
1746 {
1747 if ( !childIt->isActive() || !childIt->algorithm() )
1748 continue;
1749
1750 // child algorithm has a destination parameter set, copy it to the model
1751 const QgsProcessingParameterDefinition *source = childIt->algorithm()->parameterDefinition( outputIt->childOutputName() );
1752 if ( !source )
1753 continue;
1754
1755 std::unique_ptr< QgsProcessingParameterDefinition > param( source->clone() );
1756 // Even if an output was hidden in a child algorithm, we want to show it here for the final
1757 // outputs.
1758 param->setFlags( param->flags() & ~static_cast< int >( Qgis::ProcessingParameterFlag::Hidden ) );
1759 if ( outputIt->isMandatory() )
1760 param->setFlags( param->flags() & ~static_cast< int >( Qgis::ProcessingParameterFlag::Optional ) );
1761 if ( mInternalVersion != InternalVersion::Version1 && !outputIt->description().isEmpty() )
1762 {
1763 QString friendlyName = uniqueSafeName( outputIt->description() );
1764 param->setName( friendlyName );
1765 }
1766 else
1767 {
1768 param->setName( outputIt->childId() + ':' + outputIt->name() );
1769 }
1770 // add some metadata so we can easily link this parameter back to the child source
1771 param->metadata().insert( u"_modelChildId"_s, outputIt->childId() );
1772 param->metadata().insert( u"_modelChildOutputName"_s, outputIt->name() );
1773 param->metadata().insert( u"_modelChildProvider"_s, childIt->algorithm()->provider() ? childIt->algorithm()->provider()->id() : QString() );
1774
1775 param->setDescription( outputIt->description() );
1776 param->setDefaultValue( outputIt->defaultValue() );
1777
1778 QgsProcessingDestinationParameter *newDestParam = dynamic_cast< QgsProcessingDestinationParameter * >( param.get() );
1779 if ( addParameter( param.release() ) && newDestParam )
1780 {
1781 if ( QgsProcessingProvider *provider = childIt->algorithm()->provider() )
1782 {
1783 // we need to copy the constraints given by the provider which creates this output across
1784 // and replace those which have been set to match the model provider's constraints
1785 newDestParam->setSupportsNonFileBasedOutput( provider->supportsNonFileBasedOutput() );
1786 newDestParam->mOriginalProvider = provider;
1787 }
1788 }
1789 }
1790 }
1791}
1792
1793void QgsProcessingModelAlgorithm::addGroupBox( const QgsProcessingModelGroupBox &groupBox )
1794{
1795 mGroupBoxes.insert( groupBox.uuid(), groupBox );
1796}
1797
1798QList<QgsProcessingModelGroupBox> QgsProcessingModelAlgorithm::groupBoxes() const
1799{
1800 return mGroupBoxes.values();
1801}
1802
1803void QgsProcessingModelAlgorithm::removeGroupBox( const QString &uuid )
1804{
1805 mGroupBoxes.remove( uuid );
1806}
1807
1808QVariant QgsProcessingModelAlgorithm::toVariant() const
1809{
1810 QVariantMap map;
1811 map.insert( u"model_name"_s, mModelName );
1812 map.insert( u"model_group"_s, mModelGroup );
1813 map.insert( u"help"_s, mHelpContent );
1814 map.insert( u"internal_version"_s, qgsEnumValueToKey( mInternalVersion ) );
1815
1816 QVariantMap childMap;
1817 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
1818 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
1819 {
1820 childMap.insert( childIt.key(), childIt.value().toVariant() );
1821 }
1822 map.insert( u"children"_s, childMap );
1823
1824 QVariantMap paramMap;
1825 QMap< QString, QgsProcessingModelParameter >::const_iterator paramIt = mParameterComponents.constBegin();
1826 for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt )
1827 {
1828 paramMap.insert( paramIt.key(), paramIt.value().toVariant() );
1829 }
1830 map.insert( u"parameters"_s, paramMap );
1831
1832 QVariantMap paramDefMap;
1833 for ( const QgsProcessingParameterDefinition *def : mParameters )
1834 {
1835 paramDefMap.insert( def->name(), def->toVariantMap() );
1836 }
1837 map.insert( u"parameterDefinitions"_s, paramDefMap );
1838
1839 QVariantList groupBoxDefs;
1840 for ( auto it = mGroupBoxes.constBegin(); it != mGroupBoxes.constEnd(); ++it )
1841 {
1842 groupBoxDefs.append( it.value().toVariant() );
1843 }
1844 map.insert( u"groupBoxes"_s, groupBoxDefs );
1845
1846 map.insert( u"modelVariables"_s, mVariables );
1847
1848 map.insert( u"designerParameterValues"_s, mDesignerParameterValues );
1849
1850 map.insert( u"parameterOrder"_s, mParameterOrder );
1851 map.insert( u"outputOrder"_s, mOutputOrder );
1852 map.insert( u"outputGroup"_s, mOutputGroup );
1853
1854 return map;
1855}
1856
1857bool QgsProcessingModelAlgorithm::loadVariant( const QVariant &model )
1858{
1859 QVariantMap map = model.toMap();
1860
1861 mModelName = map.value( u"model_name"_s ).toString();
1862 mModelGroup = map.value( u"model_group"_s ).toString();
1863 mModelGroupId = map.value( u"model_group"_s ).toString();
1864 mHelpContent = map.value( u"help"_s ).toMap();
1865
1866 mInternalVersion = qgsEnumKeyToValue( map.value( u"internal_version"_s ).toString(), InternalVersion::Version1 );
1867
1868 mVariables = map.value( u"modelVariables"_s ).toMap();
1869 mDesignerParameterValues = map.value( u"designerParameterValues"_s ).toMap();
1870
1871 mParameterOrder = map.value( u"parameterOrder"_s ).toStringList();
1872 mOutputOrder = map.value( u"outputOrder"_s ).toStringList();
1873 mOutputGroup = map.value( u"outputGroup"_s ).toString();
1874
1875 mChildAlgorithms.clear();
1876 QVariantMap childMap = map.value( u"children"_s ).toMap();
1877 QVariantMap::const_iterator childIt = childMap.constBegin();
1878 for ( ; childIt != childMap.constEnd(); ++childIt )
1879 {
1880 QgsProcessingModelChildAlgorithm child;
1881 // we be lenient here - even if we couldn't load a parameter, don't interrupt the model loading
1882 // otherwise models may become unusable (e.g. due to removed plugins providing algs/parameters)
1883 // with no way for users to repair them
1884 if ( !child.loadVariant( childIt.value() ) )
1885 continue;
1886
1887 mChildAlgorithms.insert( child.childId(), child );
1888 }
1889
1890 mParameterComponents.clear();
1891 QVariantMap paramMap = map.value( u"parameters"_s ).toMap();
1892 QVariantMap::const_iterator paramIt = paramMap.constBegin();
1893 for ( ; paramIt != paramMap.constEnd(); ++paramIt )
1894 {
1895 QgsProcessingModelParameter param;
1896 if ( !param.loadVariant( paramIt.value().toMap() ) )
1897 return false;
1898
1899 mParameterComponents.insert( param.parameterName(), param );
1900 }
1901
1902 qDeleteAll( mParameters );
1903 mParameters.clear();
1904 QVariantMap paramDefMap = map.value( u"parameterDefinitions"_s ).toMap();
1905
1906 auto addParam = [this]( const QVariant &value ) {
1907 std::unique_ptr< QgsProcessingParameterDefinition > param( QgsProcessingParameters::parameterFromVariantMap( value.toMap() ) );
1908 // we be lenient here - even if we couldn't load a parameter, don't interrupt the model loading
1909 // otherwise models may become unusable (e.g. due to removed plugins providing algs/parameters)
1910 // with no way for users to repair them
1911 if ( param )
1912 {
1913 if ( param->name() == "VERBOSE_LOG"_L1 )
1914 return; // internal parameter -- some versions of QGIS incorrectly stored this in the model definition file
1915
1916 // set parameter help from help content
1917 param->setHelp( mHelpContent.value( param->name() ).toString() );
1918
1919 // add parameter
1920 addParameter( param.release() );
1921 }
1922 else
1923 {
1924 QVariantMap map = value.toMap();
1925 QString type = map.value( u"parameter_type"_s ).toString();
1926 QString name = map.value( u"name"_s ).toString();
1927
1928 QgsMessageLog::logMessage( QCoreApplication::translate( "Processing", "Could not load parameter %1 of type %2." ).arg( name, type ), QCoreApplication::translate( "Processing", "Processing" ) );
1929 }
1930 };
1931
1932 QSet< QString > loadedParams;
1933 // first add parameters respecting mParameterOrder
1934 for ( const QString &name : std::as_const( mParameterOrder ) )
1935 {
1936 if ( paramDefMap.contains( name ) )
1937 {
1938 addParam( paramDefMap.value( name ) );
1939 loadedParams << name;
1940 }
1941 }
1942 // then load any remaining parameters
1943 QVariantMap::const_iterator paramDefIt = paramDefMap.constBegin();
1944 for ( ; paramDefIt != paramDefMap.constEnd(); ++paramDefIt )
1945 {
1946 if ( !loadedParams.contains( paramDefIt.key() ) )
1947 addParam( paramDefIt.value() );
1948 }
1949
1950 mGroupBoxes.clear();
1951 const QVariantList groupBoxList = map.value( u"groupBoxes"_s ).toList();
1952 for ( const QVariant &groupBoxDef : groupBoxList )
1953 {
1954 QgsProcessingModelGroupBox groupBox;
1955 groupBox.loadVariant( groupBoxDef.toMap() );
1956 mGroupBoxes.insert( groupBox.uuid(), groupBox );
1957 }
1958
1959 updateDestinationParameters();
1960
1961 return true;
1962}
1963
1964bool QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( const QList<int> &acceptableDataTypes, Qgis::ProcessingSourceType outputType )
1965{
1966 // This method is intended to be "permissive" rather than "restrictive".
1967 // I.e. we only reject outputs which we know can NEVER be acceptable, but
1968 // if there's doubt then we default to returning true.
1969 return (
1970 acceptableDataTypes.empty()
1971 || acceptableDataTypes.contains( static_cast< int >( outputType ) )
1973 || outputType == Qgis::ProcessingSourceType::Vector
1975 || acceptableDataTypes.contains( static_cast< int >( Qgis::ProcessingSourceType::Vector ) )
1976 || acceptableDataTypes.contains( static_cast< int >( Qgis::ProcessingSourceType::MapLayer ) )
1977 || ( acceptableDataTypes.contains( static_cast< int >( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) && ( outputType == Qgis::ProcessingSourceType::VectorPoint || outputType == Qgis::ProcessingSourceType::VectorLine || outputType == Qgis::ProcessingSourceType::VectorPolygon ) )
1978 );
1979}
1980
1981void QgsProcessingModelAlgorithm::reattachAlgorithms() const
1982{
1983 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
1984 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
1985 {
1986 if ( !childIt->algorithm() )
1987 childIt->reattach();
1988 }
1989}
1990
1991bool QgsProcessingModelAlgorithm::toFile( const QString &path ) const
1992{
1993 QDomDocument doc = QDomDocument( u"model"_s );
1994 QDomElement elem = QgsXmlUtils::writeVariant( toVariant(), doc );
1995 doc.appendChild( elem );
1996
1997 QFile file( path );
1998 if ( file.open( QFile::WriteOnly | QFile::Truncate ) )
1999 {
2000 QTextStream stream( &file );
2001 doc.save( stream, 2 );
2002 file.close();
2003 return true;
2004 }
2005 return false;
2006}
2007
2008bool QgsProcessingModelAlgorithm::fromFile( const QString &path )
2009{
2010 QDomDocument doc;
2011
2012 QFile file( path );
2013 if ( file.open( QFile::ReadOnly ) )
2014 {
2015 if ( !doc.setContent( &file ) )
2016 return false;
2017
2018 file.close();
2019 }
2020 else
2021 {
2022 return false;
2023 }
2024
2025 QVariant props = QgsXmlUtils::readVariant( doc.firstChildElement() );
2026 return loadVariant( props );
2027}
2028
2029void QgsProcessingModelAlgorithm::setChildAlgorithms( const QMap<QString, QgsProcessingModelChildAlgorithm> &childAlgorithms )
2030{
2031 mChildAlgorithms = childAlgorithms;
2032 updateDestinationParameters();
2033}
2034
2035void QgsProcessingModelAlgorithm::setChildAlgorithm( const QgsProcessingModelChildAlgorithm &algorithm )
2036{
2037 mChildAlgorithms.insert( algorithm.childId(), algorithm );
2038 updateDestinationParameters();
2039}
2040
2041QString QgsProcessingModelAlgorithm::addChildAlgorithm( QgsProcessingModelChildAlgorithm &algorithm )
2042{
2043 if ( algorithm.childId().isEmpty() || mChildAlgorithms.contains( algorithm.childId() ) )
2044 algorithm.generateChildId( *this );
2045
2046 mChildAlgorithms.insert( algorithm.childId(), algorithm );
2047 updateDestinationParameters();
2048 return algorithm.childId();
2049}
2050
2051QgsProcessingModelChildAlgorithm &QgsProcessingModelAlgorithm::childAlgorithm( const QString &childId )
2052{
2053 return mChildAlgorithms[childId];
2054}
2055
2056bool QgsProcessingModelAlgorithm::removeChildAlgorithm( const QString &id )
2057{
2058 if ( !dependentChildAlgorithms( id ).isEmpty() )
2059 return false;
2060
2061 mChildAlgorithms.remove( id );
2062 updateDestinationParameters();
2063 return true;
2064}
2065
2066void QgsProcessingModelAlgorithm::deactivateChildAlgorithm( const QString &id )
2067{
2068 const auto constDependentChildAlgorithms = dependentChildAlgorithms( id );
2069 for ( const QString &child : constDependentChildAlgorithms )
2070 {
2071 childAlgorithm( child ).setActive( false );
2072 }
2073 childAlgorithm( id ).setActive( false );
2074 updateDestinationParameters();
2075}
2076
2077bool QgsProcessingModelAlgorithm::activateChildAlgorithm( const QString &id )
2078{
2079 const auto constDependsOnChildAlgorithms = dependsOnChildAlgorithms( id );
2080 for ( const QString &child : constDependsOnChildAlgorithms )
2081 {
2082 if ( !childAlgorithm( child ).isActive() )
2083 return false;
2084 }
2085 childAlgorithm( id ).setActive( true );
2086 updateDestinationParameters();
2087 return true;
2088}
2089
2090void QgsProcessingModelAlgorithm::addModelParameter( QgsProcessingParameterDefinition *definition, const QgsProcessingModelParameter &component )
2091{
2092 if ( addParameter( definition ) )
2093 mParameterComponents.insert( definition->name(), component );
2094}
2095
2096void QgsProcessingModelAlgorithm::updateModelParameter( QgsProcessingParameterDefinition *definition )
2097{
2098 removeParameter( definition->name() );
2099 addParameter( definition );
2100}
2101
2102void QgsProcessingModelAlgorithm::removeModelParameter( const QString &name )
2103{
2104 removeParameter( name );
2105 mParameterComponents.remove( name );
2106}
2107
2108void QgsProcessingModelAlgorithm::changeParameterName( const QString &oldName, const QString &newName )
2109{
2110 QgsProcessingContext context;
2111 QgsExpressionContext expressionContext = createExpressionContext( QVariantMap(), context );
2112
2113 auto replaceExpressionVariable = [oldName, newName, &expressionContext]( const QString &expressionString ) -> std::tuple< bool, QString > {
2114 QgsExpression expression( expressionString );
2115 expression.prepare( &expressionContext );
2116 QSet<QString> variables = expression.referencedVariables();
2117 if ( variables.contains( oldName ) )
2118 {
2119 QString newExpression = expressionString;
2120 newExpression.replace( u"@%1"_s.arg( oldName ), u"@%2"_s.arg( newName ) );
2121 return { true, newExpression };
2122 }
2123 return { false, QString() };
2124 };
2125
2126 QMap< QString, QgsProcessingModelChildAlgorithm >::iterator childIt = mChildAlgorithms.begin();
2127 for ( ; childIt != mChildAlgorithms.end(); ++childIt )
2128 {
2129 bool changed = false;
2130 QMap<QString, QgsProcessingModelChildParameterSources> childParams = childIt->parameterSources();
2131 QMap<QString, QgsProcessingModelChildParameterSources>::iterator paramIt = childParams.begin();
2132 for ( ; paramIt != childParams.end(); ++paramIt )
2133 {
2134 QList< QgsProcessingModelChildParameterSource > &value = paramIt.value();
2135 for ( auto valueIt = value.begin(); valueIt != value.end(); ++valueIt )
2136 {
2137 switch ( valueIt->source() )
2138 {
2140 {
2141 if ( valueIt->parameterName() == oldName )
2142 {
2143 valueIt->setParameterName( newName );
2144 changed = true;
2145 }
2146 break;
2147 }
2148
2150 {
2151 bool updatedExpression = false;
2152 QString newExpression;
2153 std::tie( updatedExpression, newExpression ) = replaceExpressionVariable( valueIt->expression() );
2154 if ( updatedExpression )
2155 {
2156 valueIt->setExpression( newExpression );
2157 changed = true;
2158 }
2159 break;
2160 }
2161
2163 {
2164 if ( valueIt->staticValue().userType() == qMetaTypeId<QgsProperty>() )
2165 {
2166 QgsProperty property = valueIt->staticValue().value< QgsProperty >();
2167 if ( property.propertyType() == Qgis::PropertyType::Expression )
2168 {
2169 bool updatedExpression = false;
2170 QString newExpression;
2171 std::tie( updatedExpression, newExpression ) = replaceExpressionVariable( property.expressionString() );
2172 if ( updatedExpression )
2173 {
2174 property.setExpressionString( newExpression );
2175 valueIt->setStaticValue( property );
2176 changed = true;
2177 }
2178 }
2179 }
2180 break;
2181 }
2182
2186 break;
2187 }
2188 }
2189 }
2190 if ( changed )
2191 childIt->setParameterSources( childParams );
2192 }
2193}
2194
2195bool QgsProcessingModelAlgorithm::childAlgorithmsDependOnParameter( const QString &name ) const
2196{
2197 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
2198 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
2199 {
2200 // check whether child requires this parameter
2201 QMap<QString, QgsProcessingModelChildParameterSources> childParams = childIt->parameterSources();
2202 QMap<QString, QgsProcessingModelChildParameterSources>::const_iterator paramIt = childParams.constBegin();
2203 for ( ; paramIt != childParams.constEnd(); ++paramIt )
2204 {
2205 const auto constValue = paramIt.value();
2206 for ( const QgsProcessingModelChildParameterSource &source : constValue )
2207 {
2208 if ( source.source() == Qgis::ProcessingModelChildParameterSource::ModelParameter && source.parameterName() == name )
2209 {
2210 return true;
2211 }
2212 }
2213 }
2214 }
2215 return false;
2216}
2217
2218bool QgsProcessingModelAlgorithm::otherParametersDependOnParameter( const QString &name ) const
2219{
2220 const auto constMParameters = mParameters;
2221 for ( const QgsProcessingParameterDefinition *def : constMParameters )
2222 {
2223 if ( def->name() == name )
2224 continue;
2225
2226 if ( def->dependsOnOtherParameters().contains( name ) )
2227 return true;
2228 }
2229 return false;
2230}
2231
2232QMap<QString, QgsProcessingModelParameter> QgsProcessingModelAlgorithm::parameterComponents() const
2233{
2234 return mParameterComponents;
2235}
2236
2237void QgsProcessingModelAlgorithm::dependentChildAlgorithmsRecursive( const QString &childId, QSet<QString> &depends, const QString &branch ) const
2238{
2239 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
2240 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
2241 {
2242 if ( depends.contains( childIt->childId() ) )
2243 continue;
2244
2245 // does alg have a direct dependency on this child?
2246 const QList< QgsProcessingModelChildDependency > constDependencies = childIt->dependencies();
2247 bool hasDependency = false;
2248 for ( const QgsProcessingModelChildDependency &dep : constDependencies )
2249 {
2250 if ( dep.childId == childId && ( branch.isEmpty() || dep.conditionalBranch == branch ) )
2251 {
2252 hasDependency = true;
2253 break;
2254 }
2255 }
2256
2257 if ( hasDependency )
2258 {
2259 depends.insert( childIt->childId() );
2260 dependentChildAlgorithmsRecursive( childIt->childId(), depends, branch );
2261 continue;
2262 }
2263
2264 // check whether child requires any outputs from the target alg
2265 QMap<QString, QgsProcessingModelChildParameterSources> childParams = childIt->parameterSources();
2266 QMap<QString, QgsProcessingModelChildParameterSources>::const_iterator paramIt = childParams.constBegin();
2267 for ( ; paramIt != childParams.constEnd(); ++paramIt )
2268 {
2269 const auto constValue = paramIt.value();
2270 for ( const QgsProcessingModelChildParameterSource &source : constValue )
2271 {
2272 if ( source.source() == Qgis::ProcessingModelChildParameterSource::ChildOutput && source.outputChildId() == childId )
2273 {
2274 depends.insert( childIt->childId() );
2275 dependentChildAlgorithmsRecursive( childIt->childId(), depends, branch );
2276 break;
2277 }
2278 }
2279 }
2280 }
2281}
2282
2283QSet<QString> QgsProcessingModelAlgorithm::dependentChildAlgorithms( const QString &childId, const QString &conditionalBranch ) const
2284{
2285 QSet< QString > algs;
2286
2287 // temporarily insert the target child algorithm to avoid
2288 // unnecessarily recursion though it
2289 algs.insert( childId );
2290
2291 dependentChildAlgorithmsRecursive( childId, algs, conditionalBranch );
2292
2293 // remove temporary target alg
2294 algs.remove( childId );
2295
2296 return algs;
2297}
2298
2299
2300void QgsProcessingModelAlgorithm::dependsOnChildAlgorithmsRecursive( const QString &childId, QSet< QString > &depends ) const
2301{
2302 const QgsProcessingModelChildAlgorithm &alg = mChildAlgorithms.value( childId );
2303
2304 // add direct dependencies
2305 const QList< QgsProcessingModelChildDependency > constDependencies = alg.dependencies();
2306 for ( const QgsProcessingModelChildDependency &val : constDependencies )
2307 {
2308 if ( !depends.contains( val.childId ) )
2309 {
2310 depends.insert( val.childId );
2311 dependsOnChildAlgorithmsRecursive( val.childId, depends );
2312 }
2313 }
2314
2315 // check through parameter dependencies
2316 QMap<QString, QgsProcessingModelChildParameterSources> childParams = alg.parameterSources();
2317 QMap<QString, QgsProcessingModelChildParameterSources>::const_iterator paramIt = childParams.constBegin();
2318 for ( ; paramIt != childParams.constEnd(); ++paramIt )
2319 {
2320 const auto constValue = paramIt.value();
2321 for ( const QgsProcessingModelChildParameterSource &source : constValue )
2322 {
2323 switch ( source.source() )
2324 {
2326 if ( !depends.contains( source.outputChildId() ) )
2327 {
2328 depends.insert( source.outputChildId() );
2329 dependsOnChildAlgorithmsRecursive( source.outputChildId(), depends );
2330 }
2331 break;
2332
2334 {
2335 const QgsExpression exp( source.expression() );
2336 const QSet<QString> vars = exp.referencedVariables();
2337 if ( vars.empty() )
2338 break;
2339
2340 // find the source of referenced variables and check if it's another child algorithm
2341 const QMap<QString, QgsProcessingModelAlgorithm::VariableDefinition> availableVariables = variablesForChildAlgorithm( childId );
2342 for ( auto childVarIt = availableVariables.constBegin(); childVarIt != availableVariables.constEnd(); ++childVarIt )
2343 {
2344 // we're only looking here for variables coming from other child algorithm outputs
2345 if ( childVarIt->source.source() != Qgis::ProcessingModelChildParameterSource::ChildOutput )
2346 continue;
2347
2348 if ( !vars.contains( childVarIt.key() ) || depends.contains( childVarIt->source.outputChildId() ) )
2349 continue;
2350
2351 // this variable is required for the child's expression, so the corresponding algorithm must be run first
2352 depends.insert( childVarIt->source.outputChildId() );
2353 dependsOnChildAlgorithmsRecursive( childVarIt->source.outputChildId(), depends );
2354 }
2355 break;
2356 }
2357
2362 break;
2363 }
2364 }
2365 }
2366}
2367
2368QSet< QString > QgsProcessingModelAlgorithm::dependsOnChildAlgorithms( const QString &childId ) const
2369{
2370 QSet< QString > algs;
2371
2372 // temporarily insert the target child algorithm to avoid
2373 // unnecessarily recursion though it
2374 algs.insert( childId );
2375
2376 dependsOnChildAlgorithmsRecursive( childId, algs );
2377
2378 // remove temporary target alg
2379 algs.remove( childId );
2380
2381 return algs;
2382}
2383
2384QList<QgsProcessingModelChildDependency> QgsProcessingModelAlgorithm::availableDependenciesForChildAlgorithm( const QString &childId ) const
2385{
2386 QSet< QString > dependent;
2387 if ( !childId.isEmpty() )
2388 {
2389 dependent.unite( dependentChildAlgorithms( childId ) );
2390 dependent.insert( childId );
2391 }
2392
2393 QList<QgsProcessingModelChildDependency> res;
2394 for ( auto it = mChildAlgorithms.constBegin(); it != mChildAlgorithms.constEnd(); ++it )
2395 {
2396 if ( !dependent.contains( it->childId() ) )
2397 {
2398 // check first if algorithm provides output branches
2399 bool hasBranches = false;
2400 if ( it->algorithm() )
2401 {
2402 const QgsProcessingOutputDefinitions defs = it->algorithm()->outputDefinitions();
2403 for ( const QgsProcessingOutputDefinition *def : defs )
2404 {
2406 {
2407 hasBranches = true;
2408 QgsProcessingModelChildDependency alg;
2409 alg.childId = it->childId();
2410 alg.conditionalBranch = def->name();
2411 res << alg;
2412 }
2413 }
2414 }
2415
2416 if ( !hasBranches )
2417 {
2418 QgsProcessingModelChildDependency alg;
2419 alg.childId = it->childId();
2420 res << alg;
2421 }
2422 }
2423 }
2424 return res;
2425}
2426
2427bool QgsProcessingModelAlgorithm::validateChildAlgorithm( const QString &childId, QStringList &issues ) const
2428{
2429 issues.clear();
2430 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constFind( childId );
2431 if ( childIt != mChildAlgorithms.constEnd() )
2432 {
2433 if ( !childIt->algorithm() )
2434 {
2435 issues << QObject::tr( "Algorithm is not available: <i>%1</i>" ).arg( childIt->algorithmId() );
2436 return false;
2437 }
2438 bool res = true;
2439
2440 // loop through child algorithm parameters and check that they are all valid
2441 const QgsProcessingParameterDefinitions defs = childIt->algorithm()->parameterDefinitions();
2442 for ( const QgsProcessingParameterDefinition *def : defs )
2443 {
2444 if ( childIt->parameterSources().contains( def->name() ) )
2445 {
2446 // is the value acceptable?
2447 const QList< QgsProcessingModelChildParameterSource > sources = childIt->parameterSources().value( def->name() );
2448 for ( const QgsProcessingModelChildParameterSource &source : sources )
2449 {
2450 switch ( source.source() )
2451 {
2453 if ( !def->checkValueIsAcceptable( source.staticValue() ) )
2454 {
2455 res = false;
2456 issues << QObject::tr( "Value for <i>%1</i> is not acceptable for this parameter" ).arg( def->name() );
2457 }
2458 break;
2459
2461 if ( !parameterComponents().contains( source.parameterName() ) )
2462 {
2463 res = false;
2464 issues << QObject::tr( "Model input <i>%1</i> used for parameter <i>%2</i> does not exist" ).arg( source.parameterName(), def->name() );
2465 }
2466 break;
2467
2469 if ( !childAlgorithms().contains( source.outputChildId() ) )
2470 {
2471 res = false;
2472 issues << QObject::tr( "Child algorithm <i>%1</i> used for parameter <i>%2</i> does not exist" ).arg( source.outputChildId(), def->name() );
2473 }
2474 break;
2475
2479 break;
2480 }
2481 }
2482 }
2483 else
2484 {
2485 // not specified. Is it optional?
2486
2487 // ignore destination parameters -- they shouldn't ever be mandatory
2488 if ( def->isDestination() )
2489 continue;
2490
2491 if ( !def->checkValueIsAcceptable( QVariant() ) )
2492 {
2493 res = false;
2494 issues << QObject::tr( "Parameter <i>%1</i> is mandatory" ).arg( def->name() );
2495 }
2496 }
2497 }
2498
2499 return res;
2500 }
2501 else
2502 {
2503 issues << QObject::tr( "Invalid child ID: <i>%1</i>" ).arg( childId );
2504 return false;
2505 }
2506}
2507
2508bool QgsProcessingModelAlgorithm::canExecute( QString *errorMessage ) const
2509{
2510 reattachAlgorithms();
2511 QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
2512 for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
2513 {
2514 if ( !childIt->algorithm() )
2515 {
2516 if ( errorMessage )
2517 {
2518 *errorMessage = QObject::tr( "The model you are trying to run contains an algorithm that is not available: <i>%1</i>" ).arg( childIt->algorithmId() );
2519 }
2520 return false;
2521 }
2522 }
2523 return true;
2524}
2525
2526QString QgsProcessingModelAlgorithm::asPythonCommand( const QVariantMap &parameters, QgsProcessingContext &context ) const
2527{
2528 if ( mSourceFile.isEmpty() )
2529 return QString(); // temporary model - can't run as python command
2530
2531 return QgsProcessingAlgorithm::asPythonCommand( parameters, context );
2532}
2533
2534QgsExpressionContext QgsProcessingModelAlgorithm::createExpressionContext( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeatureSource *source ) const
2535{
2536 QgsExpressionContext res = QgsProcessingAlgorithm::createExpressionContext( parameters, context, source );
2537 res << QgsExpressionContextUtils::processingModelAlgorithmScope( this, parameters, context );
2538 return res;
2539}
2540
2541QgsProcessingAlgorithm *QgsProcessingModelAlgorithm::createInstance() const
2542{
2543 QgsProcessingModelAlgorithm *alg = new QgsProcessingModelAlgorithm();
2544 alg->loadVariant( toVariant() );
2545 alg->setProvider( provider() );
2546 alg->setSourceFilePath( sourceFilePath() );
2547 return alg;
2548}
2549
2550QString QgsProcessingModelAlgorithm::safeName( const QString &name, bool capitalize )
2551{
2552 QString n = name.toLower().trimmed();
2553 const thread_local QRegularExpression rx( u"[^\\sa-z_A-Z0-9]"_s );
2554 n.replace( rx, QString() );
2555 const thread_local QRegularExpression rx2( u"^\\d*"_s ); // name can't start in a digit
2556 n.replace( rx2, QString() );
2557 if ( !capitalize )
2558 n = n.replace( ' ', '_' );
2560}
2561
2562QVariantMap QgsProcessingModelAlgorithm::variables() const
2563{
2564 return mVariables;
2565}
2566
2567void QgsProcessingModelAlgorithm::setVariables( const QVariantMap &variables )
2568{
2569 mVariables = variables;
2570}
2571
2572QVariantMap QgsProcessingModelAlgorithm::designerParameterValues() const
2573{
2574 return mDesignerParameterValues;
2575}
2576
ProcessingSourceType
Processing data source types.
Definition qgis.h:3747
@ Vector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition qgis.h:3755
@ MapLayer
Any map layer type (raster, vector, mesh, point cloud, annotation or plugin layer).
Definition qgis.h:3748
@ VectorAnyGeometry
Any vector layer with geometry.
Definition qgis.h:3749
@ VectorPoint
Vector point layers.
Definition qgis.h:3750
@ VectorPolygon
Vector polygon layers.
Definition qgis.h:3752
@ VectorLine
Vector line layers.
Definition qgis.h:3751
@ Success
Child was successfully executed.
Definition qgis.h:4084
@ Failed
Child encountered an error while executing.
Definition qgis.h:4085
@ 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:3612
QFlags< ProcessingAlgorithmFlag > ProcessingAlgorithmFlags
Flags indicating how and when an algorithm operates and should be exposed to users.
Definition qgis.h:3826
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:4071
@ ModelOutput
Parameter value is linked to an output parameter for the model.
Definition qgis.h:4072
@ ChildOutput
Parameter value is taken from an output generated by a child algorithm.
Definition qgis.h:4068
@ ModelParameter
Parameter value is taken from a parent model parameter.
Definition qgis.h:4067
@ StaticValue
Parameter value is a static value.
Definition qgis.h:4069
@ Expression
Parameter value is taken from an expression, evaluated just before the algorithm runs.
Definition qgis.h:4070
@ SkipGenericModelLogging
When running as part of a model, the generic algorithm setup and results logging should be skipped.
Definition qgis.h:3811
@ CustomException
Algorithm raises custom exception notices, don't use the standard ones.
Definition qgis.h:3809
@ NoThreading
Algorithm is not thread safe and cannot be run in a background thread, e.g. for algorithms which mani...
Definition qgis.h:3805
@ PruneModelBranchesBasedOnAlgorithmResults
Algorithm results will cause remaining model branches to be pruned based on the results of running th...
Definition qgis.h:3810
@ SecurityRisk
The algorithm represents a potential security risk if executed with untrusted inputs.
Definition qgis.h:3814
@ Hidden
Parameter is hidden and should not be shown to users.
Definition qgis.h:3983
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3982
@ Optional
Parameter is optional.
Definition qgis.h:3984
@ DefaultLevel
Default logging level.
Definition qgis.h:3874
@ Verbose
Verbose logging.
Definition qgis.h:3875
@ ModelDebug
Model debug level logging. Includes verbose logging and other outputs useful for debugging models.
Definition qgis.h:3876
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
Single scope for storing variables and functions for use within a QgsExpressionContext.
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:7717
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:8060
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7698
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:7603
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:8059
QMap< QString, QString > QgsStringMap
Definition qgis.h:8031
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.