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