Skip to content

Commit

Permalink
exporter: fix aborting
Browse files Browse the repository at this point in the history
- avoid crash due to exiting exporter while building layer objects
  • Loading branch information
minorua committed Mar 18, 2022
1 parent 1d46f0d commit 37ddc2b
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 29 deletions.
53 changes: 29 additions & 24 deletions q3dcontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Q3DControllerInterface(QObject):
messageReady = pyqtSignal(str, int, bool) # message, timeout, show_in_msg_bar
progressUpdated = pyqtSignal(int, str)
loadScriptsRequest = pyqtSignal(list, bool) # list of script ID, force (if False, do not load a script that is already loaded)
readyToQuit = pyqtSignal()

def __init__(self, controller=None):
super().__init__(parent=controller)
Expand Down Expand Up @@ -131,7 +132,6 @@ def __init__(self, settings=None, thread=None, parent=None):
self.iface = Q3DControllerInterface(self)
self.enabled = True
self.aborted = False # layer export aborted
self.updating = False
self.updatingLayerId = None
self.mapCanvas = None

Expand Down Expand Up @@ -169,11 +169,11 @@ def disconnectFromMapCanvas(self):
self.mapCanvas = None

def buildScene(self, update_scene_opts=True, build_layers=True, update_extent=True, base64=False):
if self.updating:
if self.updatingLayerId:
logMessage("Previous building is still in progress. Cannot start to build scene.")
return
return False

self.updating = True
self.aborted = False
self.settings.base64 = base64

