2

Vector layer created via qgis script and persisted as follows:

alg_params = {
 'FIELDS': [],
 'INPUT': input_layer,
 'OUTPUT': outputfile
 }
outputs['Dissolved'] = processing.run('native:dissolve', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
new_layer = QgsVectorLayer(outputfile, "testlayer", "ogr")
QgsProject.instance().addMapLayer(new_layer)

Layer appears in the project as expected. However, when I select the layer and then attempt to select one of the features, the feature does not appear to be selected (doesn't change to yellow) until I move the map canvas (at which time the feature goes yellow).

Other odd behavior with this layer as well - the save layer edits tool does not activate when I edit the layer and change one of the features - but asks if I want to save the edits when I turn off the edit mode.

And all this behavior resolves once I save/close/reload the project (so there is nothing wrong with the layer being created).

Is there something else I should trigger (some event) once I load the newly created layer that would mimic what happens when the layer is loaded to the project during startup?

QGIS 3.40.10 (was also occuring on 3.34.11).

asked Sep 10 at 7:59

1 Answer 1

4

The problem is that you are modifying the project in the processAlgorithm() method of your custom processing script. Processing algorithms are run in a background thread by default. Manually adding layers created in a background thread to the project (which lives in the main thread) inside the processAlgorithm() method will 100% cause the issues you describe.

The best solution is to use an output parameter e.g. QgsProcessingParameterVectorDestination which you can pass to the 'OUTPUT' parameter of the child algorithm, and let the processing framework take care of properly handling the resulting layer.

Note that you can set a default value for the output parameter so that the widget will be pre-filled with your 'hard-coded' path.

from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing, QgsProcessingAlgorithm, QgsProcessingParameterVectorLayer,
 QgsProcessingParameterVectorDestination)
import processing
 
class ExAlgo(QgsProcessingAlgorithm):
 INPUT = 'INPUT'
 OUTPUT = 'OUTPUT'
 
 def __init__(self):
 super().__init__()
 
 def name(self):
 return "exalgo"
 
 def displayName(self):
 return "Example script"
 
 def group(self):
 return "Examples"
 
 def groupId(self):
 return "examples"
 
 def shortHelpString(self):
 return "Example script without logic"
 
 def helpUrl(self):
 return "https://qgis.org"
 
 def createInstance(self):
 return type(self)()
 
 def initAlgorithm(self, config=None):
 self.addParameter(QgsProcessingParameterVectorLayer(
 self.INPUT,
 "Input layer",
 [QgsProcessing.TypeVectorPolygon]))
 
 self.addParameter(QgsProcessingParameterVectorDestination(
 self.OUTPUT,
 'Output layer',
 QgsProcessing.TypeVectorPolygon,
 defaultValue=r'C:\Path\To\Some_folder\test_layer.gpkg'))
 
 def processAlgorithm(self, parameters, context, feedback):
 input_layer = self.parameterAsVectorLayer(parameters, self.INPUT, context)
 alg_params = {
 'FIELDS': [],
 'INPUT': input_layer,
 'OUTPUT': parameters[self.OUTPUT]
 }
 result = processing.run('native:dissolve',
 alg_params,
 context=context,
 feedback=feedback,
 is_child_algorithm=True)
 dest_id = result['OUTPUT']
 if context.willLoadLayerOnCompletion(dest_id):
 details = context.layerToLoadOnCompletionDetails(dest_id)
 details.name = 'test layer'
 details.forceName = True
 return {'OUTPUT': dest_id}

If you want to rename the output layer, instead of having the default e.g. ('Dissolved') you can use this approach:

from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing, QgsProcessingAlgorithm, QgsProcessingParameterVectorLayer,
 QgsProcessingParameterVectorDestination)
import processing
 
