4

Version: 2.18, and I cannot upgrade to 3.

I am adapting a script I found elsewhere on stackexchange which is supposed to indefinitely refresh all layers in the TOC at one-second intervals. This is the code as I execute it:

from threading import Timer
import qgis.core

class RepeatedTimer(object):

    def __init__(self, interval, function, *args, **kwargs):
        self._timer = None
        self.interval = interval
        self.function = function
        self.args = args
        self.kwargs = kwargs
        self.is_running = False
        self.start()

    def _run(self):
        self.is_running = False
        self.start()
        self.function(*self.args, **self.kwargs)

    def start(self):
        if not self.is_running:
            self._timer = Timer(self.interval, self._run)
            self._timer.start()
            self.is_running = True

    def stop(self):
        self._timer.cancel()
        self.is_running = False


def repainit(_iface):

    print("refreshing")
    for layer in _iface.legendInterface().layers():
        if layer.type() == QgsMapLayer.VectorLayer:
            print(layer.name())
            layer.triggerRepaint()

But when I try to get it going with:

rt = RepeatedTimer(1, repainit, iface)

The entire application crashes (qgis-bin.exe has stopped working) after printing the first layer name. Does this have something to do with something not being thread safe here? I don't see anything that would be.

I even tried this (more hacky) method, but it doesn't actually seem to update the layers at all, because the database driving them is updating but the changes are not being reflected.

import qgis.core
import time

reservoir = [lyr for lyr in QgsMapLayerRegistry.instance().mapLayers().values() if lyr.name() == "utn9_ntw_feat_device_storage.geom"][0]
controller = [lyr for lyr in QgsMapLayerRegistry.instance().mapLayers().values() if lyr.name() == "utn9_ntw_feat_device_controller.geom"][0]
roundpipe = [lyr for lyr in QgsMapLayerRegistry.instance().mapLayers().values() if lyr.name() == "utn9_ntw_feat_distrib_elem_pipe_round.geom"][0]

i=0
while i < 30:
    i+=1
    reservoir.triggerRepaint()
    controller.triggerRepaint()
    roundpipe.triggerRepaint()
    iface.mapCanvas().refresh()
    time.sleep(1)
    print(i)

UPDATE #1:

I have made some progress, the following script actually allows me to target specific layers. But it behaves very strangely. It runs indefinitely, but at some point spits out that a minidump file has been written to the C drive. If i just ignore this error, the layers continue refreshing as normal, but I am forced to quit QGIS if I click "OK" on this error.

from threading import Timer
import qgis.core

reservoir = [lyr for lyr in QgsMapLayerRegistry.instance().mapLayers().values() if lyr.name() == "utn9_ntw_feat_device_storage.geom"][0]
controller = [lyr for lyr in QgsMapLayerRegistry.instance().mapLayers().values() if lyr.name() == "utn9_ntw_feat_device_controller.geom"][0]
roundpipe = [lyr for lyr in QgsMapLayerRegistry.instance().mapLayers().values() if lyr.name() == "utn9_ntw_feat_distrib_elem_pipe_round.geom"][0]

class RepeatedTimer(object):

    def __init__(self, interval, function, *args, **kwargs):
        self._timer = None
        self.interval = interval
        self.function = function
        self.args = args
        self.kwargs = kwargs
        self.is_running = False
        self.start()

    def _run(self):
        self.is_running = False
        self.start()
        self.function(*self.args, **self.kwargs)

    def start(self):
        if not self.is_running:
            self._timer = Timer(self.interval, self._run)
            self._timer.start()
            self.is_running = True

    def stop(self):
        self._timer.cancel()
        self.is_running = False

def repainit(_iface, _layer):
    layer = _layer
    if layer:
        layer.triggerRepaint()
        print "repaint {}".format(layer)

rt_r = RepeatedTimer(1, repainit, iface, reservoir)
rt_c = RepeatedTimer(1, repainit, iface, controller)
rt_rp = RepeatedTimer(1, repainit, iface, roundpipe)
#rt.stop()

UPDATE #2

I haven't solved the problem of iterating dynamically across all layers in a loop, nor have I been able to get the canvas itself to refresh, also refreshing all layers. What I have been able to do however is modify the original script to refresh select layers, which I have named

  • utn9_ntw_feat_device_storage
  • utn9_ntw_feat_device_controller
  • utn9_ntw_feat_device_distrib_elem_pipe_round

Like so:

from threading import Timer
import qgis.core

reservoir = [lyr for lyr in QgsMapLayerRegistry.instance().mapLayers().values() if lyr.name() == "utn9_ntw_feat_device_storage"][0]
controller = [lyr for lyr in QgsMapLayerRegistry.instance().mapLayers().values() if lyr.name() == "utn9_ntw_feat_device_controller"][0]
roundpipe = [lyr for lyr in QgsMapLayerRegistry.instance().mapLayers().values() if lyr.name() == "utn9_ntw_feat_distrib_elem_pipe_round"][0]

class RepeatedTimer(object):

    def __init__(self, interval, function, *args, **kwargs):
        self._timer = None
        self.interval = interval
        self.function = function
        self.args = args
        self.kwargs = kwargs
        self.is_running = False
        self.start()

    def _run(self):
        self.is_running = False
        self.start()
        self.function(*self.args, **self.kwargs)

    def start(self):
        if not self.is_running:
            self._timer = Timer(self.interval, self._run)
            self._timer.start()
            self.is_running = True

    def stop(self):
        self._timer.cancel()
        self.is_running = False

def repainit():
    reservoir.triggerRepaint()
    controller.triggerRepaint()
    roundpipe.triggerRepaint()

rt = RepeatedTimer(1, repainit)
#rt.stop()

I have let it run for about 20 minutes now and no crash has occurred.

I am not marking this as solved because I was looking for a way to refresh all layers in table of contents, and looping through them seems to cause a crash. This is an OK solution for me in the meantime, because I know the layers I want to refresh ahead of time.

  • Which QGIS version are you using? There is a builtin option to refresh a layer with a timer in QGIS 3. – etrimaille May 9 '18 at 3:13
  • 2.18, and I am stuck on it. – wfgeo May 9 '18 at 7:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.