From b149ef57fdecf53bddd2613829ab0acb124f2deb Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 4 Jul 2018 17:18:01 +0200 Subject: [PATCH 01/13] Add test for the version number being incremented --- .../tests/TestVersionUpgrade34to40.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py diff --git a/plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py b/plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py new file mode 100644 index 0000000000..a6a3a1febf --- /dev/null +++ b/plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py @@ -0,0 +1,34 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import configparser #To parse the resulting config files. +import pytest #To register tests with. + +import VersionUpgrade34to40 #The module we're testing. + +## Creates an instance of the upgrader to test with. +@pytest.fixture +def upgrader(): + return VersionUpgrade34to40.VersionUpgrade34to40() + +test_upgrade_version_nr_data = [ + ("Empty config file", + """[general] + version = 5 + [metadata] + setting_version = 4 +""" + ) +] + +## Tests whether the version numbers are updated. +def test_upgradeVersionNr(test_name, file_data, upgrader): + #Perform the upgrade. + _, upgraded_instances = upgrader.upgradePreferences(file_data, "") + upgraded_instance = upgraded_instances[0] + parser = configparser.ConfigParser(interpolation = None) + parser.read_string(upgraded_instance) + + #Check the new version. + assert parser["general"]["version"] == "6" + assert parser["metadata"]["setting_version"] == "5" \ No newline at end of file From c9480f2f2bd406e25ab073d14311719c2363cfe4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 4 Jul 2018 17:30:01 +0200 Subject: [PATCH 02/13] Add types for backup metadata --- cura/Backups/Backup.py | 7 ++++--- cura/Backups/BackupsManager.py | 13 +++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cura/Backups/Backup.py b/cura/Backups/Backup.py index f935aa6af5..cc47df770e 100644 --- a/cura/Backups/Backup.py +++ b/cura/Backups/Backup.py @@ -1,12 +1,13 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. + import io import os import re import shutil -from typing import Optional +from typing import Dict, Optional from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile from UM import i18nCatalog @@ -28,9 +29,9 @@ class Backup: # Re-use translation catalog. catalog = i18nCatalog("cura") - def __init__(self, zip_file: bytes = None, meta_data: dict = None) -> None: + def __init__(self, zip_file: bytes = None, meta_data: Dict[str, str] = None) -> None: self.zip_file = zip_file # type: Optional[bytes] - self.meta_data = meta_data # type: Optional[dict] + self.meta_data = meta_data # type: Optional[Dict[str, str]] ## Create a back-up from the current user config folder. def makeFromCurrent(self) -> None: diff --git a/cura/Backups/BackupsManager.py b/cura/Backups/BackupsManager.py index bc560a8dd9..67e2a222f1 100644 --- a/cura/Backups/BackupsManager.py +++ b/cura/Backups/BackupsManager.py @@ -1,6 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Optional, Tuple + +from typing import Dict, Optional, Tuple from UM.Logger import Logger from cura.Backups.Backup import Backup @@ -18,7 +19,7 @@ class BackupsManager: ## Get a back-up of the current configuration. # \return A tuple containing a ZipFile (the actual back-up) and a dict # containing some metadata (like version). - def createBackup(self) -> Tuple[Optional[bytes], Optional[dict]]: + def createBackup(self) -> Tuple[Optional[bytes], Optional[Dict[str, str]]]: self._disableAutoSave() backup = Backup() backup.makeFromCurrent() @@ -30,7 +31,7 @@ class BackupsManager: # \param zip_file A bytes object containing the actual back-up. # \param meta_data A dict containing some metadata that is needed to # restore the back-up correctly. - def restoreBackup(self, zip_file: bytes, meta_data: dict) -> None: + def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, str]) -> None: if not meta_data.get("cura_release", None): # If there is no "cura_release" specified in the meta data, we don't execute a backup restore. Logger.log("w", "Tried to restore a backup without specifying a Cura version number.") @@ -43,13 +44,13 @@ class BackupsManager: if restored: # At this point, Cura will need to restart for the changes to take effect. # We don't want to store the data at this point as that would override the just-restored backup. - self._application.windowClosed(save_data=False) + self._application.windowClosed(save_data = False) ## Here we try to disable the auto-save plug-in as it might interfere with # restoring a back-up. - def _disableAutoSave(self): + def _disableAutoSave(self) -> None: self._application.setSaveDataEnabled(False) ## Re-enable auto-save after we're done. - def _enableAutoSave(self): + def _enableAutoSave(self) -> None: self._application.setSaveDataEnabled(True) From b812989f1cbc8f9fe342e56ada7849545bf738f3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 4 Jul 2018 17:34:26 +0200 Subject: [PATCH 03/13] Add types for width, height, depth and shape --- cura/BuildVolume.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index d0563a5352..de4a33b630 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -47,10 +47,10 @@ class BuildVolume(SceneNode): self._disallowed_area_color = None self._error_area_color = None - self._width = 0 - self._height = 0 - self._depth = 0 - self._shape = "" + self._width = 0 #type: float + self._height = 0 #type: float + self._depth = 0 #type: float + self._shape = "" #type: str self._shader = None @@ -154,19 +154,19 @@ class BuildVolume(SceneNode): if active_extruder_changed is not None: active_extruder_changed.connect(self._updateDisallowedAreasAndRebuild) - def setWidth(self, width): + def setWidth(self, width: float) -> None: if width is not None: self._width = width - def setHeight(self, height): + def setHeight(self, height: float) -> None: if height is not None: self._height = height - def setDepth(self, depth): + def setDepth(self, depth: float) -> None: if depth is not None: self._depth = depth - def setShape(self, shape: str): + def setShape(self, shape: str) -> None: if shape: self._shape = shape From 2023973e98ee3576c92037c5c24c85d633d36c86 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 4 Jul 2018 17:39:24 +0200 Subject: [PATCH 04/13] Use _engine_ready to detect if there is a QML engine We already have this variable. Let's not use a private variable from another class. --- cura/BuildVolume.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index d0563a5352..885143ac69 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -294,7 +294,7 @@ class BuildVolume(SceneNode): if not self._width or not self._height or not self._depth: return - if not self._application._qml_engine: + if not self._engine_ready: return if not self._volume_outline_color: From 7d7a51d77241026ef665300b944b64dd69dbddcc Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Thu, 5 Jul 2018 08:04:19 +0200 Subject: [PATCH 05/13] Fix code-style in CuraEngineBackend --- .../CuraEngineBackend/CuraEngineBackend.py | 49 ++++++++++++------- plugins/CuraEngineBackend/StartSliceJob.py | 30 ++++++------ 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index e7dca2ae3e..1df694aa86 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -3,6 +3,8 @@ from collections import defaultdict import os +from typing import Union + from PyQt5.QtCore import QObject, QTimer, pyqtSlot import sys from time import time @@ -60,17 +62,16 @@ class CuraEngineBackend(QObject, Backend): if hasattr(sys, "frozen"): default_engine_location = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), executable_name) if Platform.isLinux() and not default_engine_location: - if not os.getenv("PATH"): + env_path = os.getenv("PATH") + if not env_path: raise OSError("There is something wrong with your Linux installation.") - for pathdir in os.getenv("PATH").split(os.pathsep): + for pathdir in env_path.split(os.pathsep): execpath = os.path.join(pathdir, executable_name) if os.path.exists(execpath): default_engine_location = execpath break self._application = CuraApplication.getInstance() #type: CuraApplication - self._multi_build_plate_model = None #type: MultiBuildPlateModel - self._machine_error_checker = None #type: MachineErrorChecker if not default_engine_location: raise EnvironmentError("Could not find CuraEngine") @@ -120,11 +121,11 @@ class CuraEngineBackend(QObject, Backend): self._engine_is_fresh = True #type: bool # Is the newly started engine used before or not? self._backend_log_max_lines = 20000 #type: int # Maximum number of lines to buffer - self._error_message = None #type: Message # Pop-up message that shows errors. + self._error_message = None #type: Optional[Message] # Pop-up message that shows errors. self._last_num_objects = defaultdict(int) #type: Dict[int, int] # Count number of objects to see if there is something changed self._postponed_scene_change_sources = [] #type: List[SceneNode] # scene change is postponed (by a tool) - self._slice_start_time = None #type: Optional[float] + self._slice_start_time = time() #type: float self._is_disabled = False #type: bool self._application.getPreferences().addPreference("general/auto_slice", False) @@ -142,8 +143,7 @@ class CuraEngineBackend(QObject, Backend): self._application.initializationFinished.connect(self.initialize) def initialize(self) -> None: - self._multi_build_plate_model = self._application.getMultiBuildPlateModel() - + self._multi_build_plate_model = self._application.getMultiBuildPlateModel() #type: MultiBuildPlateModel self._application.getController().activeViewChanged.connect(self._onActiveViewChanged) self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveViewChanged) @@ -160,7 +160,7 @@ class CuraEngineBackend(QObject, Backend): self._application.getController().toolOperationStarted.connect(self._onToolOperationStarted) self._application.getController().toolOperationStopped.connect(self._onToolOperationStopped) - self._machine_error_checker = self._application.getMachineErrorChecker() + self._machine_error_checker = self._application.getMachineErrorChecker() #type: MachineErrorChecker self._machine_error_checker.errorCheckFinished.connect(self._onStackErrorCheckFinished) ## Terminate the engine process. @@ -310,6 +310,11 @@ class CuraEngineBackend(QObject, Backend): if self._start_slice_job is job: self._start_slice_job = None + if not self._global_container_stack: + self.backendStateChange.emit(BackendState.Error) + self.backendError.emit(job) + return + if job.isCancelled() or job.getError() or job.getResult() == StartJobResult.Error: self.backendStateChange.emit(BackendState.Error) self.backendError.emit(job) @@ -447,7 +452,8 @@ class CuraEngineBackend(QObject, Backend): # Only count sliceable objects if node.callDecoration("isSliceable"): build_plate_number = node.callDecoration("getBuildPlateNumber") - num_objects[build_plate_number] += 1 + if build_plate_number is not None: + num_objects[build_plate_number] += 1 return num_objects ## Listener for when the scene has changed. @@ -464,7 +470,7 @@ class CuraEngineBackend(QObject, Backend): if source.callDecoration("isBlockSlicing") and source.callDecoration("getLayerData"): self._stored_optimized_layer_data = {} - build_plate_changed = set() + build_plate_changed = set() # type: Set[int] source_build_plate_number = source.callDecoration("getBuildPlateNumber") if source == self._scene.getRoot(): # we got the root node @@ -476,14 +482,15 @@ class CuraEngineBackend(QObject, Backend): else: # we got a single scenenode if not source.callDecoration("isGroup"): - if source.getMeshData() is None: + mesh_data = source.getMeshData() + if mesh_data is None: return - if source.getMeshData().getVertices() is None: + elif mesh_data.getVertices() is None: return - build_plate_changed.add(source_build_plate_number) + if source_build_plate_number is not None: + build_plate_changed.add(source_build_plate_number) - build_plate_changed.discard(None) build_plate_changed.discard(-1) # object not on build plate if not build_plate_changed: return @@ -577,9 +584,10 @@ class CuraEngineBackend(QObject, Backend): # # \param message The protobuf message containing sliced layer data. def _onOptimizedLayerMessage(self, message: Arcus.PythonMessage) -> None: - if self._start_slice_job_build_plate not in self._stored_optimized_layer_data: - self._stored_optimized_layer_data[self._start_slice_job_build_plate] = [] - self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message) + if self._start_slice_job_build_plate is not None: + if self._start_slice_job_build_plate not in self._stored_optimized_layer_data: + self._stored_optimized_layer_data[self._start_slice_job_build_plate] = [] + self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message) ## Called when a progress message is received from the engine. # @@ -658,7 +666,10 @@ class CuraEngineBackend(QObject, Backend): ## Creates a new socket connection. def _createSocket(self, protocol_file: str = None) -> None: if not protocol_file: - protocol_file = os.path.abspath(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "Cura.proto")) + plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) + if not plugin_path: + return + protocol_file = os.path.abspath(os.path.join(plugin_path, "Cura.proto")) super()._createSocket(protocol_file) self._engine_is_fresh = True diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 78dd4eafd2..8e429da14d 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -114,13 +114,14 @@ class StartSliceJob(Job): self.setResult(StartJobResult.Error) return - stack = CuraApplication.getInstance().getGlobalContainerStack() - if not stack: + global_stack = CuraApplication.getInstance().getGlobalContainerStack() + machine_manager = CuraApplication.getInstance().getMachineManager() + if not global_stack: self.setResult(StartJobResult.Error) return # Don't slice if there is a setting with an error value. - if CuraApplication.getInstance().getMachineManager().stacksHaveErrors: + if machine_manager.stacksHaveErrors: self.setResult(StartJobResult.SettingError) return @@ -129,12 +130,12 @@ class StartSliceJob(Job): return # Don't slice if the buildplate or the nozzle type is incompatible with the materials - if not CuraApplication.getInstance().getMachineManager().variantBuildplateCompatible and \ - not CuraApplication.getInstance().getMachineManager().variantBuildplateUsable: + if not machine_manager.variantBuildplateCompatible and \ + not machine_manager.variantBuildplateUsable: self.setResult(StartJobResult.MaterialIncompatible) return - for position, extruder_stack in stack.extruders.items(): + for position, extruder_stack in global_stack.extruders.items(): material = extruder_stack.findContainer({"type": "material"}) if not extruder_stack.isEnabled: continue @@ -162,7 +163,7 @@ class StartSliceJob(Job): # Get the objects in their groups to print. object_groups = [] - if stack.getProperty("print_sequence", "value") == "one_at_a_time": + if global_stack.getProperty("print_sequence", "value") == "one_at_a_time": for node in OneAtATimeIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. temp_list = [] @@ -216,12 +217,11 @@ class StartSliceJob(Job): if temp_list: object_groups.append(temp_list) - extruders_enabled = {position: stack.isEnabled for position, stack in CuraApplication.getInstance().getGlobalContainerStack().extruders.items()} + extruders_enabled = {position: stack.isEnabled for position, stack in global_stack.extruders.items()} filtered_object_groups = [] has_model_with_disabled_extruders = False - associated_disabled_extruders = set() + associated_disabled_extruders = set() # type: Set[str] for group in object_groups: - stack = CuraApplication.getInstance().getGlobalContainerStack() skip_group = False for node in group: extruder_position = node.callDecoration("getActiveExtruderPosition") @@ -234,7 +234,7 @@ class StartSliceJob(Job): if has_model_with_disabled_extruders: self.setResult(StartJobResult.ObjectsWithDisabledExtruder) - associated_disabled_extruders = [str(c) for c in sorted([int(p) + 1 for p in associated_disabled_extruders])] + associated_disabled_extruders = set([str(c) for c in sorted([int(p) + 1 for p in associated_disabled_extruders])]) self.setMessage(", ".join(associated_disabled_extruders)) return @@ -245,11 +245,11 @@ class StartSliceJob(Job): self.setResult(StartJobResult.NothingToSlice) return - self._buildGlobalSettingsMessage(stack) - self._buildGlobalInheritsStackMessage(stack) + self._buildGlobalSettingsMessage(global_stack) + self._buildGlobalInheritsStackMessage(global_stack) # Build messages for extruder stacks - for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()): + for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): self._buildExtruderMessage(extruder_stack) for group in filtered_object_groups: @@ -326,6 +326,8 @@ class StartSliceJob(Job): def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str: if not self._all_extruders_settings: global_stack = CuraApplication.getInstance().getGlobalContainerStack() + if not global_stack: + return str(value) # NB: keys must be strings for the string formatter self._all_extruders_settings = { From 87fd4c9e70d4695de04c9076833afc3212c8cbda Mon Sep 17 00:00:00 2001 From: Andreea Scorojitu Date: Thu, 5 Jul 2018 12:44:11 +0200 Subject: [PATCH 06/13] Add color to text in toolboxFooter, CURA-5544 "You will need to restart Cura before changes in packages have effect." wasn't visible in Ultimaker darktheme, color has been added to the label, CURA-5544. --- plugins/Toolbox/resources/qml/ToolboxFooter.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Toolbox/resources/qml/ToolboxFooter.qml b/plugins/Toolbox/resources/qml/ToolboxFooter.qml index 976ff46da6..5c2a6577ad 100644 --- a/plugins/Toolbox/resources/qml/ToolboxFooter.qml +++ b/plugins/Toolbox/resources/qml/ToolboxFooter.qml @@ -15,6 +15,7 @@ Item Label { text: catalog.i18nc("@info", "You will need to restart Cura before changes in packages have effect.") + color: UM.Theme.getColor("text") height: Math.floor(UM.Theme.getSize("toolbox_footer_button").height) verticalAlignment: Text.AlignVCenter anchors @@ -25,7 +26,7 @@ Item right: restartButton.right rightMargin: UM.Theme.getSize("default_margin").width } - color: UM.Theme.getColor("text") + } Button { From c738f306fb25ff8cbef1d817ca46c9e544c122f2 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Thu, 5 Jul 2018 14:35:12 +0200 Subject: [PATCH 07/13] Fix style in some components --- plugins/GCodeReader/FlavorParser.py | 3 +++ plugins/Toolbox/src/Toolbox.py | 10 +++++++++- .../UM3NetworkPrinting/ClusterUM3OutputDevice.py | 15 +++++++++++++-- plugins/UM3NetworkPrinting/DiscoverUM3Action.py | 5 ++++- plugins/UM3NetworkPrinting/SendMaterialJob.py | 2 +- 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py index 05f40b41e7..990bd98fb5 100644 --- a/plugins/GCodeReader/FlavorParser.py +++ b/plugins/GCodeReader/FlavorParser.py @@ -286,6 +286,9 @@ class FlavorParser: self._cancelled = False # We obtain the filament diameter from the selected extruder to calculate line widths global_stack = CuraApplication.getInstance().getGlobalContainerStack() + if not global_stack: + return None + self._filament_diameter = global_stack.extruders[str(self._extruder_number)].getProperty("material_diameter", "value") scene_node = CuraSceneNode() diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 0d0060e48c..c3e0a5916a 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -224,6 +224,11 @@ class Toolbox(QObject, Extension): if not self._dialog: self._dialog = self._createDialog("Toolbox.qml") + + if not self._dialog: + Logger.log("e", "Unexpected error trying to create the 'Toolbox' dialog.") + return + self._dialog.show() # Apply enabled/disabled state to installed plugins @@ -231,7 +236,10 @@ class Toolbox(QObject, Extension): def _createDialog(self, qml_name: str) -> Optional[QObject]: Logger.log("d", "Toolbox: Creating dialog [%s].", qml_name) - path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "resources", "qml", qml_name) + plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) + if not plugin_path: + return None + path = os.path.join(plugin_path, "resources", "qml", qml_name) dialog = self._application.createQmlComponent(path, {"toolbox": self}) return dialog diff --git a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py index 8b3ceb7809..757ed4ef66 100644 --- a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py @@ -103,8 +103,12 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): else: file_formats = CuraApplication.getInstance().getMeshFileHandler().getSupportedFileTypesWrite() + global_stack = CuraApplication.getInstance().getGlobalContainerStack() + if not global_stack: + return + #Create a list from the supported file formats string. - machine_file_formats = CuraApplication.getInstance().getGlobalContainerStack().getMetaDataEntry("file_formats").split(";") + machine_file_formats = global_stack.getMetaDataEntry("file_formats").split(";") machine_file_formats = [file_type.strip() for file_type in machine_file_formats] #Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format. if "application/x-ufp" not in machine_file_formats and self.printerType == "ultimaker3" and Version(self.firmwareVersion) >= Version("4.4"): @@ -125,6 +129,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): else: writer = CuraApplication.getInstance().getMeshFileHandler().getWriterByMimeType(cast(str, preferred_format["mime_type"])) + if not writer: + Logger.log("e", "Unexpected error when trying to get the FileWriter") + return + #This function pauses with the yield, waiting on instructions on which printer it needs to print with. self._sending_job = self._sendPrintJob(writer, preferred_format, nodes) self._sending_job.send(None) #Start the generator. @@ -205,6 +213,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): yield #To prevent having to catch the StopIteration exception. def _sendPrintJobWaitOnWriteJobFinished(self, job: WriteFileJob) -> None: + # This is the callback when the job finishes, where the message is created + assert(self._write_job_progress_message is not None) self._write_job_progress_message.hide() self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1, @@ -249,7 +259,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self.activePrinterChanged.emit() def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None: - self._progress_message.hide() + if self._progress_message is not None: + self._progress_message.hide() self._compressing_gcode = False self._sending_gcode = False diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.py b/plugins/UM3NetworkPrinting/DiscoverUM3Action.py index c51092ed98..3752cc0c25 100644 --- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.py +++ b/plugins/UM3NetworkPrinting/DiscoverUM3Action.py @@ -170,7 +170,10 @@ class DiscoverUM3Action(MachineAction): Logger.log("d", "Creating additional ui components for UM3.") # Create networking dialog - path = os.path.join(PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"), "UM3InfoComponents.qml") + plugin_path = PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting") + if not plugin_path: + return + path = os.path.join(plugin_path, "UM3InfoComponents.qml") self.__additional_components_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) if not self.__additional_components_view: Logger.log("w", "Could not create ui components for UM3.") diff --git a/plugins/UM3NetworkPrinting/SendMaterialJob.py b/plugins/UM3NetworkPrinting/SendMaterialJob.py index 02b5b68393..0ac38843a1 100644 --- a/plugins/UM3NetworkPrinting/SendMaterialJob.py +++ b/plugins/UM3NetworkPrinting/SendMaterialJob.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: # # This way it won't freeze up the interface while sending those materials. class SendMaterialJob(Job): - def __init__(self, device: "ClusterUM3OutputDevice"): + def __init__(self, device: "ClusterUM3OutputDevice") -> None: super().__init__() self.device = device #type: ClusterUM3OutputDevice From b0f1a6d85907bbd4030d2c703f3e358566275723 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 5 Jul 2018 14:36:19 +0200 Subject: [PATCH 08/13] Use Label delegate to avoid overlapping texts CURA-5544 --- .../resources/qml/ToolboxCompatibilityChart.qml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml b/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml index b4219d53bf..4978af6168 100644 --- a/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml +++ b/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml @@ -76,11 +76,26 @@ Item } } + Component + { + id: columnTextDelegate + Label + { + anchors.fill: parent + verticalAlignment: Text.AlignVCenter + text: styleData.value || "" + elide: Text.ElideRight + color: UM.Theme.getColor("text_medium") + font: UM.Theme.getFont("default") + } + } + TableViewColumn { role: "machine" title: "Machine" width: Math.floor(table.width * 0.25) + delegate: columnTextDelegate } TableViewColumn { From 8afc49e902511b5c5c80cac1f2248fbd967e3fb5 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Thu, 5 Jul 2018 14:41:17 +0200 Subject: [PATCH 09/13] Add style to a variable in X3Reader --- plugins/X3DReader/X3DReader.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/X3DReader/X3DReader.py b/plugins/X3DReader/X3DReader.py index 44a2f1443a..da0502a7ec 100644 --- a/plugins/X3DReader/X3DReader.py +++ b/plugins/X3DReader/X3DReader.py @@ -2,6 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from math import pi, sin, cos, sqrt +from typing import Dict import numpy @@ -42,7 +43,7 @@ class X3DReader(MeshReader): def __init__(self) -> None: super().__init__() self._supported_extensions = [".x3d"] - self._namespaces = {} + self._namespaces = {} # type: Dict[str, str] # Main entry point # Reads the file, returns a SceneNode (possibly with nested ones), or None From 7b284355fb5939e40d0fdf3226a6a9b29390cee3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 5 Jul 2018 15:24:55 +0200 Subject: [PATCH 10/13] Revert "Fix code-style in CuraEngineBackend" This reverts commit 7d7a51d77241026ef665300b944b64dd69dbddcc. That commit broke the start-up sequence. --- .../CuraEngineBackend/CuraEngineBackend.py | 49 +++++++------------ plugins/CuraEngineBackend/StartSliceJob.py | 30 ++++++------ 2 files changed, 33 insertions(+), 46 deletions(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 1df694aa86..e7dca2ae3e 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -3,8 +3,6 @@ from collections import defaultdict import os -from typing import Union - from PyQt5.QtCore import QObject, QTimer, pyqtSlot import sys from time import time @@ -62,16 +60,17 @@ class CuraEngineBackend(QObject, Backend): if hasattr(sys, "frozen"): default_engine_location = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), executable_name) if Platform.isLinux() and not default_engine_location: - env_path = os.getenv("PATH") - if not env_path: + if not os.getenv("PATH"): raise OSError("There is something wrong with your Linux installation.") - for pathdir in env_path.split(os.pathsep): + for pathdir in os.getenv("PATH").split(os.pathsep): execpath = os.path.join(pathdir, executable_name) if os.path.exists(execpath): default_engine_location = execpath break self._application = CuraApplication.getInstance() #type: CuraApplication + self._multi_build_plate_model = None #type: MultiBuildPlateModel + self._machine_error_checker = None #type: MachineErrorChecker if not default_engine_location: raise EnvironmentError("Could not find CuraEngine") @@ -121,11 +120,11 @@ class CuraEngineBackend(QObject, Backend): self._engine_is_fresh = True #type: bool # Is the newly started engine used before or not? self._backend_log_max_lines = 20000 #type: int # Maximum number of lines to buffer - self._error_message = None #type: Optional[Message] # Pop-up message that shows errors. + self._error_message = None #type: Message # Pop-up message that shows errors. self._last_num_objects = defaultdict(int) #type: Dict[int, int] # Count number of objects to see if there is something changed self._postponed_scene_change_sources = [] #type: List[SceneNode] # scene change is postponed (by a tool) - self._slice_start_time = time() #type: float + self._slice_start_time = None #type: Optional[float] self._is_disabled = False #type: bool self._application.getPreferences().addPreference("general/auto_slice", False) @@ -143,7 +142,8 @@ class CuraEngineBackend(QObject, Backend): self._application.initializationFinished.connect(self.initialize) def initialize(self) -> None: - self._multi_build_plate_model = self._application.getMultiBuildPlateModel() #type: MultiBuildPlateModel + self._multi_build_plate_model = self._application.getMultiBuildPlateModel() + self._application.getController().activeViewChanged.connect(self._onActiveViewChanged) self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveViewChanged) @@ -160,7 +160,7 @@ class CuraEngineBackend(QObject, Backend): self._application.getController().toolOperationStarted.connect(self._onToolOperationStarted) self._application.getController().toolOperationStopped.connect(self._onToolOperationStopped) - self._machine_error_checker = self._application.getMachineErrorChecker() #type: MachineErrorChecker + self._machine_error_checker = self._application.getMachineErrorChecker() self._machine_error_checker.errorCheckFinished.connect(self._onStackErrorCheckFinished) ## Terminate the engine process. @@ -310,11 +310,6 @@ class CuraEngineBackend(QObject, Backend): if self._start_slice_job is job: self._start_slice_job = None - if not self._global_container_stack: - self.backendStateChange.emit(BackendState.Error) - self.backendError.emit(job) - return - if job.isCancelled() or job.getError() or job.getResult() == StartJobResult.Error: self.backendStateChange.emit(BackendState.Error) self.backendError.emit(job) @@ -452,8 +447,7 @@ class CuraEngineBackend(QObject, Backend): # Only count sliceable objects if node.callDecoration("isSliceable"): build_plate_number = node.callDecoration("getBuildPlateNumber") - if build_plate_number is not None: - num_objects[build_plate_number] += 1 + num_objects[build_plate_number] += 1 return num_objects ## Listener for when the scene has changed. @@ -470,7 +464,7 @@ class CuraEngineBackend(QObject, Backend): if source.callDecoration("isBlockSlicing") and source.callDecoration("getLayerData"): self._stored_optimized_layer_data = {} - build_plate_changed = set() # type: Set[int] + build_plate_changed = set() source_build_plate_number = source.callDecoration("getBuildPlateNumber") if source == self._scene.getRoot(): # we got the root node @@ -482,15 +476,14 @@ class CuraEngineBackend(QObject, Backend): else: # we got a single scenenode if not source.callDecoration("isGroup"): - mesh_data = source.getMeshData() - if mesh_data is None: + if source.getMeshData() is None: return - elif mesh_data.getVertices() is None: + if source.getMeshData().getVertices() is None: return - if source_build_plate_number is not None: - build_plate_changed.add(source_build_plate_number) + build_plate_changed.add(source_build_plate_number) + build_plate_changed.discard(None) build_plate_changed.discard(-1) # object not on build plate if not build_plate_changed: return @@ -584,10 +577,9 @@ class CuraEngineBackend(QObject, Backend): # # \param message The protobuf message containing sliced layer data. def _onOptimizedLayerMessage(self, message: Arcus.PythonMessage) -> None: - if self._start_slice_job_build_plate is not None: - if self._start_slice_job_build_plate not in self._stored_optimized_layer_data: - self._stored_optimized_layer_data[self._start_slice_job_build_plate] = [] - self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message) + if self._start_slice_job_build_plate not in self._stored_optimized_layer_data: + self._stored_optimized_layer_data[self._start_slice_job_build_plate] = [] + self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message) ## Called when a progress message is received from the engine. # @@ -666,10 +658,7 @@ class CuraEngineBackend(QObject, Backend): ## Creates a new socket connection. def _createSocket(self, protocol_file: str = None) -> None: if not protocol_file: - plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) - if not plugin_path: - return - protocol_file = os.path.abspath(os.path.join(plugin_path, "Cura.proto")) + protocol_file = os.path.abspath(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "Cura.proto")) super()._createSocket(protocol_file) self._engine_is_fresh = True diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 8e429da14d..78dd4eafd2 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -114,14 +114,13 @@ class StartSliceJob(Job): self.setResult(StartJobResult.Error) return - global_stack = CuraApplication.getInstance().getGlobalContainerStack() - machine_manager = CuraApplication.getInstance().getMachineManager() - if not global_stack: + stack = CuraApplication.getInstance().getGlobalContainerStack() + if not stack: self.setResult(StartJobResult.Error) return # Don't slice if there is a setting with an error value. - if machine_manager.stacksHaveErrors: + if CuraApplication.getInstance().getMachineManager().stacksHaveErrors: self.setResult(StartJobResult.SettingError) return @@ -130,12 +129,12 @@ class StartSliceJob(Job): return # Don't slice if the buildplate or the nozzle type is incompatible with the materials - if not machine_manager.variantBuildplateCompatible and \ - not machine_manager.variantBuildplateUsable: + if not CuraApplication.getInstance().getMachineManager().variantBuildplateCompatible and \ + not CuraApplication.getInstance().getMachineManager().variantBuildplateUsable: self.setResult(StartJobResult.MaterialIncompatible) return - for position, extruder_stack in global_stack.extruders.items(): + for position, extruder_stack in stack.extruders.items(): material = extruder_stack.findContainer({"type": "material"}) if not extruder_stack.isEnabled: continue @@ -163,7 +162,7 @@ class StartSliceJob(Job): # Get the objects in their groups to print. object_groups = [] - if global_stack.getProperty("print_sequence", "value") == "one_at_a_time": + if stack.getProperty("print_sequence", "value") == "one_at_a_time": for node in OneAtATimeIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. temp_list = [] @@ -217,11 +216,12 @@ class StartSliceJob(Job): if temp_list: object_groups.append(temp_list) - extruders_enabled = {position: stack.isEnabled for position, stack in global_stack.extruders.items()} + extruders_enabled = {position: stack.isEnabled for position, stack in CuraApplication.getInstance().getGlobalContainerStack().extruders.items()} filtered_object_groups = [] has_model_with_disabled_extruders = False - associated_disabled_extruders = set() # type: Set[str] + associated_disabled_extruders = set() for group in object_groups: + stack = CuraApplication.getInstance().getGlobalContainerStack() skip_group = False for node in group: extruder_position = node.callDecoration("getActiveExtruderPosition") @@ -234,7 +234,7 @@ class StartSliceJob(Job): if has_model_with_disabled_extruders: self.setResult(StartJobResult.ObjectsWithDisabledExtruder) - associated_disabled_extruders = set([str(c) for c in sorted([int(p) + 1 for p in associated_disabled_extruders])]) + associated_disabled_extruders = [str(c) for c in sorted([int(p) + 1 for p in associated_disabled_extruders])] self.setMessage(", ".join(associated_disabled_extruders)) return @@ -245,11 +245,11 @@ class StartSliceJob(Job): self.setResult(StartJobResult.NothingToSlice) return - self._buildGlobalSettingsMessage(global_stack) - self._buildGlobalInheritsStackMessage(global_stack) + self._buildGlobalSettingsMessage(stack) + self._buildGlobalInheritsStackMessage(stack) # Build messages for extruder stacks - for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): + for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()): self._buildExtruderMessage(extruder_stack) for group in filtered_object_groups: @@ -326,8 +326,6 @@ class StartSliceJob(Job): def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str: if not self._all_extruders_settings: global_stack = CuraApplication.getInstance().getGlobalContainerStack() - if not global_stack: - return str(value) # NB: keys must be strings for the string formatter self._all_extruders_settings = { From c0985bec2ae0c32f2c55e51e5ced6b28a0d2c060 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Thu, 5 Jul 2018 15:32:43 +0200 Subject: [PATCH 11/13] Add the data to the tests. --- .../VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py b/plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py index a6a3a1febf..22df0d6487 100644 --- a/plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py +++ b/plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py @@ -22,6 +22,7 @@ test_upgrade_version_nr_data = [ ] ## Tests whether the version numbers are updated. +@pytest.mark.parametrize("test_name, file_data", test_upgrade_version_nr_data) def test_upgradeVersionNr(test_name, file_data, upgrader): #Perform the upgrade. _, upgraded_instances = upgrader.upgradePreferences(file_data, "") From 96896088c365b3e97cffcb0e2bb5f563da7097c1 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 5 Jul 2018 16:27:30 +0200 Subject: [PATCH 12/13] Add spaces around equals operators As per our code style regulations. --- .../scripts/PauseAtHeight.py | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py b/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py index ad83aa2a24..c78351909d 100644 --- a/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py +++ b/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py @@ -247,57 +247,57 @@ class PauseAtHeight(Script): prepend_gcode += ";added code by post processing\n" prepend_gcode += ";script: PauseAtHeight.py\n" if pause_at == "height": - prepend_gcode += ";current z: {z}\n".format(z=current_z) - prepend_gcode += ";current height: {height}\n".format(height=current_height) + prepend_gcode += ";current z: {z}\n".format(z = current_z) + prepend_gcode += ";current height: {height}\n".format(height = current_height) else: - prepend_gcode += ";current layer: {layer}\n".format(layer=current_layer) + prepend_gcode += ";current layer: {layer}\n".format(layer = current_layer) # Retraction - prepend_gcode += self.putValue(M=83) + "\n" + prepend_gcode += self.putValue(M = 83) + "\n" if retraction_amount != 0: - prepend_gcode += self.putValue(G=1, E=-retraction_amount, F=retraction_speed * 60) + "\n" + prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n" # Move the head away - prepend_gcode += self.putValue(G=1, Z=current_z + 1, F=300) + "\n" + prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n" # This line should be ok - prepend_gcode += self.putValue(G=1, X=park_x, Y=park_y, F=9000) + "\n" + prepend_gcode += self.putValue(G = 1, X = park_x, Y = park_y, F = 9000) + "\n" if current_z < 15: - prepend_gcode += self.putValue(G=1, Z=15, F=300) + "\n" + prepend_gcode += self.putValue(G = 1, Z = 15, F = 300) + "\n" # Set extruder standby temperature - prepend_gcode += self.putValue(M=104, S=standby_temperature) + "; standby temperature\n" + prepend_gcode += self.putValue(M = 104, S = standby_temperature) + "; standby temperature\n" # Wait till the user continues printing - prepend_gcode += self.putValue(M=0) + ";Do the actual pause\n" + prepend_gcode += self.putValue(M = 0) + ";Do the actual pause\n" # Set extruder resume temperature prepend_gcode += self.putValue(M = 109, S = int(target_temperature.get(current_t, 0))) + "; resume temperature\n" # Push the filament back, if retraction_amount != 0: - prepend_gcode += self.putValue(G=1, E=retraction_amount, F=retraction_speed * 60) + "\n" + prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n" # Optionally extrude material if extrude_amount != 0: - prepend_gcode += self.putValue(G=1, E=extrude_amount, F=extrude_speed * 60) + "\n" + prepend_gcode += self.putValue(G = 1, E = extrude_amount, F = extrude_speed * 60) + "\n" # and retract again, the properly primes the nozzle # when changing filament. if retraction_amount != 0: - prepend_gcode += self.putValue(G=1, E=-retraction_amount, F=retraction_speed * 60) + "\n" + prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n" # Move the head back - prepend_gcode += self.putValue(G=1, Z=current_z + 1, F=300) + "\n" - prepend_gcode += self.putValue(G=1, X=x, Y=y, F=9000) + "\n" + prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n" + prepend_gcode += self.putValue(G = 1, X = x, Y = y, F = 9000) + "\n" if retraction_amount != 0: - prepend_gcode += self.putValue(G=1, E=retraction_amount, F=retraction_speed * 60) + "\n" - prepend_gcode += self.putValue(G=1, F=9000) + "\n" - prepend_gcode += self.putValue(M=82) + "\n" + prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n" + prepend_gcode += self.putValue(G = 1, F = 9000) + "\n" + prepend_gcode += self.putValue(M = 82) + "\n" # reset extrude value to pre pause value - prepend_gcode += self.putValue(G=92, E=current_e) + "\n" + prepend_gcode += self.putValue(G = 92, E = current_e) + "\n" layer = prepend_gcode + layer From afd3ce205a093ef3cecbf96edff70f2143ef0337 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 5 Jul 2018 20:19:40 +0200 Subject: [PATCH 13/13] Use double quotes As per our code style. --- cura_app.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cura_app.py b/cura_app.py index a7059a3940..c3c766fdb1 100755 --- a/cura_app.py +++ b/cura_app.py @@ -12,14 +12,14 @@ from UM.Platform import Platform parser = argparse.ArgumentParser(prog = "cura", add_help = False) -parser.add_argument('--debug', - action='store_true', +parser.add_argument("--debug", + action="store_true", default = False, help = "Turn on the debug mode by setting this option." ) -parser.add_argument('--trigger-early-crash', - dest = 'trigger_early_crash', - action = 'store_true', +parser.add_argument("--trigger-early-crash", + dest = "trigger_early_crash", + action = "store_true", default = False, help = "FOR TESTING ONLY. Trigger an early crash to show the crash dialog." )