14

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)
PolyGeo
  • 65,136
  • 29
  • 109
  • 338
Stelios M
  • 695
  • 2
  • 8
  • 20
  • What are the conditions that should trigger an 'exit'? – nickves Mar 04 '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 04 '15 at 11:57
  • 4
    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 04 '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 05 '15 at 06:47
  • 1
    @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 05 '15 at 07:20
  • I agree, it is a clear question that should have a straight and simple answer. However, I haven't found a way to break an infinite loop in Python either (although I haven't given it much effort yet), similar to CTRL+C in Matlab. Please post an answer here when you find something out, for future reference! – Martin Mar 05 '15 at 07:26
  • I have found another question on the topic, although it's concentrating on Arcmap and not QGIS, here. There seems to be no way of stopping the process. How about you program your loop to run a certain number of times or during a specified time? You'll likely have to restart it once in a while, but that might be easier than restarting QGIS? – Martin Mar 05 '15 at 09:45
  • @Martin I will try to find something like: if (certain key is pressed): break to implement it on my script. I will try searching for signals as a subject and work my way through. Thanks anyway – Stelios M Mar 05 '15 at 12:01
  • @Martin I tried: while not msvcrt.kbhit(): after importing msvcrt but it does not work either. Funny thing is that when i do c = msvcrt.getch() print 'you entered', c the output is: you entered ÿ . WTF IS THIS ÿ????? – Stelios M Mar 05 '15 at 20:09
  • That's really odd.. and almost funny :) I get the same when I try it in Idle (for some reason it returns the unicode string for ÿ). Haven't used that function before so I'm afraid I can't be of much assistance there. – Martin Mar 06 '15 at 06:45
  • http://stackoverflow.com/a/4906442/2319028 – Matthias Kuhn Oct 17 '16 at 20:51

2 Answers2

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.

Matthias Kuhn
  • 27,780
  • 3
  • 88
  • 129
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:
    ...
user6072577
  • 1,572
  • 2
  • 11
  • 23