11

So I wrote a script that does what I want over and over again using a "while True:" in a certain time interval (every 5 seconds using time.sleep(5)). So far so good, BUT when I want to stop it I just can't.

I have tried Control+C, Control+Break, Escape and it practically ignores my keyboard. The only way to stop it is to close QGIS. Any ideas? Furthermore, when the script hits time.sleep(5) QGIS kind of lags and freezes for 5 seconds and I can not, for example, pan the layer but I assume this is normal.

Here is my script:

from PyQt4.QtGui import *
from PyQt4.QtCore import *
from qgis.core import *
from qgis.utils import iface
import time


while True: 

    def change_color():
        active_layer = iface.activeLayer()
        pipeline=[]
        txt=open('C:/users/stelios/desktop/project/Sensor.txt','r')
        for line in txt.readlines():
            pipeline.append(line.split())
        print pipeline 
        pipeline2=[]
        for label,color in pipeline:
            if "0" in color:
                pipeline2.append([label,"green"])
            else:
                pipeline2.append([label,"red"])

        print pipeline2
        elatomatikoi=""
        categories=[]

        for label,color in pipeline2:
            if 'red' in color:
                elatomatikoi=elatomatikoi + label+","
            symbol = QgsSymbolV2.defaultSymbol(active_layer.geometryType())
            symbol.setColor(QColor(color))
            category = QgsRendererCategoryV2(int(label), symbol, label)
            categories.append(category)

        expression = 'id' 
        renderer = QgsCategorizedSymbolRendererV2(expression, categories)
        active_layer.setRendererV2(renderer)
        active_layer.setCacheImage(None)
        iface.mapCanvas().refresh()
        iface.legendInterface().refreshLayerSymbology(active_layer)
        elatomatikoi= elatomatikoi[:-1]

        for label,color in pipeline2:
            if 'red' in color:
                QMessageBox.critical(None,"Warning",("Leakage at pipe(s):%s\nCheck Pipeline status " %elatomatikoi))
                break
        txt.close()

    change_color()
    time.sleep(5)
  • What are the conditions that should trigger an 'exit'? – nickves Mar 4 '15 at 11:35
  • 1
    there are many ways to implement a no blocking process in qgis. You are getting the control without leaving it to the Qt event loop. I suggest to explore: 1) create a event-driven architecture or 2) manage your process in a python subprocess or the simples way) create a Processing Toolbox script and if necessary integrate with choice 2 – Luigi Pirelli Mar 4 '15 at 11:57
  • 3
    Guys maybe i said it the wrong way. Let me rephrase the question with a new scenario: You open Python Console in QGIS, you type: while 1: print "a" and you hit enter. Then it prints 'a' forever and ever. HOW DO YOU STOP IT WITHOUT EXITING QGIS? That is the question and the real issue – Stelios M Mar 4 '15 at 12:24
  • This might be more of a general Python question, so you'd might have better luck getting an answer on StackOverflow. – Martin Mar 5 '15 at 6:47
  • @Martin Will do. But it is a pretty strtaightforward question and it amazes me that QGIS head developers havent thought of the infinite loop scenario in their python console. If you execute while 1:print 'a' in your machine, are you able to stop it with the keyboard or is it my system's fault? – Stelios M Mar 5 '15 at 7:20
2

QGIS offers the full power of python to you. This opens up amazing possibilities but also comes with potential pitfalls. Which may make QGIS unresponsive, freeze or even crash it. Use it wisely!

In your case, instead of sending the main thread to sleep for 5 seconds, you better let QGIS do something else (like listening to your keystrokes or button presses) and post a timer event to the main event loop that will hand the control back to your script 5 seconds later.

You can use the example from this answer as a good starting point. To stop it, just connect some event to the stop() slot of the timer.

def change_color():
    print('I am now red')

timer = QTimer()
timer.timeout.connect(change_color)
timer.start(5000)

someButton.clicked.connect(timer.stop)

Or just call it manually from the console when you think it's time to stop it

timer.stop()

You could also install an eventFilter() on the main window to intercept key presses if you need that.

0

As a work around, you can use a QT widget with a cancel button.

It's a bit rough, but here is the widget script I've used:

from PyQt4 import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.UnicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

class Ui_Form(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.setupUi(self)
        self.running = True
    def setupUi(self, Form):
        Form.setObjectName(_fromUtf8("Form"))
        Form.resize(100, 100)
        self.horizontalLayout_3 = QtGui.QHBoxLayout(Form)
        self.horizontalLayout_3.setObjectName(_fromUtf8("horizontalLayout_3"))
        self.horizontalLayout = QtGui.QHBoxLayout()
        self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
        self.Cancel_btn = QtGui.QPushButton(Form)
        self.Cancel_btn.setMinimumSize(QtCore.QSize(0, 0))
        self.Cancel_btn.setMaximumSize(QtCore.QSize(425, 27))
        self.Cancel_btn.setObjectName(_fromUtf8("Cancel_btn"))
        self.horizontalLayout.addWidget(self.Cancel_btn)
        self.horizontalLayout_3.addLayout(self.horizontalLayout)
        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        Form.setWindowTitle(_translate("Form", "Cancel", None))
        self.Cancel_btn.setText(_translate("Form", "Cancel", None))
        self.Cancel_btn.clicked.connect(self.Cancel)


    def Cancel(self):
        self.running = False

This can be imported into your pyQgis script (you'll have to append the directory to sys.path) and then you can use the running variable to stop your while loop:

import sys
sys.path.append("path/to/cancel_widget")

import cancel_widget

btn = cancel_widget.Ui_Form()
btn.show()

while btn.running:
    ...

Your Answer

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

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