self.iface.progress(0, "Updating scene")
Expand Down Expand Up @@ -204,41 +204,37 @@ def buildScene(self, update_scene_opts=True, build_layers=True, update_extent=Tr
if build_layers:
self.buildLayers()

self.updating = False
self.updatingLayerId = None
self.aborted = False
self.iface.progress()
self.iface.clearMessage()
self.settings.base64 = False
return True
return not self.aborted

def buildLayers(self):
self.aborted = False
self.iface.runScript('loadStart("LYRS", true)')

ret = True
layers = self.settings.layers()
for layer in sorted(layers, key=lambda lyr: lyr.type):
if layer.visible:
if not self._buildLayer(layer) or self.aborted:
ret = False
break

self.iface.runScript('loadEnd("LYRS")')
return ret

def buildLayer(self, layer):
self.aborted = False
if isinstance(layer, dict):
layer = Layer.fromDict(layer)

if self.updating:
if self.updatingLayerId:
logMessage('Previous building is still in progress. Cannot start building layer "{}".'.format(layer.name))
return False

self.updating = True
self.updatingLayerId = layer.layerId

ret = self._buildLayer(layer)

self.updating = False
self.updatingLayerId = None
self.aborted = False
self.iface.progress()
self.iface.clearMessage()

Expand All @@ -248,6 +244,8 @@ def buildLayer(self, layer):
return ret

def _buildLayer(self, layer):
self.updatingLayerId = layer.layerId

pmsg = "Building {0}...".format(layer.name)
self.iface.progress(0, pmsg)

Expand All @@ -270,6 +268,7 @@ def _buildLayer(self, layer):
self.iface.progress(i / (i + 4) * 100, pmsg)
if self.aborted:
logMessage("***** layer building aborted *****", False)
self.updatingLayerId = None
return False

t1 = time.time()
Expand All @@ -291,6 +290,7 @@ def _buildLayer(self, layer):
qDebug("{0} layer updated: {1:.3f}s\n{2}\n".format(layer.name,
time.time() - t0,
dlist).encode("utf-8"))
self.updatingLayerId = None
return True

def hideLayer(self, layer):
Expand All @@ -307,7 +307,7 @@ def processRequests(self):
self.timer.start()

def _processRequests(self):
if not self.enabled or self.updating or not self.requestQueue:
if not self.enabled or self.updatingLayerId or not self.requestQueue:
return

try:
Expand Down Expand Up @@ -346,14 +346,19 @@ def abort(self, clear_queue=True):
if clear_queue:
self.requestQueue.clear()

if self.updating and not self.aborted:
if not self.aborted:
self.aborted = True
self.iface.showMessage("Aborting processing...")

@pyqtSlot()
def quit(self):
self.abort()
self.iface.readyToQuit.emit()

@pyqtSlot(object, bool, bool)
def requestSceneUpdate(self, properties=None, update_all=True, reload=False):
if DEBUG_MODE:
logMessage("Scene update was requested: {}".format(properties), False)
logMessage("Scene update requested: {}".format(properties), False)

if properties:
self.settings.setSceneProperties(properties)
Expand All @@ -367,15 +372,15 @@ def requestSceneUpdate(self, properties=None, update_all=True, reload=False):

self.requestQueue.append(r)

if self.updating:
if self.updatingLayerId:
self.abort(clear_queue=False)
else:
self.processRequests()

@pyqtSlot(Layer)
def requestLayerUpdate(self, layer):
if DEBUG_MODE:
logMessage("Layer update for {} was requested ({}).".format(layer.layerId, "visible" if layer.visible else "hidden"), False)
logMessage("Layer update for {} requested ({}).".format(layer.layerId, "visible" if layer.visible else "hidden"), False)

# update layer properties and layer state in worker side export settings
lyr = self.settings.getLayer(layer.layerId)
Expand All @@ -399,7 +404,7 @@ def requestLayerUpdate(self, layer):
if layer.visible:
self.requestQueue.append(layer)

if not self.updating:
if not self.updatingLayerId:
self.processRequests()

else:
Expand All @@ -424,12 +429,12 @@ def requestWidgetUpdate(self, name, properties):
def requestRunScript(self, string, data=None):
self.requestQueue.append({"string": string, "data": data})

if not self.updating:
if not self.updatingLayerId:
self.processRequests()

@pyqtSlot(ExportSettings)
def updateExportSettings(self, settings):
if self.updating:
if self.updatingLayerId:
self.abort()

self.hideAllLayers()
Expand Down Expand Up @@ -479,7 +484,7 @@ def _requestSceneUpdate(self, _=None):
# if self.settings.sceneProperties().get("radioButton_FixedExtent"):
# return
# self.requestQueue.clear()
# if self.updating:
# if self.updatingLayerId:
# self.abort(clear_queue=False)


Expand Down
13 changes: 10 additions & 3 deletions q3dinterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,33 @@ def __init__(self, settings, webPage, parent=None):

self.settings = settings
self.webPage = webPage
self.enabled = True

@pyqtSlot(dict)
def loadJSONObject(self, obj):
# display the content of the object in the debug element
if not self.enabled:
return

if DEBUG_MODE == 2:
self.runScript("document.getElementById('debug').innerHTML = '{}';".format(str(obj)[:500].replace("'", "\\'")))

self.webPage.sendData(obj)

@pyqtSlot(str, object, str)
def runScript(self, string, data=None, message=""):
self.webPage.runScript(string, data, message, sourceID="q3dwindow.py")
if self.enabled:
self.webPage.runScript(string, data, message, sourceID="q3dwindow.py")

@pyqtSlot(list, bool)
def loadScriptFiles(self, ids, force):
self.webPage.loadScriptFiles(ids, force)
if self.enabled:
self.webPage.loadScriptFiles(ids, force)

# @pyqtSlot(str, int, bool) # pyqtSlot override bug in PyQt5?
def showMessage(self, msg, _1=0, _2=False):
logMessage(msg)
if self.enabled:
logMessage(msg)

# @pyqtSlot(int, str)
def progress(self, percentage=100, msg=None):
Expand Down
23 changes: 21 additions & 2 deletions q3dwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import os
from datetime import datetime

from PyQt5.QtCore import Qt, QDir, QEvent, QObject, QSettings, QThread, QUrl, pyqtSignal
from PyQt5.QtCore import Qt, QDir, QEvent, QEventLoop, QObject, QSettings, QThread, QUrl, pyqtSignal
from PyQt5.QtGui import QColor, QDesktopServices, QIcon
from PyQt5.QtWidgets import (QAction, QActionGroup, QApplication, QCheckBox, QComboBox,
QDialog, QDialogButtonBox, QFileDialog, QMainWindow, QMenu, QMessageBox, QProgressBar)
Expand Down Expand Up @@ -40,20 +40,28 @@ class Q3DViewerInterface(Q3DInterface):
layerAdded = pyqtSignal(Layer) # param: Layer object
layerRemoved = pyqtSignal(str) # param: layerId

quitRequest = pyqtSignal()

def __init__(self, settings, webPage, wnd, treeView, parent=None):
super().__init__(settings, webPage, parent=parent)
self.wnd = wnd
self.treeView = treeView

# @pyqtSlot(str, int, bool)
def showMessage(self, msg, timeout=0, show_in_msg_bar=False):
if not self.enabled:
return

if show_in_msg_bar:
self.wnd.qgisIface.messageBar().pushMessage("Qgis2threejs Error", msg, level=Qgis.Warning, duration=timeout)
else:
self.wnd.ui.statusbar.showMessage(msg, timeout)

# @pyqtSlot(int, str)
def progress(self, percentage=100, msg=None):
if not self.enabled:
return

bar = self.wnd.ui.progressBar
if percentage == 100:
bar.setVisible(False)
Expand All @@ -79,6 +87,10 @@ def requestWidgetUpdate(self, name, properties):
def requestRunScript(self, string, data=None):
self.runScriptRequest.emit(string, data)

def quit(self, controller):
self.quitRequest.connect(controller.quit)
self.quitRequest.emit()


class Q3DWindow(QMainWindow):

Expand Down Expand Up @@ -134,7 +146,8 @@ def __init__(self, qgisIface, settings, preview=True):
self.restoreState(settings.value("/Qgis2threejs/wnd/state", b""))

def closeEvent(self, event):
self.iface.abort()
self.iface.enabled = False
self.controller.iface.disconnectFromIface()

# save export settings to a settings file
try:
Expand All @@ -150,6 +163,12 @@ def closeEvent(self, event):
settings.setValue("/Qgis2threejs/wnd/geometry", self.saveGeometry())
settings.setValue("/Qgis2threejs/wnd/state", self.saveState())

# send quit request to the controller and wait until the controller gets ready to quit
loop = QEventLoop()
self.controller.iface.readyToQuit.connect(loop.quit)
self.iface.quit(self.controller)
loop.exec_()

# stop worker thread event loop
if self.thread:
self.thread.quit()
Expand Down

0 comments on commit 37ddc2b

Please sign in to comment.