From c63eb3871c3c60a1d1b153221a0c93078347ec10 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 30 May 2016 12:22:12 +0200 Subject: [PATCH 1/5] Account for the changes to BackendState in Uranium Contributes to CURA-1278 --- .../CuraEngineBackend/CuraEngineBackend.py | 9 ++--- resources/qml/SaveButton.qml | 40 +++++++++++++------ 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index c9a0efbbcb..989e3a0c50 100644 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -1,13 +1,12 @@ # Copyright (c) 2015 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -from UM.Backend.Backend import Backend +from UM.Backend.Backend import Backend, BackendState from UM.Application import Application from UM.Scene.SceneNode import SceneNode from UM.Preferences import Preferences from UM.Signal import Signal from UM.Logger import Logger -from UM.Qt.Bindings.BackendProxy import BackendState #To determine the state of the slicing job. from UM.Message import Message from UM.PluginRegistry import PluginRegistry from UM.Resources import Resources @@ -156,12 +155,12 @@ class CuraEngineBackend(Backend): # return self.processingProgress.emit(0.0) - self.backendStateChange.emit(BackendState.NOT_STARTED) if self._message: self._message.setProgress(-1) else: self._message = Message(catalog.i18nc("@info:status", "Slicing..."), 0, False, -1) self._message.show() + self.backendStateChange.emit(BackendState.NotStarted) self._scene.gcode_list = [] self._slicing = True @@ -274,13 +273,13 @@ class CuraEngineBackend(Backend): self._message.setProgress(round(message.amount * 100)) self.processingProgress.emit(message.amount) - self.backendStateChange.emit(BackendState.PROCESSING) + self.backendStateChange.emit(BackendState.Processing) ## Called when the engine sends a message that slicing is finished. # # \param message The protobuf message signalling that slicing is finished. def _onSlicingFinishedMessage(self, message): - self.backendStateChange.emit(BackendState.DONE) + self.backendStateChange.emit(BackendState.Done) self.processingProgress.emit(1.0) self._slicing = False diff --git a/resources/qml/SaveButton.qml b/resources/qml/SaveButton.qml index 64bdcdf540..db147b3e69 100644 --- a/resources/qml/SaveButton.qml +++ b/resources/qml/SaveButton.qml @@ -14,21 +14,35 @@ Rectangle { property real progress: UM.Backend.progress; property int backendState: UM.Backend.state; + property bool activity: Printer.getPlatformActivity; //Behavior on progress { NumberAnimation { duration: 250; } } property int totalHeight: childrenRect.height + UM.Theme.getSize("default_margin").height property string fileBaseName - property string statusText: { - if(base.backendState == 0) { - if(!activity) { - return catalog.i18nc("@label:PrintjobStatus","Please load a 3d model"); - } else { - return catalog.i18nc("@label:PrintjobStatus","Preparing to slice..."); + property string statusText: + { + if(base.backendState == 1) + { + if(!activity) + { + return catalog.i18nc("@label:PrintjobStatus", "Please load a 3d model"); } - } else if(base.backendState == 1) { - return catalog.i18nc("@label:PrintjobStatus","Slicing..."); - } else { - return catalog.i18nc("@label:PrintjobStatus","Ready to ") + UM.OutputDeviceManager.activeDeviceShortDescription; + else + { + return catalog.i18nc("@label:PrintjobStatus", "Preparing to slice..."); + } + } + else if(base.backendState == 2) + { + return catalog.i18nc("@label:PrintjobStatus", "Slicing..."); + } + else if(base.backendState == 3) + { + return catalog.i18nc("@label:PrintjobStatus %1 is target operation","Ready to %1").arg(UM.OutputDeviceManager.activeDeviceShortDescription); + } + else if(base.backendState == 4) + { + return catalog.i18nc("@label:PrintjobStatus", "Unable to slice due to errors") } } @@ -60,7 +74,7 @@ Rectangle { height: parent.height color: UM.Theme.getColor("progressbar_control") radius: UM.Theme.getSize("progressbar_radius").width - visible: base.backendState == 1 ? true : false + visible: base.backendState == 2 ? true : false } } @@ -76,7 +90,7 @@ Rectangle { id: saveToButton tooltip: UM.OutputDeviceManager.activeDeviceDescription; - enabled: base.backendState == 2 && base.activity == true + enabled: base.backendState == 3 && base.activity == true height: UM.Theme.getSize("save_button_save_to_button").height anchors.top: parent.top @@ -127,7 +141,7 @@ Rectangle { anchors.rightMargin: UM.Theme.getSize("default_margin").width width: UM.Theme.getSize("save_button_save_to_button").height height: UM.Theme.getSize("save_button_save_to_button").height - enabled: base.backendState == 2 && base.activity == true + enabled: base.backendState == 3 && base.activity == true visible: devicesModel.deviceCount > 1 From 0b3b718e8719f03a4c33a67636d506dc7dc91b7b Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 30 May 2016 12:23:05 +0200 Subject: [PATCH 2/5] Make CuraEngineBackend respond to changes to the global container stack This way we can properly connect to propertyChanged signals and trigger a reslice. Contributes to CURA-1278 --- plugins/CuraEngineBackend/CuraEngineBackend.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 989e3a0c50..4bc37f1215 100644 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -55,8 +55,9 @@ class CuraEngineBackend(Backend): self._stored_layer_data = [] #Triggers for when to (re)start slicing: - if Application.getInstance().getGlobalContainerStack(): - Application.getInstance().getGlobalContainerStack().propertyChanged.connect(self._onSettingChanged) #Note: Only starts slicing when the value changed. + self._global_container_stack = None + Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) + self._onGlobalStackChanged() #When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired. #This timer will group them up, and only slice for the last setting changed signal. @@ -123,7 +124,7 @@ class CuraEngineBackend(Backend): def slice(self): self._stored_layer_data = [] - if not self._enabled: #We shouldn't be slicing. + if not self._enabled or not self._global_container_stack: #We shouldn't be slicing. return if self._slicing: #We were already slicing. Stop the old job. @@ -375,3 +376,14 @@ class CuraEngineBackend(Backend): Logger.log("d", "Backend quit with return code %s. Resetting process and socket.", self._process.wait()) self._process = None self._createSocket() + + ## Called when the global container stack changes + def _onGlobalStackChanged(self): + if self._global_container_stack: + self._global_container_stack.propertyChanged.disconnect(self._onSettingChanged) + + self._global_container_stack = Application.getInstance().getGlobalContainerStack() + + if self._global_container_stack: + self._global_container_stack.propertyChanged.connect(self._onSettingChanged) #Note: Only starts slicing when the value changed. + self._onChanged() From cd2b853fff64ec026f90d66d8379a4f5df2124a7 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 30 May 2016 13:01:25 +0200 Subject: [PATCH 3/5] Remove "slicing" message since it is now displayed in the sidebar Now removed for good Contributes to CURA-1278 --- .../CuraEngineBackend/CuraEngineBackend.py | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 4bc37f1215..f90b27bf5d 100644 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -156,11 +156,6 @@ class CuraEngineBackend(Backend): # return self.processingProgress.emit(0.0) - if self._message: - self._message.setProgress(-1) - else: - self._message = Message(catalog.i18nc("@info:status", "Slicing..."), 0, False, -1) - self._message.show() self.backendStateChange.emit(BackendState.NotStarted) self._scene.gcode_list = [] @@ -193,10 +188,6 @@ class CuraEngineBackend(Backend): except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this. Logger.log("d", "Exception occurred while trying to kill the engine %s", str(e)) - if self._message: - self._message.hide() - self._message = None - ## Event handler to call when the job to initiate the slicing process is # completed. # @@ -210,9 +201,6 @@ class CuraEngineBackend(Backend): if self._start_slice_job is job: self._start_slice_job = None if job.isCancelled() or job.getError() or job.getResult() != True: - if self._message: - self._message.hide() - self._message = None return else: # Preparation completed, send it to the backend. @@ -270,9 +258,6 @@ class CuraEngineBackend(Backend): # # \param message The protobuf message containing the slicing progress. def _onProgressMessage(self, message): - if self._message: - self._message.setProgress(round(message.amount * 100)) - self.processingProgress.emit(message.amount) self.backendStateChange.emit(BackendState.Processing) @@ -285,11 +270,6 @@ class CuraEngineBackend(Backend): self._slicing = False - if self._message: - self._message.setProgress(100) - self._message.hide() - self._message = None - if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()): self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_layer_data) self._process_layers_job.start() From 8039184c18580b8909435bf0dee0a5c10302bac7 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 30 May 2016 13:03:06 +0200 Subject: [PATCH 4/5] Move setting error checking to StartSliceJob and allow the job to return a proper response Now the job can determine if we can continue with slicing or not and if not, why not. This also means we can now show a message when we cannot find any slicable objects. Contributes to CURA-1278 --- .../CuraEngineBackend/CuraEngineBackend.py | 53 ++++++++++--------- plugins/CuraEngineBackend/StartSliceJob.py | 25 +++++++-- resources/qml/SaveButton.qml | 16 +++--- 3 files changed, 56 insertions(+), 38 deletions(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index f90b27bf5d..c5804f920f 100644 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -82,7 +82,7 @@ class CuraEngineBackend(Backend): self._always_restart = True #Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness. self._process_layers_job = None #The currently active job to process layers, or None if it is not processing layers. - self._message = None #Pop-up message that shows the slicing progress bar (or an error message). + self._error_message = None #Pop-up message that shows errors. self.backendQuit.connect(self._onBackendQuit) self.backendConnected.connect(self._onBackendConnected) @@ -134,26 +134,8 @@ class CuraEngineBackend(Backend): self._process_layers_job.abort() self._process_layers_job = None - # #Don't slice if there is a setting with an error value. - # stack = Application.getInstance().getGlobalContainerStack() - # for key in stack.getAllKeys(): - # validation_state = stack.getProperty(key, "validationState") - # #Only setting instances have a validation state, so settings which - # #are not overwritten by any instance will have none. The property - # #then, and only then, evaluates to None. We make the assumption that - # #the definition defines the setting with a default value that is - # #valid. Therefore we can allow both ValidatorState.Valid and None as - # #allowable validation states. - # #TODO: This assumption is wrong! If the definition defines an inheritance function that through inheritance evaluates to a disallowed value, a setting is still invalid even though it's default! - # #TODO: Therefore we must also validate setting definitions. - # if validation_state != None and validation_state != ValidatorState.Valid: - # Logger.log("w", "Setting %s is not valid, but %s. Aborting slicing.", key, validation_state) - # if self._message: #Hide any old message before creating a new one. - # self._message.hide() - # self._message = None - # self._message = Message(catalog.i18nc("@info:status", "Unable to slice. Please check your setting values for errors.")) - # self._message.show() - # return + if self._error_message: + self._error_message.hide() self.processingProgress.emit(0.0) self.backendStateChange.emit(BackendState.NotStarted) @@ -200,12 +182,31 @@ class CuraEngineBackend(Backend): # Note that cancelled slice jobs can still call this method. if self._start_slice_job is job: self._start_slice_job = None - if job.isCancelled() or job.getError() or job.getResult() != True: + + if job.isCancelled() or job.getError() or job.getResult() == StartSliceJob.StartJobResult.Error: return - else: - # Preparation completed, send it to the backend. - self._socket.sendMessage(job.getSettingsMessage()) - self._socket.sendMessage(job.getSliceMessage()) + + if job.getResult() == StartSliceJob.StartJobResult.SettingError: + if Application.getInstance().getPlatformActivity: + self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. Please check your setting values for errors."), lifetime = 10) + self._error_message.show() + self.backendStateChange.emit(BackendState.Error) + else: + self.backendStateChange.emit(BackendState.NotStarted) + return + + if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice: + if Application.getInstance().getPlatformActivity: + self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. No suitable objects found."), lifetime = 10) + self._error_message.show() + self.backendStateChange.emit(BackendState.Error) + else: + self.backendStateChange.emit(BackendState.NotStarted) + return + + # Preparation completed, send it to the backend. + self._socket.sendMessage(job.getSettingsMessage()) + self._socket.sendMessage(job.getSliceMessage()) ## Listener for when the scene has changed. # diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 6e0da0cb34..cf008e7b6f 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -4,6 +4,7 @@ import numpy from string import Formatter import traceback +from enum import IntEnum from UM.Job import Job from UM.Application import Application @@ -12,8 +13,15 @@ from UM.Logger import Logger from UM.Scene.SceneNode import SceneNode from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator +from UM.Settings.Validator import ValidatorState + from cura.OneAtATimeIterator import OneAtATimeIterator +class StartJobResult(IntEnum): + Finished = 1 + Error = 2 + SettingError = 3 + NothingToSlice = 4 ## Formatter class that handles token expansion in start/end gcod class GcodeStartEndFormatter(Formatter): @@ -48,9 +56,19 @@ class StartSliceJob(Job): def run(self): stack = Application.getInstance().getGlobalContainerStack() if not stack: - self.setResult(False) + self.setResult(StartJobResult.Error) return + #Don't slice if there is a setting with an error value. + for key in stack.getAllKeys(): + validation_state = stack.getProperty(key, "validationState") + if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError): + Logger.log("w", "Setting %s is not valid, but %s. Aborting slicing.", key, validation_state) + self.setResult(StartJobResult.SettingError) + return + + Job.yieldThread() + with self._scene.getSceneLock(): # Remove old layer data. for node in DepthFirstIterator(self._scene.getRoot()): @@ -91,6 +109,7 @@ class StartSliceJob(Job): object_groups.append(temp_list) if not object_groups: + self.setResult(StartJobResult.NothingToSlice) return self._buildGlobalSettingsMessage(stack) @@ -116,7 +135,7 @@ class StartSliceJob(Job): Job.yieldThread() - self.setResult(True) + self.setResult(StartJobResult.Finished) def cancel(self): super().cancel() @@ -131,7 +150,7 @@ class StartSliceJob(Job): fmt = GcodeStartEndFormatter() return str(fmt.format(value, **settings)).encode("utf-8") except: - Logger.log("w", "Unabled to do token replacement on start/end gcode %s", traceback.format_exc()) + Logger.logException("w", "Unable to do token replacement on start/end gcode") return str(value).encode("utf-8") ## Sends all global settings to the engine. diff --git a/resources/qml/SaveButton.qml b/resources/qml/SaveButton.qml index db147b3e69..1307e8f820 100644 --- a/resources/qml/SaveButton.qml +++ b/resources/qml/SaveButton.qml @@ -21,16 +21,14 @@ Rectangle { property string fileBaseName property string statusText: { + if(!activity) + { + return catalog.i18nc("@label:PrintjobStatus", "Please load a 3d model"); + } + if(base.backendState == 1) { - if(!activity) - { - return catalog.i18nc("@label:PrintjobStatus", "Please load a 3d model"); - } - else - { - return catalog.i18nc("@label:PrintjobStatus", "Preparing to slice..."); - } + return catalog.i18nc("@label:PrintjobStatus", "Preparing to slice..."); } else if(base.backendState == 2) { @@ -42,7 +40,7 @@ Rectangle { } else if(base.backendState == 4) { - return catalog.i18nc("@label:PrintjobStatus", "Unable to slice due to errors") + return catalog.i18nc("@label:PrintjobStatus", "Unable to Slice") } } From 93873a2ce83edb24e33db751a7f25d8b8b340459 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 30 May 2016 14:02:51 +0200 Subject: [PATCH 5/5] Also trigger a reslice when the containerstack's containers change Contributes to CURA-1278 --- plugins/CuraEngineBackend/CuraEngineBackend.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index c5804f920f..9607ba407b 100644 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -307,7 +307,7 @@ class CuraEngineBackend(Backend): ## Called when anything has changed to the stuff that needs to be sliced. # # This indicates that we should probably re-slice soon. - def _onChanged(self): + def _onChanged(self, *args, **kwargs): self._change_timer.start() ## Called when the back-end connects to the front-end. @@ -362,9 +362,11 @@ class CuraEngineBackend(Backend): def _onGlobalStackChanged(self): if self._global_container_stack: self._global_container_stack.propertyChanged.disconnect(self._onSettingChanged) + self._global_container_stack.containersChanged.disconnect(self._onChanged) self._global_container_stack = Application.getInstance().getGlobalContainerStack() if self._global_container_stack: self._global_container_stack.propertyChanged.connect(self._onSettingChanged) #Note: Only starts slicing when the value changed. + self._global_container_stack.containersChanged.connect(self._onChanged) self._onChanged()