class ExAlgo(QgsProcessingAlgorithm):
 INPUT = 'INPUT'
 OUTPUT = 'OUTPUT'
 
 def __init__(self):
 super().__init__()
 
 def name(self):
 return "exalgo"
 
 def displayName(self):
 return "Example script"
 
 def group(self):
 return "Examples"
 
 def groupId(self):
 return "examples"
 
 def shortHelpString(self):
 return "Example script without logic"
 
 def helpUrl(self):
 return "https://qgis.org"
 
 def createInstance(self):
 return type(self)()
 
 def initAlgorithm(self, config=None):
 self.addParameter(QgsProcessingParameterVectorLayer(
 self.INPUT,
 "Input layer",
 [QgsProcessing.TypeVectorPolygon]))
 
 self.addParameter(QgsProcessingParameterVectorDestination(
 self.OUTPUT,
 'Output layer',
 QgsProcessing.TypeVectorPolygon,
 defaultValue=r'C:\Path\To\Some_folder\test_layer.gpkg'))
 
 def processAlgorithm(self, parameters, context, feedback):
 input_layer = self.parameterAsVectorLayer(parameters, self.INPUT, context)
 alg_params = {
 'FIELDS': [],
 'INPUT': input_layer,
 'OUTPUT': parameters[self.OUTPUT]
 }
 result = processing.run('native:dissolve',
 alg_params,
 context=context,
 feedback=feedback,
 is_child_algorithm=True)
 dest_id = result['OUTPUT']
 if context.willLoadLayerOnCompletion(dest_id):
 details = context.layerToLoadOnCompletionDetails(dest_id)
 details.name = 'test layer'
 details.forceName = True
 return {'OUTPUT': dest_id}

If you really want to hard code the loading of an output layer, you can do it like this without manually adding it to the project with the problematic project.addMapLayer() call. You should also declare the output in the initAlgorithm() method e.g.

self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, "Output layer"))

*Note: if you want a permanent file output, make sure you pass a hardcoded output path to the 'OUTPUT' parameter of the dissolve child algorithm.

from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing, QgsProcessingAlgorithm, QgsProcessingParameterVectorLayer,
 QgsProcessingContext, QgsFeatureRequest, QgsProcessingOutputVectorLayer)
import processing
 
class ExAlgo(QgsProcessingAlgorithm):
 INPUT = 'INPUT'
 OUTPUT = 'OUTPUT'
 
 def __init__(self):
 super().__init__()
 
 def name(self):
 return "exalgo"
 
 def displayName(self):
 return "Example script"
 
 def group(self):
 return "Examples"
 
 def groupId(self):
 return "examples"
 
 def shortHelpString(self):
 return "Example script without logic"
 
 def helpUrl(self):
 return "https://qgis.org"
 
 def createInstance(self):
 return type(self)()
 
 def initAlgorithm(self, config=None):
 self.addParameter(QgsProcessingParameterVectorLayer(
 self.INPUT,
 "Input layer",
 [QgsProcessing.TypeVectorPolygon]))
 
 self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, "Output layer"))
 
 def processAlgorithm(self, parameters, context, feedback):
 input_layer = self.parameterAsVectorLayer(parameters, self.INPUT, context)
 alg_params = {
 'FIELDS': [],
 'INPUT': input_layer,
 'OUTPUT': r'C:\Example\Path\Some_folder\test_layer.gpkg'
 }
 result = processing.run('native:dissolve',
 alg_params,
 context=context,
 feedback=feedback,
 is_child_algorithm=True)
 dest_id = result['OUTPUT']
 details = QgsProcessingContext.LayerDetails('test layer', context.project(), 'test layer')
 details.forceName = True
 context.addLayerToLoadOnCompletion(dest_id, details)
 output_layer = context.getMapLayer(dest_id)
 return {'OUTPUT': output_layer}

And finally, if you don't want to implement any of the above options and just want to make your existing code work, you can disable threading in your algorithm by overriding your algorithm's flags() method:

def flags(self):
 return Qgis.ProcessingAlgorithmFlag.NoThreading

Note that prior to QGIS 3.36, this would be:

def flags(self):
 return QgsProcessingAlgorithm.FlagNoThreading

See my answer to a similar question here:

QGIS Processing script causes intermittent crashes, visual gaps in layer tree, and delayed layer visibility

answered Sep 10 at 11:51
4
  • Great options Ben. The reason I was hard coding the output was to programmatically define the output layer name and file location (placed in the folder of the qgis project) rather than requiring the user to set the file location via a parameter. I've had a play with the above options that may allow that. Option 3 doesn't persist the file (maybe I'm doing something wrong there - it just leaves it as a temporary layer). Commented Sep 12 at 0:39
  • And option 4 (disabling threading) seems to do what I want with the exception that when the script runs it jumps back to the Parameters tab on the script rather than showing the log (which I would prefer as the user may miss errors in processing that I may want to report). I'll continue to test out the options to get Option 3 to work as I think that gets me where I want to be. Commented Sep 12 at 0:41
  • @Danny, re option 3: If you want a permanent file output, make sure you pass your hardcoded output path to the 'OUTPUT' parameter of the child algorithm. See the updated code in my answer. Commented Sep 12 at 1:26
  • @Danny, I have also updated the first 2 options to suggest that you use a default value on the vector destination parameter to pre-fill the widget with a 'hard-coded' path. Commented Sep 12 at 6:18

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.