2

Working from the Python console in QGIS, I am trying to create an 'animation' of sorts by selecting one line after another in a layer with parallel lines, with a pause on each line. I've started from some code in PyQGIS cookbook. The problem is that the display doesn't update until the script is complete.

import time
from qgis.core import *
import qgis.utils
layer = iface.activeLayer()
features = layer.getFeatures()
    
for feature in features:
    layer.select(feature.id())
    print("Feature ID: ", feature.id())
    iface.mapCanvas().refresh()
    time.sleep(0.1)
    layer.deselect(feature.id())

If I remove the deselect line from the above I'm left with all lines selected at the end, with it in there's no change at all.

In this post ( Can QGIS map canvas update while python script is running?) it is suggested to use threads but it is an old post and the context is different so I'm not sure it is relevant.

I am hoping to eventually build an interactive plugin, so although this seems like an artificial (ie frivolous) requirement, it is an important step in my learning.

I'm running QGIS 3.23.3.

1 Answer 1

3

This can be achieved by using a QTimer instead of time.sleep.

layer = iface.activeLayer()

# instantiate a Qtimer() object and set its interval
timer = QTimer()
timer.setInterval(300) # milliseconds

# create a dictionary with integers `0 -> number of features` as keys and feature ids as values.
# this is not strictly necessary but will allow for non-sequential FIDs
fids = [x.id() for x in layer.getFeatures()]
fid_dict = dict(zip(range(0, len(fids) - 1), fids))

# initialise a variable to store the current index
idx = 0

# the function that will be called on the `timeout` signal of the QTimer 
def my_function():
    # clear any currently selected features
    layer.removeSelection()

    # reference the idx variable defined earlier
    global idx

    # call the function as long as there are features left 
    if idx <= max(fid_dict.keys()):
        # make a selection by getting the feature id from the dictionary using the index
        layer.select(fid_dict[idx])
        
        print("Feature ID: ", fid_dict[idx])

        # refreshing the canvas is not necessary for this use case, but it might needed if you decide to zoom to the feature, or change the symbology of the layer within the function
        # iface.mapCanvas().refresh()

        # increment the index
        idx += 1
    else:
        # stop the QTimer when there are no features left to select
        timer.stop()

# connect the function to the timeout signal of the QTimer
timer.timeout.connect(my_function)

# start the QTimer
timer.start()

To stop the timer early, call:

timer.stop()

enter image description here

1
  • Thanks @Matt this works. I notice, though, that reducing the timer interval doesn't necessarily speed it up. At 100 msecs it's ok, at 50 it's jerky and below that I'm back to not getting any screen updates. I uncommented the refresh command as a test, and that made it even worse
    – marcp
    Feb 12 at 15:43

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.