From 3ac5342dfc0d37e7462ed77e9d9b7321ec769ef3 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 1 Aug 2018 15:51:10 +0200 Subject: [PATCH 01/97] Separate firmware updater from USBPrinterOutputDevice --- plugins/USBPrinting/AvrFirmwareUpdater.py | 122 ++++++++++++++++++ plugins/USBPrinting/USBPrinterOutputDevice.py | 118 ++--------------- 2 files changed, 130 insertions(+), 110 deletions(-) create mode 100644 plugins/USBPrinting/AvrFirmwareUpdater.py diff --git a/plugins/USBPrinting/AvrFirmwareUpdater.py b/plugins/USBPrinting/AvrFirmwareUpdater.py new file mode 100644 index 0000000000..5c2b8dc19e --- /dev/null +++ b/plugins/USBPrinting/AvrFirmwareUpdater.py @@ -0,0 +1,122 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from PyQt5.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty + +from cura.PrinterOutputDevice import PrinterOutputDevice + +from .avr_isp import stk500v2, intelHex + +from enum import IntEnum + +@signalemitter +class AvrFirmwareUpdater(QObject): + firmwareProgressChanged = pyqtSignal() + firmwareUpdateStateChanged = pyqtSignal() + + def __init__(self, output_device: PrinterOutputDevice) -> None: + self._output_device = output_device + + self._update_firmware_thread = Thread(target=self._updateFirmware, daemon = True) + + self._firmware_view = None + self._firmware_location = None + self._firmware_progress = 0 + self._firmware_update_state = FirmwareUpdateState.idle + + def updateFirmware(self, file): + # the file path could be url-encoded. + if file.startswith("file://"): + self._firmware_location = QUrl(file).toLocalFile() + else: + self._firmware_location = file + self.showFirmwareInterface() + self.setFirmwareUpdateState(FirmwareUpdateState.updating) + self._update_firmware_thread.start() + + def _updateFirmware(self): + # Ensure that other connections are closed. + if self._connection_state != ConnectionState.closed: + self.close() + + try: + hex_file = intelHex.readHex(self._firmware_location) + assert len(hex_file) > 0 + except (FileNotFoundError, AssertionError): + Logger.log("e", "Unable to read provided hex file. Could not update firmware.") + self.setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error) + return + + programmer = stk500v2.Stk500v2() + programmer.progress_callback = self._onFirmwareProgress + + try: + programmer.connect(self._serial_port) + except: + programmer.close() + Logger.logException("e", "Failed to update firmware") + self.setFirmwareUpdateState(FirmwareUpdateState.communication_error) + return + + # Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases. + sleep(1) + if not programmer.isConnected(): + Logger.log("e", "Unable to connect with serial. Could not update firmware") + self.setFirmwareUpdateState(FirmwareUpdateState.communication_error) + try: + programmer.programChip(hex_file) + except SerialException: + self.setFirmwareUpdateState(FirmwareUpdateState.io_error) + return + except: + self.setFirmwareUpdateState(FirmwareUpdateState.unknown_error) + return + + programmer.close() + + # Clean up for next attempt. + self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True) + self._firmware_location = "" + self._onFirmwareProgress(100) + self.setFirmwareUpdateState(FirmwareUpdateState.completed) + + # Try to re-connect with the machine again, which must be done on the Qt thread, so we use call later. + CuraApplication.getInstance().callLater(self.connect) + + ## Show firmware interface. + # This will create the view if its not already created. + def showFirmwareInterface(self): + if self._firmware_view is None: + path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml") + self._firmware_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) + + self._firmware_view.show() + + @pyqtProperty(float, notify = firmwareProgressChanged) + def firmwareProgress(self): + return self._firmware_progress + + @pyqtProperty(int, notify=firmwareUpdateStateChanged) + def firmwareUpdateState(self): + return self._firmware_update_state + + def setFirmwareUpdateState(self, state): + if self._firmware_update_state != state: + self._firmware_update_state = state + self.firmwareUpdateStateChanged.emit() + + # Callback function for firmware update progress. + def _onFirmwareProgress(self, progress, max_progress = 100): + self._firmware_progress = (progress / max_progress) * 100 # Convert to scale of 0-100 + self.firmwareProgressChanged.emit() + + +class FirmwareUpdateState(IntEnum): + idle = 0 + updating = 1 + completed = 2 + unknown_error = 3 + communication_error = 4 + io_error = 5 + firmware_not_found_error = 6 + diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 45b566fcab..bc2350e50f 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -13,15 +13,14 @@ from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel from cura.PrinterOutput.GenericOutputController import GenericOutputController from .AutoDetectBaudJob import AutoDetectBaudJob -from .avr_isp import stk500v2, intelHex +from .AvrFirmwareUpdater import AvrFirmwareUpdater -from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty, QUrl +from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty from serial import Serial, SerialException, SerialTimeoutException from threading import Thread, Event from time import time, sleep from queue import Queue -from enum import IntEnum from typing import Union, Optional, List, cast import re @@ -32,9 +31,6 @@ catalog = i18nCatalog("cura") class USBPrinterOutputDevice(PrinterOutputDevice): - firmwareProgressChanged = pyqtSignal() - firmwareUpdateStateChanged = pyqtSignal() - def __init__(self, serial_port: str, baud_rate: Optional[int] = None) -> None: super().__init__(serial_port) self.setName(catalog.i18nc("@item:inmenu", "USB printing")) @@ -61,8 +57,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice): # Instead of using a timer, we really need the update to be as a thread, as reading from serial can block. self._update_thread = Thread(target=self._update, daemon = True) - self._update_firmware_thread = Thread(target=self._updateFirmware, daemon = True) - self._last_temperature_request = None # type: Optional[int] self._is_printing = False # A print is being sent. @@ -75,11 +69,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._paused = False - self._firmware_view = None - self._firmware_location = None - self._firmware_progress = 0 - self._firmware_update_state = FirmwareUpdateState.idle - self.setConnectionText(catalog.i18nc("@info:status", "Connected via USB")) # Queue for commands that need to be sent. @@ -88,6 +77,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._command_received = Event() self._command_received.set() + self._firmware_updater = AvrFirmwareUpdater(self) + CuraApplication.getInstance().getOnExitCallbackManager().addCallback(self._checkActivePrintingUponAppExit) # This is a callback function that checks if there is any printing in progress via USB when the application tries @@ -107,6 +98,10 @@ class USBPrinterOutputDevice(PrinterOutputDevice): application = CuraApplication.getInstance() application.triggerNextExitCheck() + @pyqtSlot(str) + def updateFirmware(self, file): + self._firmware_updater.updateFirmware(file) + ## Reset USB device settings # def resetDeviceSettings(self): @@ -135,93 +130,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._printGCode(gcode_list) - ## Show firmware interface. - # This will create the view if its not already created. - def showFirmwareInterface(self): - if self._firmware_view is None: - path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml") - self._firmware_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) - - self._firmware_view.show() - - @pyqtSlot(str) - def updateFirmware(self, file): - # the file path could be url-encoded. - if file.startswith("file://"): - self._firmware_location = QUrl(file).toLocalFile() - else: - self._firmware_location = file - self.showFirmwareInterface() - self.setFirmwareUpdateState(FirmwareUpdateState.updating) - self._update_firmware_thread.start() - - def _updateFirmware(self): - # Ensure that other connections are closed. - if self._connection_state != ConnectionState.closed: - self.close() - - try: - hex_file = intelHex.readHex(self._firmware_location) - assert len(hex_file) > 0 - except (FileNotFoundError, AssertionError): - Logger.log("e", "Unable to read provided hex file. Could not update firmware.") - self.setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error) - return - - programmer = stk500v2.Stk500v2() - programmer.progress_callback = self._onFirmwareProgress - - try: - programmer.connect(self._serial_port) - except: - programmer.close() - Logger.logException("e", "Failed to update firmware") - self.setFirmwareUpdateState(FirmwareUpdateState.communication_error) - return - - # Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases. - sleep(1) - if not programmer.isConnected(): - Logger.log("e", "Unable to connect with serial. Could not update firmware") - self.setFirmwareUpdateState(FirmwareUpdateState.communication_error) - try: - programmer.programChip(hex_file) - except SerialException: - self.setFirmwareUpdateState(FirmwareUpdateState.io_error) - return - except: - self.setFirmwareUpdateState(FirmwareUpdateState.unknown_error) - return - - programmer.close() - - # Clean up for next attempt. - self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True) - self._firmware_location = "" - self._onFirmwareProgress(100) - self.setFirmwareUpdateState(FirmwareUpdateState.completed) - - # Try to re-connect with the machine again, which must be done on the Qt thread, so we use call later. - CuraApplication.getInstance().callLater(self.connect) - - @pyqtProperty(float, notify = firmwareProgressChanged) - def firmwareProgress(self): - return self._firmware_progress - - @pyqtProperty(int, notify=firmwareUpdateStateChanged) - def firmwareUpdateState(self): - return self._firmware_update_state - - def setFirmwareUpdateState(self, state): - if self._firmware_update_state != state: - self._firmware_update_state = state - self.firmwareUpdateStateChanged.emit() - - # Callback function for firmware update progress. - def _onFirmwareProgress(self, progress, max_progress = 100): - self._firmware_progress = (progress / max_progress) * 100 # Convert to scale of 0-100 - self.firmwareProgressChanged.emit() - ## Start a print based on a g-code. # \param gcode_list List with gcode (strings). def _printGCode(self, gcode_list: List[str]): @@ -456,13 +364,3 @@ class USBPrinterOutputDevice(PrinterOutputDevice): print_job.updateTimeTotal(estimated_time) self._gcode_position += 1 - - -class FirmwareUpdateState(IntEnum): - idle = 0 - updating = 1 - completed = 2 - unknown_error = 3 - communication_error = 4 - io_error = 5 - firmware_not_found_error = 6 From 339987be9d3a7cf80d7932c49cd418009e9182df Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 2 Aug 2018 11:50:28 +0200 Subject: [PATCH 02/97] Move hardcoded firmware-file table to definitions --- .../USBPrinterOutputDeviceManager.py | 44 ++++--------------- resources/definitions/bq_hephestos.def.json | 3 +- resources/definitions/bq_witbox.def.json | 3 +- resources/definitions/ultimaker2.def.json | 3 +- .../definitions/ultimaker2_extended.def.json | 3 +- .../ultimaker2_extended_plus.def.json | 3 +- resources/definitions/ultimaker2_go.def.json | 3 +- .../definitions/ultimaker2_plus.def.json | 3 +- .../definitions/ultimaker_original.def.json | 4 +- .../ultimaker_original_dual.def.json | 2 + .../ultimaker_original_plus.def.json | 3 +- 11 files changed, 30 insertions(+), 44 deletions(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py index 2ee85187ee..f444e72908 100644 --- a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py +++ b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py @@ -93,57 +93,31 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin): global_container_stack = self._application.getGlobalContainerStack() if not global_container_stack: Logger.log("e", "There is no global container stack. Can not update firmware.") - self._firmware_view.close() return "" # The bottom of the containerstack is the machine definition machine_id = global_container_stack.getBottom().id - machine_has_heated_bed = global_container_stack.getProperty("machine_heated_bed", "value") + baudrate = 250000 if platform.system() == "Linux": + # Linux prefers a baudrate of 115200 here because older versions of + # pySerial did not support a baudrate of 250000 baudrate = 115200 - else: - baudrate = 250000 - # NOTE: The keyword used here is the id of the machine. You can find the id of your machine in the *.json file, eg. - # https://github.com/Ultimaker/Cura/blob/master/resources/machines/ultimaker_original.json#L2 - # The *.hex files are stored at a seperate repository: - # https://github.com/Ultimaker/cura-binary-data/tree/master/cura/resources/firmware - machine_without_extras = {"bq_witbox" : "MarlinWitbox.hex", - "bq_hephestos_2" : "MarlinHephestos2.hex", - "ultimaker_original" : "MarlinUltimaker-{baudrate}.hex", - "ultimaker_original_plus" : "MarlinUltimaker-UMOP-{baudrate}.hex", - "ultimaker_original_dual" : "MarlinUltimaker-{baudrate}-dual.hex", - "ultimaker2" : "MarlinUltimaker2.hex", - "ultimaker2_go" : "MarlinUltimaker2go.hex", - "ultimaker2_plus" : "MarlinUltimaker2plus.hex", - "ultimaker2_extended" : "MarlinUltimaker2extended.hex", - "ultimaker2_extended_plus" : "MarlinUltimaker2extended-plus.hex", - } - machine_with_heated_bed = {"ultimaker_original" : "MarlinUltimaker-HBK-{baudrate}.hex", - "ultimaker_original_dual" : "MarlinUltimaker-HBK-{baudrate}-dual.hex", - } - ##TODO: Add check for multiple extruders - hex_file = None - if machine_id in machine_without_extras.keys(): # The machine needs to be defined here! - if machine_id in machine_with_heated_bed.keys() and machine_has_heated_bed: - Logger.log("d", "Choosing firmware with heated bed enabled for machine %s.", machine_id) - hex_file = machine_with_heated_bed[machine_id] # Return firmware with heated bed enabled - else: - Logger.log("d", "Choosing basic firmware for machine %s.", machine_id) - hex_file = machine_without_extras[machine_id] # Return "basic" firmware - else: - Logger.log("w", "There is no firmware for machine %s.", machine_id) + # If a firmware file is available, it should be specified in the definition for the printer + hex_file = global_container_stack.getMetaDataEntry("firmware_file", None) + if machine_has_heated_bed: + hex_file = global_container_stack.getMetaDataEntry("firmware_hbk_file", hex_file) if hex_file: try: return Resources.getPath(CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate)) except FileNotFoundError: - Logger.log("w", "Could not find any firmware for machine %s.", machine_id) + Logger.log("w", "Firmware file %s not found.", hex_file) return "" else: - Logger.log("w", "Could not find any firmware for machine %s.", machine_id) + Logger.log("w", "There is no firmware for machine %s.", machine_id) return "" ## Helper to identify serial ports (and scan for them) diff --git a/resources/definitions/bq_hephestos.def.json b/resources/definitions/bq_hephestos.def.json index 8dc67a8cad..be024cd6fa 100644 --- a/resources/definitions/bq_hephestos.def.json +++ b/resources/definitions/bq_hephestos.def.json @@ -12,7 +12,8 @@ "machine_extruder_trains": { "0": "bq_hephestos_extruder_0" - } + }, + "firmware_file": "MarlinHephestos2.hex" }, "overrides": { diff --git a/resources/definitions/bq_witbox.def.json b/resources/definitions/bq_witbox.def.json index 0ae1c5e339..b96da6179c 100644 --- a/resources/definitions/bq_witbox.def.json +++ b/resources/definitions/bq_witbox.def.json @@ -12,7 +12,8 @@ "machine_extruder_trains": { "0": "bq_witbox_extruder_0" - } + }, + "firmware_file": "MarlinWitbox.hex" }, "overrides": { diff --git a/resources/definitions/ultimaker2.def.json b/resources/definitions/ultimaker2.def.json index aa684946c2..ca7a784bfc 100644 --- a/resources/definitions/ultimaker2.def.json +++ b/resources/definitions/ultimaker2.def.json @@ -20,7 +20,8 @@ "machine_extruder_trains": { "0": "ultimaker2_extruder_0" - } + }, + "firmware_file": "MarlinUltimaker2.hex" }, "overrides": { "machine_name": { "default_value": "Ultimaker 2" }, diff --git a/resources/definitions/ultimaker2_extended.def.json b/resources/definitions/ultimaker2_extended.def.json index af169c94fb..39a1ca37b3 100644 --- a/resources/definitions/ultimaker2_extended.def.json +++ b/resources/definitions/ultimaker2_extended.def.json @@ -14,7 +14,8 @@ "machine_extruder_trains": { "0": "ultimaker2_extended_extruder_0" - } + }, + "firmware_file": "MarlinUltimaker2extended.hex" }, "overrides": { diff --git a/resources/definitions/ultimaker2_extended_plus.def.json b/resources/definitions/ultimaker2_extended_plus.def.json index f3a8bfcf9f..c296ecd43e 100644 --- a/resources/definitions/ultimaker2_extended_plus.def.json +++ b/resources/definitions/ultimaker2_extended_plus.def.json @@ -14,7 +14,8 @@ "machine_extruder_trains": { "0": "ultimaker2_extended_plus_extruder_0" - } + }, + "firmware_file": "MarlinUltimaker2extended-plus.hex" }, "overrides": { diff --git a/resources/definitions/ultimaker2_go.def.json b/resources/definitions/ultimaker2_go.def.json index c66fb38fc0..5301fd7db9 100644 --- a/resources/definitions/ultimaker2_go.def.json +++ b/resources/definitions/ultimaker2_go.def.json @@ -17,7 +17,8 @@ "machine_extruder_trains": { "0": "ultimaker2_go_extruder_0" - } + }, + "firmware_file": "MarlinUltimaker2go.hex" }, "overrides": { diff --git a/resources/definitions/ultimaker2_plus.def.json b/resources/definitions/ultimaker2_plus.def.json index bc4d3a6230..45019789bf 100644 --- a/resources/definitions/ultimaker2_plus.def.json +++ b/resources/definitions/ultimaker2_plus.def.json @@ -19,7 +19,8 @@ "machine_extruder_trains": { "0": "ultimaker2_plus_extruder_0" - } + }, + "firmware_file": "MarlinUltimaker2plus.hex" }, "overrides": { diff --git a/resources/definitions/ultimaker_original.def.json b/resources/definitions/ultimaker_original.def.json index c961423504..bb6a64d8dc 100644 --- a/resources/definitions/ultimaker_original.def.json +++ b/resources/definitions/ultimaker_original.def.json @@ -18,7 +18,9 @@ "machine_extruder_trains": { "0": "ultimaker_original_extruder_0" - } + }, + "firmware_file": "MarlinUltimaker-{baudrate}.hex", + "firmware_hbk_file": "MarlinUltimaker-HKB-{baudrate}.hex" }, "overrides": { diff --git a/resources/definitions/ultimaker_original_dual.def.json b/resources/definitions/ultimaker_original_dual.def.json index 55eddba85f..c6002ef396 100644 --- a/resources/definitions/ultimaker_original_dual.def.json +++ b/resources/definitions/ultimaker_original_dual.def.json @@ -19,6 +19,8 @@ "0": "ultimaker_original_dual_1st", "1": "ultimaker_original_dual_2nd" }, + "firmware_file": "MarlinUltimaker-{baudrate}-dual.hex", + "firmware_hbk_file": "MarlinUltimaker-HKB-{baudrate}-dual.hex", "first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"], "supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"] }, diff --git a/resources/definitions/ultimaker_original_plus.def.json b/resources/definitions/ultimaker_original_plus.def.json index 71aa53b2bf..46d95f8028 100644 --- a/resources/definitions/ultimaker_original_plus.def.json +++ b/resources/definitions/ultimaker_original_plus.def.json @@ -16,7 +16,8 @@ "machine_extruder_trains": { "0": "ultimaker_original_plus_extruder_0" - } + }, + "firmware_file": "MarlinUltimaker-UMOP-{baudrate}.hex" }, "overrides": { From 688a5083d223b84067d9c4b6a7ccc301b6618db1 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 8 Aug 2018 15:53:26 +0200 Subject: [PATCH 03/97] Add canUpdateFirmware property to printer output devices --- cura/PrinterOutput/GenericOutputController.py | 2 ++ cura/PrinterOutput/PrinterOutputController.py | 1 + cura/PrinterOutput/PrinterOutputModel.py | 7 +++++++ plugins/USBPrinting/AvrFirmwareUpdater.py | 1 - .../UpgradeFirmwareMachineAction.qml | 1 + 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cura/PrinterOutput/GenericOutputController.py b/cura/PrinterOutput/GenericOutputController.py index e6310e5bff..32ad9d8022 100644 --- a/cura/PrinterOutput/GenericOutputController.py +++ b/cura/PrinterOutput/GenericOutputController.py @@ -29,6 +29,8 @@ class GenericOutputController(PrinterOutputController): self._output_device.printersChanged.connect(self._onPrintersChanged) self._active_printer = None + self.can_update_firmware = True + def _onPrintersChanged(self): if self._active_printer: self._active_printer.stateChanged.disconnect(self._onPrinterStateChanged) diff --git a/cura/PrinterOutput/PrinterOutputController.py b/cura/PrinterOutput/PrinterOutputController.py index 58c6ef05a7..4fe5c04a65 100644 --- a/cura/PrinterOutput/PrinterOutputController.py +++ b/cura/PrinterOutput/PrinterOutputController.py @@ -18,6 +18,7 @@ class PrinterOutputController: self.can_pre_heat_hotends = True self.can_send_raw_gcode = True self.can_control_manually = True + self.can_update_firmware = False self._output_device = output_device def setTargetHotendTemperature(self, printer: "PrinterOutputModel", extruder: "ExtruderOutputModel", temperature: int): diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index 6fafa368bb..f43dbf570e 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -277,6 +277,13 @@ class PrinterOutputModel(QObject): return self._controller.can_control_manually return False + # Does the printer support upgrading firmware + @pyqtProperty(bool, notify = canUpdateFirmwareChanged) + def canUpdateFirmware(self): + if self._controller: + return self._controller.can_update_firmware + return False + # Returns the configuration (material, variant and buildplate) of the current printer @pyqtProperty(QObject, notify = configurationChanged) def printerConfiguration(self): diff --git a/plugins/USBPrinting/AvrFirmwareUpdater.py b/plugins/USBPrinting/AvrFirmwareUpdater.py index 5c2b8dc19e..681601e3a5 100644 --- a/plugins/USBPrinting/AvrFirmwareUpdater.py +++ b/plugins/USBPrinting/AvrFirmwareUpdater.py @@ -9,7 +9,6 @@ from .avr_isp import stk500v2, intelHex from enum import IntEnum -@signalemitter class AvrFirmwareUpdater(QObject): firmwareProgressChanged = pyqtSignal() firmwareUpdateStateChanged = pyqtSignal() diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml index ed771d2a04..03c17cd811 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml @@ -16,6 +16,7 @@ Cura.MachineAction anchors.fill: parent; property bool printerConnected: Cura.MachineManager.printerConnected property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null + property var canUpdateFirmware: activeOutputDevice ? activeOutputDevice.canUpdateFirmware : False Item { From 4bea1410b8ca194ed64dec4f53864c89b84667e8 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 22 Aug 2018 14:37:48 +0200 Subject: [PATCH 04/97] Allow printer output devices to set their ability to update firmware --- cura/PrinterOutput/GenericOutputController.py | 2 -- cura/PrinterOutput/PrinterOutputController.py | 7 ++++ cura/PrinterOutput/PrinterOutputModel.py | 6 ++++ plugins/USBPrinting/USBPrinterOutputDevice.py | 8 +++-- .../UpgradeFirmwareMachineAction.qml | 34 ++++++++++++------- 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/cura/PrinterOutput/GenericOutputController.py b/cura/PrinterOutput/GenericOutputController.py index 32ad9d8022..e6310e5bff 100644 --- a/cura/PrinterOutput/GenericOutputController.py +++ b/cura/PrinterOutput/GenericOutputController.py @@ -29,8 +29,6 @@ class GenericOutputController(PrinterOutputController): self._output_device.printersChanged.connect(self._onPrintersChanged) self._active_printer = None - self.can_update_firmware = True - def _onPrintersChanged(self): if self._active_printer: self._active_printer.stateChanged.disconnect(self._onPrinterStateChanged) diff --git a/cura/PrinterOutput/PrinterOutputController.py b/cura/PrinterOutput/PrinterOutputController.py index 4fe5c04a65..eb5f15cceb 100644 --- a/cura/PrinterOutput/PrinterOutputController.py +++ b/cura/PrinterOutput/PrinterOutputController.py @@ -2,6 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from UM.Logger import Logger +from UM.Signal import Signal MYPY = False if MYPY: @@ -56,3 +57,9 @@ class PrinterOutputController: def sendRawCommand(self, printer: "PrinterOutputModel", command: str): Logger.log("w", "Custom command not implemented in controller") + + canUpdateFirmwareChanged = Signal() + def setCanUpdateFirmware(self, can_update_firmware: bool): + if can_update_firmware != self.can_update_firmware: + self.can_update_firmware = can_update_firmware + self.canUpdateFirmwareChanged.emit() \ No newline at end of file diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index 252fc35080..5d63f6f1ce 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -26,6 +26,7 @@ class PrinterOutputModel(QObject): buildplateChanged = pyqtSignal() cameraChanged = pyqtSignal() configurationChanged = pyqtSignal() + canUpdateFirmwareChanged = pyqtSignal() def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = "") -> None: super().__init__(parent) @@ -34,6 +35,7 @@ class PrinterOutputModel(QObject): self._name = "" self._key = "" # Unique identifier self._controller = output_controller + self._controller.canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged) self._extruders = [ExtruderOutputModel(printer = self, position = i) for i in range(number_of_extruders)] self._printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer self._head_position = Vector(0, 0, 0) @@ -284,6 +286,10 @@ class PrinterOutputModel(QObject): return self._controller.can_update_firmware return False + # Stub to connect UM.Signal to pyqtSignal + def _onControllerCanUpdateFirmwareChanged(self): + self.canUpdateFirmwareChanged.emit() + # Returns the configuration (material, variant and buildplate) of the current printer @pyqtProperty(QObject, notify = configurationChanged) def printerConfiguration(self): diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index bc2350e50f..957269f155 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -183,7 +183,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice): container_stack = CuraApplication.getInstance().getGlobalContainerStack() num_extruders = container_stack.getProperty("machine_extruder_count", "value") # Ensure that a printer is created. - self._printers = [PrinterOutputModel(output_controller=GenericOutputController(self), number_of_extruders=num_extruders)] + controller = GenericOutputController(self) + controller.setCanUpdateFirmware(True) + self._printers = [PrinterOutputModel(output_controller=controller, number_of_extruders=num_extruders)] self._printers[0].updateName(container_stack.getName()) self.setConnectionState(ConnectionState.connected) self._update_thread.start() @@ -353,7 +355,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice): elapsed_time = int(time() - self._print_start_time) print_job = self._printers[0].activePrintJob if print_job is None: - print_job = PrintJobOutputModel(output_controller = GenericOutputController(self), name= CuraApplication.getInstance().getPrintInformation().jobName) + controller = GenericOutputController(self) + controller.setCanUpdateFirmware(True) + print_job = PrintJobOutputModel(output_controller = controller, name= CuraApplication.getInstance().getPrintInformation().jobName) print_job.updateState("printing") self._printers[0].updateActivePrintJob(print_job) diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml index 03c17cd811..7c15c303b5 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml @@ -16,17 +16,17 @@ Cura.MachineAction anchors.fill: parent; property bool printerConnected: Cura.MachineManager.printerConnected property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null - property var canUpdateFirmware: activeOutputDevice ? activeOutputDevice.canUpdateFirmware : False + property var canUpdateFirmware: activeOutputDevice ? activeOutputDevice.activePrinter.canUpdateFirmware : False - Item + Column { id: upgradeFirmwareMachineAction anchors.fill: parent; UM.I18nCatalog { id: catalog; name:"cura"} + spacing: UM.Theme.getSize("default_margin").height Label { - id: pageTitle width: parent.width text: catalog.i18nc("@title", "Upgrade Firmware") wrapMode: Text.WordWrap @@ -34,9 +34,6 @@ Cura.MachineAction } Label { - id: pageDescription - anchors.top: pageTitle.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height width: parent.width wrapMode: Text.WordWrap text: catalog.i18nc("@label", "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work.") @@ -44,9 +41,6 @@ Cura.MachineAction Label { - id: upgradeText1 - anchors.top: pageDescription.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height width: parent.width wrapMode: Text.WordWrap text: catalog.i18nc("@label", "The firmware shipping with new printers works, but new versions tend to have more features and improvements."); @@ -54,8 +48,6 @@ Cura.MachineAction Row { - anchors.top: upgradeText1.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.horizontalCenter: parent.horizontalCenter width: childrenRect.width spacing: UM.Theme.getSize("default_margin").width @@ -64,7 +56,7 @@ Cura.MachineAction { id: autoUpgradeButton text: catalog.i18nc("@action:button", "Automatically upgrade Firmware"); - enabled: parent.firmwareName != "" && activeOutputDevice + enabled: parent.firmwareName != "" && canUpdateFirmware onClicked: { activeOutputDevice.updateFirmware(parent.firmwareName) @@ -74,7 +66,7 @@ Cura.MachineAction { id: manualUpgradeButton text: catalog.i18nc("@action:button", "Upload custom Firmware"); - enabled: activeOutputDevice != null + enabled: canUpdateFirmware onClicked: { customFirmwareDialog.open() @@ -82,6 +74,22 @@ Cura.MachineAction } } + Label + { + width: parent.width + wrapMode: Text.WordWrap + visible: !printerConnected + text: catalog.i18nc("@label", "Firmware can not be upgraded because there is no connection with the printer."); + } + + Label + { + width: parent.width + wrapMode: Text.WordWrap + visible: printerConnected && !canUpdateFirmware + text: catalog.i18nc("@label", "Firmware can not be upgraded because the connection with the printer does not support upgrading firmware."); + } + FileDialog { id: customFirmwareDialog From 5f81c6d1f4a0c7f52feaa0d860946a90d762ee82 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 22 Aug 2018 15:21:03 +0200 Subject: [PATCH 05/97] Add a FirmwareUpdater class and make AvrFirmwareUpdater a subclass --- cura/FirmwareUpdater.py | 81 +++++++++++++++++++ plugins/USBPrinting/AvrFirmwareUpdater.py | 77 +----------------- .../qml}/FirmwareUpdateWindow.qml | 0 3 files changed, 85 insertions(+), 73 deletions(-) create mode 100644 cura/FirmwareUpdater.py rename {plugins/USBPrinting => resources/qml}/FirmwareUpdateWindow.qml (100%) diff --git a/cura/FirmwareUpdater.py b/cura/FirmwareUpdater.py new file mode 100644 index 0000000000..ca5997bfb1 --- /dev/null +++ b/cura/FirmwareUpdater.py @@ -0,0 +1,81 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from PyQt5.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty + +from UM.Resources import Resources +from cura.PrinterOutputDevice import PrinterOutputDevice + +from enum import IntEnum + +class FirmwareUpdater(QObject): + firmwareProgressChanged = pyqtSignal() + firmwareUpdateStateChanged = pyqtSignal() + + def __init__(self, output_device: PrinterOutputDevice) -> None: + self._output_device = output_device + + self._update_firmware_thread = Thread(target=self._updateFirmware, daemon = True) + + self._firmware_view = None + self._firmware_location = None + self._firmware_progress = 0 + self._firmware_update_state = FirmwareUpdateState.idle + + def updateFirmware(self, file): + # the file path could be url-encoded. + if file.startswith("file://"): + self._firmware_location = QUrl(file).toLocalFile() + else: + self._firmware_location = file + self.showFirmwareInterface() + self.setFirmwareUpdateState(FirmwareUpdateState.updating) + self._update_firmware_thread.start() + + def _updateFirmware(self): + raise NotImplementedError("_updateFirmware needs to be implemented") + + def cleanupAfterUpdate(self): + # Clean up for next attempt. + self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True) + self._firmware_location = "" + self._onFirmwareProgress(100) + self.setFirmwareUpdateState(FirmwareUpdateState.completed) + + ## Show firmware interface. + # This will create the view if its not already created. + def showFirmwareInterface(self): + if self._firmware_view is None: + path = Resources.getPath(self.ResourceTypes.QmlFiles, "FirmwareUpdateWindow.qml") + self._firmware_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) + + self._firmware_view.show() + + @pyqtProperty(float, notify = firmwareProgressChanged) + def firmwareProgress(self): + return self._firmware_progress + + @pyqtProperty(int, notify=firmwareUpdateStateChanged) + def firmwareUpdateState(self): + return self._firmware_update_state + + def setFirmwareUpdateState(self, state): + if self._firmware_update_state != state: + self._firmware_update_state = state + self.firmwareUpdateStateChanged.emit() + + # Callback function for firmware update progress. + def _onFirmwareProgress(self, progress, max_progress = 100): + self._firmware_progress = (progress / max_progress) * 100 # Convert to scale of 0-100 + self.firmwareProgressChanged.emit() + + +class FirmwareUpdateState(IntEnum): + idle = 0 + updating = 1 + completed = 2 + unknown_error = 3 + communication_error = 4 + io_error = 5 + firmware_not_found_error = 6 + diff --git a/plugins/USBPrinting/AvrFirmwareUpdater.py b/plugins/USBPrinting/AvrFirmwareUpdater.py index 681601e3a5..d3028be0e4 100644 --- a/plugins/USBPrinting/AvrFirmwareUpdater.py +++ b/plugins/USBPrinting/AvrFirmwareUpdater.py @@ -1,43 +1,16 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from PyQt5.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty - from cura.PrinterOutputDevice import PrinterOutputDevice +from cura.FirmwareUpdater import FirmwareUpdater, FirmwareUpdateState from .avr_isp import stk500v2, intelHex -from enum import IntEnum - -class AvrFirmwareUpdater(QObject): - firmwareProgressChanged = pyqtSignal() - firmwareUpdateStateChanged = pyqtSignal() - +class AvrFirmwareUpdater(FirmwareUpdater): def __init__(self, output_device: PrinterOutputDevice) -> None: - self._output_device = output_device - - self._update_firmware_thread = Thread(target=self._updateFirmware, daemon = True) - - self._firmware_view = None - self._firmware_location = None - self._firmware_progress = 0 - self._firmware_update_state = FirmwareUpdateState.idle - - def updateFirmware(self, file): - # the file path could be url-encoded. - if file.startswith("file://"): - self._firmware_location = QUrl(file).toLocalFile() - else: - self._firmware_location = file - self.showFirmwareInterface() - self.setFirmwareUpdateState(FirmwareUpdateState.updating) - self._update_firmware_thread.start() + super().__init__(output_device) def _updateFirmware(self): - # Ensure that other connections are closed. - if self._connection_state != ConnectionState.closed: - self.close() - try: hex_file = intelHex.readHex(self._firmware_location) assert len(hex_file) > 0 @@ -73,49 +46,7 @@ class AvrFirmwareUpdater(QObject): programmer.close() - # Clean up for next attempt. - self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True) - self._firmware_location = "" - self._onFirmwareProgress(100) - self.setFirmwareUpdateState(FirmwareUpdateState.completed) - # Try to re-connect with the machine again, which must be done on the Qt thread, so we use call later. CuraApplication.getInstance().callLater(self.connect) - ## Show firmware interface. - # This will create the view if its not already created. - def showFirmwareInterface(self): - if self._firmware_view is None: - path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml") - self._firmware_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) - - self._firmware_view.show() - - @pyqtProperty(float, notify = firmwareProgressChanged) - def firmwareProgress(self): - return self._firmware_progress - - @pyqtProperty(int, notify=firmwareUpdateStateChanged) - def firmwareUpdateState(self): - return self._firmware_update_state - - def setFirmwareUpdateState(self, state): - if self._firmware_update_state != state: - self._firmware_update_state = state - self.firmwareUpdateStateChanged.emit() - - # Callback function for firmware update progress. - def _onFirmwareProgress(self, progress, max_progress = 100): - self._firmware_progress = (progress / max_progress) * 100 # Convert to scale of 0-100 - self.firmwareProgressChanged.emit() - - -class FirmwareUpdateState(IntEnum): - idle = 0 - updating = 1 - completed = 2 - unknown_error = 3 - communication_error = 4 - io_error = 5 - firmware_not_found_error = 6 - + self.cleanupAfterUpdate() diff --git a/plugins/USBPrinting/FirmwareUpdateWindow.qml b/resources/qml/FirmwareUpdateWindow.qml similarity index 100% rename from plugins/USBPrinting/FirmwareUpdateWindow.qml rename to resources/qml/FirmwareUpdateWindow.qml From 7b00d6879a5cd197f220e2351686e40d71dcb1d4 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 22 Aug 2018 15:44:11 +0200 Subject: [PATCH 06/97] Factor out USBPrinterManager singleton --- cura/Settings/MachineManager.py | 34 ++++++++++++++++++ .../USBPrinterOutputDeviceManager.py | 35 ------------------- plugins/USBPrinting/__init__.py | 1 - .../UMOCheckupMachineAction.qml | 32 ++++++++--------- .../UpgradeFirmwareMachineAction.qml | 2 +- 5 files changed, 51 insertions(+), 53 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index d65bbfddd9..f330d70225 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -4,6 +4,7 @@ import collections import time from typing import Any, Callable, List, Dict, TYPE_CHECKING, Optional, cast +import platform from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator @@ -16,6 +17,7 @@ from UM.FlameProfiler import pyqtSlot from UM import Util from UM.Logger import Logger from UM.Message import Message +from UM.Resources import Resources from UM.Settings.SettingFunction import SettingFunction from UM.Signal import postponeSignals, CompressTechnique @@ -1531,3 +1533,35 @@ class MachineManager(QObject): with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self.updateMaterialWithVariant(None) self._updateQualityWithMaterial() + + ## Get default firmware file name if one is specified in the firmware + @pyqtSlot(result = str) + def getDefaultFirmwareName(self): + # Check if there is a valid global container stack + if not self._global_container_stack: + return "" + + # The bottom of the containerstack is the machine definition + machine_id = self._global_container_stack.getBottom().id + machine_has_heated_bed = self._global_container_stack.getProperty("machine_heated_bed", "value") + + baudrate = 250000 + if platform.system() == "Linux": + # Linux prefers a baudrate of 115200 here because older versions of + # pySerial did not support a baudrate of 250000 + baudrate = 115200 + + # If a firmware file is available, it should be specified in the definition for the printer + hex_file = self._global_container_stack.getMetaDataEntry("firmware_file", None) + if machine_has_heated_bed: + hex_file = self._global_container_stack.getMetaDataEntry("firmware_hbk_file", hex_file) + + if hex_file: + try: + return Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate)) + except FileNotFoundError: + Logger.log("w", "Firmware file %s not found.", hex_file) + return "" + else: + Logger.log("w", "There is no firmware for machine %s.", machine_id) + return "" diff --git a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py index f444e72908..bd207d9d96 100644 --- a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py +++ b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py @@ -2,14 +2,12 @@ # Cura is released under the terms of the LGPLv3 or higher. import threading -import platform import time import serial.tools.list_ports from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal from UM.Logger import Logger -from UM.Resources import Resources from UM.Signal import Signal, signalemitter from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from UM.i18n import i18nCatalog @@ -87,39 +85,6 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin): self._addRemovePorts(port_list) time.sleep(5) - @pyqtSlot(result = str) - def getDefaultFirmwareName(self): - # Check if there is a valid global container stack - global_container_stack = self._application.getGlobalContainerStack() - if not global_container_stack: - Logger.log("e", "There is no global container stack. Can not update firmware.") - return "" - - # The bottom of the containerstack is the machine definition - machine_id = global_container_stack.getBottom().id - machine_has_heated_bed = global_container_stack.getProperty("machine_heated_bed", "value") - - baudrate = 250000 - if platform.system() == "Linux": - # Linux prefers a baudrate of 115200 here because older versions of - # pySerial did not support a baudrate of 250000 - baudrate = 115200 - - # If a firmware file is available, it should be specified in the definition for the printer - hex_file = global_container_stack.getMetaDataEntry("firmware_file", None) - if machine_has_heated_bed: - hex_file = global_container_stack.getMetaDataEntry("firmware_hbk_file", hex_file) - - if hex_file: - try: - return Resources.getPath(CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate)) - except FileNotFoundError: - Logger.log("w", "Firmware file %s not found.", hex_file) - return "" - else: - Logger.log("w", "There is no firmware for machine %s.", machine_id) - return "" - ## Helper to identify serial ports (and scan for them) def _addRemovePorts(self, serial_ports): # First, find and add all new or changed keys diff --git a/plugins/USBPrinting/__init__.py b/plugins/USBPrinting/__init__.py index fd5488eead..0cb68d3865 100644 --- a/plugins/USBPrinting/__init__.py +++ b/plugins/USBPrinting/__init__.py @@ -14,5 +14,4 @@ def getMetaData(): def register(app): # We are violating the QT API here (as we use a factory, which is technically not allowed). # but we don't really have another means for doing this (and it seems to you know -work-) - qmlRegisterSingletonType(USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager, "Cura", 1, 0, "USBPrinterManager", USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance) return {"output_device": USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager(app)} diff --git a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml index b92638aa12..4a1d42e248 100644 --- a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml +++ b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml @@ -17,7 +17,7 @@ Cura.MachineAction property int rightRow: (checkupMachineAction.width * 0.60) | 0 property bool heatupHotendStarted: false property bool heatupBedStarted: false - property bool usbConnected: Cura.USBPrinterManager.connectedPrinterList.rowCount() > 0 + property bool printerConnected: Cura.MachineManager.printerConnected UM.I18nCatalog { id: catalog; name:"cura"} Label @@ -86,7 +86,7 @@ Cura.MachineAction anchors.left: connectionLabel.right anchors.top: parent.top wrapMode: Text.WordWrap - text: checkupMachineAction.usbConnected ? catalog.i18nc("@info:status","Connected"): catalog.i18nc("@info:status","Not connected") + text: checkupMachineAction.printerConnected ? catalog.i18nc("@info:status","Connected"): catalog.i18nc("@info:status","Not connected") } ////////////////////////////////////////////////////////// Label @@ -97,7 +97,7 @@ Cura.MachineAction anchors.top: connectionLabel.bottom wrapMode: Text.WordWrap text: catalog.i18nc("@label","Min endstop X: ") - visible: checkupMachineAction.usbConnected + visible: checkupMachineAction.printerConnected } Label { @@ -107,7 +107,7 @@ Cura.MachineAction anchors.top: connectionLabel.bottom wrapMode: Text.WordWrap text: manager.xMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked") - visible: checkupMachineAction.usbConnected + visible: checkupMachineAction.printerConnected } ////////////////////////////////////////////////////////////// Label @@ -118,7 +118,7 @@ Cura.MachineAction anchors.top: endstopXLabel.bottom wrapMode: Text.WordWrap text: catalog.i18nc("@label","Min endstop Y: ") - visible: checkupMachineAction.usbConnected + visible: checkupMachineAction.printerConnected } Label { @@ -128,7 +128,7 @@ Cura.MachineAction anchors.top: endstopXLabel.bottom wrapMode: Text.WordWrap text: manager.yMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked") - visible: checkupMachineAction.usbConnected + visible: checkupMachineAction.printerConnected } ///////////////////////////////////////////////////////////////////// Label @@ -139,7 +139,7 @@ Cura.MachineAction anchors.top: endstopYLabel.bottom wrapMode: Text.WordWrap text: catalog.i18nc("@label","Min endstop Z: ") - visible: checkupMachineAction.usbConnected + visible: checkupMachineAction.printerConnected } Label { @@ -149,7 +149,7 @@ Cura.MachineAction anchors.top: endstopYLabel.bottom wrapMode: Text.WordWrap text: manager.zMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked") - visible: checkupMachineAction.usbConnected + visible: checkupMachineAction.printerConnected } //////////////////////////////////////////////////////////// Label @@ -161,7 +161,7 @@ Cura.MachineAction anchors.top: endstopZLabel.bottom wrapMode: Text.WordWrap text: catalog.i18nc("@label","Nozzle temperature check: ") - visible: checkupMachineAction.usbConnected + visible: checkupMachineAction.printerConnected } Label { @@ -171,7 +171,7 @@ Cura.MachineAction anchors.left: nozzleTempLabel.right wrapMode: Text.WordWrap text: catalog.i18nc("@info:status","Not checked") - visible: checkupMachineAction.usbConnected + visible: checkupMachineAction.printerConnected } Item { @@ -181,7 +181,7 @@ Cura.MachineAction anchors.top: nozzleTempLabel.top anchors.left: bedTempStatus.right anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width/2) - visible: checkupMachineAction.usbConnected + visible: checkupMachineAction.printerConnected Button { text: checkupMachineAction.heatupHotendStarted ? catalog.i18nc("@action:button","Stop Heating") : catalog.i18nc("@action:button","Start Heating") @@ -209,7 +209,7 @@ Cura.MachineAction wrapMode: Text.WordWrap text: manager.hotendTemperature + "°C" font.bold: true - visible: checkupMachineAction.usbConnected + visible: checkupMachineAction.printerConnected } ///////////////////////////////////////////////////////////////////////////// Label @@ -221,7 +221,7 @@ Cura.MachineAction anchors.top: nozzleTempLabel.bottom wrapMode: Text.WordWrap text: catalog.i18nc("@label","Build plate temperature check:") - visible: checkupMachineAction.usbConnected && manager.hasHeatedBed + visible: checkupMachineAction.printerConnected && manager.hasHeatedBed } Label @@ -232,7 +232,7 @@ Cura.MachineAction anchors.left: bedTempLabel.right wrapMode: Text.WordWrap text: manager.bedTestCompleted ? catalog.i18nc("@info:status","Not checked"): catalog.i18nc("@info:status","Checked") - visible: checkupMachineAction.usbConnected && manager.hasHeatedBed + visible: checkupMachineAction.printerConnected && manager.hasHeatedBed } Item { @@ -242,7 +242,7 @@ Cura.MachineAction anchors.top: bedTempLabel.top anchors.left: bedTempStatus.right anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width/2) - visible: checkupMachineAction.usbConnected && manager.hasHeatedBed + visible: checkupMachineAction.printerConnected && manager.hasHeatedBed Button { text: checkupMachineAction.heatupBedStarted ?catalog.i18nc("@action:button","Stop Heating") : catalog.i18nc("@action:button","Start Heating") @@ -270,7 +270,7 @@ Cura.MachineAction wrapMode: Text.WordWrap text: manager.bedTemperature + "°C" font.bold: true - visible: checkupMachineAction.usbConnected && manager.hasHeatedBed + visible: checkupMachineAction.printerConnected && manager.hasHeatedBed } Label { diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml index 7c15c303b5..0d12f72a0a 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml @@ -51,7 +51,7 @@ Cura.MachineAction anchors.horizontalCenter: parent.horizontalCenter width: childrenRect.width spacing: UM.Theme.getSize("default_margin").width - property var firmwareName: Cura.USBPrinterManager.getDefaultFirmwareName() + property var firmwareName: Cura.MachineManager.getDefaultFirmwareName() Button { id: autoUpgradeButton From 5d5223920194e20108d7666e7ce6c8350323728e Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 24 Aug 2018 09:09:49 +0200 Subject: [PATCH 07/97] Code style --- plugins/USBPrinting/USBPrinterOutputDevice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 957269f155..18373d34d2 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -205,6 +205,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._command_queue.put(command) else: self._sendCommand(command) + def _sendCommand(self, command: Union[str, bytes]): if self._serial is None or self._connection_state != ConnectionState.connected: return From 77f99ecf20a6f70662ba0304366567d602e411dc Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 24 Aug 2018 15:48:11 +0200 Subject: [PATCH 08/97] Moved FirmwareUpdater to cura.PrinterOutput --- cura/{ => PrinterOutput}/FirmwareUpdater.py | 0 plugins/USBPrinting/AvrFirmwareUpdater.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename cura/{ => PrinterOutput}/FirmwareUpdater.py (100%) diff --git a/cura/FirmwareUpdater.py b/cura/PrinterOutput/FirmwareUpdater.py similarity index 100% rename from cura/FirmwareUpdater.py rename to cura/PrinterOutput/FirmwareUpdater.py diff --git a/plugins/USBPrinting/AvrFirmwareUpdater.py b/plugins/USBPrinting/AvrFirmwareUpdater.py index d3028be0e4..171c81d557 100644 --- a/plugins/USBPrinting/AvrFirmwareUpdater.py +++ b/plugins/USBPrinting/AvrFirmwareUpdater.py @@ -2,7 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from cura.PrinterOutputDevice import PrinterOutputDevice -from cura.FirmwareUpdater import FirmwareUpdater, FirmwareUpdateState +from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater, FirmwareUpdateState from .avr_isp import stk500v2, intelHex From 9739dbd5f69597b281b61f773d9d6418e0e3eaae Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Sun, 2 Sep 2018 17:18:04 +0200 Subject: [PATCH 09/97] Fix missing import --- cura/PrinterOutput/FirmwareUpdater.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cura/PrinterOutput/FirmwareUpdater.py b/cura/PrinterOutput/FirmwareUpdater.py index ca5997bfb1..17089ad17f 100644 --- a/cura/PrinterOutput/FirmwareUpdater.py +++ b/cura/PrinterOutput/FirmwareUpdater.py @@ -7,6 +7,7 @@ from UM.Resources import Resources from cura.PrinterOutputDevice import PrinterOutputDevice from enum import IntEnum +from threading import Thread class FirmwareUpdater(QObject): firmwareProgressChanged = pyqtSignal() From 43b4ca30440a56b843a538ad464e15fccb2fc5f5 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Sun, 2 Sep 2018 18:02:33 +0200 Subject: [PATCH 10/97] Fix code-style --- cura/PrinterOutput/FirmwareUpdater.py | 2 +- plugins/USBPrinting/USBPrinterOutputDevice.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/PrinterOutput/FirmwareUpdater.py b/cura/PrinterOutput/FirmwareUpdater.py index 17089ad17f..e7ffc2a2b5 100644 --- a/cura/PrinterOutput/FirmwareUpdater.py +++ b/cura/PrinterOutput/FirmwareUpdater.py @@ -16,7 +16,7 @@ class FirmwareUpdater(QObject): def __init__(self, output_device: PrinterOutputDevice) -> None: self._output_device = output_device - self._update_firmware_thread = Thread(target=self._updateFirmware, daemon = True) + self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True) self._firmware_view = None self._firmware_location = None diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 18373d34d2..b04b51314c 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -55,7 +55,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._all_baud_rates = [115200, 250000, 230400, 57600, 38400, 19200, 9600] # Instead of using a timer, we really need the update to be as a thread, as reading from serial can block. - self._update_thread = Thread(target=self._update, daemon = True) + self._update_thread = Thread(target=self._update, daemon=True) self._last_temperature_request = None # type: Optional[int] @@ -358,7 +358,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): if print_job is None: controller = GenericOutputController(self) controller.setCanUpdateFirmware(True) - print_job = PrintJobOutputModel(output_controller = controller, name= CuraApplication.getInstance().getPrintInformation().jobName) + print_job = PrintJobOutputModel(output_controller=controller, name=CuraApplication.getInstance().getPrintInformation().jobName) print_job.updateState("printing") self._printers[0].updateActivePrintJob(print_job) From 4a5451576d1894b7eb40bda7f27ac81a20ea6c0f Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Sun, 2 Sep 2018 18:07:30 +0200 Subject: [PATCH 11/97] Update copyright --- cura/PrinterOutput/PrinterOutputController.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/PrinterOutput/PrinterOutputController.py b/cura/PrinterOutput/PrinterOutputController.py index eb5f15cceb..dd2276d771 100644 --- a/cura/PrinterOutput/PrinterOutputController.py +++ b/cura/PrinterOutput/PrinterOutputController.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from UM.Logger import Logger From eb253827be2e331502ec7892286549f66e037eb9 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Thu, 13 Sep 2018 13:48:08 +0200 Subject: [PATCH 12/97] JSON setting: option to let support be replaced by brim or not --- resources/definitions/fdmprinter.def.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 4c87a3bcf0..e5726afdca 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -4538,6 +4538,17 @@ } } }, + "brim_replaces_support": + { + "label": "Brim Replaces Support", + "description": "Enforce brim to be printed around the model even if that space would otherwise be occupied by support. This replaces some regions fo the first layer of supprot by brim regions.", + "type": "bool", + "default_value": true, + "enabled": "resolveOrValue('adhesion_type') == 'brim' and support_enable", + "settable_per_mesh": false, + "settable_per_extruder": true, + "limit_to_extruder": "adhesion_extruder_nr" + }, "brim_outside_only": { "label": "Brim Only on Outside", From 945cc7c3e63d70fa3f66be601dda6be14302b608 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Thu, 13 Sep 2018 14:25:57 +0200 Subject: [PATCH 13/97] JSon feat: support brim settings --- resources/definitions/fdmprinter.def.json | 42 +++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index e5726afdca..74e9bab14d 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -3888,6 +3888,48 @@ "settable_per_mesh": false, "settable_per_extruder": true }, + "support_brim_enable": + { + "label": "Enable Support Brim", + "description": "Generate a brim within the support infill regions of the first layer. This brim is printed underneath the support, not around it. Enabling this setting increases the adhesion of support to the build plate.", + "type": "bool", + "default_value": false, + "enabled": "support_enable or support_tree_enable", + "limit_to_extruder": "support_infill_extruder_nr", + "settable_per_mesh": false, + "settable_per_extruder": true + }, + "support_brim_width": + { + "label": "Support Brim Width", + "description": "The width of the brim to print underneath the support. A larger brim enhances adhesion to the build plate, at the cost of some extra material.", + "type": "float", + "unit": "mm", + "default_value": 8.0, + "minimum_value": "0.0", + "maximum_value_warning": "50.0", + "enabled": "support_enable", + "settable_per_mesh": false, + "settable_per_extruder": true, + "limit_to_extruder": "support_infill_extruder_nr", + "children": + { + "support_brim_line_count": + { + "label": "Support Brim Line Count", + "description": "The number of lines used for the support brim. More brim lines enhance adhesion to the build plate, at the cost of some extra material.", + "type": "int", + "default_value": 20, + "minimum_value": "0", + "maximum_value_warning": "50 / skirt_brim_line_width", + "value": "math.ceil(support_brim_width / (skirt_brim_line_width * initial_layer_line_width_factor / 100.0))", + "enabled": "support_enable", + "settable_per_mesh": false, + "settable_per_extruder": true, + "limit_to_extruder": "support_infill_extruder_nr" + } + } + }, "support_z_distance": { "label": "Support Z Distance", From 7a681a2ae4260b0777072564e78a499ab0257eca Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 26 Sep 2018 16:54:00 +0200 Subject: [PATCH 14/97] Move Cura custom setting functions to a separate file --- cura/CuraApplication.py | 16 ++- cura/Settings/CustomSettingFunctions.py | 134 ++++++++++++++++++++ cura/Settings/ExtruderManager.py | 162 +----------------------- cura/Settings/UserChangesModel.py | 26 ++-- 4 files changed, 161 insertions(+), 177 deletions(-) create mode 100644 cura/Settings/CustomSettingFunctions.py diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index dbaef4df34..857aafb567 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -107,6 +107,7 @@ from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisi from cura.Settings.ContainerManager import ContainerManager from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel import cura.Settings.cura_empty_instance_containers +from cura.Settings.CustomSettingFunctions import CustomSettingFunctions from cura.ObjectsModel import ObjectsModel @@ -174,6 +175,8 @@ class CuraApplication(QtApplication): self._single_instance = None + self._custom_setting_functions = None + self._cura_package_manager = None self._machine_action_manager = None @@ -317,6 +320,8 @@ class CuraApplication(QtApplication): # Adds custom property types, settings types, and extra operators (functions) that need to be registered in # SettingDefinition and SettingFunction. def __initializeSettingDefinitionsAndFunctions(self): + self._custom_setting_functions = CustomSettingFunctions(self) + # Need to do this before ContainerRegistry tries to load the machines SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True, read_only = True) SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True, read_only = True) @@ -337,10 +342,10 @@ class CuraApplication(QtApplication): SettingDefinition.addSettingType("optional_extruder", None, str, None) SettingDefinition.addSettingType("[int]", None, str, None) - SettingFunction.registerOperator("extruderValues", ExtruderManager.getExtruderValues) - SettingFunction.registerOperator("extruderValue", ExtruderManager.getExtruderValue) - SettingFunction.registerOperator("resolveOrValue", ExtruderManager.getResolveOrValue) - SettingFunction.registerOperator("defaultExtruderPosition", ExtruderManager.getDefaultExtruderPosition) + SettingFunction.registerOperator("extruderValue", self._custom_setting_functions.getValueInExtruder) + SettingFunction.registerOperator("extruderValues", self._custom_setting_functions.getValuesInAllExtruders) + SettingFunction.registerOperator("resolveOrValue", self._custom_setting_functions.getResolveOrValue) + SettingFunction.registerOperator("defaultExtruderPosition", self._custom_setting_functions.getDefaultExtruderPosition) # Adds all resources and container related resources. def __addAllResourcesAndContainerResources(self) -> None: @@ -804,6 +809,9 @@ class CuraApplication(QtApplication): def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel: return self._setting_visibility_presets_model + def getCustomSettingFunctions(self, *args) -> CustomSettingFunctions: + return self._custom_setting_functions + def getMachineErrorChecker(self, *args) -> MachineErrorChecker: return self._machine_error_checker diff --git a/cura/Settings/CustomSettingFunctions.py b/cura/Settings/CustomSettingFunctions.py new file mode 100644 index 0000000000..fe3ea1a935 --- /dev/null +++ b/cura/Settings/CustomSettingFunctions.py @@ -0,0 +1,134 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from typing import Any, List, Optional, TYPE_CHECKING + +from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext +from UM.Settings.SettingFunction import SettingFunction + +if TYPE_CHECKING: + from cura.CuraApplication import CuraApplication + from cura.Settings.CuraContainerStack import CuraContainerStack + + +# +# This class contains all Cura-related custom setting functions. Some functions requires information such as the +# currently active machine, so this is made into a class instead of standalone functions. +# +class CustomSettingFunctions: + + def __init__(self, application: "CuraApplication") -> None: + self._application = application + + # ================ + # Custom Functions + # ================ + + # Gets the default extruder position of the currently active machine. + def getDefaultExtruderPosition(self) -> str: + machine_manager = self._application.getMachineManager() + return machine_manager.defaultExtruderPosition + + # Gets the given setting key from the given extruder position. + def getValueInExtruder(self, extruder_position: int, property_key: str, + context: Optional["PropertyEvaluationContext"] = None) -> Any: + machine_manager = self._application.getMachineManager() + + if extruder_position == -1: + extruder_position = int(machine_manager.defaultExtruderPosition) + + global_stack = machine_manager.activeMachine + extruder_stack = global_stack.extruders[str(extruder_position)] + + if extruder_stack: + value = extruder_stack.getRawProperty(property_key, "value", context = context) + if isinstance(value, SettingFunction): + value = value(extruder_stack, context = context) + else: + # Just a value from global. + value = global_stack.getProperty(property_key, "value", context = context) + + return value + + # Gets all extruder values as a list for the given property. + def getValuesInAllExtruders(self, property_key: str, + context: Optional["PropertyEvaluationContext"] = None) -> List[Any]: + machine_manager = self._application.getMachineManager() + extruder_manager = self._application.getExtruderManager() + + global_stack = machine_manager.activeMachine + + result = [] + for extruder in extruder_manager.getActiveExtruderStacks(): + if not extruder.isEnabled: + continue + # only include values from extruders that are "active" for the current machine instance + if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value", context = context): + continue + + value = extruder.getRawProperty(property_key, "value", context = context) + + if value is None: + continue + + if isinstance(value, SettingFunction): + value = value(extruder, context= context) + + result.append(value) + + if not result: + result.append(global_stack.getProperty(property_key, "value", context = context)) + + return result + + # Get the resolve value or value for a given key. + def getResolveOrValue(self, property_key: str, context: Optional["PropertyEvaluationContext"] = None) -> Any: + machine_manager = self._application.getMachineManager() + + global_stack = machine_manager.activeMachine + resolved_value = global_stack.getProperty(property_key, "value", context = context) + + return resolved_value + + # Gets the default setting value from given extruder position. The default value is what excludes the values in + # the user_changes container. + def getDefaultValueInExtruder(self, extruder_position: int, property_key: str) -> Any: + machine_manager = self._application.getMachineManager() + + global_stack = machine_manager.activeMachine + extruder_stack = global_stack.extruders[str(extruder_position)] + + context = self.createContextForDefaultValueEvaluation(extruder_stack) + + return self.getValueInExtruder(extruder_position, property_key, context = context) + + # Gets all default setting values as a list from all extruders of the currently active machine. + # The default values are those excluding the values in the user_changes container. + def getDefaultValuesInAllExtruders(self, property_key: str) -> List[Any]: + machine_manager = self._application.getMachineManager() + + global_stack = machine_manager.activeMachine + + context = self.createContextForDefaultValueEvaluation(global_stack) + + return self.getValuesInAllExtruders(property_key, context = context) + + # Gets the resolve value or value for a given key without looking the first container (user container). + def getDefaultResolveOrValue(self, property_key: str) -> Any: + machine_manager = self._application.getMachineManager() + + global_stack = machine_manager.activeMachine + + context = self.createContextForDefaultValueEvaluation(global_stack) + return self.getResolveOrValue(property_key, context = context) + + # Creates a context for evaluating default values (skip the user_changes container). + def createContextForDefaultValueEvaluation(self, source_stack: "CuraContainerStack") -> "PropertyEvaluationContext": + context = PropertyEvaluationContext(source_stack) + context.context["evaluate_from_container_index"] = 1 # skip the user settings container + context.context["override_operators"] = { + "extruderValue": self.getDefaultValueInExtruder, + "extruderValues": self.getDefaultValuesInAllExtruders, + "resolveOrValue": self.getDefaultResolveOrValue, + } + return context diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 803491d1b3..99bd7e9b56 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -12,9 +12,7 @@ from UM.Scene.SceneNode import SceneNode from UM.Scene.Selection import Selection from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID. -from UM.Settings.SettingFunction import SettingFunction from UM.Settings.ContainerStack import ContainerStack -from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING, Union @@ -376,86 +374,6 @@ class ExtruderManager(QObject): extruder_definition = container_registry.findDefinitionContainers(id = expected_extruder_definition_0_id)[0] extruder_stack_0.definition = extruder_definition - ## Get all extruder values for a certain setting. - # - # This is exposed to SettingFunction so it can be used in value functions. - # - # \param key The key of the setting to retrieve values for. - # - # \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list. - # If no extruder has the value, the list will contain the global value. - @staticmethod - def getExtruderValues(key: str) -> List[Any]: - global_stack = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()) #We know that there must be a global stack by the time you're requesting setting values. - - result = [] - for extruder in ExtruderManager.getInstance().getActiveExtruderStacks(): - if not extruder.isEnabled: - continue - # only include values from extruders that are "active" for the current machine instance - if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value"): - continue - - value = extruder.getRawProperty(key, "value") - - if value is None: - continue - - if isinstance(value, SettingFunction): - value = value(extruder) - - result.append(value) - - if not result: - result.append(global_stack.getProperty(key, "value")) - - return result - - ## Get all extruder values for a certain setting. This function will skip the user settings container. - # - # This is exposed to SettingFunction so it can be used in value functions. - # - # \param key The key of the setting to retrieve values for. - # - # \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list. - # If no extruder has the value, the list will contain the global value. - @staticmethod - def getDefaultExtruderValues(key: str) -> List[Any]: - global_stack = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()) #We know that there must be a global stack by the time you're requesting setting values. - context = PropertyEvaluationContext(global_stack) - context.context["evaluate_from_container_index"] = 1 # skip the user settings container - context.context["override_operators"] = { - "extruderValue": ExtruderManager.getDefaultExtruderValue, - "extruderValues": ExtruderManager.getDefaultExtruderValues, - "resolveOrValue": ExtruderManager.getDefaultResolveOrValue - } - - result = [] - for extruder in ExtruderManager.getInstance().getActiveExtruderStacks(): - # only include values from extruders that are "active" for the current machine instance - if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value", context = context): - continue - - value = extruder.getRawProperty(key, "value", context = context) - - if value is None: - continue - - if isinstance(value, SettingFunction): - value = value(extruder, context = context) - - result.append(value) - - if not result: - result.append(global_stack.getProperty(key, "value", context = context)) - - return result - - ## Return the default extruder position from the machine manager - @staticmethod - def getDefaultExtruderPosition() -> str: - return cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition - ## Get all extruder values for a certain setting. # # This is exposed to qml for display purposes @@ -464,62 +382,8 @@ class ExtruderManager(QObject): # # \return String representing the extruder values @pyqtSlot(str, result="QVariant") - def getInstanceExtruderValues(self, key) -> List: - return ExtruderManager.getExtruderValues(key) - - ## Get the value for a setting from a specific extruder. - # - # This is exposed to SettingFunction to use in value functions. - # - # \param extruder_index The index of the extruder to get the value from. - # \param key The key of the setting to get the value of. - # - # \return The value of the setting for the specified extruder or for the - # global stack if not found. - @staticmethod - def getExtruderValue(extruder_index: int, key: str) -> Any: - if extruder_index == -1: - extruder_index = int(cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition) - extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index) - - if extruder: - value = extruder.getRawProperty(key, "value") - if isinstance(value, SettingFunction): - value = value(extruder) - else: - # Just a value from global. - value = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()).getProperty(key, "value") - - return value - - ## Get the default value from the given extruder. This function will skip the user settings container. - # - # This is exposed to SettingFunction to use in value functions. - # - # \param extruder_index The index of the extruder to get the value from. - # \param key The key of the setting to get the value of. - # - # \return The value of the setting for the specified extruder or for the - # global stack if not found. - @staticmethod - def getDefaultExtruderValue(extruder_index: int, key: str) -> Any: - extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index) - context = PropertyEvaluationContext(extruder) - context.context["evaluate_from_container_index"] = 1 # skip the user settings container - context.context["override_operators"] = { - "extruderValue": ExtruderManager.getDefaultExtruderValue, - "extruderValues": ExtruderManager.getDefaultExtruderValues, - "resolveOrValue": ExtruderManager.getDefaultResolveOrValue - } - - if extruder: - value = extruder.getRawProperty(key, "value", context = context) - if isinstance(value, SettingFunction): - value = value(extruder, context = context) - else: # Just a value from global. - value = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()).getProperty(key, "value", context = context) - - return value + def getInstanceExtruderValues(self, key: str) -> List: + return self._application.getCustomSettingFunctions().getValuesInAllExtruders(key) ## Get the resolve value or value for a given key # @@ -535,28 +399,6 @@ class ExtruderManager(QObject): return resolved_value - ## Get the resolve value or value for a given key without looking the first container (user container) - # - # This is the effective value for a given key, it is used for values in the global stack. - # This is exposed to SettingFunction to use in value functions. - # \param key The key of the setting to get the value of. - # - # \return The effective value - @staticmethod - def getDefaultResolveOrValue(key: str) -> Any: - global_stack = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()) - context = PropertyEvaluationContext(global_stack) - context.context["evaluate_from_container_index"] = 1 # skip the user settings container - context.context["override_operators"] = { - "extruderValue": ExtruderManager.getDefaultExtruderValue, - "extruderValues": ExtruderManager.getDefaultExtruderValues, - "resolveOrValue": ExtruderManager.getDefaultResolveOrValue - } - - resolved_value = global_stack.getProperty(key, "value", context = context) - - return resolved_value - __instance = None # type: ExtruderManager @classmethod diff --git a/cura/Settings/UserChangesModel.py b/cura/Settings/UserChangesModel.py index 95674e5ecd..d2ea84f79d 100644 --- a/cura/Settings/UserChangesModel.py +++ b/cura/Settings/UserChangesModel.py @@ -1,15 +1,17 @@ -from UM.Qt.ListModel import ListModel +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import os +from collections import OrderedDict from PyQt5.QtCore import pyqtSlot, Qt + from UM.Application import Application -from cura.Settings.ExtruderManager import ExtruderManager from UM.Settings.ContainerRegistry import ContainerRegistry from UM.i18n import i18nCatalog from UM.Settings.SettingFunction import SettingFunction -from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext -from collections import OrderedDict -import os +from UM.Qt.ListModel import ListModel class UserChangesModel(ListModel): @@ -38,9 +40,13 @@ class UserChangesModel(ListModel): self._update() def _update(self): + application = Application.getInstance() + machine_manager = application.getMachineManager() + custom_setting_functions = application.getCustomSettingFunctions() + item_dict = OrderedDict() item_list = [] - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = machine_manager.activeMachine if not global_stack: return @@ -71,13 +77,7 @@ class UserChangesModel(ListModel): # Override "getExtruderValue" with "getDefaultExtruderValue" so we can get the default values user_changes = containers.pop(0) - default_value_resolve_context = PropertyEvaluationContext(stack) - default_value_resolve_context.context["evaluate_from_container_index"] = 1 # skip the user settings container - default_value_resolve_context.context["override_operators"] = { - "extruderValue": ExtruderManager.getDefaultExtruderValue, - "extruderValues": ExtruderManager.getDefaultExtruderValues, - "resolveOrValue": ExtruderManager.getDefaultResolveOrValue - } + default_value_resolve_context = custom_setting_functions.createContextForDefaultValueEvaluation(stack) for setting_key in user_changes.getAllKeys(): original_value = None From 3c8368827bb9f86fce870d9767a04cba8eaa2a09 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 26 Sep 2018 17:04:15 +0200 Subject: [PATCH 15/97] Remove unused functions in ExtruderManager --- cura/Settings/ExtruderManager.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 99bd7e9b56..86ee546240 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -67,16 +67,6 @@ class ExtruderManager(QObject): except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong. return None - ## Return extruder count according to extruder trains. - @pyqtProperty(int, notify = extrudersChanged) - def extruderCount(self) -> int: - if not self._application.getGlobalContainerStack(): - return 0 # No active machine, so no extruders. - try: - return len(self._extruder_trains[self._application.getGlobalContainerStack().getId()]) - except KeyError: - return 0 - ## Gets a dict with the extruder stack ids with the extruder number as the key. @pyqtProperty("QVariantMap", notify = extrudersChanged) def extruderIds(self) -> Dict[str, str]: From b1198ee1b8e8c24db4986599324c2acbbceb9850 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 27 Sep 2018 11:43:18 +0200 Subject: [PATCH 16/97] Remove an if-else block that assumes no ExtruderStack There is always an ExtruderStack, so the else-block will never be executed. --- cura/Settings/CustomSettingFunctions.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/cura/Settings/CustomSettingFunctions.py b/cura/Settings/CustomSettingFunctions.py index fe3ea1a935..03cff6b069 100644 --- a/cura/Settings/CustomSettingFunctions.py +++ b/cura/Settings/CustomSettingFunctions.py @@ -40,13 +40,9 @@ class CustomSettingFunctions: global_stack = machine_manager.activeMachine extruder_stack = global_stack.extruders[str(extruder_position)] - if extruder_stack: - value = extruder_stack.getRawProperty(property_key, "value", context = context) - if isinstance(value, SettingFunction): - value = value(extruder_stack, context = context) - else: - # Just a value from global. - value = global_stack.getProperty(property_key, "value", context = context) + value = extruder_stack.getRawProperty(property_key, "value", context = context) + if isinstance(value, SettingFunction): + value = value(extruder_stack, context = context) return value From 329b38663eaa0786997aeff092b825e3f5e65176 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 27 Sep 2018 11:43:51 +0200 Subject: [PATCH 17/97] Fix code style --- cura/Settings/CustomSettingFunctions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Settings/CustomSettingFunctions.py b/cura/Settings/CustomSettingFunctions.py index 03cff6b069..5951ac1e73 100644 --- a/cura/Settings/CustomSettingFunctions.py +++ b/cura/Settings/CustomSettingFunctions.py @@ -68,7 +68,7 @@ class CustomSettingFunctions: continue if isinstance(value, SettingFunction): - value = value(extruder, context= context) + value = value(extruder, context = context) result.append(value) From 9a98341bda96681388aefe2d2728fd7f1328c4e5 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 28 Sep 2018 11:38:42 +0200 Subject: [PATCH 18/97] Fix code-style and typing --- cura/Settings/MachineManager.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 6fd945fc31..911022b6ac 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -4,13 +4,13 @@ import collections import time from typing import Any, Callable, List, Dict, TYPE_CHECKING, Optional, cast -import platform from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.Interfaces import ContainerInterface from UM.Signal import Signal +from UM.Platform import Platform from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer from UM.FlameProfiler import pyqtSlot @@ -1543,17 +1543,16 @@ class MachineManager(QObject): ## Get default firmware file name if one is specified in the firmware @pyqtSlot(result = str) - def getDefaultFirmwareName(self): + def getDefaultFirmwareName(self) -> str: # Check if there is a valid global container stack if not self._global_container_stack: return "" # The bottom of the containerstack is the machine definition - machine_id = self._global_container_stack.getBottom().id machine_has_heated_bed = self._global_container_stack.getProperty("machine_heated_bed", "value") baudrate = 250000 - if platform.system() == "Linux": + if Platform.isLinux(): # Linux prefers a baudrate of 115200 here because older versions of # pySerial did not support a baudrate of 250000 baudrate = 115200 @@ -1570,5 +1569,5 @@ class MachineManager(QObject): Logger.log("w", "Firmware file %s not found.", hex_file) return "" else: - Logger.log("w", "There is no firmware for machine %s.", machine_id) + Logger.log("w", "There is no firmware for machine %s.", self._global_container_stack.getBottom().id) return "" From bc52830c8902f14a53bd40fa009856e545876436 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 28 Sep 2018 11:49:00 +0200 Subject: [PATCH 19/97] Move getDefaultFirmwareName() into GlobalStack --- cura/Settings/GlobalStack.py | 29 ++++++++++++++++- cura/Settings/MachineManager.py | 32 ------------------- .../UpgradeFirmwareMachineAction.qml | 2 +- 3 files changed, 29 insertions(+), 34 deletions(-) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 517b45eb98..e3ae8c2deb 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -4,7 +4,7 @@ from collections import defaultdict import threading from typing import Any, Dict, Optional, Set, TYPE_CHECKING -from PyQt5.QtCore import pyqtProperty +from PyQt5.QtCore import pyqtProperty, pyqtSlot from UM.Decorators import override from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase @@ -13,6 +13,8 @@ from UM.Settings.SettingInstance import InstanceState from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.Interfaces import PropertyEvaluationContext from UM.Logger import Logger +from UM.Resources import Resources +from UM.Platform import Platform from UM.Util import parseBool import cura.CuraApplication @@ -200,6 +202,31 @@ class GlobalStack(CuraContainerStack): def getHasMachineQuality(self) -> bool: return parseBool(self.getMetaDataEntry("has_machine_quality", False)) + ## Get default firmware file name if one is specified in the firmware + @pyqtSlot(result = str) + def getDefaultFirmwareName(self) -> str: + machine_has_heated_bed = self.getProperty("machine_heated_bed", "value") + + baudrate = 250000 + if Platform.isLinux(): + # Linux prefers a baudrate of 115200 here because older versions of + # pySerial did not support a baudrate of 250000 + baudrate = 115200 + + # If a firmware file is available, it should be specified in the definition for the printer + hex_file = self.getMetaDataEntry("firmware_file", None) + if machine_has_heated_bed: + hex_file = self.getMetaDataEntry("firmware_hbk_file", hex_file) + + if hex_file: + try: + return Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate)) + except FileNotFoundError: + Logger.log("w", "Firmware file %s not found.", hex_file) + return "" + else: + Logger.log("w", "There is no firmware for machine %s.", self.getBottom().id) + return "" ## private: global_stack_mime = MimeType( diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 911022b6ac..0abb1a5dc2 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -10,7 +10,6 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.Interfaces import ContainerInterface from UM.Signal import Signal -from UM.Platform import Platform from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer from UM.FlameProfiler import pyqtSlot @@ -1540,34 +1539,3 @@ class MachineManager(QObject): with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self.updateMaterialWithVariant(None) self._updateQualityWithMaterial() - - ## Get default firmware file name if one is specified in the firmware - @pyqtSlot(result = str) - def getDefaultFirmwareName(self) -> str: - # Check if there is a valid global container stack - if not self._global_container_stack: - return "" - - # The bottom of the containerstack is the machine definition - machine_has_heated_bed = self._global_container_stack.getProperty("machine_heated_bed", "value") - - baudrate = 250000 - if Platform.isLinux(): - # Linux prefers a baudrate of 115200 here because older versions of - # pySerial did not support a baudrate of 250000 - baudrate = 115200 - - # If a firmware file is available, it should be specified in the definition for the printer - hex_file = self._global_container_stack.getMetaDataEntry("firmware_file", None) - if machine_has_heated_bed: - hex_file = self._global_container_stack.getMetaDataEntry("firmware_hbk_file", hex_file) - - if hex_file: - try: - return Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate)) - except FileNotFoundError: - Logger.log("w", "Firmware file %s not found.", hex_file) - return "" - else: - Logger.log("w", "There is no firmware for machine %s.", self._global_container_stack.getBottom().id) - return "" diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml index 0d12f72a0a..469ada7afb 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml @@ -51,7 +51,7 @@ Cura.MachineAction anchors.horizontalCenter: parent.horizontalCenter width: childrenRect.width spacing: UM.Theme.getSize("default_margin").width - property var firmwareName: Cura.MachineManager.getDefaultFirmwareName() + property var firmwareName: Cura.MachineManager.activeMachine.getDefaultFirmwareName() Button { id: autoUpgradeButton From a12c0e8d9eadb1bdf0d8609f1b1ffcfb09e706cc Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 28 Sep 2018 11:51:33 +0200 Subject: [PATCH 20/97] Remove superfluous import --- cura/Settings/MachineManager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 0abb1a5dc2..0059b7aad2 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -16,7 +16,6 @@ from UM.FlameProfiler import pyqtSlot from UM import Util from UM.Logger import Logger from UM.Message import Message -from UM.Resources import Resources from UM.Settings.SettingFunction import SettingFunction from UM.Signal import postponeSignals, CompressTechnique From a573a598b01445d0f785e4b9256d6c66e96012ab Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 28 Sep 2018 12:40:44 +0200 Subject: [PATCH 21/97] Add typing and appease MYPY --- cura/PrinterOutput/FirmwareUpdater.py | 23 +++-- cura/PrinterOutput/PrintJobOutputModel.py | 2 +- cura/PrinterOutput/PrinterOutputController.py | 31 +++--- cura/PrinterOutput/PrinterOutputModel.py | 97 ++++++++++--------- plugins/USBPrinting/AvrFirmwareUpdater.py | 2 +- plugins/USBPrinting/USBPrinterOutputDevice.py | 4 +- 6 files changed, 83 insertions(+), 76 deletions(-) diff --git a/cura/PrinterOutput/FirmwareUpdater.py b/cura/PrinterOutput/FirmwareUpdater.py index e7ffc2a2b5..06e019c593 100644 --- a/cura/PrinterOutput/FirmwareUpdater.py +++ b/cura/PrinterOutput/FirmwareUpdater.py @@ -5,9 +5,11 @@ from PyQt5.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty from UM.Resources import Resources from cura.PrinterOutputDevice import PrinterOutputDevice +from cura.CuraApplication import CuraApplication from enum import IntEnum from threading import Thread +from typing import Any class FirmwareUpdater(QObject): firmwareProgressChanged = pyqtSignal() @@ -19,11 +21,11 @@ class FirmwareUpdater(QObject): self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True) self._firmware_view = None - self._firmware_location = None + self._firmware_location = "" self._firmware_progress = 0 self._firmware_update_state = FirmwareUpdateState.idle - def updateFirmware(self, file): + def updateFirmware(self, file: Any[str, QUrl]) -> None: # the file path could be url-encoded. if file.startswith("file://"): self._firmware_location = QUrl(file).toLocalFile() @@ -33,10 +35,10 @@ class FirmwareUpdater(QObject): self.setFirmwareUpdateState(FirmwareUpdateState.updating) self._update_firmware_thread.start() - def _updateFirmware(self): + def _updateFirmware(self) -> None: raise NotImplementedError("_updateFirmware needs to be implemented") - def cleanupAfterUpdate(self): + def cleanupAfterUpdate(self) -> None: # Clean up for next attempt. self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True) self._firmware_location = "" @@ -45,28 +47,29 @@ class FirmwareUpdater(QObject): ## Show firmware interface. # This will create the view if its not already created. - def showFirmwareInterface(self): + def showFirmwareInterface(self) -> None: if self._firmware_view is None: path = Resources.getPath(self.ResourceTypes.QmlFiles, "FirmwareUpdateWindow.qml") self._firmware_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) - self._firmware_view.show() + if self._firmware_view: + self._firmware_view.show() @pyqtProperty(float, notify = firmwareProgressChanged) - def firmwareProgress(self): + def firmwareProgress(self) -> float: return self._firmware_progress @pyqtProperty(int, notify=firmwareUpdateStateChanged) - def firmwareUpdateState(self): + def firmwareUpdateState(self) -> FirmwareUpdateState: return self._firmware_update_state - def setFirmwareUpdateState(self, state): + def setFirmwareUpdateState(self, state) -> None: if self._firmware_update_state != state: self._firmware_update_state = state self.firmwareUpdateStateChanged.emit() # Callback function for firmware update progress. - def _onFirmwareProgress(self, progress, max_progress = 100): + def _onFirmwareProgress(self, progress, max_progress = 100) -> None: self._firmware_progress = (progress / max_progress) * 100 # Convert to scale of 0-100 self.firmwareProgressChanged.emit() diff --git a/cura/PrinterOutput/PrintJobOutputModel.py b/cura/PrinterOutput/PrintJobOutputModel.py index 7366b95f86..5b8cc39ad8 100644 --- a/cura/PrinterOutput/PrintJobOutputModel.py +++ b/cura/PrinterOutput/PrintJobOutputModel.py @@ -91,7 +91,7 @@ class PrintJobOutputModel(QObject): def assignedPrinter(self): return self._assigned_printer - def updateAssignedPrinter(self, assigned_printer: "PrinterOutputModel"): + def updateAssignedPrinter(self, assigned_printer: Optional[PrinterOutputModel]): if self._assigned_printer != assigned_printer: old_printer = self._assigned_printer self._assigned_printer = assigned_printer diff --git a/cura/PrinterOutput/PrinterOutputController.py b/cura/PrinterOutput/PrinterOutputController.py index dd2276d771..9a29233f95 100644 --- a/cura/PrinterOutput/PrinterOutputController.py +++ b/cura/PrinterOutput/PrinterOutputController.py @@ -4,15 +4,18 @@ from UM.Logger import Logger from UM.Signal import Signal +from typing import Any + MYPY = False if MYPY: from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel + from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice class PrinterOutputController: - def __init__(self, output_device): + def __init__(self, output_device: PrinterOutputDevice) -> None: self.can_pause = True self.can_abort = True self.can_pre_heat_bed = True @@ -22,44 +25,44 @@ class PrinterOutputController: self.can_update_firmware = False self._output_device = output_device - def setTargetHotendTemperature(self, printer: "PrinterOutputModel", extruder: "ExtruderOutputModel", temperature: int): + def setTargetHotendTemperature(self, printer: "PrinterOutputModel", extruder: "ExtruderOutputModel", temperature: Any[int, float]) -> None: Logger.log("w", "Set target hotend temperature not implemented in controller") - def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int): + def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int) -> None: Logger.log("w", "Set target bed temperature not implemented in controller") - def setJobState(self, job: "PrintJobOutputModel", state: str): + def setJobState(self, job: "PrintJobOutputModel", state: str) -> None: Logger.log("w", "Set job state not implemented in controller") - def cancelPreheatBed(self, printer: "PrinterOutputModel"): + def cancelPreheatBed(self, printer: "PrinterOutputModel") -> None: Logger.log("w", "Cancel preheat bed not implemented in controller") - def preheatBed(self, printer: "PrinterOutputModel", temperature, duration): + def preheatBed(self, printer: "PrinterOutputModel", temperature, duration) -> None: Logger.log("w", "Preheat bed not implemented in controller") - def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"): + def cancelPreheatHotend(self, extruder: "ExtruderOutputModel") -> None: Logger.log("w", "Cancel preheat hotend not implemented in controller") - def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration): + def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration) -> None: Logger.log("w", "Preheat hotend not implemented in controller") - def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed): + def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed) -> None: Logger.log("w", "Set head position not implemented in controller") - def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed): + def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed) -> None: Logger.log("w", "Move head not implemented in controller") - def homeBed(self, printer: "PrinterOutputModel"): + def homeBed(self, printer: "PrinterOutputModel") -> None: Logger.log("w", "Home bed not implemented in controller") - def homeHead(self, printer: "PrinterOutputModel"): + def homeHead(self, printer: "PrinterOutputModel") -> None: Logger.log("w", "Home head not implemented in controller") - def sendRawCommand(self, printer: "PrinterOutputModel", command: str): + def sendRawCommand(self, printer: "PrinterOutputModel", command: str) -> None: Logger.log("w", "Custom command not implemented in controller") canUpdateFirmwareChanged = Signal() - def setCanUpdateFirmware(self, can_update_firmware: bool): + def setCanUpdateFirmware(self, can_update_firmware: bool) -> None: if can_update_firmware != self.can_update_firmware: self.can_update_firmware = can_update_firmware self.canUpdateFirmwareChanged.emit() \ No newline at end of file diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index 859165aef3..96feef1b55 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -2,7 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot -from typing import Optional +from typing import List, Dict, Optional from UM.Math.Vector import Vector from cura.PrinterOutput.ConfigurationModel import ConfigurationModel from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel @@ -11,6 +11,7 @@ MYPY = False if MYPY: from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel from cura.PrinterOutput.PrinterOutputController import PrinterOutputController + from cura.PrinterOutput.NetworkCamera import NetworkCamera class PrinterOutputModel(QObject): @@ -44,7 +45,7 @@ class PrinterOutputModel(QObject): self._printer_state = "unknown" self._is_preheating = False self._printer_type = "" - self._buildplate_name = None + self._buildplate_name = "" self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in self._extruders] @@ -52,32 +53,32 @@ class PrinterOutputModel(QObject): self._camera = None @pyqtProperty(str, constant = True) - def firmwareVersion(self): + def firmwareVersion(self) -> str: return self._firmware_version - def setCamera(self, camera): + def setCamera(self, camera: Optional["NetworkCamera"]) -> None: if self._camera is not camera: self._camera = camera self.cameraChanged.emit() - def updateIsPreheating(self, pre_heating): + def updateIsPreheating(self, pre_heating: bool) -> None: if self._is_preheating != pre_heating: self._is_preheating = pre_heating self.isPreheatingChanged.emit() @pyqtProperty(bool, notify=isPreheatingChanged) - def isPreheating(self): + def isPreheating(self) -> bool: return self._is_preheating @pyqtProperty(QObject, notify=cameraChanged) - def camera(self): + def camera(self) -> Optional["NetworkCamera"]: return self._camera @pyqtProperty(str, notify = printerTypeChanged) - def type(self): + def type(self) -> str: return self._printer_type - def updateType(self, printer_type): + def updateType(self, printer_type: str) -> None: if self._printer_type != printer_type: self._printer_type = printer_type self._printer_configuration.printerType = self._printer_type @@ -85,10 +86,10 @@ class PrinterOutputModel(QObject): self.configurationChanged.emit() @pyqtProperty(str, notify = buildplateChanged) - def buildplate(self): + def buildplate(self) -> str: return self._buildplate_name - def updateBuildplateName(self, buildplate_name): + def updateBuildplateName(self, buildplate_name: str) -> None: if self._buildplate_name != buildplate_name: self._buildplate_name = buildplate_name self._printer_configuration.buildplateConfiguration = self._buildplate_name @@ -96,66 +97,66 @@ class PrinterOutputModel(QObject): self.configurationChanged.emit() @pyqtProperty(str, notify=keyChanged) - def key(self): + def key(self) -> str: return self._key - def updateKey(self, key: str): + def updateKey(self, key: str) -> None: if self._key != key: self._key = key self.keyChanged.emit() @pyqtSlot() - def homeHead(self): + def homeHead(self) -> None: self._controller.homeHead(self) @pyqtSlot() - def homeBed(self): + def homeBed(self) -> None: self._controller.homeBed(self) @pyqtSlot(str) - def sendRawCommand(self, command: str): + def sendRawCommand(self, command: str) -> None: self._controller.sendRawCommand(self, command) @pyqtProperty("QVariantList", constant = True) - def extruders(self): + def extruders(self) -> List["ExtruderOutputModel"]: return self._extruders @pyqtProperty(QVariant, notify = headPositionChanged) - def headPosition(self): + def headPosition(self) -> Dict[str, float]: return {"x": self._head_position.x, "y": self._head_position.y, "z": self.head_position.z} - def updateHeadPosition(self, x, y, z): + def updateHeadPosition(self, x: float, y: float, z: float) -> None: if self._head_position.x != x or self._head_position.y != y or self._head_position.z != z: self._head_position = Vector(x, y, z) self.headPositionChanged.emit() @pyqtProperty(float, float, float) @pyqtProperty(float, float, float, float) - def setHeadPosition(self, x, y, z, speed = 3000): + def setHeadPosition(self, x: float, y: float, z: float, speed: float = 3000) -> None: self.updateHeadPosition(x, y, z) self._controller.setHeadPosition(self, x, y, z, speed) @pyqtProperty(float) @pyqtProperty(float, float) - def setHeadX(self, x, speed = 3000): + def setHeadX(self, x: float, speed: float = 3000) -> None: self.updateHeadPosition(x, self._head_position.y, self._head_position.z) self._controller.setHeadPosition(self, x, self._head_position.y, self._head_position.z, speed) @pyqtProperty(float) @pyqtProperty(float, float) - def setHeadY(self, y, speed = 3000): + def setHeadY(self, y: float, speed: float = 3000) -> None: self.updateHeadPosition(self._head_position.x, y, self._head_position.z) self._controller.setHeadPosition(self, self._head_position.x, y, self._head_position.z, speed) @pyqtProperty(float) @pyqtProperty(float, float) - def setHeadZ(self, z, speed = 3000): + def setHeadZ(self, z: float, speed:float = 3000) -> None: self.updateHeadPosition(self._head_position.x, self._head_position.y, z) self._controller.setHeadPosition(self, self._head_position.x, self._head_position.y, z, speed) @pyqtSlot(float, float, float) @pyqtSlot(float, float, float, float) - def moveHead(self, x = 0, y = 0, z = 0, speed = 3000): + def moveHead(self, x: float = 0, y: float = 0, z: float = 0, speed: float = 3000) -> None: self._controller.moveHead(self, x, y, z, speed) ## Pre-heats the heated bed of the printer. @@ -164,47 +165,47 @@ class PrinterOutputModel(QObject): # Celsius. # \param duration How long the bed should stay warm, in seconds. @pyqtSlot(float, float) - def preheatBed(self, temperature, duration): + def preheatBed(self, temperature: float, duration: float) -> None: self._controller.preheatBed(self, temperature, duration) @pyqtSlot() - def cancelPreheatBed(self): + def cancelPreheatBed(self) -> None: self._controller.cancelPreheatBed(self) - def getController(self): + def getController(self) -> PrinterOutputController: return self._controller @pyqtProperty(str, notify=nameChanged) - def name(self): + def name(self) -> str: return self._name - def setName(self, name): + def setName(self, name: str) -> None: self._setName(name) self.updateName(name) - def updateName(self, name): + def updateName(self, name: str) -> None: if self._name != name: self._name = name self.nameChanged.emit() ## Update the bed temperature. This only changes it locally. - def updateBedTemperature(self, temperature): + def updateBedTemperature(self, temperature: int) -> None: if self._bed_temperature != temperature: self._bed_temperature = temperature self.bedTemperatureChanged.emit() - def updateTargetBedTemperature(self, temperature): + def updateTargetBedTemperature(self, temperature: int) -> None: if self._target_bed_temperature != temperature: self._target_bed_temperature = temperature self.targetBedTemperatureChanged.emit() ## Set the target bed temperature. This ensures that it's actually sent to the remote. @pyqtSlot(int) - def setTargetBedTemperature(self, temperature): + def setTargetBedTemperature(self, temperature: int) -> None: self._controller.setTargetBedTemperature(self, temperature) self.updateTargetBedTemperature(temperature) - def updateActivePrintJob(self, print_job): + def updateActivePrintJob(self, print_job: Optional[PrintJobOutputModel]) -> None: if self._active_print_job != print_job: old_print_job = self._active_print_job @@ -216,83 +217,83 @@ class PrinterOutputModel(QObject): old_print_job.updateAssignedPrinter(None) self.activePrintJobChanged.emit() - def updateState(self, printer_state): + def updateState(self, printer_state: str) -> None: if self._printer_state != printer_state: self._printer_state = printer_state self.stateChanged.emit() @pyqtProperty(QObject, notify = activePrintJobChanged) - def activePrintJob(self): + def activePrintJob(self) -> Optional[PrintJobOutputModel]: return self._active_print_job @pyqtProperty(str, notify=stateChanged) - def state(self): + def state(self) -> str: return self._printer_state @pyqtProperty(int, notify = bedTemperatureChanged) - def bedTemperature(self): + def bedTemperature(self) -> int: return self._bed_temperature @pyqtProperty(int, notify=targetBedTemperatureChanged) - def targetBedTemperature(self): + def targetBedTemperature(self) -> int: return self._target_bed_temperature # Does the printer support pre-heating the bed at all @pyqtProperty(bool, constant=True) - def canPreHeatBed(self): + def canPreHeatBed(self) -> bool: if self._controller: return self._controller.can_pre_heat_bed return False # Does the printer support pre-heating the bed at all @pyqtProperty(bool, constant=True) - def canPreHeatHotends(self): + def canPreHeatHotends(self) -> bool: if self._controller: return self._controller.can_pre_heat_hotends return False # Does the printer support sending raw G-code at all @pyqtProperty(bool, constant=True) - def canSendRawGcode(self): + def canSendRawGcode(self) -> bool: if self._controller: return self._controller.can_send_raw_gcode return False # Does the printer support pause at all @pyqtProperty(bool, constant=True) - def canPause(self): + def canPause(self) -> bool: if self._controller: return self._controller.can_pause return False # Does the printer support abort at all @pyqtProperty(bool, constant=True) - def canAbort(self): + def canAbort(self) -> bool: if self._controller: return self._controller.can_abort return False # Does the printer support manual control at all @pyqtProperty(bool, constant=True) - def canControlManually(self): + def canControlManually(self) -> bool: if self._controller: return self._controller.can_control_manually return False # Does the printer support upgrading firmware @pyqtProperty(bool, notify = canUpdateFirmwareChanged) - def canUpdateFirmware(self): + def canUpdateFirmware(self) -> bool: if self._controller: return self._controller.can_update_firmware return False # Stub to connect UM.Signal to pyqtSignal - def _onControllerCanUpdateFirmwareChanged(self): + def _onControllerCanUpdateFirmwareChanged(self) -> None: self.canUpdateFirmwareChanged.emit() # Returns the configuration (material, variant and buildplate) of the current printer @pyqtProperty(QObject, notify = configurationChanged) - def printerConfiguration(self): + def printerConfiguration(self) -> Optional[ConfigurationModel]: if self._printer_configuration.isValid(): return self._printer_configuration return None \ No newline at end of file diff --git a/plugins/USBPrinting/AvrFirmwareUpdater.py b/plugins/USBPrinting/AvrFirmwareUpdater.py index 171c81d557..c3852c46f6 100644 --- a/plugins/USBPrinting/AvrFirmwareUpdater.py +++ b/plugins/USBPrinting/AvrFirmwareUpdater.py @@ -10,7 +10,7 @@ class AvrFirmwareUpdater(FirmwareUpdater): def __init__(self, output_device: PrinterOutputDevice) -> None: super().__init__(output_device) - def _updateFirmware(self): + def _updateFirmware(self) -> None: try: hex_file = intelHex.readHex(self._firmware_location) assert len(hex_file) > 0 diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 4813696ffe..5e18e216bc 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -99,12 +99,12 @@ class USBPrinterOutputDevice(PrinterOutputDevice): application.triggerNextExitCheck() @pyqtSlot(str) - def updateFirmware(self, file): + def updateFirmware(self, file: Any[str, QUrl]) -> None: self._firmware_updater.updateFirmware(file) ## Reset USB device settings # - def resetDeviceSettings(self): + def resetDeviceSettings(self) -> None: self._firmware_name = None ## Request the current scene to be sent to a USB-connected printer. From 6be6d6cfc322630c20db6fbfa5271967324b60d4 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 28 Sep 2018 12:49:53 +0200 Subject: [PATCH 22/97] Fixed missing typing import --- plugins/USBPrinting/USBPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 5e18e216bc..c9fcdbe625 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -21,7 +21,7 @@ from serial import Serial, SerialException, SerialTimeoutException from threading import Thread, Event from time import time, sleep from queue import Queue -from typing import Union, Optional, List, cast +from typing import Union, Optional, List, cast, Any import re import functools # Used for reduce From 6ecc9366cb6ee80ad11bab33371dec7799a899ae Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 28 Sep 2018 13:06:09 +0200 Subject: [PATCH 23/97] Fix filename typos --- resources/definitions/ultimaker_original.def.json | 2 +- resources/definitions/ultimaker_original_dual.def.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/definitions/ultimaker_original.def.json b/resources/definitions/ultimaker_original.def.json index bb6a64d8dc..bb21e4b82e 100644 --- a/resources/definitions/ultimaker_original.def.json +++ b/resources/definitions/ultimaker_original.def.json @@ -20,7 +20,7 @@ "0": "ultimaker_original_extruder_0" }, "firmware_file": "MarlinUltimaker-{baudrate}.hex", - "firmware_hbk_file": "MarlinUltimaker-HKB-{baudrate}.hex" + "firmware_hbk_file": "MarlinUltimaker-HBK-{baudrate}.hex" }, "overrides": { diff --git a/resources/definitions/ultimaker_original_dual.def.json b/resources/definitions/ultimaker_original_dual.def.json index c6002ef396..1ffb6e840b 100644 --- a/resources/definitions/ultimaker_original_dual.def.json +++ b/resources/definitions/ultimaker_original_dual.def.json @@ -20,7 +20,7 @@ "1": "ultimaker_original_dual_2nd" }, "firmware_file": "MarlinUltimaker-{baudrate}-dual.hex", - "firmware_hbk_file": "MarlinUltimaker-HKB-{baudrate}-dual.hex", + "firmware_hbk_file": "MarlinUltimaker-HBK-{baudrate}-dual.hex", "first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"], "supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"] }, From b73a71746eb4ac44c6387388c1fac68ddcc618b7 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 28 Sep 2018 13:06:36 +0200 Subject: [PATCH 24/97] Fix missing imports --- plugins/USBPrinting/AvrFirmwareUpdater.py | 12 ++++++++++-- plugins/USBPrinting/USBPrinterOutputDevice.py | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/USBPrinting/AvrFirmwareUpdater.py b/plugins/USBPrinting/AvrFirmwareUpdater.py index c3852c46f6..ab71f70e30 100644 --- a/plugins/USBPrinting/AvrFirmwareUpdater.py +++ b/plugins/USBPrinting/AvrFirmwareUpdater.py @@ -1,10 +1,16 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from UM.Logger import Logger + +from cura.CuraApplication import CuraApplication from cura.PrinterOutputDevice import PrinterOutputDevice from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater, FirmwareUpdateState from .avr_isp import stk500v2, intelHex +from serial import SerialException + +from time import sleep class AvrFirmwareUpdater(FirmwareUpdater): def __init__(self, output_device: PrinterOutputDevice) -> None: @@ -37,10 +43,12 @@ class AvrFirmwareUpdater(FirmwareUpdater): self.setFirmwareUpdateState(FirmwareUpdateState.communication_error) try: programmer.programChip(hex_file) - except SerialException: + except SerialException as e: + Logger.log("e", "A serial port exception occured during firmware update: %s" % e) self.setFirmwareUpdateState(FirmwareUpdateState.io_error) return - except: + except Exception as e: + Logger.log("e", "An unknown exception occured during firmware update: %s" % e) self.setFirmwareUpdateState(FirmwareUpdateState.unknown_error) return diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index c9fcdbe625..9ab2a06d50 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -15,7 +15,7 @@ from cura.PrinterOutput.GenericOutputController import GenericOutputController from .AutoDetectBaudJob import AutoDetectBaudJob from .AvrFirmwareUpdater import AvrFirmwareUpdater -from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty +from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty, QUrl from serial import Serial, SerialException, SerialTimeoutException from threading import Thread, Event From 09742f0cf51769b46ac8be2402d1db9df10a5061 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 28 Sep 2018 13:09:59 +0200 Subject: [PATCH 25/97] Simplify code --- cura/Settings/GlobalStack.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index e3ae8c2deb..da1ec61254 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -218,16 +218,16 @@ class GlobalStack(CuraContainerStack): if machine_has_heated_bed: hex_file = self.getMetaDataEntry("firmware_hbk_file", hex_file) - if hex_file: - try: - return Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate)) - except FileNotFoundError: - Logger.log("w", "Firmware file %s not found.", hex_file) - return "" - else: + if not hex_file: Logger.log("w", "There is no firmware for machine %s.", self.getBottom().id) return "" + try: + return Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate)) + except FileNotFoundError: + Logger.log("w", "Firmware file %s not found.", hex_file) + return "" + ## private: global_stack_mime = MimeType( name = "application/x-cura-globalstack", From 9af71f2888c1de1616af67f3241c08544f0958df Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 28 Sep 2018 13:40:39 +0200 Subject: [PATCH 26/97] Fix incorrect typing and issues caused by typing --- cura/PrinterOutput/FirmwareUpdater.py | 6 +++--- cura/PrinterOutput/GenericOutputController.py | 4 ++-- cura/PrinterOutput/PrintJobOutputModel.py | 2 +- cura/PrinterOutput/PrinterOutputController.py | 6 +++--- cura/PrinterOutput/PrinterOutputModel.py | 8 ++++---- plugins/USBPrinting/USBPrinterOutputDevice.py | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cura/PrinterOutput/FirmwareUpdater.py b/cura/PrinterOutput/FirmwareUpdater.py index 06e019c593..2f200118a9 100644 --- a/cura/PrinterOutput/FirmwareUpdater.py +++ b/cura/PrinterOutput/FirmwareUpdater.py @@ -9,7 +9,7 @@ from cura.CuraApplication import CuraApplication from enum import IntEnum from threading import Thread -from typing import Any +from typing import Union class FirmwareUpdater(QObject): firmwareProgressChanged = pyqtSignal() @@ -25,7 +25,7 @@ class FirmwareUpdater(QObject): self._firmware_progress = 0 self._firmware_update_state = FirmwareUpdateState.idle - def updateFirmware(self, file: Any[str, QUrl]) -> None: + def updateFirmware(self, file: Union[str, QUrl]) -> None: # the file path could be url-encoded. if file.startswith("file://"): self._firmware_location = QUrl(file).toLocalFile() @@ -60,7 +60,7 @@ class FirmwareUpdater(QObject): return self._firmware_progress @pyqtProperty(int, notify=firmwareUpdateStateChanged) - def firmwareUpdateState(self) -> FirmwareUpdateState: + def firmwareUpdateState(self) -> "FirmwareUpdateState": return self._firmware_update_state def setFirmwareUpdateState(self, state) -> None: diff --git a/cura/PrinterOutput/GenericOutputController.py b/cura/PrinterOutput/GenericOutputController.py index e6310e5bff..e26fefb520 100644 --- a/cura/PrinterOutput/GenericOutputController.py +++ b/cura/PrinterOutput/GenericOutputController.py @@ -1,7 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union from cura.PrinterOutput.PrinterOutputController import PrinterOutputController from PyQt5.QtCore import QTimer @@ -109,7 +109,7 @@ class GenericOutputController(PrinterOutputController): self.setTargetBedTemperature(self._preheat_printer, 0) self._preheat_printer.updateIsPreheating(False) - def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: int): + def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: Union[int, float]) -> None: self._output_device.sendCommand("M104 S%s T%s" % (temperature, position)) def _onTargetHotendTemperatureChanged(self): diff --git a/cura/PrinterOutput/PrintJobOutputModel.py b/cura/PrinterOutput/PrintJobOutputModel.py index 5b8cc39ad8..70878a7573 100644 --- a/cura/PrinterOutput/PrintJobOutputModel.py +++ b/cura/PrinterOutput/PrintJobOutputModel.py @@ -91,7 +91,7 @@ class PrintJobOutputModel(QObject): def assignedPrinter(self): return self._assigned_printer - def updateAssignedPrinter(self, assigned_printer: Optional[PrinterOutputModel]): + def updateAssignedPrinter(self, assigned_printer: Optional["PrinterOutputModel"]): if self._assigned_printer != assigned_printer: old_printer = self._assigned_printer self._assigned_printer = assigned_printer diff --git a/cura/PrinterOutput/PrinterOutputController.py b/cura/PrinterOutput/PrinterOutputController.py index 9a29233f95..cc7b78ac11 100644 --- a/cura/PrinterOutput/PrinterOutputController.py +++ b/cura/PrinterOutput/PrinterOutputController.py @@ -4,7 +4,7 @@ from UM.Logger import Logger from UM.Signal import Signal -from typing import Any +from typing import Union MYPY = False if MYPY: @@ -15,7 +15,7 @@ if MYPY: class PrinterOutputController: - def __init__(self, output_device: PrinterOutputDevice) -> None: + def __init__(self, output_device: "PrinterOutputDevice") -> None: self.can_pause = True self.can_abort = True self.can_pre_heat_bed = True @@ -25,7 +25,7 @@ class PrinterOutputController: self.can_update_firmware = False self._output_device = output_device - def setTargetHotendTemperature(self, printer: "PrinterOutputModel", extruder: "ExtruderOutputModel", temperature: Any[int, float]) -> None: + def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: Union[int, float]) -> None: Logger.log("w", "Set target hotend temperature not implemented in controller") def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int) -> None: diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index 96feef1b55..abfee41e80 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -172,7 +172,7 @@ class PrinterOutputModel(QObject): def cancelPreheatBed(self) -> None: self._controller.cancelPreheatBed(self) - def getController(self) -> PrinterOutputController: + def getController(self) -> "PrinterOutputController": return self._controller @pyqtProperty(str, notify=nameChanged) @@ -205,7 +205,7 @@ class PrinterOutputModel(QObject): self._controller.setTargetBedTemperature(self, temperature) self.updateTargetBedTemperature(temperature) - def updateActivePrintJob(self, print_job: Optional[PrintJobOutputModel]) -> None: + def updateActivePrintJob(self, print_job: Optional["PrintJobOutputModel"]) -> None: if self._active_print_job != print_job: old_print_job = self._active_print_job @@ -223,14 +223,14 @@ class PrinterOutputModel(QObject): self.stateChanged.emit() @pyqtProperty(QObject, notify = activePrintJobChanged) - def activePrintJob(self) -> Optional[PrintJobOutputModel]: + def activePrintJob(self) -> Optional["PrintJobOutputModel"]: return self._active_print_job @pyqtProperty(str, notify=stateChanged) def state(self) -> str: return self._printer_state - @pyqtProperty(int, notify = bedTemperatureChanged) + @pyqtProperty(int, notify=bedTemperatureChanged) def bedTemperature(self) -> int: return self._bed_temperature diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 9ab2a06d50..ebfdca2dab 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -21,7 +21,7 @@ from serial import Serial, SerialException, SerialTimeoutException from threading import Thread, Event from time import time, sleep from queue import Queue -from typing import Union, Optional, List, cast, Any +from typing import Union, Optional, List, cast import re import functools # Used for reduce @@ -99,7 +99,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): application.triggerNextExitCheck() @pyqtSlot(str) - def updateFirmware(self, file: Any[str, QUrl]) -> None: + def updateFirmware(self, file: Union[str, QUrl]) -> None: self._firmware_updater.updateFirmware(file) ## Reset USB device settings From fa5ee4c5a270111dad13803d4149ec06f49f8e20 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 28 Sep 2018 13:44:18 +0200 Subject: [PATCH 27/97] Fix typo --- .../UltimakerMachineActions/UpgradeFirmwareMachineAction.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml index 469ada7afb..1d0aabcae3 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml @@ -16,7 +16,7 @@ Cura.MachineAction anchors.fill: parent; property bool printerConnected: Cura.MachineManager.printerConnected property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null - property var canUpdateFirmware: activeOutputDevice ? activeOutputDevice.activePrinter.canUpdateFirmware : False + property var canUpdateFirmware: activeOutputDevice ? activeOutputDevice.activePrinter.canUpdateFirmware : false Column { From f69005fef989a9c9af7834ce931fa343d2989cd6 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 1 Oct 2018 11:24:31 +0200 Subject: [PATCH 28/97] Rename to CuraFormulaFunctions to avoid confusion with "SettingFunction" in Uranium. --- cura/CuraApplication.py | 18 +++++++++--------- ...ingFunctions.py => CuraFormulaFunctions.py} | 6 +++--- cura/Settings/ExtruderManager.py | 2 +- cura/Settings/UserChangesModel.py | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) rename cura/Settings/{CustomSettingFunctions.py => CuraFormulaFunctions.py} (95%) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 857aafb567..b40b65358b 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -107,7 +107,7 @@ from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisi from cura.Settings.ContainerManager import ContainerManager from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel import cura.Settings.cura_empty_instance_containers -from cura.Settings.CustomSettingFunctions import CustomSettingFunctions +from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions from cura.ObjectsModel import ObjectsModel @@ -175,7 +175,7 @@ class CuraApplication(QtApplication): self._single_instance = None - self._custom_setting_functions = None + self._cura_formula_functions = None self._cura_package_manager = None @@ -320,7 +320,7 @@ class CuraApplication(QtApplication): # Adds custom property types, settings types, and extra operators (functions) that need to be registered in # SettingDefinition and SettingFunction. def __initializeSettingDefinitionsAndFunctions(self): - self._custom_setting_functions = CustomSettingFunctions(self) + self._cura_formula_functions = CuraFormulaFunctions(self) # Need to do this before ContainerRegistry tries to load the machines SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True, read_only = True) @@ -342,10 +342,10 @@ class CuraApplication(QtApplication): SettingDefinition.addSettingType("optional_extruder", None, str, None) SettingDefinition.addSettingType("[int]", None, str, None) - SettingFunction.registerOperator("extruderValue", self._custom_setting_functions.getValueInExtruder) - SettingFunction.registerOperator("extruderValues", self._custom_setting_functions.getValuesInAllExtruders) - SettingFunction.registerOperator("resolveOrValue", self._custom_setting_functions.getResolveOrValue) - SettingFunction.registerOperator("defaultExtruderPosition", self._custom_setting_functions.getDefaultExtruderPosition) + SettingFunction.registerOperator("extruderValue", self._cura_formula_functions.getValueInExtruder) + SettingFunction.registerOperator("extruderValues", self._cura_formula_functions.getValuesInAllExtruders) + SettingFunction.registerOperator("resolveOrValue", self._cura_formula_functions.getResolveOrValue) + SettingFunction.registerOperator("defaultExtruderPosition", self._cura_formula_functions.getDefaultExtruderPosition) # Adds all resources and container related resources. def __addAllResourcesAndContainerResources(self) -> None: @@ -809,8 +809,8 @@ class CuraApplication(QtApplication): def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel: return self._setting_visibility_presets_model - def getCustomSettingFunctions(self, *args) -> CustomSettingFunctions: - return self._custom_setting_functions + def getCuraFormulaFunctions(self, *args) -> "CuraFormulaFunctions": + return self._cura_formula_functions def getMachineErrorChecker(self, *args) -> MachineErrorChecker: return self._machine_error_checker diff --git a/cura/Settings/CustomSettingFunctions.py b/cura/Settings/CuraFormulaFunctions.py similarity index 95% rename from cura/Settings/CustomSettingFunctions.py rename to cura/Settings/CuraFormulaFunctions.py index 5951ac1e73..1db01857f8 100644 --- a/cura/Settings/CustomSettingFunctions.py +++ b/cura/Settings/CuraFormulaFunctions.py @@ -12,10 +12,10 @@ if TYPE_CHECKING: # -# This class contains all Cura-related custom setting functions. Some functions requires information such as the -# currently active machine, so this is made into a class instead of standalone functions. +# This class contains all Cura-related custom functions that can be used in formulas. Some functions requires +# information such as the currently active machine, so this is made into a class instead of standalone functions. # -class CustomSettingFunctions: +class CuraFormulaFunctions: def __init__(self, application: "CuraApplication") -> None: self._application = application diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 86ee546240..ee5cf93fab 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -373,7 +373,7 @@ class ExtruderManager(QObject): # \return String representing the extruder values @pyqtSlot(str, result="QVariant") def getInstanceExtruderValues(self, key: str) -> List: - return self._application.getCustomSettingFunctions().getValuesInAllExtruders(key) + return self._application.getCuraFormulaFunctions().getValuesInAllExtruders(key) ## Get the resolve value or value for a given key # diff --git a/cura/Settings/UserChangesModel.py b/cura/Settings/UserChangesModel.py index d2ea84f79d..9a26e5607e 100644 --- a/cura/Settings/UserChangesModel.py +++ b/cura/Settings/UserChangesModel.py @@ -42,7 +42,7 @@ class UserChangesModel(ListModel): def _update(self): application = Application.getInstance() machine_manager = application.getMachineManager() - custom_setting_functions = application.getCustomSettingFunctions() + cura_formula_functions = application.getCuraFormulaFunctions() item_dict = OrderedDict() item_list = [] @@ -77,7 +77,7 @@ class UserChangesModel(ListModel): # Override "getExtruderValue" with "getDefaultExtruderValue" so we can get the default values user_changes = containers.pop(0) - default_value_resolve_context = custom_setting_functions.createContextForDefaultValueEvaluation(stack) + default_value_resolve_context = cura_formula_functions.createContextForDefaultValueEvaluation(stack) for setting_key in user_changes.getAllKeys(): original_value = None From 3908781f6f7d7072f71ae7011731c62d6c6583bc Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 2 Oct 2018 17:08:39 +0200 Subject: [PATCH 29/97] Fix this sh*t Sorry, I kind of dropped the ball before. --- cura/PrinterOutput/FirmwareUpdater.py | 38 ++++++++++++++--------- plugins/USBPrinting/AvrFirmwareUpdater.py | 20 +++++++----- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/cura/PrinterOutput/FirmwareUpdater.py b/cura/PrinterOutput/FirmwareUpdater.py index 2f200118a9..88169b1d75 100644 --- a/cura/PrinterOutput/FirmwareUpdater.py +++ b/cura/PrinterOutput/FirmwareUpdater.py @@ -16,6 +16,8 @@ class FirmwareUpdater(QObject): firmwareUpdateStateChanged = pyqtSignal() def __init__(self, output_device: PrinterOutputDevice) -> None: + super().__init__() + self._output_device = output_device self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True) @@ -31,29 +33,35 @@ class FirmwareUpdater(QObject): self._firmware_location = QUrl(file).toLocalFile() else: self._firmware_location = file - self.showFirmwareInterface() - self.setFirmwareUpdateState(FirmwareUpdateState.updating) + self._showFirmwareInterface() + self._setFirmwareUpdateState(FirmwareUpdateState.updating) + self._update_firmware_thread.start() def _updateFirmware(self) -> None: raise NotImplementedError("_updateFirmware needs to be implemented") - def cleanupAfterUpdate(self) -> None: + ## Show firmware interface. + # This will create the view if its not already created. + def _showFirmwareInterface(self) -> None: + if self._firmware_view is None: + path = Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "FirmwareUpdateWindow.qml") + self._firmware_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) + + if not self._firmware_view: + return + + self._onFirmwareProgress(0) + self._setFirmwareUpdateState(FirmwareUpdateState.idle) + self._firmware_view.show() + + ## Cleanup after a succesful update + def _cleanupAfterUpdate(self) -> None: # Clean up for next attempt. self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True) self._firmware_location = "" self._onFirmwareProgress(100) - self.setFirmwareUpdateState(FirmwareUpdateState.completed) - - ## Show firmware interface. - # This will create the view if its not already created. - def showFirmwareInterface(self) -> None: - if self._firmware_view is None: - path = Resources.getPath(self.ResourceTypes.QmlFiles, "FirmwareUpdateWindow.qml") - self._firmware_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) - - if self._firmware_view: - self._firmware_view.show() + self._setFirmwareUpdateState(FirmwareUpdateState.completed) @pyqtProperty(float, notify = firmwareProgressChanged) def firmwareProgress(self) -> float: @@ -63,7 +71,7 @@ class FirmwareUpdater(QObject): def firmwareUpdateState(self) -> "FirmwareUpdateState": return self._firmware_update_state - def setFirmwareUpdateState(self, state) -> None: + def _setFirmwareUpdateState(self, state) -> None: if self._firmware_update_state != state: self._firmware_update_state = state self.firmwareUpdateStateChanged.emit() diff --git a/plugins/USBPrinting/AvrFirmwareUpdater.py b/plugins/USBPrinting/AvrFirmwareUpdater.py index ab71f70e30..505e1ddb7e 100644 --- a/plugins/USBPrinting/AvrFirmwareUpdater.py +++ b/plugins/USBPrinting/AvrFirmwareUpdater.py @@ -22,39 +22,43 @@ class AvrFirmwareUpdater(FirmwareUpdater): assert len(hex_file) > 0 except (FileNotFoundError, AssertionError): Logger.log("e", "Unable to read provided hex file. Could not update firmware.") - self.setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error) + self._setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error) return programmer = stk500v2.Stk500v2() programmer.progress_callback = self._onFirmwareProgress + # Ensure that other connections are closed. + if self._output_device.isConnected(): + self._output_device.close() + try: - programmer.connect(self._serial_port) + programmer.connect(self._output_device._serial_port) except: programmer.close() Logger.logException("e", "Failed to update firmware") - self.setFirmwareUpdateState(FirmwareUpdateState.communication_error) + self._setFirmwareUpdateState(FirmwareUpdateState.communication_error) return # Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases. sleep(1) if not programmer.isConnected(): Logger.log("e", "Unable to connect with serial. Could not update firmware") - self.setFirmwareUpdateState(FirmwareUpdateState.communication_error) + self._setFirmwareUpdateState(FirmwareUpdateState.communication_error) try: programmer.programChip(hex_file) except SerialException as e: Logger.log("e", "A serial port exception occured during firmware update: %s" % e) - self.setFirmwareUpdateState(FirmwareUpdateState.io_error) + self._setFirmwareUpdateState(FirmwareUpdateState.io_error) return except Exception as e: Logger.log("e", "An unknown exception occured during firmware update: %s" % e) - self.setFirmwareUpdateState(FirmwareUpdateState.unknown_error) + self._setFirmwareUpdateState(FirmwareUpdateState.unknown_error) return programmer.close() # Try to re-connect with the machine again, which must be done on the Qt thread, so we use call later. - CuraApplication.getInstance().callLater(self.connect) + CuraApplication.getInstance().callLater(self._output_device.connect) - self.cleanupAfterUpdate() + self._cleanupAfterUpdate() From b4e186ce789c920ab4a1387910ea4a89ad3ce843 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 2 Oct 2018 17:14:22 +0200 Subject: [PATCH 30/97] Disable close button while updating firmware --- resources/qml/FirmwareUpdateWindow.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/FirmwareUpdateWindow.qml b/resources/qml/FirmwareUpdateWindow.qml index e0f9de314e..c71d70fc97 100644 --- a/resources/qml/FirmwareUpdateWindow.qml +++ b/resources/qml/FirmwareUpdateWindow.qml @@ -82,7 +82,7 @@ UM.Dialog Button { text: catalog.i18nc("@action:button","Close"); - enabled: manager.firmwareUpdateCompleteStatus; + enabled: manager.firmwareUpdateState != 1; onClicked: base.visible = false; } ] From 718ac0a30731dec368422b5d230945ee09f5595e Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 3 Oct 2018 09:17:51 +0200 Subject: [PATCH 31/97] Create firmware update progress window from QML --- cura/PrinterOutput/FirmwareUpdater.py | 27 +---- cura/PrinterOutputDevice.py | 7 +- .../UpgradeFirmwareMachineAction.py | 43 ++++++- .../UpgradeFirmwareMachineAction.qml | 107 ++++++++++++++++-- resources/qml/FirmwareUpdateWindow.qml | 89 --------------- 5 files changed, 151 insertions(+), 122 deletions(-) delete mode 100644 resources/qml/FirmwareUpdateWindow.qml diff --git a/cura/PrinterOutput/FirmwareUpdater.py b/cura/PrinterOutput/FirmwareUpdater.py index 88169b1d75..92e92437ad 100644 --- a/cura/PrinterOutput/FirmwareUpdater.py +++ b/cura/PrinterOutput/FirmwareUpdater.py @@ -3,26 +3,25 @@ from PyQt5.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty -from UM.Resources import Resources -from cura.PrinterOutputDevice import PrinterOutputDevice -from cura.CuraApplication import CuraApplication - from enum import IntEnum from threading import Thread from typing import Union +MYPY = False +if MYPY: + from cura.PrinterOutputDevice import PrinterOutputDevice + class FirmwareUpdater(QObject): firmwareProgressChanged = pyqtSignal() firmwareUpdateStateChanged = pyqtSignal() - def __init__(self, output_device: PrinterOutputDevice) -> None: + def __init__(self, output_device: "PrinterOutputDevice") -> None: super().__init__() self._output_device = output_device self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True) - self._firmware_view = None self._firmware_location = "" self._firmware_progress = 0 self._firmware_update_state = FirmwareUpdateState.idle @@ -33,7 +32,7 @@ class FirmwareUpdater(QObject): self._firmware_location = QUrl(file).toLocalFile() else: self._firmware_location = file - self._showFirmwareInterface() + self._setFirmwareUpdateState(FirmwareUpdateState.updating) self._update_firmware_thread.start() @@ -41,20 +40,6 @@ class FirmwareUpdater(QObject): def _updateFirmware(self) -> None: raise NotImplementedError("_updateFirmware needs to be implemented") - ## Show firmware interface. - # This will create the view if its not already created. - def _showFirmwareInterface(self) -> None: - if self._firmware_view is None: - path = Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "FirmwareUpdateWindow.qml") - self._firmware_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) - - if not self._firmware_view: - return - - self._onFirmwareProgress(0) - self._setFirmwareUpdateState(FirmwareUpdateState.idle) - self._firmware_view.show() - ## Cleanup after a succesful update def _cleanupAfterUpdate(self) -> None: # Clean up for next attempt. diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py index 5ea65adb8e..c63f9c35b5 100644 --- a/cura/PrinterOutputDevice.py +++ b/cura/PrinterOutputDevice.py @@ -20,6 +20,7 @@ MYPY = False if MYPY: from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.ConfigurationModel import ConfigurationModel + from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater i18n_catalog = i18nCatalog("cura") @@ -83,6 +84,7 @@ class PrinterOutputDevice(QObject, OutputDevice): self._connection_state = ConnectionState.closed #type: ConnectionState + self._firmware_updater = None #type: Optional[FirmwareUpdater] self._firmware_name = None #type: Optional[str] self._address = "" #type: str self._connection_text = "" #type: str @@ -225,4 +227,7 @@ class PrinterOutputDevice(QObject, OutputDevice): # # This name can be used to define device type def getFirmwareName(self) -> Optional[str]: - return self._firmware_name \ No newline at end of file + return self._firmware_name + + def getFirmwareUpdater(self) -> Optional["FirmwareUpdater"]: + return self._firmware_updater \ No newline at end of file diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py index 1f0e640f04..671ed22d5a 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py @@ -1,19 +1,58 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + from UM.Application import Application from UM.Settings.DefinitionContainer import DefinitionContainer from cura.MachineAction import MachineAction from UM.i18n import i18nCatalog from UM.Settings.ContainerRegistry import ContainerRegistry +from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject +from typing import Optional + +MYPY = False +if MYPY: + from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater + catalog = i18nCatalog("cura") ## Upgrade the firmware of a machine by USB with this action. class UpgradeFirmwareMachineAction(MachineAction): - def __init__(self): + def __init__(self) -> None: super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Upgrade Firmware")) self._qml_url = "UpgradeFirmwareMachineAction.qml" ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) - def _onContainerAdded(self, container): + self._active_output_device = None + + Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated) + + def _onEngineCreated(self) -> None: + Application.getInstance().getMachineManager().outputDevicesChanged.connect(self._onOutputDevicesChanged) + + def _onContainerAdded(self, container) -> None: # Add this action as a supported action to all machine definitions if they support USB connection if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"): Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) + + def _onOutputDevicesChanged(self) -> None: + if self._active_output_device: + self._active_output_device.activePrinter.getController().canUpdateFirmwareChanged.disconnect(self._onControllerCanUpdateFirmwareChanged) + output_devices = Application.getInstance().getMachineManager().printerOutputDevices + print(output_devices) + self._active_output_device = output_devices[0] if output_devices else None + if self._active_output_device: + self._active_output_device.activePrinter.getController().canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged) + + self.outputDeviceCanUpdateFirmwareChanged.emit() + + def _onControllerCanUpdateFirmwareChanged(self) -> None: + self.outputDeviceCanUpdateFirmwareChanged.emit() + + outputDeviceCanUpdateFirmwareChanged = pyqtSignal() + @pyqtProperty(QObject, notify = outputDeviceCanUpdateFirmwareChanged) + def firmwareUpdater(self) -> Optional["firmwareUpdater"]: + if self._active_output_device and self._active_output_device.activePrinter.getController().can_update_firmware: + return self._active_output_device.getFirmwareUpdater() + + return None \ No newline at end of file diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml index 1d0aabcae3..1c1f39edd0 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Ultimaker B.V. +// Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.2 @@ -59,7 +59,8 @@ Cura.MachineAction enabled: parent.firmwareName != "" && canUpdateFirmware onClicked: { - activeOutputDevice.updateFirmware(parent.firmwareName) + firmwareUpdateWindow.visible = true; + activeOutputDevice.updateFirmware(parent.firmwareName); } } Button @@ -78,7 +79,7 @@ Cura.MachineAction { width: parent.width wrapMode: Text.WordWrap - visible: !printerConnected + visible: !printerConnected && !firmwareUpdateWindow.visible text: catalog.i18nc("@label", "Firmware can not be upgraded because there is no connection with the printer."); } @@ -89,14 +90,102 @@ Cura.MachineAction visible: printerConnected && !canUpdateFirmware text: catalog.i18nc("@label", "Firmware can not be upgraded because the connection with the printer does not support upgrading firmware."); } + } - FileDialog + FileDialog + { + id: customFirmwareDialog + title: catalog.i18nc("@title:window", "Select custom firmware") + nameFilters: "Firmware image files (*.hex)" + selectExisting: true + onAccepted: { - id: customFirmwareDialog - title: catalog.i18nc("@title:window", "Select custom firmware") - nameFilters: "Firmware image files (*.hex)" - selectExisting: true - onAccepted: activeOutputDevice.updateFirmware(fileUrl) + firmwareUpdateWindow.visible = true; + activeOutputDevice.updateFirmware(fileUrl); } } + + UM.Dialog + { + id: firmwareUpdateWindow + + width: minimumWidth + minimumWidth: 500 * screenScaleFactor + height: minimumHeight + minimumHeight: 100 * screenScaleFactor + + modality: Qt.ApplicationModal + + title: catalog.i18nc("@title:window","Firmware Update") + + Column + { + anchors.fill: parent + + Label + { + anchors + { + left: parent.left + right: parent.right + } + + text: { + if(manager.firmwareUpdater == null) + { + return ""; + } + switch (manager.firmwareUpdater.firmwareUpdateState) + { + case 0: + return ""; //Not doing anything (eg; idling) + case 1: + return catalog.i18nc("@label","Updating firmware."); + case 2: + return catalog.i18nc("@label","Firmware update completed."); + case 3: + return catalog.i18nc("@label","Firmware update failed due to an unknown error."); + case 4: + return catalog.i18nc("@label","Firmware update failed due to an communication error."); + case 5: + return catalog.i18nc("@label","Firmware update failed due to an input/output error."); + case 6: + return catalog.i18nc("@label","Firmware update failed due to missing firmware."); + } + } + + wrapMode: Text.Wrap + } + + ProgressBar + { + id: prog + value: (manager.firmwareUpdater != null) ? manager.firmwareUpdater.firmwareProgress : 0 + minimumValue: 0 + maximumValue: 100 + indeterminate: + { + if(manager.firmwareUpdater == null) + { + return false; + } + return manager.firmwareUpdater.firmwareProgress < 1 && manager.firmwareUpdater.firmwareProgress > 0; + } + anchors + { + left: parent.left; + right: parent.right; + } + } + } + + rightButtons: [ + Button + { + text: catalog.i18nc("@action:button","Close"); + enabled: (manager.firmwareUpdater != null) ? manager.firmwareUpdater.firmwareUpdateState != 1 : true; + onClicked: firmwareUpdateWindow.visible = false; + } + ] + } } \ No newline at end of file diff --git a/resources/qml/FirmwareUpdateWindow.qml b/resources/qml/FirmwareUpdateWindow.qml deleted file mode 100644 index c71d70fc97..0000000000 --- a/resources/qml/FirmwareUpdateWindow.qml +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2017 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.2 -import QtQuick.Window 2.2 -import QtQuick.Controls 1.2 - -import UM 1.1 as UM - -UM.Dialog -{ - id: base; - - width: minimumWidth; - minimumWidth: 500 * screenScaleFactor; - height: minimumHeight; - minimumHeight: 100 * screenScaleFactor; - - visible: true; - modality: Qt.ApplicationModal; - - title: catalog.i18nc("@title:window","Firmware Update"); - - Column - { - anchors.fill: parent; - - Label - { - anchors - { - left: parent.left; - right: parent.right; - } - - text: { - switch (manager.firmwareUpdateState) - { - case 0: - return "" //Not doing anything (eg; idling) - case 1: - return catalog.i18nc("@label","Updating firmware.") - case 2: - return catalog.i18nc("@label","Firmware update completed.") - case 3: - return catalog.i18nc("@label","Firmware update failed due to an unknown error.") - case 4: - return catalog.i18nc("@label","Firmware update failed due to an communication error.") - case 5: - return catalog.i18nc("@label","Firmware update failed due to an input/output error.") - case 6: - return catalog.i18nc("@label","Firmware update failed due to missing firmware.") - } - } - - wrapMode: Text.Wrap; - } - - ProgressBar - { - id: prog - value: manager.firmwareProgress - minimumValue: 0 - maximumValue: 100 - indeterminate: manager.firmwareProgress < 1 && manager.firmwareProgress > 0 - anchors - { - left: parent.left; - right: parent.right; - } - } - - SystemPalette - { - id: palette; - } - - UM.I18nCatalog { id: catalog; name: "cura"; } - } - - rightButtons: [ - Button - { - text: catalog.i18nc("@action:button","Close"); - enabled: manager.firmwareUpdateState != 1; - onClicked: base.visible = false; - } - ] -} From f3fdb46dbaa37a515f31a322df75f1999726efda Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 3 Oct 2018 13:33:30 +0200 Subject: [PATCH 32/97] Add missing types --- cura/CuraApplication.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index b40b65358b..eb5abf79d6 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -4,7 +4,7 @@ import os import sys import time -from typing import cast, TYPE_CHECKING +from typing import cast, TYPE_CHECKING, Optional import numpy @@ -175,7 +175,7 @@ class CuraApplication(QtApplication): self._single_instance = None - self._cura_formula_functions = None + self._cura_formula_functions = None # type: Optional[CuraFormulaFunctions] self._cura_package_manager = None @@ -810,6 +810,8 @@ class CuraApplication(QtApplication): return self._setting_visibility_presets_model def getCuraFormulaFunctions(self, *args) -> "CuraFormulaFunctions": + if self._cura_formula_functions is None: + self._cura_formula_functions = CuraFormulaFunctions(self) return self._cura_formula_functions def getMachineErrorChecker(self, *args) -> MachineErrorChecker: From cf3d7df8a6998c9982b1a2a1e5136898c96e0b2e Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 3 Oct 2018 13:59:46 +0200 Subject: [PATCH 33/97] Fix showing progress --- .../UpgradeFirmwareMachineAction.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py index 671ed22d5a..478ea9b6bb 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py @@ -6,6 +6,7 @@ from UM.Settings.DefinitionContainer import DefinitionContainer from cura.MachineAction import MachineAction from UM.i18n import i18nCatalog from UM.Settings.ContainerRegistry import ContainerRegistry +from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdateState from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject from typing import Optional @@ -13,6 +14,7 @@ from typing import Optional MYPY = False if MYPY: from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater + from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice catalog = i18nCatalog("cura") @@ -23,7 +25,8 @@ class UpgradeFirmwareMachineAction(MachineAction): self._qml_url = "UpgradeFirmwareMachineAction.qml" ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) - self._active_output_device = None + self._active_output_device = None #type: Optional[PrinterOutputDevice] + self._active_firmware_updater = None #type: Optional[FirmwareUpdater] Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated) @@ -38,9 +41,10 @@ class UpgradeFirmwareMachineAction(MachineAction): def _onOutputDevicesChanged(self) -> None: if self._active_output_device: self._active_output_device.activePrinter.getController().canUpdateFirmwareChanged.disconnect(self._onControllerCanUpdateFirmwareChanged) + output_devices = Application.getInstance().getMachineManager().printerOutputDevices - print(output_devices) self._active_output_device = output_devices[0] if output_devices else None + if self._active_output_device: self._active_output_device.activePrinter.getController().canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged) @@ -53,6 +57,12 @@ class UpgradeFirmwareMachineAction(MachineAction): @pyqtProperty(QObject, notify = outputDeviceCanUpdateFirmwareChanged) def firmwareUpdater(self) -> Optional["firmwareUpdater"]: if self._active_output_device and self._active_output_device.activePrinter.getController().can_update_firmware: - return self._active_output_device.getFirmwareUpdater() + self._active_firmware_updater = self._active_output_device.getFirmwareUpdater() + return self._active_firmware_updater - return None \ No newline at end of file + elif self._active_firmware_updater and self._active_firmware_updater.firmwareUpdateState not in [FirmwareUpdateState.idle, FirmwareUpdateState.completed]: + # During a firmware update, the PrinterOutputDevice is disconnected but the FirmwareUpdater is still there + return self._active_firmware_updater + + self._active_firmware_updater = None + return None From 7d7de32dbdd16ceb87a0bb7651a0ab43ebc5317b Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 3 Oct 2018 16:53:07 +0200 Subject: [PATCH 34/97] Add ExtruderStack to GlobalStack in single extrusion machine fix --- cura/Settings/ExtruderManager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 9089ba96e9..2514e17075 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -374,6 +374,8 @@ class ExtruderManager(QObject): extruder_definition = container_registry.findDefinitionContainers(id = expected_extruder_definition_0_id)[0] extruder_stack_0.definition = extruder_definition + extruder_stack_0.setNextStack(global_stack) + ## Get all extruder values for a certain setting. # # This is exposed to qml for display purposes From 61ffdf23d70d79857156020dcd1508496d036511 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 3 Oct 2018 14:10:02 +0200 Subject: [PATCH 35/97] Fix MYPY/typing errors --- plugins/USBPrinting/USBPrinterOutputDevice.py | 3 +++ .../UpgradeFirmwareMachineAction.py | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 15136491f8..1fd2fdeb5c 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -100,6 +100,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice): @pyqtSlot(str) def updateFirmware(self, file: Union[str, QUrl]) -> None: + if not self._firmware_updater: + return + self._firmware_updater.updateFirmware(file) ## Reset USB device settings diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py index 478ea9b6bb..8d03a15b38 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py @@ -1,7 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from UM.Application import Application +from cura.CuraApplication import CuraApplication from UM.Settings.DefinitionContainer import DefinitionContainer from cura.MachineAction import MachineAction from UM.i18n import i18nCatalog @@ -28,21 +28,21 @@ class UpgradeFirmwareMachineAction(MachineAction): self._active_output_device = None #type: Optional[PrinterOutputDevice] self._active_firmware_updater = None #type: Optional[FirmwareUpdater] - Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated) + CuraApplication.getInstance().engineCreatedSignal.connect(self._onEngineCreated) def _onEngineCreated(self) -> None: - Application.getInstance().getMachineManager().outputDevicesChanged.connect(self._onOutputDevicesChanged) + CuraApplication.getInstance().getMachineManager().outputDevicesChanged.connect(self._onOutputDevicesChanged) def _onContainerAdded(self, container) -> None: # Add this action as a supported action to all machine definitions if they support USB connection if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"): - Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) + CuraApplication.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) def _onOutputDevicesChanged(self) -> None: if self._active_output_device: self._active_output_device.activePrinter.getController().canUpdateFirmwareChanged.disconnect(self._onControllerCanUpdateFirmwareChanged) - output_devices = Application.getInstance().getMachineManager().printerOutputDevices + output_devices = CuraApplication.getInstance().getMachineManager().printerOutputDevices self._active_output_device = output_devices[0] if output_devices else None if self._active_output_device: @@ -55,7 +55,7 @@ class UpgradeFirmwareMachineAction(MachineAction): outputDeviceCanUpdateFirmwareChanged = pyqtSignal() @pyqtProperty(QObject, notify = outputDeviceCanUpdateFirmwareChanged) - def firmwareUpdater(self) -> Optional["firmwareUpdater"]: + def firmwareUpdater(self) -> Optional["FirmwareUpdater"]: if self._active_output_device and self._active_output_device.activePrinter.getController().can_update_firmware: self._active_firmware_updater = self._active_output_device.getFirmwareUpdater() return self._active_firmware_updater From b7542a8ef8ff17491c1366e7ca5532ebc9e0c526 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 3 Oct 2018 20:55:51 +0200 Subject: [PATCH 36/97] Move Firmware Updater into a plugin of its own --- .../FirmwareUpdaterMachineAction.py} | 4 ++-- .../FirmwareUpdaterMachineAction.qml} | 18 +++++++++--------- plugins/FirmwareUpdater/__init__.py | 13 +++++++++++++ plugins/FirmwareUpdater/plugin.json | 8 ++++++++ plugins/UltimakerMachineActions/__init__.py | 5 ----- resources/bundled_packages/cura.json | 17 +++++++++++++++++ 6 files changed, 49 insertions(+), 16 deletions(-) rename plugins/{UltimakerMachineActions/UpgradeFirmwareMachineAction.py => FirmwareUpdater/FirmwareUpdaterMachineAction.py} (96%) rename plugins/{UltimakerMachineActions/UpgradeFirmwareMachineAction.qml => FirmwareUpdater/FirmwareUpdaterMachineAction.qml} (92%) create mode 100644 plugins/FirmwareUpdater/__init__.py create mode 100644 plugins/FirmwareUpdater/plugin.json diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py similarity index 96% rename from plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py rename to plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py index 8d03a15b38..4faa3abc64 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py +++ b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py @@ -19,10 +19,10 @@ if MYPY: catalog = i18nCatalog("cura") ## Upgrade the firmware of a machine by USB with this action. -class UpgradeFirmwareMachineAction(MachineAction): +class FirmwareUpdaterMachineAction(MachineAction): def __init__(self) -> None: super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Upgrade Firmware")) - self._qml_url = "UpgradeFirmwareMachineAction.qml" + self._qml_url = "FirmwareUpdaterMachineAction.qml" ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) self._active_output_device = None #type: Optional[PrinterOutputDevice] diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml similarity index 92% rename from plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml rename to plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml index 1c1f39edd0..ab5bb89347 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml +++ b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml @@ -20,7 +20,7 @@ Cura.MachineAction Column { - id: upgradeFirmwareMachineAction + id: firmwareUpdaterMachineAction anchors.fill: parent; UM.I18nCatalog { id: catalog; name:"cura"} spacing: UM.Theme.getSize("default_margin").height @@ -28,7 +28,7 @@ Cura.MachineAction Label { width: parent.width - text: catalog.i18nc("@title", "Upgrade Firmware") + text: catalog.i18nc("@title", "Update Firmware") wrapMode: Text.WordWrap font.pointSize: 18 } @@ -59,7 +59,7 @@ Cura.MachineAction enabled: parent.firmwareName != "" && canUpdateFirmware onClicked: { - firmwareUpdateWindow.visible = true; + updateProgressDialog.visible = true; activeOutputDevice.updateFirmware(parent.firmwareName); } } @@ -79,8 +79,8 @@ Cura.MachineAction { width: parent.width wrapMode: Text.WordWrap - visible: !printerConnected && !firmwareUpdateWindow.visible - text: catalog.i18nc("@label", "Firmware can not be upgraded because there is no connection with the printer."); + visible: !printerConnected && !updateProgressDialog.visible + text: catalog.i18nc("@label", "Firmware can not be updated because there is no connection with the printer."); } Label @@ -88,7 +88,7 @@ Cura.MachineAction width: parent.width wrapMode: Text.WordWrap visible: printerConnected && !canUpdateFirmware - text: catalog.i18nc("@label", "Firmware can not be upgraded because the connection with the printer does not support upgrading firmware."); + text: catalog.i18nc("@label", "Firmware can not be updated because the connection with the printer does not support upgrading firmware."); } } @@ -100,14 +100,14 @@ Cura.MachineAction selectExisting: true onAccepted: { - firmwareUpdateWindow.visible = true; + updateProgressDialog.visible = true; activeOutputDevice.updateFirmware(fileUrl); } } UM.Dialog { - id: firmwareUpdateWindow + id: updateProgressDialog width: minimumWidth minimumWidth: 500 * screenScaleFactor @@ -184,7 +184,7 @@ Cura.MachineAction { text: catalog.i18nc("@action:button","Close"); enabled: (manager.firmwareUpdater != null) ? manager.firmwareUpdater.firmwareUpdateState != 1 : true; - onClicked: firmwareUpdateWindow.visible = false; + onClicked: updateProgressDialog.visible = false; } ] } diff --git a/plugins/FirmwareUpdater/__init__.py b/plugins/FirmwareUpdater/__init__.py new file mode 100644 index 0000000000..58c351a4ea --- /dev/null +++ b/plugins/FirmwareUpdater/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from . import FirmwareUpdaterMachineAction + +def getMetaData(): + return { + } + +def register(app): + return { "machine_action": [ + FirmwareUpdaterMachineAction.FirmwareUpdaterMachineAction(), + ]} diff --git a/plugins/FirmwareUpdater/plugin.json b/plugins/FirmwareUpdater/plugin.json new file mode 100644 index 0000000000..3e09eab2b5 --- /dev/null +++ b/plugins/FirmwareUpdater/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "Firmware Updater", + "author": "Ultimaker B.V.", + "version": "1.0.0", + "description": "Provides a machine actions for updating firmware.", + "api": 5, + "i18n-catalog": "cura" +} diff --git a/plugins/UltimakerMachineActions/__init__.py b/plugins/UltimakerMachineActions/__init__.py index 495f212736..30493536ce 100644 --- a/plugins/UltimakerMachineActions/__init__.py +++ b/plugins/UltimakerMachineActions/__init__.py @@ -2,13 +2,9 @@ # Cura is released under the terms of the LGPLv3 or higher. from . import BedLevelMachineAction -from . import UpgradeFirmwareMachineAction from . import UMOUpgradeSelection from . import UM2UpgradeSelection -from UM.i18n import i18nCatalog -catalog = i18nCatalog("cura") - def getMetaData(): return { } @@ -16,7 +12,6 @@ def getMetaData(): def register(app): return { "machine_action": [ BedLevelMachineAction.BedLevelMachineAction(), - UpgradeFirmwareMachineAction.UpgradeFirmwareMachineAction(), UMOUpgradeSelection.UMOUpgradeSelection(), UM2UpgradeSelection.UM2UpgradeSelection() ]} diff --git a/resources/bundled_packages/cura.json b/resources/bundled_packages/cura.json index 7107bbe4f0..ad97f3595b 100644 --- a/resources/bundled_packages/cura.json +++ b/resources/bundled_packages/cura.json @@ -118,6 +118,23 @@ } } }, + "FirmwareUpdater": { + "package_info": { + "package_id": "FirmwareUpdater", + "package_type": "plugin", + "display_name": "Firmware Updater", + "description": "Provides a machine actions for updating firmware.", + "package_version": "1.0.0", + "sdk_version": 5, + "website": "https://ultimaker.com", + "author": { + "author_id": "Ultimaker", + "display_name": "Ultimaker B.V.", + "email": "plugins@ultimaker.com", + "website": "https://ultimaker.com" + } + } + }, "GCodeGzReader": { "package_info": { "package_id": "GCodeGzReader", From c2558f91dd26095ddb5d019f1fa07e1e5b67c1e1 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 3 Oct 2018 21:00:23 +0200 Subject: [PATCH 37/97] Remove UpgradeFirmware as supported machine action... because the plugin adds itself as a supported action --- resources/definitions/makeit_pro_l.def.json | 1 - resources/definitions/makeit_pro_m.def.json | 1 - resources/definitions/tam.def.json | 1 - resources/definitions/ultimaker2.def.json | 2 +- resources/definitions/ultimaker2_extended_plus.def.json | 1 - resources/definitions/ultimaker2_go.def.json | 1 - resources/definitions/ultimaker2_plus.def.json | 1 - resources/definitions/ultimaker_original.def.json | 2 +- resources/definitions/ultimaker_original_dual.def.json | 2 +- resources/definitions/ultimaker_original_plus.def.json | 2 +- resources/definitions/wanhao_d6.def.json | 3 --- 11 files changed, 4 insertions(+), 13 deletions(-) diff --git a/resources/definitions/makeit_pro_l.def.json b/resources/definitions/makeit_pro_l.def.json index 2f9173c90e..d40d63f97b 100644 --- a/resources/definitions/makeit_pro_l.def.json +++ b/resources/definitions/makeit_pro_l.def.json @@ -8,7 +8,6 @@ "manufacturer": "NA", "file_formats": "text/x-gcode", "has_materials": false, - "supported_actions": [ "MachineSettingsAction", "UpgradeFirmware" ], "machine_extruder_trains": { "0": "makeit_l_dual_1st", diff --git a/resources/definitions/makeit_pro_m.def.json b/resources/definitions/makeit_pro_m.def.json index 0cd7b42df3..1f0381df86 100644 --- a/resources/definitions/makeit_pro_m.def.json +++ b/resources/definitions/makeit_pro_m.def.json @@ -8,7 +8,6 @@ "manufacturer": "NA", "file_formats": "text/x-gcode", "has_materials": false, - "supported_actions": [ "MachineSettingsAction", "UpgradeFirmware" ], "machine_extruder_trains": { "0": "makeit_dual_1st", diff --git a/resources/definitions/tam.def.json b/resources/definitions/tam.def.json index 9865abedda..0ed8d657a2 100644 --- a/resources/definitions/tam.def.json +++ b/resources/definitions/tam.def.json @@ -10,7 +10,6 @@ "platform": "tam_series1.stl", "platform_offset": [-580.0, -6.23, 253.5], "has_materials": false, - "supported_actions": ["UpgradeFirmware"], "machine_extruder_trains": { "0": "tam_extruder_0" diff --git a/resources/definitions/ultimaker2.def.json b/resources/definitions/ultimaker2.def.json index f367558df0..bbe61d49fb 100644 --- a/resources/definitions/ultimaker2.def.json +++ b/resources/definitions/ultimaker2.def.json @@ -17,7 +17,7 @@ "preferred_variant_name": "0.4 mm", "exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "ultimaker_bam", "generic_pva", "ultimaker_pva", "generic_tough_pla", "ultimaker_tough_pla_black", "ultimaker_tough_pla_green", "ultimaker_tough_pla_red", "ultimaker_tough_pla_white"], "first_start_actions": ["UM2UpgradeSelection"], - "supported_actions":["UM2UpgradeSelection", "UpgradeFirmware"], + "supported_actions":["UM2UpgradeSelection"], "machine_extruder_trains": { "0": "ultimaker2_extruder_0" diff --git a/resources/definitions/ultimaker2_extended_plus.def.json b/resources/definitions/ultimaker2_extended_plus.def.json index c296ecd43e..0242115057 100644 --- a/resources/definitions/ultimaker2_extended_plus.def.json +++ b/resources/definitions/ultimaker2_extended_plus.def.json @@ -10,7 +10,6 @@ "file_formats": "text/x-gcode", "platform": "ultimaker2_platform.obj", "platform_texture": "Ultimaker2ExtendedPlusbackplate.png", - "supported_actions": ["UpgradeFirmware"], "machine_extruder_trains": { "0": "ultimaker2_extended_plus_extruder_0" diff --git a/resources/definitions/ultimaker2_go.def.json b/resources/definitions/ultimaker2_go.def.json index 5301fd7db9..e2ad2b00a1 100644 --- a/resources/definitions/ultimaker2_go.def.json +++ b/resources/definitions/ultimaker2_go.def.json @@ -13,7 +13,6 @@ "platform_texture": "Ultimaker2Gobackplate.png", "platform_offset": [0, 0, 0], "first_start_actions": [], - "supported_actions": ["UpgradeFirmware"], "machine_extruder_trains": { "0": "ultimaker2_go_extruder_0" diff --git a/resources/definitions/ultimaker2_plus.def.json b/resources/definitions/ultimaker2_plus.def.json index 45019789bf..bf48353f59 100644 --- a/resources/definitions/ultimaker2_plus.def.json +++ b/resources/definitions/ultimaker2_plus.def.json @@ -15,7 +15,6 @@ "has_machine_materials": true, "has_machine_quality": true, "first_start_actions": [], - "supported_actions": ["UpgradeFirmware"], "machine_extruder_trains": { "0": "ultimaker2_plus_extruder_0" diff --git a/resources/definitions/ultimaker_original.def.json b/resources/definitions/ultimaker_original.def.json index bb21e4b82e..4714fc1217 100644 --- a/resources/definitions/ultimaker_original.def.json +++ b/resources/definitions/ultimaker_original.def.json @@ -14,7 +14,7 @@ "has_machine_quality": true, "exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "ultimaker_bam", "generic_pva", "ultimaker_pva", "generic_tough_pla", "ultimaker_tough_pla_black", "ultimaker_tough_pla_green", "ultimaker_tough_pla_red", "ultimaker_tough_pla_white"], "first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"], - "supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"], + "supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"], "machine_extruder_trains": { "0": "ultimaker_original_extruder_0" diff --git a/resources/definitions/ultimaker_original_dual.def.json b/resources/definitions/ultimaker_original_dual.def.json index 1ffb6e840b..0dc1cb3d2d 100644 --- a/resources/definitions/ultimaker_original_dual.def.json +++ b/resources/definitions/ultimaker_original_dual.def.json @@ -22,7 +22,7 @@ "firmware_file": "MarlinUltimaker-{baudrate}-dual.hex", "firmware_hbk_file": "MarlinUltimaker-HBK-{baudrate}-dual.hex", "first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"], - "supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"] + "supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"] }, "overrides": { diff --git a/resources/definitions/ultimaker_original_plus.def.json b/resources/definitions/ultimaker_original_plus.def.json index 46d95f8028..01523f34b7 100644 --- a/resources/definitions/ultimaker_original_plus.def.json +++ b/resources/definitions/ultimaker_original_plus.def.json @@ -12,7 +12,7 @@ "platform_texture": "UltimakerPlusbackplate.png", "quality_definition": "ultimaker_original", "first_start_actions": ["UMOCheckup", "BedLevel"], - "supported_actions": ["UMOCheckup", "BedLevel", "UpgradeFirmware"], + "supported_actions": ["UMOCheckup", "BedLevel"], "machine_extruder_trains": { "0": "ultimaker_original_plus_extruder_0" diff --git a/resources/definitions/wanhao_d6.def.json b/resources/definitions/wanhao_d6.def.json index 6164f4d016..e269615c4a 100644 --- a/resources/definitions/wanhao_d6.def.json +++ b/resources/definitions/wanhao_d6.def.json @@ -18,9 +18,6 @@ 0, -28, 0 - ], - "supported_actions": [ - "UpgradeFirmware" ] }, "overrides": { From 9ac744b9ba33cacf767f9b9ba3873fef3bdb50bf Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 3 Oct 2018 22:00:24 +0200 Subject: [PATCH 38/97] Remove unnecessary import and declaration of i18n in plugins --- plugins/3MFWriter/__init__.py | 2 +- plugins/ChangeLogPlugin/__init__.py | 2 -- plugins/FirmwareUpdateChecker/__init__.py | 4 ---- plugins/MachineSettingsAction/__init__.py | 2 -- plugins/ModelChecker/__init__.py | 5 +---- plugins/MonitorStage/__init__.py | 1 + plugins/PostProcessingPlugin/__init__.py | 6 +++--- plugins/RemovableDriveOutputDevice/__init__.py | 6 ++---- plugins/SliceInfoPlugin/__init__.py | 7 +++---- plugins/UM3NetworkPrinting/__init__.py | 3 --- plugins/VersionUpgrade/VersionUpgrade21to22/__init__.py | 3 --- plugins/VersionUpgrade/VersionUpgrade22to24/__init__.py | 3 --- plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py | 3 --- plugins/VersionUpgrade/VersionUpgrade26to27/__init__.py | 3 --- plugins/VersionUpgrade/VersionUpgrade33to34/__init__.py | 1 - plugins/VersionUpgrade/VersionUpgrade34to35/__init__.py | 1 - plugins/XmlMaterialProfile/__init__.py | 3 --- 17 files changed, 11 insertions(+), 44 deletions(-) diff --git a/plugins/3MFWriter/__init__.py b/plugins/3MFWriter/__init__.py index 4b8a03888d..eff1648489 100644 --- a/plugins/3MFWriter/__init__.py +++ b/plugins/3MFWriter/__init__.py @@ -12,7 +12,7 @@ from . import ThreeMFWorkspaceWriter from UM.i18n import i18nCatalog from UM.Platform import Platform -i18n_catalog = i18nCatalog("uranium") +i18n_catalog = i18nCatalog("cura") def getMetaData(): workspace_extension = "3mf" diff --git a/plugins/ChangeLogPlugin/__init__.py b/plugins/ChangeLogPlugin/__init__.py index 97d9e411e5..a5452b60c8 100644 --- a/plugins/ChangeLogPlugin/__init__.py +++ b/plugins/ChangeLogPlugin/__init__.py @@ -3,8 +3,6 @@ from . import ChangeLog -from UM.i18n import i18nCatalog -catalog = i18nCatalog("cura") def getMetaData(): return {} diff --git a/plugins/FirmwareUpdateChecker/__init__.py b/plugins/FirmwareUpdateChecker/__init__.py index 3fae15e826..892c9c0320 100644 --- a/plugins/FirmwareUpdateChecker/__init__.py +++ b/plugins/FirmwareUpdateChecker/__init__.py @@ -1,12 +1,8 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from UM.i18n import i18nCatalog - from . import FirmwareUpdateChecker -i18n_catalog = i18nCatalog("cura") - def getMetaData(): return {} diff --git a/plugins/MachineSettingsAction/__init__.py b/plugins/MachineSettingsAction/__init__.py index b1c4a75fec..ff80a12551 100644 --- a/plugins/MachineSettingsAction/__init__.py +++ b/plugins/MachineSettingsAction/__init__.py @@ -3,8 +3,6 @@ from . import MachineSettingsAction -from UM.i18n import i18nCatalog -catalog = i18nCatalog("cura") def getMetaData(): return {} diff --git a/plugins/ModelChecker/__init__.py b/plugins/ModelChecker/__init__.py index 5f4d443729..dffee21723 100644 --- a/plugins/ModelChecker/__init__.py +++ b/plugins/ModelChecker/__init__.py @@ -1,11 +1,8 @@ # Copyright (c) 2018 Ultimaker B.V. -# This example is released under the terms of the AGPLv3 or higher. +# Cura is released under the terms of the LGPLv3 or higher. from . import ModelChecker -from UM.i18n import i18nCatalog -i18n_catalog = i18nCatalog("cura") - def getMetaData(): return {} diff --git a/plugins/MonitorStage/__init__.py b/plugins/MonitorStage/__init__.py index 884d43a8af..bdaf53a36c 100644 --- a/plugins/MonitorStage/__init__.py +++ b/plugins/MonitorStage/__init__.py @@ -3,6 +3,7 @@ from . import MonitorStage + from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") diff --git a/plugins/PostProcessingPlugin/__init__.py b/plugins/PostProcessingPlugin/__init__.py index 85f1126136..8064d1132a 100644 --- a/plugins/PostProcessingPlugin/__init__.py +++ b/plugins/PostProcessingPlugin/__init__.py @@ -2,10 +2,10 @@ # The PostProcessingPlugin is released under the terms of the AGPLv3 or higher. from . import PostProcessingPlugin -from UM.i18n import i18nCatalog -catalog = i18nCatalog("cura") + + def getMetaData(): return {} - + def register(app): return {"extension": PostProcessingPlugin.PostProcessingPlugin()} \ No newline at end of file diff --git a/plugins/RemovableDriveOutputDevice/__init__.py b/plugins/RemovableDriveOutputDevice/__init__.py index dc547b7bcc..1758801f8a 100644 --- a/plugins/RemovableDriveOutputDevice/__init__.py +++ b/plugins/RemovableDriveOutputDevice/__init__.py @@ -3,12 +3,10 @@ from UM.Platform import Platform from UM.Logger import Logger -from UM.i18n import i18nCatalog -catalog = i18nCatalog("cura") + def getMetaData(): - return { - } + return {} def register(app): if Platform.isWindows(): diff --git a/plugins/SliceInfoPlugin/__init__.py b/plugins/SliceInfoPlugin/__init__.py index 7f1dfa5336..440ca8ec40 100644 --- a/plugins/SliceInfoPlugin/__init__.py +++ b/plugins/SliceInfoPlugin/__init__.py @@ -1,12 +1,11 @@ # Copyright (c) 2015 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. + from . import SliceInfo -from UM.i18n import i18nCatalog -catalog = i18nCatalog("cura") + def getMetaData(): - return { - } + return {} def register(app): return { "extension": SliceInfo.SliceInfo()} \ No newline at end of file diff --git a/plugins/UM3NetworkPrinting/__init__.py b/plugins/UM3NetworkPrinting/__init__.py index 7f2b34223c..e2ad5a2b12 100644 --- a/plugins/UM3NetworkPrinting/__init__.py +++ b/plugins/UM3NetworkPrinting/__init__.py @@ -2,9 +2,6 @@ # Cura is released under the terms of the LGPLv3 or higher. from .src import DiscoverUM3Action -from UM.i18n import i18nCatalog -catalog = i18nCatalog("cura") - from .src import UM3OutputDevicePlugin def getMetaData(): diff --git a/plugins/VersionUpgrade/VersionUpgrade21to22/__init__.py b/plugins/VersionUpgrade/VersionUpgrade21to22/__init__.py index 435621ec54..609781ebfe 100644 --- a/plugins/VersionUpgrade/VersionUpgrade21to22/__init__.py +++ b/plugins/VersionUpgrade/VersionUpgrade21to22/__init__.py @@ -3,9 +3,6 @@ from . import VersionUpgrade21to22 -from UM.i18n import i18nCatalog -catalog = i18nCatalog("cura") - upgrade = VersionUpgrade21to22.VersionUpgrade21to22() def getMetaData(): diff --git a/plugins/VersionUpgrade/VersionUpgrade22to24/__init__.py b/plugins/VersionUpgrade/VersionUpgrade22to24/__init__.py index fbdbf92a4b..278b660ec1 100644 --- a/plugins/VersionUpgrade/VersionUpgrade22to24/__init__.py +++ b/plugins/VersionUpgrade/VersionUpgrade22to24/__init__.py @@ -3,9 +3,6 @@ from . import VersionUpgrade -from UM.i18n import i18nCatalog -catalog = i18nCatalog("cura") - upgrade = VersionUpgrade.VersionUpgrade22to24() def getMetaData(): diff --git a/plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py b/plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py index 1419325cc1..67aa73233f 100644 --- a/plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py +++ b/plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py @@ -3,9 +3,6 @@ from . import VersionUpgrade25to26 -from UM.i18n import i18nCatalog -catalog = i18nCatalog("cura") - upgrade = VersionUpgrade25to26.VersionUpgrade25to26() def getMetaData(): diff --git a/plugins/VersionUpgrade/VersionUpgrade26to27/__init__.py b/plugins/VersionUpgrade/VersionUpgrade26to27/__init__.py index 79ed5e8b68..0e26ca8bbf 100644 --- a/plugins/VersionUpgrade/VersionUpgrade26to27/__init__.py +++ b/plugins/VersionUpgrade/VersionUpgrade26to27/__init__.py @@ -3,9 +3,6 @@ from . import VersionUpgrade26to27 -from UM.i18n import i18nCatalog -catalog = i18nCatalog("cura") - upgrade = VersionUpgrade26to27.VersionUpgrade26to27() def getMetaData(): diff --git a/plugins/VersionUpgrade/VersionUpgrade33to34/__init__.py b/plugins/VersionUpgrade/VersionUpgrade33to34/__init__.py index 4faa1290b5..8213f195d5 100644 --- a/plugins/VersionUpgrade/VersionUpgrade33to34/__init__.py +++ b/plugins/VersionUpgrade/VersionUpgrade33to34/__init__.py @@ -5,7 +5,6 @@ from . import VersionUpgrade33to34 upgrade = VersionUpgrade33to34.VersionUpgrade33to34() - def getMetaData(): return { "version_upgrade": { diff --git a/plugins/VersionUpgrade/VersionUpgrade34to35/__init__.py b/plugins/VersionUpgrade/VersionUpgrade34to35/__init__.py index 9d3410e40d..de0fdccb7d 100644 --- a/plugins/VersionUpgrade/VersionUpgrade34to35/__init__.py +++ b/plugins/VersionUpgrade/VersionUpgrade34to35/__init__.py @@ -5,7 +5,6 @@ from . import VersionUpgrade34to35 upgrade = VersionUpgrade34to35.VersionUpgrade34to35() - def getMetaData(): return { "version_upgrade": { diff --git a/plugins/XmlMaterialProfile/__init__.py b/plugins/XmlMaterialProfile/__init__.py index 70a359ee76..e8bde78424 100644 --- a/plugins/XmlMaterialProfile/__init__.py +++ b/plugins/XmlMaterialProfile/__init__.py @@ -5,10 +5,7 @@ from . import XmlMaterialProfile from . import XmlMaterialUpgrader from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase -from UM.i18n import i18nCatalog - -catalog = i18nCatalog("cura") upgrader = XmlMaterialUpgrader.XmlMaterialUpgrader() From 477862d779b5f590ec16d223e917582579c7972c Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 3 Oct 2018 23:07:37 +0200 Subject: [PATCH 39/97] Fix code style and unused imports --- plugins/FirmwareUpdater/__init__.py | 5 ++--- plugins/USBPrinting/__init__.py | 3 --- plugins/UltimakerMachineActions/__init__.py | 5 ++--- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/plugins/FirmwareUpdater/__init__.py b/plugins/FirmwareUpdater/__init__.py index 58c351a4ea..5a008d7d15 100644 --- a/plugins/FirmwareUpdater/__init__.py +++ b/plugins/FirmwareUpdater/__init__.py @@ -4,10 +4,9 @@ from . import FirmwareUpdaterMachineAction def getMetaData(): - return { - } + return {} def register(app): return { "machine_action": [ - FirmwareUpdaterMachineAction.FirmwareUpdaterMachineAction(), + FirmwareUpdaterMachineAction.FirmwareUpdaterMachineAction() ]} diff --git a/plugins/USBPrinting/__init__.py b/plugins/USBPrinting/__init__.py index 0cb68d3865..075ad2943b 100644 --- a/plugins/USBPrinting/__init__.py +++ b/plugins/USBPrinting/__init__.py @@ -2,9 +2,6 @@ # Cura is released under the terms of the LGPLv3 or higher. from . import USBPrinterOutputDeviceManager -from PyQt5.QtQml import qmlRegisterSingletonType -from UM.i18n import i18nCatalog -i18n_catalog = i18nCatalog("cura") def getMetaData(): diff --git a/plugins/UltimakerMachineActions/__init__.py b/plugins/UltimakerMachineActions/__init__.py index 30493536ce..e87949580a 100644 --- a/plugins/UltimakerMachineActions/__init__.py +++ b/plugins/UltimakerMachineActions/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from . import BedLevelMachineAction @@ -6,8 +6,7 @@ from . import UMOUpgradeSelection from . import UM2UpgradeSelection def getMetaData(): - return { - } + return {} def register(app): return { "machine_action": [ From 28dc32adaba8e9bae6361ba06fb0872b4a275530 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 4 Oct 2018 11:47:51 +0200 Subject: [PATCH 40/97] Fix typing in GenericOutputController --- cura/PrinterOutput/GenericOutputController.py | 65 ++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/cura/PrinterOutput/GenericOutputController.py b/cura/PrinterOutput/GenericOutputController.py index c8caa85caf..9434feea62 100644 --- a/cura/PrinterOutput/GenericOutputController.py +++ b/cura/PrinterOutput/GenericOutputController.py @@ -1,7 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING, Set, Union, Optional from cura.PrinterOutput.PrinterOutputController import PrinterOutputController from PyQt5.QtCore import QTimer @@ -9,27 +9,28 @@ from PyQt5.QtCore import QTimer if TYPE_CHECKING: from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel + from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel class GenericOutputController(PrinterOutputController): - def __init__(self, output_device): + def __init__(self, output_device: "PrinterOutputDevice") -> None: super().__init__(output_device) self._preheat_bed_timer = QTimer() self._preheat_bed_timer.setSingleShot(True) self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished) - self._preheat_printer = None + self._preheat_printer = None #type: Optional[PrinterOutputModel] self._preheat_hotends_timer = QTimer() self._preheat_hotends_timer.setSingleShot(True) self._preheat_hotends_timer.timeout.connect(self._onPreheatHotendsTimerFinished) - self._preheat_hotends = set() + self._preheat_hotends = set() #type: Set[ExtruderOutputModel] self._output_device.printersChanged.connect(self._onPrintersChanged) - self._active_printer = None + self._active_printer = None #type: Optional[PrinterOutputModel] - def _onPrintersChanged(self): + def _onPrintersChanged(self) -> None: if self._active_printer: self._active_printer.stateChanged.disconnect(self._onPrinterStateChanged) self._active_printer.targetBedTemperatureChanged.disconnect(self._onTargetBedTemperatureChanged) @@ -43,32 +44,33 @@ class GenericOutputController(PrinterOutputController): for extruder in self._active_printer.extruders: extruder.targetHotendTemperatureChanged.connect(self._onTargetHotendTemperatureChanged) - def _onPrinterStateChanged(self): - if self._active_printer.state != "idle": + def _onPrinterStateChanged(self) -> None: + if self._active_printer and self._active_printer.state != "idle": if self._preheat_bed_timer.isActive(): self._preheat_bed_timer.stop() - self._preheat_printer.updateIsPreheating(False) + if self._preheat_printer: + self._preheat_printer.updateIsPreheating(False) if self._preheat_hotends_timer.isActive(): self._preheat_hotends_timer.stop() for extruder in self._preheat_hotends: extruder.updateIsPreheating(False) - self._preheat_hotends = set() + self._preheat_hotends = set() #type: Set[ExtruderOutputModel] - def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed): + def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed) -> None: self._output_device.sendCommand("G91") self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed)) self._output_device.sendCommand("G90") - def homeHead(self, printer): + def homeHead(self, printer: "PrinterOutputModel") -> None: self._output_device.sendCommand("G28 X Y") - def homeBed(self, printer): + def homeBed(self, printer: "PrinterOutputModel") -> None: self._output_device.sendCommand("G28 Z") - def sendRawCommand(self, printer: "PrinterOutputModel", command: str): + def sendRawCommand(self, printer: "PrinterOutputModel", command: str) -> None: self._output_device.sendCommand(command.upper()) #Most printers only understand uppercase g-code commands. - def setJobState(self, job: "PrintJobOutputModel", state: str): + def setJobState(self, job: "PrintJobOutputModel", state: str) -> None: if state == "pause": self._output_device.pausePrint() job.updateState("paused") @@ -79,15 +81,15 @@ class GenericOutputController(PrinterOutputController): self._output_device.cancelPrint() pass - def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int): + def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int) -> None: self._output_device.sendCommand("M140 S%s" % temperature) - def _onTargetBedTemperatureChanged(self): - if self._preheat_bed_timer.isActive() and self._preheat_printer.targetBedTemperature == 0: + def _onTargetBedTemperatureChanged(self) -> None: + if self._preheat_bed_timer.isActive() and self._preheat_printer and self._preheat_printer.targetBedTemperature == 0: self._preheat_bed_timer.stop() self._preheat_printer.updateIsPreheating(False) - def preheatBed(self, printer: "PrinterOutputModel", temperature, duration): + def preheatBed(self, printer: "PrinterOutputModel", temperature, duration) -> None: try: temperature = round(temperature) # The API doesn't allow floating point. duration = round(duration) @@ -100,21 +102,25 @@ class GenericOutputController(PrinterOutputController): self._preheat_printer = printer printer.updateIsPreheating(True) - def cancelPreheatBed(self, printer: "PrinterOutputModel"): + def cancelPreheatBed(self, printer: "PrinterOutputModel") -> None: self.setTargetBedTemperature(printer, temperature=0) self._preheat_bed_timer.stop() printer.updateIsPreheating(False) - def _onPreheatBedTimerFinished(self): + def _onPreheatBedTimerFinished(self) -> None: + if not self._preheat_printer: + return self.setTargetBedTemperature(self._preheat_printer, 0) self._preheat_printer.updateIsPreheating(False) def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: Union[int, float]) -> None: self._output_device.sendCommand("M104 S%s T%s" % (temperature, position)) - def _onTargetHotendTemperatureChanged(self): + def _onTargetHotendTemperatureChanged(self) -> None: if not self._preheat_hotends_timer.isActive(): return + if not self._active_printer: + return for extruder in self._active_printer.extruders: if extruder in self._preheat_hotends and extruder.targetHotendTemperature == 0: @@ -123,7 +129,7 @@ class GenericOutputController(PrinterOutputController): if not self._preheat_hotends: self._preheat_hotends_timer.stop() - def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration): + def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration) -> None: position = extruder.getPosition() number_of_extruders = len(extruder.getPrinter().extruders) if position >= number_of_extruders: @@ -141,7 +147,7 @@ class GenericOutputController(PrinterOutputController): self._preheat_hotends.add(extruder) extruder.updateIsPreheating(True) - def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"): + def cancelPreheatHotend(self, extruder: "ExtruderOutputModel") -> None: self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), temperature=0) if extruder in self._preheat_hotends: extruder.updateIsPreheating(False) @@ -149,21 +155,22 @@ class GenericOutputController(PrinterOutputController): if not self._preheat_hotends and self._preheat_hotends_timer.isActive(): self._preheat_hotends_timer.stop() - def _onPreheatHotendsTimerFinished(self): + def _onPreheatHotendsTimerFinished(self) -> None: for extruder in self._preheat_hotends: self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), 0) - self._preheat_hotends = set() + self._preheat_hotends = set() #type: Set[ExtruderOutputModel] # Cancel any ongoing preheating timers, without setting back the temperature to 0 # This can be used eg at the start of a print - def stopPreheatTimers(self): + def stopPreheatTimers(self) -> None: if self._preheat_hotends_timer.isActive(): for extruder in self._preheat_hotends: extruder.updateIsPreheating(False) - self._preheat_hotends = set() + self._preheat_hotends = set() #type: Set[ExtruderOutputModel] self._preheat_hotends_timer.stop() if self._preheat_bed_timer.isActive(): - self._preheat_printer.updateIsPreheating(False) + if self._preheat_printer: + self._preheat_printer.updateIsPreheating(False) self._preheat_bed_timer.stop() From 5b2dc804cac706537f7daf5bd0d6c24bf4e3b5d8 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 4 Oct 2018 12:32:42 +0200 Subject: [PATCH 41/97] Fix a crash when adding a printer part of a cluster --- plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py index 4faa3abc64..4a172b6557 100644 --- a/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py +++ b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py @@ -39,13 +39,13 @@ class FirmwareUpdaterMachineAction(MachineAction): CuraApplication.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) def _onOutputDevicesChanged(self) -> None: - if self._active_output_device: + if self._active_output_device and self._active_output_device.activePrinter: self._active_output_device.activePrinter.getController().canUpdateFirmwareChanged.disconnect(self._onControllerCanUpdateFirmwareChanged) output_devices = CuraApplication.getInstance().getMachineManager().printerOutputDevices self._active_output_device = output_devices[0] if output_devices else None - if self._active_output_device: + if self._active_output_device and self._active_output_device.activePrinter: self._active_output_device.activePrinter.getController().canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged) self.outputDeviceCanUpdateFirmwareChanged.emit() From 04bca109ba67404081f069da488832c027a51571 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 4 Oct 2018 12:48:35 +0200 Subject: [PATCH 42/97] Fix update/upgrade consistency --- plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py index 4a172b6557..981fb819eb 100644 --- a/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py +++ b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py @@ -21,7 +21,7 @@ catalog = i18nCatalog("cura") ## Upgrade the firmware of a machine by USB with this action. class FirmwareUpdaterMachineAction(MachineAction): def __init__(self) -> None: - super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Upgrade Firmware")) + super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Update Firmware")) self._qml_url = "FirmwareUpdaterMachineAction.qml" ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) From 110d2daa81e6e28cbfe420974cbeefb4c561e921 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 4 Oct 2018 15:54:22 +0200 Subject: [PATCH 43/97] [CURA-5775] The snaphot-creation of the UFP-writer should only be called when writing to UFP-files. --- plugins/UFPWriter/UFPWriter.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/UFPWriter/UFPWriter.py b/plugins/UFPWriter/UFPWriter.py index d318a0e77d..b3088fc863 100644 --- a/plugins/UFPWriter/UFPWriter.py +++ b/plugins/UFPWriter/UFPWriter.py @@ -27,14 +27,13 @@ class UFPWriter(MeshWriter): MimeTypeDatabase.addMimeType( MimeType( - name = "application/x-cura-stl-file", + name = "application/x-ufp", comment = "Cura UFP File", suffixes = ["ufp"] ) ) self._snapshot = None - Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._createSnapshot) def _createSnapshot(self, *args): # must be called from the main thread because of OpenGL @@ -62,6 +61,8 @@ class UFPWriter(MeshWriter): gcode.write(gcode_textio.getvalue().encode("UTF-8")) archive.addRelation(virtual_path = "/3D/model.gcode", relation_type = "http://schemas.ultimaker.org/package/2018/relationships/gcode") + self._createSnapshot() + #Store the thumbnail. if self._snapshot: archive.addContentType(extension = "png", mime_type = "image/png") From 6abd43f6903c7e0e1f3509a5ffacb27d22115882 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Thu, 4 Oct 2018 17:37:28 +0200 Subject: [PATCH 44/97] Add Polish to the list of available languages. Forgotten by mistake. --- resources/qml/Preferences/GeneralPage.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index 5f60b23477..d751d3f101 100644 --- a/resources/qml/Preferences/GeneralPage.qml +++ b/resources/qml/Preferences/GeneralPage.qml @@ -170,7 +170,7 @@ UM.PreferencesPage append({ text: "日本語", code: "ja_JP" }) append({ text: "한국어", code: "ko_KR" }) append({ text: "Nederlands", code: "nl_NL" }) - //Polish is disabled for being incomplete: append({ text: "Polski", code: "pl_PL" }) + append({ text: "Polski", code: "pl_PL" }) append({ text: "Português do Brasil", code: "pt_BR" }) append({ text: "Português", code: "pt_PT" }) append({ text: "Русский", code: "ru_RU" }) From 866110d70c4379ab86131b2a031da69bed4c4fe7 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Thu, 4 Oct 2018 18:03:01 +0200 Subject: [PATCH 45/97] Take into account that can be empty layers below the model when the setting Remove empty layers is disabled. The empty layers are skipped if they are at the bottom. Also keeps the models printed with raft showing correclty. Contributes to CURA-5768. --- .../ProcessSlicedLayersJob.py | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py index 3953625c7e..594bf3a43e 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py @@ -2,6 +2,7 @@ #Cura is released under the terms of the LGPLv3 or higher. import gc +import sys from UM.Job import Job from UM.Application import Application @@ -95,23 +96,35 @@ class ProcessSlicedLayersJob(Job): layer_count = len(self._layers) # Find the minimum layer number + # When disabling the remove empty first layers setting, the minimum layer number will be a positive + # value. In that case the first empty layers will be discarded and start processing layers from the + # first layer with data. # When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we - # instead simply offset all other layers so the lowest layer is always 0. It could happens that - # the first raft layer has value -8 but there are just 4 raft (negative) layers. - min_layer_number = 0 + # simply offset all other layers so the lowest layer is always 0. It could happens that the first + # raft layer has value -8 but there are just 4 raft (negative) layers. + min_layer_number = sys.maxsize negative_layers = 0 for layer in self._layers: - if layer.id < min_layer_number: - min_layer_number = layer.id - if layer.id < 0: - negative_layers += 1 + if layer.repeatedMessageCount("path_segment") > 0: + if layer.id < min_layer_number: + min_layer_number = layer.id + if layer.id < 0: + negative_layers += 1 current_layer = 0 for layer in self._layers: - # Negative layers are offset by the minimum layer number, but the positive layers are just - # offset by the number of negative layers so there is no layer gap between raft and model - abs_layer_number = layer.id + abs(min_layer_number) if layer.id < 0 else layer.id + negative_layers + # If the layer is below the minimum, it means that there is no data, so that we don't create a layer + # data. However, if there are empty layers in between, we compute them. + if layer.id < min_layer_number: + continue + + # Layers are offset by the minimum layer number. In case the raft (negative layers) is being used, + # then the absolute layer number is adjusted by removing the empty layers that can be in between raft + # and the model + abs_layer_number = layer.id - min_layer_number + if layer.id >= 0 and negative_layers != 0: + abs_layer_number += (min_layer_number + negative_layers) layer_data.addLayer(abs_layer_number) this_layer = layer_data.getLayer(abs_layer_number) From 94164c58654bf121db9fd8212b70771827293632 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 5 Oct 2018 15:29:52 +0200 Subject: [PATCH 46/97] Add Charon, Shapely, Trimesh and NetworkX to credits --- resources/qml/AboutDialog.qml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/resources/qml/AboutDialog.qml b/resources/qml/AboutDialog.qml index 5d3b1d1544..9a7e53260b 100644 --- a/resources/qml/AboutDialog.qml +++ b/resources/qml/AboutDialog.qml @@ -19,6 +19,18 @@ UM.Dialog width: minimumWidth height: minimumHeight + Rectangle + { + width: parent.width + 2 * margin // margin from Dialog.qml + height: version.y + version.height + margin + + anchors.top: parent.top + anchors.topMargin: - margin + anchors.horizontalCenter: parent.horizontalCenter + + color: UM.Theme.getColor("viewport_background") + } + Image { id: logo @@ -42,6 +54,7 @@ UM.Dialog text: catalog.i18nc("@label","version: %1").arg(UM.Application.version) font: UM.Theme.getFont("large") + color: UM.Theme.getColor("text") anchors.right : logo.right anchors.top: logo.bottom anchors.topMargin: (UM.Theme.getSize("default_margin").height / 2) | 0 @@ -75,6 +88,7 @@ UM.Dialog ScrollView { + id: credits anchors.top: creditsNotes.bottom anchors.topMargin: UM.Theme.getSize("default_margin").height @@ -128,7 +142,11 @@ UM.Dialog projectsModel.append({ name:"SciPy", description: catalog.i18nc("@label", "Support library for scientific computing"), license: "BSD-new", url: "https://www.scipy.org/" }); projectsModel.append({ name:"NumPy", description: catalog.i18nc("@label", "Support library for faster math"), license: "BSD", url: "http://www.numpy.org/" }); projectsModel.append({ name:"NumPy-STL", description: catalog.i18nc("@label", "Support library for handling STL files"), license: "BSD", url: "https://github.com/WoLpH/numpy-stl" }); + projectsModel.append({ name:"Shapely", description: catalog.i18nc("@label", "Support library for handling planar objects"), license: "BSD", url: "https://github.com/Toblerity/Shapely" }); + projectsModel.append({ name:"Trimesh", description: catalog.i18nc("@label", "Support library for handling triangular meshes"), license: "MIT", url: "https://trimsh.org" }); + projectsModel.append({ name:"NetworkX", description: catalog.i18nc("@label", "Support library for analysis of complex networks"), license: "3-clause BSD", url: "https://networkx.github.io/" }); projectsModel.append({ name:"libSavitar", description: catalog.i18nc("@label", "Support library for handling 3MF files"), license: "LGPLv3", url: "https://github.com/ultimaker/libsavitar" }); + projectsModel.append({ name:"libCharon", description: catalog.i18nc("@label", "Support library for file metadata and streaming"), license: "LGPLv3", url: "https://github.com/ultimaker/libcharon" }); projectsModel.append({ name:"PySerial", description: catalog.i18nc("@label", "Serial communication library"), license: "Python", url: "http://pyserial.sourceforge.net/" }); projectsModel.append({ name:"python-zeroconf", description: catalog.i18nc("@label", "ZeroConf discovery library"), license: "LGPL", url: "https://github.com/jstasiak/python-zeroconf" }); projectsModel.append({ name:"Clipper", description: catalog.i18nc("@label", "Polygon clipping library"), license: "Boost", url: "http://www.angusj.com/delphi/clipper.php" }); From 39652185011d9b3f171ff88bf86336f9167be3b6 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 8 Oct 2018 14:40:38 +0200 Subject: [PATCH 47/97] Fix value templating for gcode CURA-5793 Fix GcodeStartEndFormatter to take the correct default_extruder_nr instead of always using -1. --- plugins/CuraEngineBackend/StartSliceJob.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 28e442033b..bb9387a65d 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -41,11 +41,15 @@ class StartJobResult(IntEnum): ## Formatter class that handles token expansion in start/end gcode class GcodeStartEndFormatter(Formatter): - def get_value(self, key: str, args: str, kwargs: dict, default_extruder_nr: str = "-1") -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class] + def __init__(self, default_extruder_nr: int = -1) -> None: + super().__init__() + self._default_extruder_nr = default_extruder_nr + + def get_value(self, key: str, args: str, kwargs: dict) -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class] # The kwargs dictionary contains a dictionary for each stack (with a string of the extruder_nr as their key), # and a default_extruder_nr to use when no extruder_nr is specified - extruder_nr = int(default_extruder_nr) + extruder_nr = self._default_extruder_nr key_fragments = [fragment.strip() for fragment in key.split(",")] if len(key_fragments) == 2: @@ -339,7 +343,7 @@ class StartSliceJob(Job): try: # any setting can be used as a token - fmt = GcodeStartEndFormatter() + fmt = GcodeStartEndFormatter(default_extruder_nr = default_extruder_nr) settings = self._all_extruders_settings.copy() settings["default_extruder_nr"] = default_extruder_nr return str(fmt.format(value, **settings)) From 314b966cc90198b2526e405cc2bbea19201ee234 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 8 Oct 2018 15:03:21 +0200 Subject: [PATCH 48/97] Improvements to translated strings These strings were recently found to have been confusing to the translators. Improvements are: - Pulling the (untranslated) error message out of the message sentence. We really want the error message to be at the end so we'll force the translators to translate it as a prefix. - Remove extra spaces at the end. - Remove Python logic from within the i18nc call, since gettext doesn't understand that. Contributes to issue CURA-5741. --- cura/Settings/CuraContainerRegistry.py | 17 ++++++++--------- plugins/ImageReader/ConfigUI.qml | 10 +++++----- .../qml/ToolboxConfirmUninstallResetDialog.qml | 2 +- .../src/LegacyUM3OutputDevice.py | 3 +-- resources/qml/WorkspaceSummaryDialog.qml | 2 +- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index e1f50a157d..962f4162b5 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -187,11 +187,11 @@ class CuraContainerRegistry(ContainerRegistry): try: profile_or_list = profile_reader.read(file_name) # Try to open the file with the profile reader. except NoProfileException: - return { "status": "ok", "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "No custom profile to import in file {0}", file_name)} + return { "status": "ok", "message": catalog.i18nc("@info:status Don't translate the XML tags !", "No custom profile to import in file {0}", file_name)} except Exception as e: # Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None. Logger.log("e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name, profile_reader.getPluginId(), str(e)) - return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "Failed to import profile from {0}: {1}", file_name, "\n" + str(e))} + return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags !", "Failed to import profile from {0}:", file_name) + "\n" + str(e) + ""} if profile_or_list: # Ensure it is always a list of profiles @@ -215,7 +215,7 @@ class CuraContainerRegistry(ContainerRegistry): if not global_profile: Logger.log("e", "Incorrect profile [%s]. Could not find global profile", file_name) return { "status": "error", - "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "This profile {0} contains incorrect data, could not import it.", file_name)} + "message": catalog.i18nc("@info:status Don't translate the XML tags !", "This profile {0} contains incorrect data, could not import it.", file_name)} profile_definition = global_profile.getMetaDataEntry("definition") # Make sure we have a profile_definition in the file: @@ -225,7 +225,7 @@ class CuraContainerRegistry(ContainerRegistry): if not machine_definition: Logger.log("e", "Incorrect profile [%s]. Unknown machine type [%s]", file_name, profile_definition) return {"status": "error", - "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "This profile {0} contains incorrect data, could not import it.", file_name) + "message": catalog.i18nc("@info:status Don't translate the XML tags !", "This profile {0} contains incorrect data, could not import it.", file_name) } machine_definition = machine_definition[0] @@ -238,7 +238,7 @@ class CuraContainerRegistry(ContainerRegistry): if profile_definition != expected_machine_definition: Logger.log("e", "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile", file_name, profile_definition, expected_machine_definition) return { "status": "error", - "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)} + "message": catalog.i18nc("@info:status Don't translate the XML tags !", "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)} # Fix the global quality profile's definition field in case it's not correct global_profile.setMetaDataEntry("definition", expected_machine_definition) @@ -269,8 +269,7 @@ class CuraContainerRegistry(ContainerRegistry): if idx == 0: # move all per-extruder settings to the first extruder's quality_changes for qc_setting_key in global_profile.getAllKeys(): - settable_per_extruder = global_stack.getProperty(qc_setting_key, - "settable_per_extruder") + settable_per_extruder = global_stack.getProperty(qc_setting_key, "settable_per_extruder") if settable_per_extruder: setting_value = global_profile.getProperty(qc_setting_key, "value") @@ -310,8 +309,8 @@ class CuraContainerRegistry(ContainerRegistry): if result is not None: return {"status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags or !", - "Failed to import profile from {0}: {1}", - file_name, result)} + "Failed to import profile from {0}:", + file_name) + " " + result + ""} return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())} diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index d829f46459..12c6aa8dde 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -35,7 +35,7 @@ UM.Dialog width: parent.width Label { - text: catalog.i18nc("@action:label","Height (mm)") + text: catalog.i18nc("@action:label", "Height (mm)") width: 150 * screenScaleFactor anchors.verticalCenter: parent.verticalCenter } @@ -58,7 +58,7 @@ UM.Dialog width: parent.width Label { - text: catalog.i18nc("@action:label","Base (mm)") + text: catalog.i18nc("@action:label", "Base (mm)") width: 150 * screenScaleFactor anchors.verticalCenter: parent.verticalCenter } @@ -81,7 +81,7 @@ UM.Dialog width: parent.width Label { - text: catalog.i18nc("@action:label","Width (mm)") + text: catalog.i18nc("@action:label", "Width (mm)") width: 150 * screenScaleFactor anchors.verticalCenter: parent.verticalCenter } @@ -105,7 +105,7 @@ UM.Dialog width: parent.width Label { - text: catalog.i18nc("@action:label","Depth (mm)") + text: catalog.i18nc("@action:label", "Depth (mm)") width: 150 * screenScaleFactor anchors.verticalCenter: parent.verticalCenter } @@ -151,7 +151,7 @@ UM.Dialog width: parent.width Label { - text: catalog.i18nc("@action:label","Smoothing") + text: catalog.i18nc("@action:label", "Smoothing") width: 150 * screenScaleFactor anchors.verticalCenter: parent.verticalCenter } diff --git a/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml b/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml index 4aa8b883b7..2c5d08aa72 100644 --- a/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml +++ b/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml @@ -17,7 +17,7 @@ UM.Dialog // This dialog asks the user whether he/she wants to open a project file as a project or import models. id: base - title: catalog.i18nc("@title:window", "Confirm uninstall ") + toolbox.pluginToUninstall + title: catalog.i18nc("@title:window", "Confirm uninstall") + toolbox.pluginToUninstall width: 450 * screenScaleFactor height: 50 * screenScaleFactor + dialogText.height + buttonBar.height diff --git a/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py index fe94500aa1..e786840803 100644 --- a/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py @@ -100,8 +100,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): title=i18n_catalog.i18nc("@info:title", "Authentication status")) - self._authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", ""), - title=i18n_catalog.i18nc("@info:title", "Authentication Status")) + self._authentication_failed_message = Message("", title=i18n_catalog.i18nc("@info:title", "Authentication Status")) self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry"), None, i18n_catalog.i18nc("@info:tooltip", "Re-send the access request")) self._authentication_failed_message.actionTriggered.connect(self._messageCallback) diff --git a/resources/qml/WorkspaceSummaryDialog.qml b/resources/qml/WorkspaceSummaryDialog.qml index 24e94beb88..1b3a7aac55 100644 --- a/resources/qml/WorkspaceSummaryDialog.qml +++ b/resources/qml/WorkspaceSummaryDialog.qml @@ -117,7 +117,7 @@ UM.Dialog height: childrenRect.height Label { - text: catalog.i18nc("@action:label", Cura.MachineManager.activeMachineNetworkGroupName != "" ? "Printer Group" : "Name") + text: Cura.MachineManager.activeMachineNetworkGroupName != "" ? catalog.i18nc("@action:label", "Printer Group") : catalog.i18nc("@action:label", "Name") width: Math.floor(scroll.width / 3) | 0 } Label From 5ba60de20939754241edcd04903038ade60e7ca8 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 8 Oct 2018 15:59:46 +0200 Subject: [PATCH 49/97] Send extruder settings ordered by extruder position CURA-5799 --- plugins/CuraEngineBackend/StartSliceJob.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 28e442033b..780a495de8 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -247,7 +247,10 @@ class StartSliceJob(Job): self._buildGlobalInheritsStackMessage(stack) # Build messages for extruder stacks - for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()): + # Send the extruder settings in the order of extruder positions. Somehow, if you send e.g. extruder 3 first, + # then CuraEngine can slice with the wrong settings. This I think should be fixed in CuraEngine as well. + extruder_stack_list = sorted(list(global_stack.extruders.items()), key = lambda item: int(item[0])) + for _, extruder_stack in extruder_stack_list: self._buildExtruderMessage(extruder_stack) for group in filtered_object_groups: From d1a51b26f765badf7c02ec84ee67d0ff63e8df27 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 8 Oct 2018 17:22:04 +0200 Subject: [PATCH 50/97] Simplified QML expression --- resources/qml/Preferences/Materials/MaterialsSlot.qml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/resources/qml/Preferences/Materials/MaterialsSlot.qml b/resources/qml/Preferences/Materials/MaterialsSlot.qml index c75c34b81a..a5af17f47a 100644 --- a/resources/qml/Preferences/Materials/MaterialsSlot.qml +++ b/resources/qml/Preferences/Materials/MaterialsSlot.qml @@ -41,15 +41,7 @@ Rectangle anchors.left: swatch.right anchors.verticalCenter: materialSlot.verticalCenter anchors.leftMargin: UM.Theme.getSize("narrow_margin").width - font.italic: - { - var selected_material = Cura.MachineManager.currentRootMaterialId[Cura.ExtruderManager.activeExtruderIndex] - if(selected_material == material.root_material_id) - { - return true - } - return false - } + font.italic: Cura.MachineManager.currentRootMaterialId[Cura.ExtruderManager.activeExtruderIndex] == material.root_material_id } MouseArea { From 18821b6527c918f622fd57caa0d5cfb711e15644 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 9 Oct 2018 11:43:40 +0200 Subject: [PATCH 51/97] Add missing extruder def for Creality Ender 3 CURA-5806 --- resources/definitions/creality_ender3.def.json | 6 +++++- .../creality_ender3_extruder_0.def.json | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 resources/extruders/creality_ender3_extruder_0.def.json diff --git a/resources/definitions/creality_ender3.def.json b/resources/definitions/creality_ender3.def.json index 4ae4d4ad93..9745f28a93 100755 --- a/resources/definitions/creality_ender3.def.json +++ b/resources/definitions/creality_ender3.def.json @@ -8,7 +8,11 @@ "manufacturer": "Creality3D", "file_formats": "text/x-gcode", "platform": "creality_ender3_platform.stl", - "preferred_quality_type": "draft" + "preferred_quality_type": "draft", + "machine_extruder_trains": + { + "0": "creality_ender3_extruder_0" + } }, "overrides": { "machine_name": { diff --git a/resources/extruders/creality_ender3_extruder_0.def.json b/resources/extruders/creality_ender3_extruder_0.def.json new file mode 100644 index 0000000000..431366c777 --- /dev/null +++ b/resources/extruders/creality_ender3_extruder_0.def.json @@ -0,0 +1,16 @@ +{ + "id": "creality_ender3_extruder_0", + "version": 2, + "name": "Extruder 1", + "inherits": "fdmextruder", + "metadata": { + "machine": "creality_ender3", + "position": "0" + }, + + "overrides": { + "extruder_nr": { "default_value": 0 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} From a36deea651b6139f9290d585bb8945a29058c408 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 9 Oct 2018 16:26:45 +0200 Subject: [PATCH 52/97] Move updateFirmware to PrinterOutputDevice... along with codestyle and typing fixes --- cura/PrinterOutput/FirmwareUpdater.py | 22 +++++++++---------- cura/PrinterOutput/GenericOutputController.py | 8 +++---- cura/PrinterOutput/PrintJobOutputModel.py | 2 +- cura/PrinterOutputDevice.py | 14 +++++++++--- .../FirmwareUpdaterMachineAction.py | 7 +++--- .../FirmwareUpdaterMachineAction.qml | 4 ++-- plugins/USBPrinting/USBPrinterOutputDevice.py | 11 +--------- 7 files changed, 34 insertions(+), 34 deletions(-) diff --git a/cura/PrinterOutput/FirmwareUpdater.py b/cura/PrinterOutput/FirmwareUpdater.py index 92e92437ad..c6d9513ee0 100644 --- a/cura/PrinterOutput/FirmwareUpdater.py +++ b/cura/PrinterOutput/FirmwareUpdater.py @@ -22,16 +22,16 @@ class FirmwareUpdater(QObject): self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True) - self._firmware_location = "" + self._firmware_file = "" self._firmware_progress = 0 self._firmware_update_state = FirmwareUpdateState.idle - def updateFirmware(self, file: Union[str, QUrl]) -> None: + def updateFirmware(self, firmware_file: Union[str, QUrl]) -> None: # the file path could be url-encoded. - if file.startswith("file://"): - self._firmware_location = QUrl(file).toLocalFile() + if firmware_file.startswith("file://"): + self._firmware_file = QUrl(firmware_file).toLocalFile() else: - self._firmware_location = file + self._firmware_file = firmware_file self._setFirmwareUpdateState(FirmwareUpdateState.updating) @@ -44,26 +44,26 @@ class FirmwareUpdater(QObject): def _cleanupAfterUpdate(self) -> None: # Clean up for next attempt. self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True) - self._firmware_location = "" + self._firmware_file = "" self._onFirmwareProgress(100) self._setFirmwareUpdateState(FirmwareUpdateState.completed) - @pyqtProperty(float, notify = firmwareProgressChanged) - def firmwareProgress(self) -> float: + @pyqtProperty(int, notify = firmwareProgressChanged) + def firmwareProgress(self) -> int: return self._firmware_progress @pyqtProperty(int, notify=firmwareUpdateStateChanged) def firmwareUpdateState(self) -> "FirmwareUpdateState": return self._firmware_update_state - def _setFirmwareUpdateState(self, state) -> None: + def _setFirmwareUpdateState(self, state: "FirmwareUpdateState") -> None: if self._firmware_update_state != state: self._firmware_update_state = state self.firmwareUpdateStateChanged.emit() # Callback function for firmware update progress. - def _onFirmwareProgress(self, progress, max_progress = 100) -> None: - self._firmware_progress = (progress / max_progress) * 100 # Convert to scale of 0-100 + def _onFirmwareProgress(self, progress: int, max_progress: int = 100) -> None: + self._firmware_progress = int(progress * 100 / max_progress) # Convert to scale of 0-100 self.firmwareProgressChanged.emit() diff --git a/cura/PrinterOutput/GenericOutputController.py b/cura/PrinterOutput/GenericOutputController.py index 9434feea62..c538ae79f8 100644 --- a/cura/PrinterOutput/GenericOutputController.py +++ b/cura/PrinterOutput/GenericOutputController.py @@ -20,15 +20,15 @@ class GenericOutputController(PrinterOutputController): self._preheat_bed_timer = QTimer() self._preheat_bed_timer.setSingleShot(True) self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished) - self._preheat_printer = None #type: Optional[PrinterOutputModel] + self._preheat_printer = None # type: Optional[PrinterOutputModel] self._preheat_hotends_timer = QTimer() self._preheat_hotends_timer.setSingleShot(True) self._preheat_hotends_timer.timeout.connect(self._onPreheatHotendsTimerFinished) - self._preheat_hotends = set() #type: Set[ExtruderOutputModel] + self._preheat_hotends = set() # type: Set[ExtruderOutputModel] self._output_device.printersChanged.connect(self._onPrintersChanged) - self._active_printer = None #type: Optional[PrinterOutputModel] + self._active_printer = None # type: Optional[PrinterOutputModel] def _onPrintersChanged(self) -> None: if self._active_printer: @@ -54,7 +54,7 @@ class GenericOutputController(PrinterOutputController): self._preheat_hotends_timer.stop() for extruder in self._preheat_hotends: extruder.updateIsPreheating(False) - self._preheat_hotends = set() #type: Set[ExtruderOutputModel] + self._preheat_hotends = set() # type: Set[ExtruderOutputModel] def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed) -> None: self._output_device.sendCommand("G91") diff --git a/cura/PrinterOutput/PrintJobOutputModel.py b/cura/PrinterOutput/PrintJobOutputModel.py index 70878a7573..1415db16bd 100644 --- a/cura/PrinterOutput/PrintJobOutputModel.py +++ b/cura/PrinterOutput/PrintJobOutputModel.py @@ -91,7 +91,7 @@ class PrintJobOutputModel(QObject): def assignedPrinter(self): return self._assigned_printer - def updateAssignedPrinter(self, assigned_printer: Optional["PrinterOutputModel"]): + def updateAssignedPrinter(self, assigned_printer: Optional["PrinterOutputModel"]) -> None: if self._assigned_printer != assigned_printer: old_printer = self._assigned_printer self._assigned_printer = assigned_printer diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py index c63f9c35b5..236b658eba 100644 --- a/cura/PrinterOutputDevice.py +++ b/cura/PrinterOutputDevice.py @@ -4,7 +4,7 @@ from UM.Decorators import deprecated from UM.i18n import i18nCatalog from UM.OutputDevice.OutputDevice import OutputDevice -from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal +from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl from PyQt5.QtWidgets import QMessageBox from UM.Logger import Logger @@ -12,9 +12,10 @@ from UM.FileHandler.FileHandler import FileHandler #For typing. from UM.Scene.SceneNode import SceneNode #For typing. from UM.Signal import signalemitter from UM.Qt.QtApplication import QtApplication +from UM.FlameProfiler import pyqtSlot from enum import IntEnum # For the connection state tracking. -from typing import Callable, List, Optional +from typing import Callable, List, Optional, Union MYPY = False if MYPY: @@ -230,4 +231,11 @@ class PrinterOutputDevice(QObject, OutputDevice): return self._firmware_name def getFirmwareUpdater(self) -> Optional["FirmwareUpdater"]: - return self._firmware_updater \ No newline at end of file + return self._firmware_updater + + @pyqtSlot(str) + def updateFirmware(self, firmware_file: Union[str, QUrl]) -> None: + if not self._firmware_updater: + return + + self._firmware_updater.updateFirmware(firmware_file) \ No newline at end of file diff --git a/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py index 981fb819eb..0a3e3a0ff0 100644 --- a/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py +++ b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py @@ -15,6 +15,7 @@ MYPY = False if MYPY: from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice + from UM.Settings.ContainerInterface import ContainerInterface catalog = i18nCatalog("cura") @@ -25,15 +26,15 @@ class FirmwareUpdaterMachineAction(MachineAction): self._qml_url = "FirmwareUpdaterMachineAction.qml" ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) - self._active_output_device = None #type: Optional[PrinterOutputDevice] - self._active_firmware_updater = None #type: Optional[FirmwareUpdater] + self._active_output_device = None # type: Optional[PrinterOutputDevice] + self._active_firmware_updater = None # type: Optional[FirmwareUpdater] CuraApplication.getInstance().engineCreatedSignal.connect(self._onEngineCreated) def _onEngineCreated(self) -> None: CuraApplication.getInstance().getMachineManager().outputDevicesChanged.connect(self._onOutputDevicesChanged) - def _onContainerAdded(self, container) -> None: + def _onContainerAdded(self, container: "ContainerInterface") -> None: # Add this action as a supported action to all machine definitions if they support USB connection if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"): CuraApplication.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) diff --git a/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml index ab5bb89347..9a56dbb20a 100644 --- a/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml +++ b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml @@ -16,7 +16,7 @@ Cura.MachineAction anchors.fill: parent; property bool printerConnected: Cura.MachineManager.printerConnected property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null - property var canUpdateFirmware: activeOutputDevice ? activeOutputDevice.activePrinter.canUpdateFirmware : false + property bool canUpdateFirmware: activeOutputDevice ? activeOutputDevice.activePrinter.canUpdateFirmware : false Column { @@ -51,7 +51,7 @@ Cura.MachineAction anchors.horizontalCenter: parent.horizontalCenter width: childrenRect.width spacing: UM.Theme.getSize("default_margin").width - property var firmwareName: Cura.MachineManager.activeMachine.getDefaultFirmwareName() + property string firmwareName: Cura.MachineManager.activeMachine.getDefaultFirmwareName() Button { id: autoUpgradeButton diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 1fd2fdeb5c..b5ada76e6c 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -15,8 +15,6 @@ from cura.PrinterOutput.GenericOutputController import GenericOutputController from .AutoDetectBaudJob import AutoDetectBaudJob from .AvrFirmwareUpdater import AvrFirmwareUpdater -from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty, QUrl - from serial import Serial, SerialException, SerialTimeoutException from threading import Thread, Event from time import time, sleep @@ -98,13 +96,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice): application = CuraApplication.getInstance() application.triggerNextExitCheck() - @pyqtSlot(str) - def updateFirmware(self, file: Union[str, QUrl]) -> None: - if not self._firmware_updater: - return - - self._firmware_updater.updateFirmware(file) - ## Reset USB device settings # def resetDeviceSettings(self) -> None: @@ -169,7 +160,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._baud_rate = baud_rate def connect(self): - self._firmware_name = None # after each connection ensure that the firmware name is removed + self._firmware_name = None # after each connection ensure that the firmware name is removed if self._baud_rate is None: if self._use_auto_detect: From ab7fe3138d8bdfa2e1ef1d5a91be93709896099f Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 9 Oct 2018 17:06:20 +0200 Subject: [PATCH 53/97] Remove unused imports --- cura/PrinterOutputDevice.py | 6 +++--- plugins/USBPrinting/AvrFirmwareUpdater.py | 8 ++++++-- plugins/USBPrinting/USBPrinterOutputDevice.py | 4 +--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py index 236b658eba..969aa3c460 100644 --- a/cura/PrinterOutputDevice.py +++ b/cura/PrinterOutputDevice.py @@ -8,8 +8,6 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl from PyQt5.QtWidgets import QMessageBox from UM.Logger import Logger -from UM.FileHandler.FileHandler import FileHandler #For typing. -from UM.Scene.SceneNode import SceneNode #For typing. from UM.Signal import signalemitter from UM.Qt.QtApplication import QtApplication from UM.FlameProfiler import pyqtSlot @@ -22,6 +20,8 @@ if MYPY: from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.ConfigurationModel import ConfigurationModel from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater + from UM.FileHandler.FileHandler import FileHandler + from UM.Scene.SceneNode import SceneNode i18n_catalog = i18nCatalog("cura") @@ -131,7 +131,7 @@ class PrinterOutputDevice(QObject, OutputDevice): return None - def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None: + def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional["FileHandler"] = None, **kwargs: str) -> None: raise NotImplementedError("requestWrite needs to be implemented") @pyqtProperty(QObject, notify = printersChanged) diff --git a/plugins/USBPrinting/AvrFirmwareUpdater.py b/plugins/USBPrinting/AvrFirmwareUpdater.py index 505e1ddb7e..b8650e9208 100644 --- a/plugins/USBPrinting/AvrFirmwareUpdater.py +++ b/plugins/USBPrinting/AvrFirmwareUpdater.py @@ -4,7 +4,6 @@ from UM.Logger import Logger from cura.CuraApplication import CuraApplication -from cura.PrinterOutputDevice import PrinterOutputDevice from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater, FirmwareUpdateState from .avr_isp import stk500v2, intelHex @@ -12,8 +11,13 @@ from serial import SerialException from time import sleep +MYPY = False +if MYPY: + from cura.PrinterOutputDevice import PrinterOutputDevice + + class AvrFirmwareUpdater(FirmwareUpdater): - def __init__(self, output_device: PrinterOutputDevice) -> None: + def __init__(self, output_device: "PrinterOutputDevice") -> None: super().__init__(output_device) def _updateFirmware(self) -> None: diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index b5ada76e6c..4c3e7ee131 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -4,7 +4,6 @@ from UM.Logger import Logger from UM.i18n import i18nCatalog from UM.Qt.Duration import DurationFormat -from UM.PluginRegistry import PluginRegistry from cura.CuraApplication import CuraApplication from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState @@ -17,13 +16,12 @@ from .AvrFirmwareUpdater import AvrFirmwareUpdater from serial import Serial, SerialException, SerialTimeoutException from threading import Thread, Event -from time import time, sleep +from time import time from queue import Queue from typing import Union, Optional, List, cast import re import functools # Used for reduce -import os catalog = i18nCatalog("cura") From 382adf57dfad00fe69ded8a2f99af5d7b99d5450 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 10 Oct 2018 07:55:49 +0200 Subject: [PATCH 54/97] Use 0.1 layer height as normal for Ender 3 --- resources/definitions/creality_ender3.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/creality_ender3.def.json b/resources/definitions/creality_ender3.def.json index 9745f28a93..d3765ca9b3 100755 --- a/resources/definitions/creality_ender3.def.json +++ b/resources/definitions/creality_ender3.def.json @@ -60,7 +60,7 @@ "default_value": 20 }, "layer_height": { - "default_value": 0.15 + "default_value": 0.10 }, "layer_height_0": { "default_value": 0.2 From d3101b2fd95735a910750f4fbcfae5e702966a04 Mon Sep 17 00:00:00 2001 From: THeijmans Date: Wed, 10 Oct 2018 09:26:16 +0200 Subject: [PATCH 55/97] Fixed prim blob visibility Changed the wrong prime_blob setting, now it should be visible but disabled. --- resources/definitions/ultimaker_s5.def.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/definitions/ultimaker_s5.def.json b/resources/definitions/ultimaker_s5.def.json index 2024acdf73..57ba6e864e 100644 --- a/resources/definitions/ultimaker_s5.def.json +++ b/resources/definitions/ultimaker_s5.def.json @@ -63,7 +63,8 @@ "machine_end_gcode": { "default_value": "" }, "prime_tower_position_x": { "default_value": 345 }, "prime_tower_position_y": { "default_value": 222.5 }, - "prime_blob_enable": { "enabled": false }, + "prime_blob_enable": { "enabled": true }, + "prime_blob_enable": { "default_value": false }, "speed_travel": { From 6d852228e31fff075cc7381551e5c3287718c733 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 10 Oct 2018 09:29:42 +0200 Subject: [PATCH 56/97] Update ultimaker_s5.def.json --- resources/definitions/ultimaker_s5.def.json | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/definitions/ultimaker_s5.def.json b/resources/definitions/ultimaker_s5.def.json index 57ba6e864e..94e28eb8a5 100644 --- a/resources/definitions/ultimaker_s5.def.json +++ b/resources/definitions/ultimaker_s5.def.json @@ -63,7 +63,6 @@ "machine_end_gcode": { "default_value": "" }, "prime_tower_position_x": { "default_value": 345 }, "prime_tower_position_y": { "default_value": 222.5 }, - "prime_blob_enable": { "enabled": true }, "prime_blob_enable": { "default_value": false }, "speed_travel": From 01d95f51c8168e78a3900ca5127dfb99cfa134b9 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 10 Oct 2018 09:34:27 +0200 Subject: [PATCH 57/97] Fix prime blob enabled for S5 Still need that enabled = true... --- resources/definitions/ultimaker_s5.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/ultimaker_s5.def.json b/resources/definitions/ultimaker_s5.def.json index 94e28eb8a5..2e634787af 100644 --- a/resources/definitions/ultimaker_s5.def.json +++ b/resources/definitions/ultimaker_s5.def.json @@ -63,7 +63,7 @@ "machine_end_gcode": { "default_value": "" }, "prime_tower_position_x": { "default_value": 345 }, "prime_tower_position_y": { "default_value": 222.5 }, - "prime_blob_enable": { "default_value": false }, + "prime_blob_enable": { "enabled": true, "default_value": false }, "speed_travel": { From cc34e14215c234d0b448e0665a76d7d8a6360360 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 10 Oct 2018 11:52:14 +0200 Subject: [PATCH 58/97] Use ElideRight for long script names CURA-5683 --- plugins/PostProcessingPlugin/PostProcessingPlugin.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.qml b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml index e91fc73cf4..22555562c0 100644 --- a/plugins/PostProcessingPlugin/PostProcessingPlugin.qml +++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml @@ -115,6 +115,7 @@ UM.Dialog { wrapMode: Text.Wrap text: control.text + elide: Text.ElideRight color: activeScriptButton.checked ? palette.highlightedText : palette.text } } From e3861b0d90a6d90d94fb5e82a74a8c0d5144db14 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 10 Oct 2018 12:48:22 +0200 Subject: [PATCH 59/97] Add few more elide properties to ensure text doesnt' overlap --- plugins/PostProcessingPlugin/PostProcessingPlugin.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.qml b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml index 22555562c0..d492e06462 100644 --- a/plugins/PostProcessingPlugin/PostProcessingPlugin.qml +++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml @@ -62,6 +62,7 @@ UM.Dialog anchors.right: parent.right anchors.rightMargin: base.textMargin font: UM.Theme.getFont("large") + elide: Text.ElideRight } ListView { @@ -276,6 +277,7 @@ UM.Dialog anchors.leftMargin: base.textMargin anchors.right: parent.right anchors.rightMargin: base.textMargin + elide: Text.ElideRight height: 20 * screenScaleFactor font: UM.Theme.getFont("large") color: UM.Theme.getColor("text") From 4c6744b6fc75a3a9403bdd8c98664c807b8c597b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 10 Oct 2018 14:28:50 +0200 Subject: [PATCH 60/97] Code style: Space around binary operators I just looked for lines with interpolation = None because I was looking for another possible bug, but fixing this in the meanwhile too. --- cura/Settings/CuraContainerRegistry.py | 2 +- plugins/3MFReader/ThreeMFWorkspaceReader.py | 4 ++-- plugins/CuraProfileReader/CuraProfileReader.py | 2 +- plugins/LegacyProfileReader/LegacyProfileReader.py | 2 +- .../VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py | 2 +- .../VersionUpgrade25to26/VersionUpgrade25to26.py | 2 +- .../VersionUpgrade30to31/VersionUpgrade30to31.py | 6 +++--- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 962f4162b5..11640adc0f 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -685,7 +685,7 @@ class CuraContainerRegistry(ContainerRegistry): if not os.path.isfile(file_path): continue - parser = configparser.ConfigParser(interpolation=None) + parser = configparser.ConfigParser(interpolation = None) try: parser.read([file_path]) except: diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 36a725d148..429d4ab7d4 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -1012,7 +1012,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): ## Get the list of ID's of all containers in a container stack by partially parsing it's serialized data. def _getContainerIdListFromSerialized(self, serialized): - parser = ConfigParser(interpolation=None, empty_lines_in_values=False) + parser = ConfigParser(interpolation = None, empty_lines_in_values = False) parser.read_string(serialized) container_ids = [] @@ -1033,7 +1033,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): return container_ids def _getMachineNameFromSerializedStack(self, serialized): - parser = ConfigParser(interpolation=None, empty_lines_in_values=False) + parser = ConfigParser(interpolation = None, empty_lines_in_values = False) parser.read_string(serialized) return parser["general"].get("name", "") diff --git a/plugins/CuraProfileReader/CuraProfileReader.py b/plugins/CuraProfileReader/CuraProfileReader.py index 5957b2cecf..11e58dac6d 100644 --- a/plugins/CuraProfileReader/CuraProfileReader.py +++ b/plugins/CuraProfileReader/CuraProfileReader.py @@ -50,7 +50,7 @@ class CuraProfileReader(ProfileReader): # \param profile_id \type{str} The name of the profile. # \return \type{List[Tuple[str,str]]} List of serialized profile strings and matching profile names. def _upgradeProfile(self, serialized, profile_id): - parser = configparser.ConfigParser(interpolation=None) + parser = configparser.ConfigParser(interpolation = None) parser.read_string(serialized) if "general" not in parser: diff --git a/plugins/LegacyProfileReader/LegacyProfileReader.py b/plugins/LegacyProfileReader/LegacyProfileReader.py index 93c15ca8e0..cd577218d5 100644 --- a/plugins/LegacyProfileReader/LegacyProfileReader.py +++ b/plugins/LegacyProfileReader/LegacyProfileReader.py @@ -152,7 +152,7 @@ class LegacyProfileReader(ProfileReader): profile.setDirty(True) #Serialise and deserialise in order to perform the version upgrade. - parser = configparser.ConfigParser(interpolation=None) + parser = configparser.ConfigParser(interpolation = None) data = profile.serialize() parser.read_string(data) parser["general"]["version"] = "1" diff --git a/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py b/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py index 730a62e591..a56f1f807b 100644 --- a/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py +++ b/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py @@ -73,7 +73,7 @@ class VersionUpgrade22to24(VersionUpgrade): def __convertVariant(self, variant_path): # Copy the variant to the machine_instances/*_settings.inst.cfg - variant_config = configparser.ConfigParser(interpolation=None) + variant_config = configparser.ConfigParser(interpolation = None) with open(variant_path, "r", encoding = "utf-8") as fhandle: variant_config.read_file(fhandle) diff --git a/plugins/VersionUpgrade/VersionUpgrade25to26/VersionUpgrade25to26.py b/plugins/VersionUpgrade/VersionUpgrade25to26/VersionUpgrade25to26.py index 2430b35ea0..6643edb765 100644 --- a/plugins/VersionUpgrade/VersionUpgrade25to26/VersionUpgrade25to26.py +++ b/plugins/VersionUpgrade/VersionUpgrade25to26/VersionUpgrade25to26.py @@ -117,7 +117,7 @@ class VersionUpgrade25to26(VersionUpgrade): # \param serialised The serialised form of a quality profile. # \param filename The name of the file to upgrade. def upgradeMachineStack(self, serialised, filename): - parser = configparser.ConfigParser(interpolation=None) + parser = configparser.ConfigParser(interpolation = None) parser.read_string(serialised) # NOTE: This is for Custom FDM printers diff --git a/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py b/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py index a88ff5ac1c..399eb18b5d 100644 --- a/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py +++ b/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py @@ -84,7 +84,7 @@ class VersionUpgrade30to31(VersionUpgrade): # \param serialised The serialised form of a preferences file. # \param filename The name of the file to upgrade. def upgradePreferences(self, serialised, filename): - parser = configparser.ConfigParser(interpolation=None) + parser = configparser.ConfigParser(interpolation = None) parser.read_string(serialised) # Update version numbers @@ -105,7 +105,7 @@ class VersionUpgrade30to31(VersionUpgrade): # \param serialised The serialised form of the container file. # \param filename The name of the file to upgrade. def upgradeInstanceContainer(self, serialised, filename): - parser = configparser.ConfigParser(interpolation=None) + parser = configparser.ConfigParser(interpolation = None) parser.read_string(serialised) for each_section in ("general", "metadata"): @@ -130,7 +130,7 @@ class VersionUpgrade30to31(VersionUpgrade): # \param serialised The serialised form of a container stack. # \param filename The name of the file to upgrade. def upgradeStack(self, serialised, filename): - parser = configparser.ConfigParser(interpolation=None) + parser = configparser.ConfigParser(interpolation = None) parser.read_string(serialised) for each_section in ("general", "metadata"): From 10b5584ca68457dd25659a3273d9375602667c19 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 10 Oct 2018 16:24:13 +0200 Subject: [PATCH 61/97] [CURA-5483] Support more than just the UM3(E) for the firmware-update-check (add S5 only for now). --- .../FirmwareUpdateChecker.py | 22 +++- .../FirmwareUpdateCheckerJob.py | 103 ++++++++++++++---- 2 files changed, 100 insertions(+), 25 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index f01e8cb276..80a954c1cc 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -12,23 +12,34 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack -from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob +from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob, MachineId, get_settings_key_for_machine i18n_catalog = i18nCatalog("cura") - ## This Extension checks for new versions of the firmware based on the latest checked version number. # The plugin is currently only usable for applications maintained by Ultimaker. But it should be relatively easy # to change it to work for other applications. class FirmwareUpdateChecker(Extension): JEDI_VERSION_URL = "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources" + UM_NEW_URL_TEMPLATE = "http://software.ultimaker.com/releases/firmware/{0}/stable/version.txt" + VERSION_URLS_PER_MACHINE = \ + { + MachineId.UM3: [JEDI_VERSION_URL, UM_NEW_URL_TEMPLATE.format(MachineId.UM3.value)], + MachineId.UM3E: [JEDI_VERSION_URL, UM_NEW_URL_TEMPLATE.format(MachineId.UM3E.value)], + MachineId.S5: [UM_NEW_URL_TEMPLATE.format(MachineId.S5.value)] + } + # The 'new'-style URL is the only way to check for S5 firmware, + # and in the future, the UM3 line will also switch over, but for now the old 'JEDI'-style URL is still needed. + # TODO: Parse all of that from a file, because this will be a big mess of large static values which gets worse with each printer. + # See also the to-do in FirmWareCheckerJob. def __init__(self): super().__init__() # Initialize the Preference called `latest_checked_firmware` that stores the last version - # checked for the UM3. In the future if we need to check other printers' firmware - Application.getInstance().getPreferences().addPreference("info/latest_checked_firmware", "") + # checked for each printer. + for machine_id in MachineId: + Application.getInstance().getPreferences().addPreference(get_settings_key_for_machine(machine_id), "") # Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the # 'check for updates' option @@ -68,7 +79,8 @@ class FirmwareUpdateChecker(Extension): Logger.log("i", "A firmware update check is already running, do nothing.") return - self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, url = self.JEDI_VERSION_URL, + self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, + urls = self.VERSION_URLS_PER_MACHINE, callback = self._onActionTriggered, set_download_url_callback = self._onSetDownloadUrl) self._check_job.start() diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index eadacf2c02..658e820b4b 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -1,10 +1,13 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from enum import Enum, unique + from UM.Application import Application from UM.Message import Message from UM.Logger import Logger from UM.Job import Job +from UM.Version import Version import urllib.request import codecs @@ -12,49 +15,104 @@ import codecs from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") +# For UM-machines, these need to match the unique firmware-ID (also used in the URLs), i.o.t. only define in one place. +@unique +class MachineId(Enum): + UM3 = 9066 + UM3E = 9511 + S5 = 9051 + + +def get_settings_key_for_machine(machine_id: MachineId) -> str: + return "info/latest_checked_firmware_for_{0}".format(machine_id.value) + + +def default_parse_version_response(response: str) -> Version: + raw_str = response.split('\n', 1)[0].rstrip() + return Version(raw_str.split('.')) # Split it into a list; the default parsing of 'single string' is different. + ## This job checks if there is an update available on the provided URL. class FirmwareUpdateCheckerJob(Job): - def __init__(self, container = None, silent = False, url = None, callback = None, set_download_url_callback = None): + MACHINE_PER_NAME = \ + { + "ultimaker 3": MachineId.UM3, + "ultimaker 3 extended": MachineId.UM3E, + "ultimaker s5": MachineId.S5 + } + PARSE_VERSION_URL_PER_MACHINE = \ + { + MachineId.UM3: default_parse_version_response, + MachineId.UM3E: default_parse_version_response, + MachineId.S5: default_parse_version_response + } + REDIRECT_USER_PER_MACHINE = \ + { + MachineId.UM3: "https://ultimaker.com/en/resources/20500-upgrade-firmware", + MachineId.UM3E: "https://ultimaker.com/en/resources/20500-upgrade-firmware", + MachineId.S5: "https://ultimaker.com/en/resources/20500-upgrade-firmware" + } + # TODO: Parse all of that from a file, because this will be a big mess of large static values which gets worse with each printer. + + def __init__(self, container=None, silent=False, urls=None, callback=None, set_download_url_callback=None): super().__init__() self._container = container self.silent = silent - self._url = url + self._urls = urls self._callback = callback self._set_download_url_callback = set_download_url_callback + application_name = Application.getInstance().getApplicationName() + application_version = Application.getInstance().getVersion() + self._headers = {"User-Agent": "%s - %s" % (application_name, application_version)} + + def getUrlResponse(self, url: str) -> str: + request = urllib.request.Request(url, headers=self._headers) + current_version_file = urllib.request.urlopen(request) + reader = codecs.getreader("utf-8") + + return reader(current_version_file).read(firstline=True) + + def getCurrentVersionForMachine(self, machine_id: MachineId) -> Version: + max_version = Version([0, 0, 0]) + + machine_urls = self._urls.get(machine_id) + parse_function = self.PARSE_VERSION_URL_PER_MACHINE.get(machine_id) + if machine_urls is not None and parse_function is not None: + for url in machine_urls: + version = parse_function(self.getUrlResponse(url)) + if version > max_version: + max_version = version + + if max_version < Version([0, 0, 1]): + Logger.log('w', "MachineID {0} not handled!".format(repr(machine_id))) + + return max_version + def run(self): - if not self._url: + if not self._urls or self._urls is None: Logger.log("e", "Can not check for a new release. URL not set!") return try: - application_name = Application.getInstance().getApplicationName() - headers = {"User-Agent": "%s - %s" % (application_name, Application.getInstance().getVersion())} - request = urllib.request.Request(self._url, headers = headers) - current_version_file = urllib.request.urlopen(request) - reader = codecs.getreader("utf-8") - # get machine name from the definition container machine_name = self._container.definition.getName() machine_name_parts = machine_name.lower().split(" ") # If it is not None, then we compare between the checked_version and the current_version - # Now we just do that if the active printer is Ultimaker 3 or Ultimaker 3 Extended or any - # other Ultimaker 3 that will come in the future - if len(machine_name_parts) >= 2 and machine_name_parts[:2] == ["ultimaker", "3"]: - Logger.log("i", "You have a UM3 in printer list. Let's check the firmware!") + machine_id = self.MACHINE_PER_NAME.get(machine_name.lower()) + if machine_id is not None: + Logger.log("i", "You have a {0} in the printer list. Let's check the firmware!".format(machine_name)) - # Nothing to parse, just get the string - # TODO: In the future may be done by parsing a JSON file with diferent version for each printer model - current_version = reader(current_version_file).readline().rstrip() + current_version = self.getCurrentVersionForMachine(machine_id) # If it is the first time the version is checked, the checked_version is '' - checked_version = Application.getInstance().getPreferences().getValue("info/latest_checked_firmware") + setting_key_str = get_settings_key_for_machine(machine_id) + checked_version = Application.getInstance().getPreferences().getValue(setting_key_str) # If the checked_version is '', it's because is the first time we check firmware and in this case # we will not show the notification, but we will store it for the next time - Application.getInstance().getPreferences().setValue("info/latest_checked_firmware", current_version) + Application.getInstance().getPreferences().setValue(setting_key_str, current_version) Logger.log("i", "Reading firmware version of %s: checked = %s - latest = %s", machine_name, checked_version, current_version) # The first time we want to store the current version, the notification will not be shown, @@ -78,12 +136,17 @@ class FirmwareUpdateCheckerJob(Job): button_style=Message.ActionButtonStyle.LINK, button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) - # If we do this in a cool way, the download url should be available in the JSON file if self._set_download_url_callback: - self._set_download_url_callback("https://ultimaker.com/en/resources/20500-upgrade-firmware") + redirect = self.REDIRECT_USER_PER_MACHINE.get(machine_id) + if redirect is not None: + self._set_download_url_callback(redirect) + else: + Logger.log('w', "No callback-url for firmware of {0}".format(repr(machine_id))) message.actionTriggered.connect(self._callback) message.show() + else: + Logger.log('i', "No machine with name {0} in list of firmware to check.".format(repr(machine_id))) except Exception as e: Logger.log("w", "Failed to check for new version: %s", e) From 487ef52c6616b8f15af9d7cd3140eaff5e46fbcb Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 10 Oct 2018 16:52:47 +0200 Subject: [PATCH 62/97] Warn on error and continue on encountering 'future-proof' (now) or old (later) version-URLs. --- .../FirmwareUpdateCheckerJob.py | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 658e820b4b..40546d4a05 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -10,6 +10,7 @@ from UM.Job import Job from UM.Version import Version import urllib.request +from urllib.error import URLError import codecs from UM.i18n import i18nCatalog @@ -62,16 +63,20 @@ class FirmwareUpdateCheckerJob(Job): self._callback = callback self._set_download_url_callback = set_download_url_callback - application_name = Application.getInstance().getApplicationName() - application_version = Application.getInstance().getVersion() - self._headers = {"User-Agent": "%s - %s" % (application_name, application_version)} + self._headers = {} # Don't set headers yet. def getUrlResponse(self, url: str) -> str: - request = urllib.request.Request(url, headers=self._headers) - current_version_file = urllib.request.urlopen(request) - reader = codecs.getreader("utf-8") + result = "0.0.0" - return reader(current_version_file).read(firstline=True) + try: + request = urllib.request.Request(url, headers=self._headers) + current_version_file = urllib.request.urlopen(request) + reader = codecs.getreader("utf-8") + result = reader(current_version_file).read(firstline=True) + except URLError: + Logger.log('w', "Could not reach '{0}', if this URL is old, consider removal.".format(url)) + + return result def getCurrentVersionForMachine(self, machine_id: MachineId) -> Version: max_version = Version([0, 0, 0]) @@ -95,6 +100,10 @@ class FirmwareUpdateCheckerJob(Job): return try: + application_name = Application.getInstance().getApplicationName() + application_version = Application.getInstance().getVersion() + self._headers = {"User-Agent": "%s - %s" % (application_name, application_version)} + # get machine name from the definition container machine_name = self._container.definition.getName() machine_name_parts = machine_name.lower().split(" ") From d8ed3d607403e34205dd46da300f2feaef82b2b0 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 11 Oct 2018 14:56:07 +0200 Subject: [PATCH 63/97] Check the whole list for firmware-updates instead of just the first added container. --- plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py | 7 ++++--- plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 80a954c1cc..459d29265d 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -49,6 +49,7 @@ class FirmwareUpdateChecker(Extension): self._download_url = None self._check_job = None + self._name_cache = [] ## Callback for the message that is spawned when there is a new version. def _onActionTriggered(self, message, action): @@ -74,10 +75,10 @@ class FirmwareUpdateChecker(Extension): # \param silent type(boolean) Suppresses messages other than "new version found" messages. # This is used when checking for a new firmware version at startup. def checkFirmwareVersion(self, container = None, silent = False): - # Do not run multiple check jobs in parallel - if self._check_job is not None: - Logger.log("i", "A firmware update check is already running, do nothing.") + container_name = container.definition.getName() + if container_name in self._name_cache: return + self._name_cache.append(container_name) self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, urls = self.VERSION_URLS_PER_MACHINE, diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 40546d4a05..14a40e3cce 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -34,6 +34,7 @@ def default_parse_version_response(response: str) -> Version: ## This job checks if there is an update available on the provided URL. + class FirmwareUpdateCheckerJob(Job): MACHINE_PER_NAME = \ { @@ -155,7 +156,7 @@ class FirmwareUpdateCheckerJob(Job): message.actionTriggered.connect(self._callback) message.show() else: - Logger.log('i', "No machine with name {0} in list of firmware to check.".format(repr(machine_id))) + Logger.log('i', "No machine with name {0} in list of firmware to check.".format(machine_name)) except Exception as e: Logger.log("w", "Failed to check for new version: %s", e) From 12999f48c848359e6ed33d8997bb9c193a63ae30 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 11 Oct 2018 15:27:04 +0200 Subject: [PATCH 64/97] FirmwareUpdateCheckerJob: Move introduced hardcoded values to static variables. --- .../FirmwareUpdateCheckerJob.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 14a40e3cce..41710e7e86 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -34,8 +34,11 @@ def default_parse_version_response(response: str) -> Version: ## This job checks if there is an update available on the provided URL. - class FirmwareUpdateCheckerJob(Job): + STRING_ZERO_VERSION = "0.0.0" + STRING_EPSILON_VERSION = "0.0.1" + ZERO_VERSION = Version(STRING_ZERO_VERSION) + EPSILON_VERSION = Version(STRING_EPSILON_VERSION) MACHINE_PER_NAME = \ { "ultimaker 3": MachineId.UM3, @@ -67,7 +70,7 @@ class FirmwareUpdateCheckerJob(Job): self._headers = {} # Don't set headers yet. def getUrlResponse(self, url: str) -> str: - result = "0.0.0" + result = self.STRING_ZERO_VERSION try: request = urllib.request.Request(url, headers=self._headers) @@ -80,7 +83,7 @@ class FirmwareUpdateCheckerJob(Job): return result def getCurrentVersionForMachine(self, machine_id: MachineId) -> Version: - max_version = Version([0, 0, 0]) + max_version = self.ZERO_VERSION machine_urls = self._urls.get(machine_id) parse_function = self.PARSE_VERSION_URL_PER_MACHINE.get(machine_id) @@ -90,7 +93,7 @@ class FirmwareUpdateCheckerJob(Job): if version > max_version: max_version = version - if max_version < Version([0, 0, 1]): + if max_version < self.EPSILON_VERSION: Logger.log('w', "MachineID {0} not handled!".format(repr(machine_id))) return max_version @@ -107,7 +110,6 @@ class FirmwareUpdateCheckerJob(Job): # get machine name from the definition container machine_name = self._container.definition.getName() - machine_name_parts = machine_name.lower().split(" ") # If it is not None, then we compare between the checked_version and the current_version machine_id = self.MACHINE_PER_NAME.get(machine_name.lower()) @@ -118,7 +120,7 @@ class FirmwareUpdateCheckerJob(Job): # If it is the first time the version is checked, the checked_version is '' setting_key_str = get_settings_key_for_machine(machine_id) - checked_version = Application.getInstance().getPreferences().getValue(setting_key_str) + checked_version = Version(Application.getInstance().getPreferences().getValue(setting_key_str)) # If the checked_version is '', it's because is the first time we check firmware and in this case # we will not show the notification, but we will store it for the next time From 6c2791f38240992465014969b7d0fb9c6335dfa6 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 11 Oct 2018 17:16:01 +0200 Subject: [PATCH 65/97] Parse the firmware-update-check lookup-tables from a (new) .json instead of hardcoded. --- .../FirmwareUpdateChecker.py | 47 +++++++----- .../FirmwareUpdateCheckerJob.py | 73 ++++++++++--------- .../resources/machines.json | 36 +++++++++ 3 files changed, 101 insertions(+), 55 deletions(-) create mode 100644 plugins/FirmwareUpdateChecker/resources/machines.json diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 459d29265d..1736bb228a 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -1,18 +1,20 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import json, os from PyQt5.QtCore import QUrl from PyQt5.QtGui import QDesktopServices from UM.Extension import Extension from UM.Application import Application from UM.Logger import Logger +from UM.PluginRegistry import PluginRegistry from UM.i18n import i18nCatalog from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack -from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob, MachineId, get_settings_key_for_machine +from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob, get_settings_key_for_machine i18n_catalog = i18nCatalog("cura") @@ -20,38 +22,23 @@ i18n_catalog = i18nCatalog("cura") # The plugin is currently only usable for applications maintained by Ultimaker. But it should be relatively easy # to change it to work for other applications. class FirmwareUpdateChecker(Extension): - JEDI_VERSION_URL = "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources" - UM_NEW_URL_TEMPLATE = "http://software.ultimaker.com/releases/firmware/{0}/stable/version.txt" - VERSION_URLS_PER_MACHINE = \ - { - MachineId.UM3: [JEDI_VERSION_URL, UM_NEW_URL_TEMPLATE.format(MachineId.UM3.value)], - MachineId.UM3E: [JEDI_VERSION_URL, UM_NEW_URL_TEMPLATE.format(MachineId.UM3E.value)], - MachineId.S5: [UM_NEW_URL_TEMPLATE.format(MachineId.S5.value)] - } - # The 'new'-style URL is the only way to check for S5 firmware, - # and in the future, the UM3 line will also switch over, but for now the old 'JEDI'-style URL is still needed. - # TODO: Parse all of that from a file, because this will be a big mess of large static values which gets worse with each printer. - # See also the to-do in FirmWareCheckerJob. def __init__(self): super().__init__() - # Initialize the Preference called `latest_checked_firmware` that stores the last version - # checked for each printer. - for machine_id in MachineId: - Application.getInstance().getPreferences().addPreference(get_settings_key_for_machine(machine_id), "") - # Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the # 'check for updates' option Application.getInstance().getPreferences().addPreference("info/automatic_update_check", True) if Application.getInstance().getPreferences().getValue("info/automatic_update_check"): ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) + self._late_init = True # Init some things after creation, since we need the path from the plugin-mgr. self._download_url = None self._check_job = None self._name_cache = [] ## Callback for the message that is spawned when there is a new version. + # TODO: Set the right download URL for each message! def _onActionTriggered(self, message, action): if action == "download": if self._download_url is not None: @@ -68,6 +55,25 @@ class FirmwareUpdateChecker(Extension): def _onJobFinished(self, *args, **kwargs): self._check_job = None + def lateInit(self): + self._late_init = False + + # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve 'raw' json. + self._machines_json = None + json_path = os.path.join(PluginRegistry.getInstance().getPluginPath("FirmwareUpdateChecker"), + "resources/machines.json") + with open(json_path, "r", encoding="utf-8") as json_file: + self._machines_json = json.load(json_file).get("machines") + if self._machines_json is None: + Logger.log('e', "Missing or inaccessible: {0}".format(json_path)) + return + + # Initialize the Preference called `latest_checked_firmware` that stores the last version + # checked for each printer. + for machine_json in self._machines_json: + machine_id = machine_json.get("id") + Application.getInstance().getPreferences().addPreference(get_settings_key_for_machine(machine_id), "") + ## Connect with software.ultimaker.com, load latest.version and check version info. # If the version info is different from the current version, spawn a message to # allow the user to download it. @@ -75,13 +81,16 @@ class FirmwareUpdateChecker(Extension): # \param silent type(boolean) Suppresses messages other than "new version found" messages. # This is used when checking for a new firmware version at startup. def checkFirmwareVersion(self, container = None, silent = False): + if self._late_init: + self.lateInit() + container_name = container.definition.getName() if container_name in self._name_cache: return self._name_cache.append(container_name) self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, - urls = self.VERSION_URLS_PER_MACHINE, + machines_json = self._machines_json, callback = self._onActionTriggered, set_download_url_callback = self._onSetDownloadUrl) self._check_job.start() diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 41710e7e86..336b954f5e 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -16,16 +16,9 @@ import codecs from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") -# For UM-machines, these need to match the unique firmware-ID (also used in the URLs), i.o.t. only define in one place. -@unique -class MachineId(Enum): - UM3 = 9066 - UM3E = 9511 - S5 = 9051 - -def get_settings_key_for_machine(machine_id: MachineId) -> str: - return "info/latest_checked_firmware_for_{0}".format(machine_id.value) +def get_settings_key_for_machine(machine_id: int) -> str: + return "info/latest_checked_firmware_for_{0}".format(machine_id) def default_parse_version_response(response: str) -> Version: @@ -39,31 +32,39 @@ class FirmwareUpdateCheckerJob(Job): STRING_EPSILON_VERSION = "0.0.1" ZERO_VERSION = Version(STRING_ZERO_VERSION) EPSILON_VERSION = Version(STRING_EPSILON_VERSION) - MACHINE_PER_NAME = \ - { - "ultimaker 3": MachineId.UM3, - "ultimaker 3 extended": MachineId.UM3E, - "ultimaker s5": MachineId.S5 - } - PARSE_VERSION_URL_PER_MACHINE = \ - { - MachineId.UM3: default_parse_version_response, - MachineId.UM3E: default_parse_version_response, - MachineId.S5: default_parse_version_response - } - REDIRECT_USER_PER_MACHINE = \ - { - MachineId.UM3: "https://ultimaker.com/en/resources/20500-upgrade-firmware", - MachineId.UM3E: "https://ultimaker.com/en/resources/20500-upgrade-firmware", - MachineId.S5: "https://ultimaker.com/en/resources/20500-upgrade-firmware" - } - # TODO: Parse all of that from a file, because this will be a big mess of large static values which gets worse with each printer. + JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": default_parse_version_response} - def __init__(self, container=None, silent=False, urls=None, callback=None, set_download_url_callback=None): + def __init__(self, container=None, silent=False, machines_json=None, callback=None, set_download_url_callback=None): super().__init__() self._container = container self.silent = silent - self._urls = urls + + # Parse all the needed lookup-tables from the '.json' file(s) in the resources folder. + # TODO: This should not be here when the merge to master is done, as it will be repeatedly recreated. + # It should be a separate object this constructor receives instead. + self._machine_ids = [] + self._machine_per_name = {} + self._parse_version_url_per_machine = {} + self._check_urls_per_machine = {} + self._redirect_user_per_machine = {} + try: + for machine_json in machines_json: + machine_id = machine_json.get("id") + machine_name = machine_json.get("name") + self._machine_ids.append(machine_id) + self._machine_per_name[machine_name] = machine_id + version_parse_function = self.JSON_NAME_TO_VERSION_PARSE_FUNCTION.get(machine_json.get("version_parser")) + if version_parse_function is None: + Logger.log('w', "No version-parse-function specified for machine {0}.".format(machine_name)) + version_parse_function = default_parse_version_response # Use default instead if nothing is found. + self._parse_version_url_per_machine[machine_id] = version_parse_function + self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see '_comment' in the .json file. + for check_url in machine_json.get("check_urls"): + self._check_urls_per_machine[machine_id].append(check_url) + self._redirect_user_per_machine[machine_id] = machine_json.get("update_url") + except: + Logger.log('e', "Couldn't parse firmware-update-check loopup-lists from file.") + self._callback = callback self._set_download_url_callback = set_download_url_callback @@ -82,11 +83,11 @@ class FirmwareUpdateCheckerJob(Job): return result - def getCurrentVersionForMachine(self, machine_id: MachineId) -> Version: + def getCurrentVersionForMachine(self, machine_id: int) -> Version: max_version = self.ZERO_VERSION - machine_urls = self._urls.get(machine_id) - parse_function = self.PARSE_VERSION_URL_PER_MACHINE.get(machine_id) + machine_urls = self._check_urls_per_machine.get(machine_id) + parse_function = self._parse_version_url_per_machine.get(machine_id) if machine_urls is not None and parse_function is not None: for url in machine_urls: version = parse_function(self.getUrlResponse(url)) @@ -99,7 +100,7 @@ class FirmwareUpdateCheckerJob(Job): return max_version def run(self): - if not self._urls or self._urls is None: + if not self._machine_ids or self._machine_ids is None: Logger.log("e", "Can not check for a new release. URL not set!") return @@ -112,7 +113,7 @@ class FirmwareUpdateCheckerJob(Job): machine_name = self._container.definition.getName() # If it is not None, then we compare between the checked_version and the current_version - machine_id = self.MACHINE_PER_NAME.get(machine_name.lower()) + machine_id = self._machine_per_name.get(machine_name.lower()) if machine_id is not None: Logger.log("i", "You have a {0} in the printer list. Let's check the firmware!".format(machine_name)) @@ -150,7 +151,7 @@ class FirmwareUpdateCheckerJob(Job): # If we do this in a cool way, the download url should be available in the JSON file if self._set_download_url_callback: - redirect = self.REDIRECT_USER_PER_MACHINE.get(machine_id) + redirect = self._redirect_user_per_machine.get(machine_id) if redirect is not None: self._set_download_url_callback(redirect) else: diff --git a/plugins/FirmwareUpdateChecker/resources/machines.json b/plugins/FirmwareUpdateChecker/resources/machines.json new file mode 100644 index 0000000000..5dc9aadbbf --- /dev/null +++ b/plugins/FirmwareUpdateChecker/resources/machines.json @@ -0,0 +1,36 @@ +{ + "_comment": "Multiple 'update_urls': The 'new'-style URL is the only way to check for S5 firmware, and in the future, the UM3 line will also switch over, but for now the old 'JEDI'-style URL is still needed.", + + "machines": + [ + { + "id": 9066, + "name": "ultimaker 3", + "check_urls": + [ + "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", + "http://software.ultimaker.com/releases/firmware/9066/stable/version.txt" + ], + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware", + "version_parser": "default" + }, + { + "id": 9511, + "name": "ultimaker 3 extended", + "check_urls": + [ + "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", + "http://software.ultimaker.com/releases/firmware/9511/stable/version.txt" + ], + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware", + "version_parser": "default" + }, + { + "id": 9051, + "name": "ultimaker s5", + "check_urls": ["http://software.ultimaker.com/releases/firmware/9051/stable/version.txt"], + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware", + "version_parser": "default" + } + ] +} From 472d012c08f8c28a8eba29cf65baa50fa802aeda Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 11 Oct 2018 17:52:06 +0200 Subject: [PATCH 66/97] Move firmware-update-checker json-parsing to its own class (also don't repeat parsing each time). --- .../FirmwareUpdateChecker.py | 17 ++--- .../FirmwareUpdateCheckerJob.py | 51 +++----------- .../FirmwareUpdateCheckerLookup.py | 67 +++++++++++++++++++ 3 files changed, 81 insertions(+), 54 deletions(-) create mode 100644 plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 1736bb228a..223cf2d433 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -15,6 +15,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob, get_settings_key_for_machine +from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup i18n_catalog = i18nCatalog("cura") @@ -58,20 +59,12 @@ class FirmwareUpdateChecker(Extension): def lateInit(self): self._late_init = False - # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve 'raw' json. - self._machines_json = None - json_path = os.path.join(PluginRegistry.getInstance().getPluginPath("FirmwareUpdateChecker"), - "resources/machines.json") - with open(json_path, "r", encoding="utf-8") as json_file: - self._machines_json = json.load(json_file).get("machines") - if self._machines_json is None: - Logger.log('e', "Missing or inaccessible: {0}".format(json_path)) - return + self._lookups = FirmwareUpdateCheckerLookup(os.path.join(PluginRegistry.getInstance().getPluginPath( + "FirmwareUpdateChecker"), "resources/machines.json")) # Initialize the Preference called `latest_checked_firmware` that stores the last version # checked for each printer. - for machine_json in self._machines_json: - machine_id = machine_json.get("id") + for machine_id in self._lookups.getMachineIds(): Application.getInstance().getPreferences().addPreference(get_settings_key_for_machine(machine_id), "") ## Connect with software.ultimaker.com, load latest.version and check version info. @@ -90,7 +83,7 @@ class FirmwareUpdateChecker(Extension): self._name_cache.append(container_name) self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, - machines_json = self._machines_json, + lookups = self._lookups, callback = self._onActionTriggered, set_download_url_callback = self._onSetDownloadUrl) self._check_job.start() diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 336b954f5e..6d72e130b2 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -1,8 +1,6 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from enum import Enum, unique - from UM.Application import Application from UM.Message import Message from UM.Logger import Logger @@ -13,6 +11,8 @@ import urllib.request from urllib.error import URLError import codecs +from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup + from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") @@ -21,53 +21,20 @@ def get_settings_key_for_machine(machine_id: int) -> str: return "info/latest_checked_firmware_for_{0}".format(machine_id) -def default_parse_version_response(response: str) -> Version: - raw_str = response.split('\n', 1)[0].rstrip() - return Version(raw_str.split('.')) # Split it into a list; the default parsing of 'single string' is different. - - ## This job checks if there is an update available on the provided URL. class FirmwareUpdateCheckerJob(Job): STRING_ZERO_VERSION = "0.0.0" STRING_EPSILON_VERSION = "0.0.1" ZERO_VERSION = Version(STRING_ZERO_VERSION) EPSILON_VERSION = Version(STRING_EPSILON_VERSION) - JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": default_parse_version_response} - def __init__(self, container=None, silent=False, machines_json=None, callback=None, set_download_url_callback=None): + def __init__(self, container=None, silent=False, lookups:FirmwareUpdateCheckerLookup=None, callback=None, set_download_url_callback=None): super().__init__() self._container = container self.silent = silent - - # Parse all the needed lookup-tables from the '.json' file(s) in the resources folder. - # TODO: This should not be here when the merge to master is done, as it will be repeatedly recreated. - # It should be a separate object this constructor receives instead. - self._machine_ids = [] - self._machine_per_name = {} - self._parse_version_url_per_machine = {} - self._check_urls_per_machine = {} - self._redirect_user_per_machine = {} - try: - for machine_json in machines_json: - machine_id = machine_json.get("id") - machine_name = machine_json.get("name") - self._machine_ids.append(machine_id) - self._machine_per_name[machine_name] = machine_id - version_parse_function = self.JSON_NAME_TO_VERSION_PARSE_FUNCTION.get(machine_json.get("version_parser")) - if version_parse_function is None: - Logger.log('w', "No version-parse-function specified for machine {0}.".format(machine_name)) - version_parse_function = default_parse_version_response # Use default instead if nothing is found. - self._parse_version_url_per_machine[machine_id] = version_parse_function - self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see '_comment' in the .json file. - for check_url in machine_json.get("check_urls"): - self._check_urls_per_machine[machine_id].append(check_url) - self._redirect_user_per_machine[machine_id] = machine_json.get("update_url") - except: - Logger.log('e', "Couldn't parse firmware-update-check loopup-lists from file.") - self._callback = callback self._set_download_url_callback = set_download_url_callback - + self._lookups = lookups self._headers = {} # Don't set headers yet. def getUrlResponse(self, url: str) -> str: @@ -86,8 +53,8 @@ class FirmwareUpdateCheckerJob(Job): def getCurrentVersionForMachine(self, machine_id: int) -> Version: max_version = self.ZERO_VERSION - machine_urls = self._check_urls_per_machine.get(machine_id) - parse_function = self._parse_version_url_per_machine.get(machine_id) + machine_urls = self._lookups.getCheckUrlsFor(machine_id) + parse_function = self._lookups.getParseVersionUrlFor(machine_id) if machine_urls is not None and parse_function is not None: for url in machine_urls: version = parse_function(self.getUrlResponse(url)) @@ -100,7 +67,7 @@ class FirmwareUpdateCheckerJob(Job): return max_version def run(self): - if not self._machine_ids or self._machine_ids is None: + if self._lookups is None: Logger.log("e", "Can not check for a new release. URL not set!") return @@ -113,7 +80,7 @@ class FirmwareUpdateCheckerJob(Job): machine_name = self._container.definition.getName() # If it is not None, then we compare between the checked_version and the current_version - machine_id = self._machine_per_name.get(machine_name.lower()) + machine_id = self._lookups.getMachineByName(machine_name.lower()) if machine_id is not None: Logger.log("i", "You have a {0} in the printer list. Let's check the firmware!".format(machine_name)) @@ -151,7 +118,7 @@ class FirmwareUpdateCheckerJob(Job): # If we do this in a cool way, the download url should be available in the JSON file if self._set_download_url_callback: - redirect = self._redirect_user_per_machine.get(machine_id) + redirect = self._lookups.getRedirectUseror(machine_id) if redirect is not None: self._set_download_url_callback(redirect) else: diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py new file mode 100644 index 0000000000..62d43553c1 --- /dev/null +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -0,0 +1,67 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import json, os + +from UM.Logger import Logger +from UM.Version import Version + +from UM.i18n import i18nCatalog +i18n_catalog = i18nCatalog("cura") + +def default_parse_version_response(response: str) -> Version: + raw_str = response.split('\n', 1)[0].rstrip() + return Version(raw_str.split('.')) # Split it into a list; the default parsing of 'single string' is different. + + +class FirmwareUpdateCheckerLookup: + JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": default_parse_version_response} + + def __init__(self, json_path): + # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve 'raw' json. + machines_json = None + with open(json_path, "r", encoding="utf-8") as json_file: + machines_json = json.load(json_file).get("machines") + if machines_json is None: + Logger.log('e', "Missing or inaccessible: {0}".format(json_path)) + return + + # Parse all the needed lookup-tables from the '.json' file(s) in the resources folder. + self._machine_ids = [] + self._machine_per_name = {} + self._parse_version_url_per_machine = {} + self._check_urls_per_machine = {} + self._redirect_user_per_machine = {} + try: + for machine_json in machines_json: + machine_id = machine_json.get("id") + machine_name = machine_json.get("name") + self._machine_ids.append(machine_id) + self._machine_per_name[machine_name] = machine_id + version_parse_function = \ + self.JSON_NAME_TO_VERSION_PARSE_FUNCTION.get(machine_json.get("version_parser")) + if version_parse_function is None: + Logger.log('w', "No version-parse-function specified for machine {0}.".format(machine_name)) + version_parse_function = default_parse_version_response # Use default instead if nothing is found. + self._parse_version_url_per_machine[machine_id] = version_parse_function + self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see '_comment' in the .json file. + for check_url in machine_json.get("check_urls"): + self._check_urls_per_machine[machine_id].append(check_url) + self._redirect_user_per_machine[machine_id] = machine_json.get("update_url") + except: + Logger.log('e', "Couldn't parse firmware-update-check loopup-lists from file.") + + def getMachineIds(self) -> [int]: + return self._machine_ids + + def getMachineByName(self, machine_name: str) -> int: + return self._machine_per_name.get(machine_name) + + def getParseVersionUrlFor(self, machine_id: int) -> str: + return self._parse_version_url_per_machine.get(machine_id) + + def getCheckUrlsFor(self, machine_id: int) -> [str]: + return self._check_urls_per_machine.get(machine_id) + + def getRedirectUseror(self, machine_id: int) -> str: + return self._redirect_user_per_machine.get(machine_id) From 4ecac6e27f71b9b8e6cd65aa8e96cc816c7e5428 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 11 Oct 2018 18:24:07 +0200 Subject: [PATCH 67/97] Set the right firmware-download-URL in the actual update-firmware-message. --- .../FirmwareUpdateChecker.py | 26 +++++++++---------- .../FirmwareUpdateCheckerJob.py | 19 +++----------- .../FirmwareUpdateCheckerLookup.py | 7 ++++- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 223cf2d433..90590fc5a2 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -14,8 +14,8 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack -from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob, get_settings_key_for_machine -from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup +from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob +from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, get_settings_key_for_machine i18n_catalog = i18nCatalog("cura") @@ -39,14 +39,15 @@ class FirmwareUpdateChecker(Extension): self._name_cache = [] ## Callback for the message that is spawned when there is a new version. - # TODO: Set the right download URL for each message! def _onActionTriggered(self, message, action): - if action == "download": - if self._download_url is not None: - QDesktopServices.openUrl(QUrl(self._download_url)) - - def _onSetDownloadUrl(self, download_url): - self._download_url = download_url + try: + download_url = self._lookups.getRedirectUserFor(int(action)) + if download_url is not None: + QDesktopServices.openUrl(QUrl(download_url)) + else: + Logger.log('e', "Can't find URL for {0}".format(action)) + except: + Logger.log('e', "Don't know what to do with {0}".format(action)) def _onContainerAdded(self, container): # Only take care when a new GlobalStack was added @@ -56,7 +57,7 @@ class FirmwareUpdateChecker(Extension): def _onJobFinished(self, *args, **kwargs): self._check_job = None - def lateInit(self): + def doLateInit(self): self._late_init = False self._lookups = FirmwareUpdateCheckerLookup(os.path.join(PluginRegistry.getInstance().getPluginPath( @@ -75,7 +76,7 @@ class FirmwareUpdateChecker(Extension): # This is used when checking for a new firmware version at startup. def checkFirmwareVersion(self, container = None, silent = False): if self._late_init: - self.lateInit() + self.doLateInit() container_name = container.definition.getName() if container_name in self._name_cache: @@ -84,7 +85,6 @@ class FirmwareUpdateChecker(Extension): self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, lookups = self._lookups, - callback = self._onActionTriggered, - set_download_url_callback = self._onSetDownloadUrl) + callback = self._onActionTriggered) self._check_job.start() self._check_job.finished.connect(self._onJobFinished) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 6d72e130b2..342287ca76 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -11,16 +11,12 @@ import urllib.request from urllib.error import URLError import codecs -from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup +from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, get_settings_key_for_machine from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") -def get_settings_key_for_machine(machine_id: int) -> str: - return "info/latest_checked_firmware_for_{0}".format(machine_id) - - ## This job checks if there is an update available on the provided URL. class FirmwareUpdateCheckerJob(Job): STRING_ZERO_VERSION = "0.0.0" @@ -28,12 +24,12 @@ class FirmwareUpdateCheckerJob(Job): ZERO_VERSION = Version(STRING_ZERO_VERSION) EPSILON_VERSION = Version(STRING_EPSILON_VERSION) - def __init__(self, container=None, silent=False, lookups:FirmwareUpdateCheckerLookup=None, callback=None, set_download_url_callback=None): + def __init__(self, container=None, silent=False, lookups:FirmwareUpdateCheckerLookup=None, callback=None): super().__init__() self._container = container self.silent = silent self._callback = callback - self._set_download_url_callback = set_download_url_callback + self._lookups = lookups self._headers = {} # Don't set headers yet. @@ -109,20 +105,13 @@ class FirmwareUpdateCheckerJob(Job): "@info:title The %s gets replaced with the printer name.", "New %s firmware available") % machine_name) - message.addAction("download", + message.addAction(machine_id, i18n_catalog.i18nc("@action:button", "How to update"), "[no_icon]", "[no_description]", button_style=Message.ActionButtonStyle.LINK, button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) - # If we do this in a cool way, the download url should be available in the JSON file - if self._set_download_url_callback: - redirect = self._lookups.getRedirectUseror(machine_id) - if redirect is not None: - self._set_download_url_callback(redirect) - else: - Logger.log('w', "No callback-url for firmware of {0}".format(repr(machine_id))) message.actionTriggered.connect(self._callback) message.show() else: diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index 62d43553c1..f2c9082f76 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -9,6 +9,11 @@ from UM.Version import Version from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") + +def get_settings_key_for_machine(machine_id: int) -> str: + return "info/latest_checked_firmware_for_{0}".format(machine_id) + + def default_parse_version_response(response: str) -> Version: raw_str = response.split('\n', 1)[0].rstrip() return Version(raw_str.split('.')) # Split it into a list; the default parsing of 'single string' is different. @@ -63,5 +68,5 @@ class FirmwareUpdateCheckerLookup: def getCheckUrlsFor(self, machine_id: int) -> [str]: return self._check_urls_per_machine.get(machine_id) - def getRedirectUseror(self, machine_id: int) -> str: + def getRedirectUserFor(self, machine_id: int) -> str: return self._redirect_user_per_machine.get(machine_id) From 6a50487bf0c2a0226650fbb96cd2bf3d0fd28db6 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 11 Oct 2018 19:16:10 +0200 Subject: [PATCH 68/97] Catch the one that got away --- plugins/USBPrinting/AvrFirmwareUpdater.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/USBPrinting/AvrFirmwareUpdater.py b/plugins/USBPrinting/AvrFirmwareUpdater.py index b8650e9208..56e3f99c23 100644 --- a/plugins/USBPrinting/AvrFirmwareUpdater.py +++ b/plugins/USBPrinting/AvrFirmwareUpdater.py @@ -22,7 +22,7 @@ class AvrFirmwareUpdater(FirmwareUpdater): def _updateFirmware(self) -> None: try: - hex_file = intelHex.readHex(self._firmware_location) + hex_file = intelHex.readHex(self._firmware_file) assert len(hex_file) > 0 except (FileNotFoundError, AssertionError): Logger.log("e", "Unable to read provided hex file. Could not update firmware.") From f2b50c748c1aea35e61de119fb3a08a28afdb295 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 11 Oct 2018 21:54:27 +0200 Subject: [PATCH 69/97] Fix typing in the FirmwareUpdateChecker plugin. --- .../FirmwareUpdateChecker.py | 14 +++++---- .../FirmwareUpdateCheckerJob.py | 5 ++-- .../FirmwareUpdateCheckerLookup.py | 30 ++++++++++--------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 90590fc5a2..71bdd0bc23 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -1,10 +1,12 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import json, os +import os from PyQt5.QtCore import QUrl from PyQt5.QtGui import QDesktopServices +from typing import List + from UM.Extension import Extension from UM.Application import Application from UM.Logger import Logger @@ -19,12 +21,13 @@ from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, get_settin i18n_catalog = i18nCatalog("cura") + ## This Extension checks for new versions of the firmware based on the latest checked version number. # The plugin is currently only usable for applications maintained by Ultimaker. But it should be relatively easy # to change it to work for other applications. class FirmwareUpdateChecker(Extension): - def __init__(self): + def __init__(self) -> None: super().__init__() # Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the @@ -36,7 +39,8 @@ class FirmwareUpdateChecker(Extension): self._late_init = True # Init some things after creation, since we need the path from the plugin-mgr. self._download_url = None self._check_job = None - self._name_cache = [] + self._name_cache = [] # type: List[str] + self._lookups = None ## Callback for the message that is spawned when there is a new version. def _onActionTriggered(self, message, action): @@ -46,8 +50,8 @@ class FirmwareUpdateChecker(Extension): QDesktopServices.openUrl(QUrl(download_url)) else: Logger.log('e', "Can't find URL for {0}".format(action)) - except: - Logger.log('e', "Don't know what to do with {0}".format(action)) + except Exception as ex: + Logger.log('e', "Don't know what to do with '{0}' because {1}".format(action, ex)) def _onContainerAdded(self, container): # Only take care when a new GlobalStack was added diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 342287ca76..d186cbb4e4 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -9,6 +9,7 @@ from UM.Version import Version import urllib.request from urllib.error import URLError +from typing import Dict import codecs from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, get_settings_key_for_machine @@ -24,14 +25,14 @@ class FirmwareUpdateCheckerJob(Job): ZERO_VERSION = Version(STRING_ZERO_VERSION) EPSILON_VERSION = Version(STRING_EPSILON_VERSION) - def __init__(self, container=None, silent=False, lookups:FirmwareUpdateCheckerLookup=None, callback=None): + def __init__(self, container, silent, lookups: FirmwareUpdateCheckerLookup, callback) -> None: super().__init__() self._container = container self.silent = silent self._callback = callback self._lookups = lookups - self._headers = {} # Don't set headers yet. + self._headers = {} # type:Dict[str, str] # Don't set headers yet. def getUrlResponse(self, url: str) -> str: result = self.STRING_ZERO_VERSION diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index f2c9082f76..f6d7a24da0 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -1,7 +1,9 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import json, os +import json + +from typing import Callable, Dict, List, Optional from UM.Logger import Logger from UM.Version import Version @@ -22,7 +24,7 @@ def default_parse_version_response(response: str) -> Version: class FirmwareUpdateCheckerLookup: JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": default_parse_version_response} - def __init__(self, json_path): + def __init__(self, json_path) -> None: # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve 'raw' json. machines_json = None with open(json_path, "r", encoding="utf-8") as json_file: @@ -32,11 +34,11 @@ class FirmwareUpdateCheckerLookup: return # Parse all the needed lookup-tables from the '.json' file(s) in the resources folder. - self._machine_ids = [] - self._machine_per_name = {} - self._parse_version_url_per_machine = {} - self._check_urls_per_machine = {} - self._redirect_user_per_machine = {} + self._machine_ids = [] # type:List[int] + self._machine_per_name = {} # type:Dict[str, int] + self._parse_version_url_per_machine = {} # type:Dict[int, Callable] + self._check_urls_per_machine = {} # type:Dict[int, List[str]] + self._redirect_user_per_machine = {} # type:Dict[int, str] try: for machine_json in machines_json: machine_id = machine_json.get("id") @@ -53,20 +55,20 @@ class FirmwareUpdateCheckerLookup: for check_url in machine_json.get("check_urls"): self._check_urls_per_machine[machine_id].append(check_url) self._redirect_user_per_machine[machine_id] = machine_json.get("update_url") - except: - Logger.log('e', "Couldn't parse firmware-update-check loopup-lists from file.") + except Exception as ex: + Logger.log('e', "Couldn't parse firmware-update-check loopup-lists from file because {0}.".format(ex)) - def getMachineIds(self) -> [int]: + def getMachineIds(self) -> List[int]: return self._machine_ids - def getMachineByName(self, machine_name: str) -> int: + def getMachineByName(self, machine_name: str) -> Optional[int]: return self._machine_per_name.get(machine_name) - def getParseVersionUrlFor(self, machine_id: int) -> str: + def getParseVersionUrlFor(self, machine_id: int) -> Optional[Callable]: return self._parse_version_url_per_machine.get(machine_id) - def getCheckUrlsFor(self, machine_id: int) -> [str]: + def getCheckUrlsFor(self, machine_id: int) -> Optional[List[str]]: return self._check_urls_per_machine.get(machine_id) - def getRedirectUserFor(self, machine_id: int) -> str: + def getRedirectUserFor(self, machine_id: int) -> Optional[str]: return self._redirect_user_per_machine.get(machine_id) From 69cef98c3041244bc9edb77ffe3f8c85f517ba19 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 12 Oct 2018 10:11:46 +0200 Subject: [PATCH 70/97] FirmwareUpdateChecker: Small fixes (typing and lowercase input). --- plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py | 5 ++--- plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index d186cbb4e4..09be95597b 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -39,9 +39,8 @@ class FirmwareUpdateCheckerJob(Job): try: request = urllib.request.Request(url, headers=self._headers) - current_version_file = urllib.request.urlopen(request) - reader = codecs.getreader("utf-8") - result = reader(current_version_file).read(firstline=True) + response = urllib.request.urlopen(request) + result = response.read().decode('utf-8') except URLError: Logger.log('w', "Could not reach '{0}', if this URL is old, consider removal.".format(url)) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index f6d7a24da0..2e97a8869d 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -42,7 +42,7 @@ class FirmwareUpdateCheckerLookup: try: for machine_json in machines_json: machine_id = machine_json.get("id") - machine_name = machine_json.get("name") + machine_name = machine_json.get("name").lower() # Lower in case upper-case char are added to the json. self._machine_ids.append(machine_id) self._machine_per_name[machine_name] = machine_id version_parse_function = \ From f7bef851db0ab01d2bbd832ab3b466e17049661f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 11:09:46 +0200 Subject: [PATCH 71/97] Remove code duplication for recreate network timer --- cura/PrinterOutput/NetworkedPrinterOutputDevice.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py index d9c5707a03..f7c7f5d233 100644 --- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py +++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py @@ -130,9 +130,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): # We need to check if the manager needs to be re-created. If we don't, we get some issues when OSX goes to # sleep. if time_since_last_response > self._recreate_network_manager_time: - if self._last_manager_create_time is None: - self._createNetworkManager() - elif time() - self._last_manager_create_time > self._recreate_network_manager_time: + if self._last_manager_create_time is None or time() - self._last_manager_create_time > self._recreate_network_manager_time: self._createNetworkManager() assert(self._manager is not None) elif self._connection_state == ConnectionState.closed: From ad80ea6dd47fdff1a67db98a66afa9e29202f031 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Fri, 12 Oct 2018 11:16:24 +0200 Subject: [PATCH 72/97] fix: limit to extruder of top/bottom polygon connector --- resources/definitions/fdmprinter.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 305d841175..9da27f5040 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -1221,7 +1221,7 @@ "type": "bool", "default_value": false, "enabled": "(top_layers > 0 or bottom_layers > 0) and top_bottom_pattern == 'concentric'", - "limit_to_extruder": "infill_extruder_nr", + "limit_to_extruder": "top_bottom_extruder_nr", "settable_per_mesh": true }, "skin_angles": From 3c626453a69c3c66c76540d8ca2035a396c58b74 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 11:28:13 +0200 Subject: [PATCH 73/97] Fix spelling --- resources/definitions/fdmprinter.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 9da27f5040..f17dd63c0a 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -1217,7 +1217,7 @@ "connect_skin_polygons": { "label": "Connect Top/Bottom Polygons", - "description": "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happend midway over infill this feature can reduce the top surface quality.", + "description": "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happen midway over infill this feature can reduce the top surface quality.", "type": "bool", "default_value": false, "enabled": "(top_layers > 0 or bottom_layers > 0) and top_bottom_pattern == 'concentric'", From 85b835118dc91d829f7f5c31eb2b6b254f023d92 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 13:24:09 +0200 Subject: [PATCH 74/97] Log which firmware file you're about to upload Kind of critical information, really. Contributes to issue CURA-5749. --- plugins/USBPrinting/USBPrinterOutputDevice.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 36c5321180..769820d6d0 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -59,9 +59,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._all_baud_rates = [115200, 250000, 230400, 57600, 38400, 19200, 9600] # Instead of using a timer, we really need the update to be as a thread, as reading from serial can block. - self._update_thread = Thread(target=self._update, daemon = True) + self._update_thread = Thread(target = self._update, daemon = True) - self._update_firmware_thread = Thread(target=self._updateFirmware, daemon = True) + self._update_firmware_thread = Thread(target = self._updateFirmware, daemon = True) self._last_temperature_request = None # type: Optional[int] @@ -160,6 +160,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): if self._connection_state != ConnectionState.closed: self.close() + Logger.log("i", "Uploading hex file from: {firmware_location}".format(firmware_location = self._firmware_location)) try: hex_file = intelHex.readHex(self._firmware_location) assert len(hex_file) > 0 From 287689a073befd0d4da7612d26810410b1a03ae0 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 13:25:34 +0200 Subject: [PATCH 75/97] Don't cache the automatic firmware name The QML property was not updated when you change the printer. By not caching it, it gets the current printer's firmware file upon clicking the button. Simple and effective, and not that tough on computational power that it needs caching. Contributes to issue CURA-5749. --- .../UltimakerMachineActions/UpgradeFirmwareMachineAction.qml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml index ed771d2a04..fff7d2c46f 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Ultimaker B.V. +// Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.2 @@ -58,7 +58,6 @@ Cura.MachineAction anchors.horizontalCenter: parent.horizontalCenter width: childrenRect.width spacing: UM.Theme.getSize("default_margin").width - property var firmwareName: Cura.USBPrinterManager.getDefaultFirmwareName() Button { id: autoUpgradeButton @@ -66,7 +65,7 @@ Cura.MachineAction enabled: parent.firmwareName != "" && activeOutputDevice onClicked: { - activeOutputDevice.updateFirmware(parent.firmwareName) + activeOutputDevice.updateFirmware(Cura.USBPrinterManager.getDefaultFirmwareName()) } } Button From 99fc372b32058287e4113c211231458f1d129fae Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 14:55:13 +0200 Subject: [PATCH 76/97] Update printer information when switching global container stacks This was just evaluated once during the creating of a USB connection. But you can switch out the printer without breaking/making a USB connection, so in that case we have to update it here. Contributes to issue CURA-5749. --- cura/PrinterOutput/PrinterOutputModel.py | 2 +- plugins/USBPrinting/USBPrinterOutputDevice.py | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index f009a33178..cc9463baec 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -172,7 +172,7 @@ class PrinterOutputModel(QObject): def getController(self): return self._controller - @pyqtProperty(str, notify=nameChanged) + @pyqtProperty(str, notify = nameChanged) def name(self): return self._name diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 769820d6d0..b61a62adc0 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -273,14 +273,18 @@ class USBPrinterOutputDevice(PrinterOutputDevice): except SerialException: Logger.log("w", "An exception occured while trying to create serial connection") return - container_stack = CuraApplication.getInstance().getGlobalContainerStack() - num_extruders = container_stack.getProperty("machine_extruder_count", "value") - # Ensure that a printer is created. - self._printers = [PrinterOutputModel(output_controller=GenericOutputController(self), number_of_extruders=num_extruders)] - self._printers[0].updateName(container_stack.getName()) + CuraApplication.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) + self._onGlobalContainerStackChanged() self.setConnectionState(ConnectionState.connected) self._update_thread.start() + def _onGlobalContainerStackChanged(self): + container_stack = CuraApplication.getInstance().getGlobalContainerStack() + num_extruders = container_stack.getProperty("machine_extruder_count", "value") + #Ensure that a printer is created. + self._printers = [PrinterOutputModel(output_controller = GenericOutputController(self), number_of_extruders = num_extruders)] + self._printers[0].updateName(container_stack.getName()) + def close(self): super().close() if self._serial is not None: From 9e4fcd820eaf67cf431c2e2fe9a3957d6c14d4f9 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 14:56:27 +0200 Subject: [PATCH 77/97] Update outputDevice when the global container changed And directly link the active printer name to it, so that that also gets updated. With the property var it just gets evaluated upon creating the rectangle. Contributes to issue CURA-5749. --- resources/qml/PrintMonitor.qml | 2 +- resources/qml/PrinterOutput/OutputDeviceHeader.qml | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/resources/qml/PrintMonitor.qml b/resources/qml/PrintMonitor.qml index 3bfcea7025..12e95d1e89 100644 --- a/resources/qml/PrintMonitor.qml +++ b/resources/qml/PrintMonitor.qml @@ -44,7 +44,7 @@ Column Repeater { id: extrudersRepeater - model: activePrinter!=null ? activePrinter.extruders : null + model: activePrinter != null ? activePrinter.extruders : null ExtruderBox { diff --git a/resources/qml/PrinterOutput/OutputDeviceHeader.qml b/resources/qml/PrinterOutput/OutputDeviceHeader.qml index 03e6d78699..b5ed1b7b4e 100644 --- a/resources/qml/PrinterOutput/OutputDeviceHeader.qml +++ b/resources/qml/PrinterOutput/OutputDeviceHeader.qml @@ -14,11 +14,19 @@ Item implicitHeight: Math.floor(childrenRect.height + UM.Theme.getSize("default_margin").height * 2) property var outputDevice: null + Connections + { + target: Cura.MachineManager + onGlobalContainerChanged: + { + outputDevice = Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null; + } + } + Rectangle { height: childrenRect.height color: UM.Theme.getColor("setting_category") - property var activePrinter: outputDevice != null ? outputDevice.activePrinter : null Label { @@ -28,7 +36,7 @@ Item anchors.left: parent.left anchors.top: parent.top anchors.margins: UM.Theme.getSize("default_margin").width - text: outputDevice != null ? activePrinter.name : "" + text: outputDevice != null ? outputDevice.activePrinter.name : "" } Label From 6ac10db58248a3e0492b112ab816f39a9278a24f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 15:37:43 +0200 Subject: [PATCH 78/97] Code style: Use double quotes for strings Contributes to issue CURA-5483. --- .../FirmwareUpdateChecker.py | 8 ++++---- .../FirmwareUpdateCheckerJob.py | 12 ++++++------ .../FirmwareUpdateCheckerLookup.py | 16 ++++++++-------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 71bdd0bc23..e030d8f796 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import os @@ -31,7 +31,7 @@ class FirmwareUpdateChecker(Extension): super().__init__() # Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the - # 'check for updates' option + # "check for updates" option Application.getInstance().getPreferences().addPreference("info/automatic_update_check", True) if Application.getInstance().getPreferences().getValue("info/automatic_update_check"): ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) @@ -49,9 +49,9 @@ class FirmwareUpdateChecker(Extension): if download_url is not None: QDesktopServices.openUrl(QUrl(download_url)) else: - Logger.log('e', "Can't find URL for {0}".format(action)) + Logger.log("e", "Can't find URL for {0}".format(action)) except Exception as ex: - Logger.log('e', "Don't know what to do with '{0}' because {1}".format(action, ex)) + Logger.log("e", "Don't know what to do with '{0}' because {1}".format(action, ex)) def _onContainerAdded(self, container): # Only take care when a new GlobalStack was added diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 09be95597b..41cc2358c1 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -40,9 +40,9 @@ class FirmwareUpdateCheckerJob(Job): try: request = urllib.request.Request(url, headers=self._headers) response = urllib.request.urlopen(request) - result = response.read().decode('utf-8') + result = response.read().decode("utf-8") except URLError: - Logger.log('w', "Could not reach '{0}', if this URL is old, consider removal.".format(url)) + Logger.log("w", "Could not reach '{0}', if this URL is old, consider removal.".format(url)) return result @@ -58,7 +58,7 @@ class FirmwareUpdateCheckerJob(Job): max_version = version if max_version < self.EPSILON_VERSION: - Logger.log('w', "MachineID {0} not handled!".format(repr(machine_id))) + Logger.log("w", "MachineID {0} not handled!".format(repr(machine_id))) return max_version @@ -82,11 +82,11 @@ class FirmwareUpdateCheckerJob(Job): current_version = self.getCurrentVersionForMachine(machine_id) - # If it is the first time the version is checked, the checked_version is '' + # If it is the first time the version is checked, the checked_version is "" setting_key_str = get_settings_key_for_machine(machine_id) checked_version = Version(Application.getInstance().getPreferences().getValue(setting_key_str)) - # If the checked_version is '', it's because is the first time we check firmware and in this case + # If the checked_version is "", it's because is the first time we check firmware and in this case # we will not show the notification, but we will store it for the next time Application.getInstance().getPreferences().setValue(setting_key_str, current_version) Logger.log("i", "Reading firmware version of %s: checked = %s - latest = %s", machine_name, checked_version, current_version) @@ -115,7 +115,7 @@ class FirmwareUpdateCheckerJob(Job): message.actionTriggered.connect(self._callback) message.show() else: - Logger.log('i', "No machine with name {0} in list of firmware to check.".format(machine_name)) + Logger.log("i", "No machine with name {0} in list of firmware to check.".format(machine_name)) except Exception as e: Logger.log("w", "Failed to check for new version: %s", e) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index 2e97a8869d..ec8e7cc073 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -17,23 +17,23 @@ def get_settings_key_for_machine(machine_id: int) -> str: def default_parse_version_response(response: str) -> Version: - raw_str = response.split('\n', 1)[0].rstrip() - return Version(raw_str.split('.')) # Split it into a list; the default parsing of 'single string' is different. + raw_str = response.split("\n", 1)[0].rstrip() + return Version(raw_str.split(".")) # Split it into a list; the default parsing of "single string" is different. class FirmwareUpdateCheckerLookup: JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": default_parse_version_response} def __init__(self, json_path) -> None: - # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve 'raw' json. + # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve "raw" json. machines_json = None with open(json_path, "r", encoding="utf-8") as json_file: machines_json = json.load(json_file).get("machines") if machines_json is None: - Logger.log('e', "Missing or inaccessible: {0}".format(json_path)) + Logger.log("e", "Missing or inaccessible: {0}".format(json_path)) return - # Parse all the needed lookup-tables from the '.json' file(s) in the resources folder. + # Parse all the needed lookup-tables from the ".json" file(s) in the resources folder. self._machine_ids = [] # type:List[int] self._machine_per_name = {} # type:Dict[str, int] self._parse_version_url_per_machine = {} # type:Dict[int, Callable] @@ -48,15 +48,15 @@ class FirmwareUpdateCheckerLookup: version_parse_function = \ self.JSON_NAME_TO_VERSION_PARSE_FUNCTION.get(machine_json.get("version_parser")) if version_parse_function is None: - Logger.log('w', "No version-parse-function specified for machine {0}.".format(machine_name)) + Logger.log("w", "No version-parse-function specified for machine {0}.".format(machine_name)) version_parse_function = default_parse_version_response # Use default instead if nothing is found. self._parse_version_url_per_machine[machine_id] = version_parse_function - self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see '_comment' in the .json file. + self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see "_comment" in the .json file. for check_url in machine_json.get("check_urls"): self._check_urls_per_machine[machine_id].append(check_url) self._redirect_user_per_machine[machine_id] = machine_json.get("update_url") except Exception as ex: - Logger.log('e', "Couldn't parse firmware-update-check loopup-lists from file because {0}.".format(ex)) + Logger.log("e", "Couldn't parse firmware-update-check loopup-lists from file because {0}.".format(ex)) def getMachineIds(self) -> List[int]: return self._machine_ids From e3b05f086740b0faba00d13a21ab5d3a23a0c224 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 16:46:39 +0200 Subject: [PATCH 79/97] Code style: Spaces around binary operators Also removed the unused machines_json value. Contributes to issue CURA-5483. --- .../FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py | 10 +++++----- .../FirmwareUpdateCheckerLookup.py | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 41cc2358c1..ee5eaac25b 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -38,7 +38,7 @@ class FirmwareUpdateCheckerJob(Job): result = self.STRING_ZERO_VERSION try: - request = urllib.request.Request(url, headers=self._headers) + request = urllib.request.Request(url, headers = self._headers) response = urllib.request.urlopen(request) result = response.read().decode("utf-8") except URLError: @@ -100,8 +100,8 @@ class FirmwareUpdateCheckerJob(Job): message = Message(i18n_catalog.i18nc( "@info Don't translate {machine_name}, since it gets replaced by a printer name!", "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format( - machine_name=machine_name), - title=i18n_catalog.i18nc( + machine_name = machine_name), + title = i18n_catalog.i18nc( "@info:title The %s gets replaced with the printer name.", "New %s firmware available") % machine_name) @@ -109,8 +109,8 @@ class FirmwareUpdateCheckerJob(Job): i18n_catalog.i18nc("@action:button", "How to update"), "[no_icon]", "[no_description]", - button_style=Message.ActionButtonStyle.LINK, - button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) + button_style = Message.ActionButtonStyle.LINK, + button_align = Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) message.actionTriggered.connect(self._callback) message.show() diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index ec8e7cc073..e283d58b2b 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -26,8 +26,7 @@ class FirmwareUpdateCheckerLookup: def __init__(self, json_path) -> None: # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve "raw" json. - machines_json = None - with open(json_path, "r", encoding="utf-8") as json_file: + with open(json_path, "r", encoding = "utf-8") as json_file: machines_json = json.load(json_file).get("machines") if machines_json is None: Logger.log("e", "Missing or inaccessible: {0}".format(json_path)) From 1b7055f0f39f339f8bd1d4801203732ed8b1d318 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 17:03:48 +0200 Subject: [PATCH 80/97] Fix spelling of error message Loopup -> Lookup. Contributes to issue CURA-5483. --- plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index e283d58b2b..6d96ee36bb 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -55,7 +55,7 @@ class FirmwareUpdateCheckerLookup: self._check_urls_per_machine[machine_id].append(check_url) self._redirect_user_per_machine[machine_id] = machine_json.get("update_url") except Exception as ex: - Logger.log("e", "Couldn't parse firmware-update-check loopup-lists from file because {0}.".format(ex)) + Logger.log("e", "Couldn't parse firmware-update-check lookup-lists from file because {0}.".format(ex)) def getMachineIds(self) -> List[int]: return self._machine_ids From 6cf2e89f6b37b5d077d20b8e8dae2dd81c97c18c Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Sat, 13 Oct 2018 16:40:26 +0200 Subject: [PATCH 81/97] Document CameraImageProvider Makes it easier than looking up the Qt documentation online. --- cura/CameraImageProvider.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/cura/CameraImageProvider.py b/cura/CameraImageProvider.py index 6a07f6b029..edb0f205c7 100644 --- a/cura/CameraImageProvider.py +++ b/cura/CameraImageProvider.py @@ -1,15 +1,26 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + from PyQt5.QtGui import QImage from PyQt5.QtQuick import QQuickImageProvider from PyQt5.QtCore import QSize from UM.Application import Application - +## Creates screenshots of the current scene. class CameraImageProvider(QQuickImageProvider): def __init__(self): super().__init__(QQuickImageProvider.Image) ## Request a new image. + # + # The image will be taken using the current camera position. + # Only the actual objects in the scene will get rendered. Not the build + # plate and such! + # \param id The ID for the image to create. This is the requested image + # source, with the "image:" scheme and provider identifier removed. It's + # a Qt thing, they'll provide this parameter. + # \param size The dimensions of the image to scale to. def requestImage(self, id, size): for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices(): try: From 60408c14bcac5aac9ac8b623f1d455b06032cd09 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Sat, 13 Oct 2018 19:21:22 +0200 Subject: [PATCH 82/97] FirmwareUpdateChecker: Small refactors due to code review. --- .../FirmwareUpdateChecker.py | 33 ++++++++++--------- .../FirmwareUpdateCheckerJob.py | 6 ++-- .../FirmwareUpdateCheckerLookup.py | 10 +++--- .../resources/machines.json | 2 +- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index e030d8f796..61604ff78b 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -5,19 +5,20 @@ import os from PyQt5.QtCore import QUrl from PyQt5.QtGui import QDesktopServices -from typing import List +from typing import Set from UM.Extension import Extension from UM.Application import Application from UM.Logger import Logger from UM.PluginRegistry import PluginRegistry +from UM.Qt.QtApplication import QtApplication from UM.i18n import i18nCatalog from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob -from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, get_settings_key_for_machine +from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine i18n_catalog = i18nCatalog("cura") @@ -36,22 +37,23 @@ class FirmwareUpdateChecker(Extension): if Application.getInstance().getPreferences().getValue("info/automatic_update_check"): ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) - self._late_init = True # Init some things after creation, since we need the path from the plugin-mgr. + # Partly initialize after creation, since we need our own path from the plugin-manager. self._download_url = None self._check_job = None - self._name_cache = [] # type: List[str] + self._checked_printer_names = [] # type: Set[str] self._lookups = None + QtApplication.pluginsLoaded.connect(self._onPluginsLoaded) ## Callback for the message that is spawned when there is a new version. def _onActionTriggered(self, message, action): - try: download_url = self._lookups.getRedirectUserFor(int(action)) if download_url is not None: - QDesktopServices.openUrl(QUrl(download_url)) + if QDesktopServices.openUrl(QUrl(download_url)): + Logger.log("i", "Redirected browser to {0} to show newly available firmware.".format(download_url)) + else: + Logger.log("e", "Can't reach URL: {0}".format(download_url)) else: Logger.log("e", "Can't find URL for {0}".format(action)) - except Exception as ex: - Logger.log("e", "Don't know what to do with '{0}' because {1}".format(action, ex)) def _onContainerAdded(self, container): # Only take care when a new GlobalStack was added @@ -61,8 +63,9 @@ class FirmwareUpdateChecker(Extension): def _onJobFinished(self, *args, **kwargs): self._check_job = None - def doLateInit(self): - self._late_init = False + def _onPluginsLoaded(self): + if self._lookups is not None: + return self._lookups = FirmwareUpdateCheckerLookup(os.path.join(PluginRegistry.getInstance().getPluginPath( "FirmwareUpdateChecker"), "resources/machines.json")) @@ -70,7 +73,7 @@ class FirmwareUpdateChecker(Extension): # Initialize the Preference called `latest_checked_firmware` that stores the last version # checked for each printer. for machine_id in self._lookups.getMachineIds(): - Application.getInstance().getPreferences().addPreference(get_settings_key_for_machine(machine_id), "") + Application.getInstance().getPreferences().addPreference(getSettingsKeyForMachine(machine_id), "") ## Connect with software.ultimaker.com, load latest.version and check version info. # If the version info is different from the current version, spawn a message to @@ -79,13 +82,13 @@ class FirmwareUpdateChecker(Extension): # \param silent type(boolean) Suppresses messages other than "new version found" messages. # This is used when checking for a new firmware version at startup. def checkFirmwareVersion(self, container = None, silent = False): - if self._late_init: - self.doLateInit() + if self._lookups is None: + self._onPluginsLoaded() container_name = container.definition.getName() - if container_name in self._name_cache: + if container_name in self._checked_printer_names: return - self._name_cache.append(container_name) + self._checked_printer_names.append(container_name) self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, lookups = self._lookups, diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index ee5eaac25b..5bb9d076b6 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -12,7 +12,7 @@ from urllib.error import URLError from typing import Dict import codecs -from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, get_settings_key_for_machine +from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") @@ -78,12 +78,12 @@ class FirmwareUpdateCheckerJob(Job): # If it is not None, then we compare between the checked_version and the current_version machine_id = self._lookups.getMachineByName(machine_name.lower()) if machine_id is not None: - Logger.log("i", "You have a {0} in the printer list. Let's check the firmware!".format(machine_name)) + Logger.log("i", "You have a(n) {0} in the printer list. Let's check the firmware!".format(machine_name)) current_version = self.getCurrentVersionForMachine(machine_id) # If it is the first time the version is checked, the checked_version is "" - setting_key_str = get_settings_key_for_machine(machine_id) + setting_key_str = getSettingsKeyForMachine(machine_id) checked_version = Version(Application.getInstance().getPreferences().getValue(setting_key_str)) # If the checked_version is "", it's because is the first time we check firmware and in this case diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index 6d96ee36bb..ceecef61ba 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -12,17 +12,17 @@ from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") -def get_settings_key_for_machine(machine_id: int) -> str: +def getSettingsKeyForMachine(machine_id: int) -> str: return "info/latest_checked_firmware_for_{0}".format(machine_id) -def default_parse_version_response(response: str) -> Version: +def defaultParseVersionResponse(response: str) -> Version: raw_str = response.split("\n", 1)[0].rstrip() - return Version(raw_str.split(".")) # Split it into a list; the default parsing of "single string" is different. + return Version(raw_str) class FirmwareUpdateCheckerLookup: - JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": default_parse_version_response} + JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": defaultParseVersionResponse} def __init__(self, json_path) -> None: # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve "raw" json. @@ -48,7 +48,7 @@ class FirmwareUpdateCheckerLookup: self.JSON_NAME_TO_VERSION_PARSE_FUNCTION.get(machine_json.get("version_parser")) if version_parse_function is None: Logger.log("w", "No version-parse-function specified for machine {0}.".format(machine_name)) - version_parse_function = default_parse_version_response # Use default instead if nothing is found. + version_parse_function = defaultParseVersionResponse # Use default instead if nothing is found. self._parse_version_url_per_machine[machine_id] = version_parse_function self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see "_comment" in the .json file. for check_url in machine_json.get("check_urls"): diff --git a/plugins/FirmwareUpdateChecker/resources/machines.json b/plugins/FirmwareUpdateChecker/resources/machines.json index 5dc9aadbbf..ee072f75c3 100644 --- a/plugins/FirmwareUpdateChecker/resources/machines.json +++ b/plugins/FirmwareUpdateChecker/resources/machines.json @@ -1,5 +1,5 @@ { - "_comment": "Multiple 'update_urls': The 'new'-style URL is the only way to check for S5 firmware, and in the future, the UM3 line will also switch over, but for now the old 'JEDI'-style URL is still needed.", + "_comment": "There are multiple 'check_urls', because sometimes an URL is about to be phased out, and it's useful to have a new 'future-proof' one at the ready.", "machines": [ From 8c71a8855c9f80ce0beb5daad6bd643633db010b Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Sat, 13 Oct 2018 19:36:11 +0200 Subject: [PATCH 83/97] FirmwareUpdateChecker: Remove superfluous 'version_parser' as a setting, since it broke lean principles. --- .../FirmwareUpdateCheckerJob.py | 9 ++++++--- .../FirmwareUpdateCheckerLookup.py | 12 ------------ .../FirmwareUpdateChecker/resources/machines.json | 9 +++------ 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 5bb9d076b6..a873f17d61 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -46,14 +46,17 @@ class FirmwareUpdateCheckerJob(Job): return result + def parseVersionResponse(self, response: str) -> Version: + raw_str = response.split("\n", 1)[0].rstrip() + return Version(raw_str) + def getCurrentVersionForMachine(self, machine_id: int) -> Version: max_version = self.ZERO_VERSION machine_urls = self._lookups.getCheckUrlsFor(machine_id) - parse_function = self._lookups.getParseVersionUrlFor(machine_id) - if machine_urls is not None and parse_function is not None: + if machine_urls is not None: for url in machine_urls: - version = parse_function(self.getUrlResponse(url)) + version = self.parseVersionResponse(self.getUrlResponse(url)) if version > max_version: max_version = version diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index ceecef61ba..4813e3ecbb 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -16,13 +16,7 @@ def getSettingsKeyForMachine(machine_id: int) -> str: return "info/latest_checked_firmware_for_{0}".format(machine_id) -def defaultParseVersionResponse(response: str) -> Version: - raw_str = response.split("\n", 1)[0].rstrip() - return Version(raw_str) - - class FirmwareUpdateCheckerLookup: - JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": defaultParseVersionResponse} def __init__(self, json_path) -> None: # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve "raw" json. @@ -44,12 +38,6 @@ class FirmwareUpdateCheckerLookup: machine_name = machine_json.get("name").lower() # Lower in case upper-case char are added to the json. self._machine_ids.append(machine_id) self._machine_per_name[machine_name] = machine_id - version_parse_function = \ - self.JSON_NAME_TO_VERSION_PARSE_FUNCTION.get(machine_json.get("version_parser")) - if version_parse_function is None: - Logger.log("w", "No version-parse-function specified for machine {0}.".format(machine_name)) - version_parse_function = defaultParseVersionResponse # Use default instead if nothing is found. - self._parse_version_url_per_machine[machine_id] = version_parse_function self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see "_comment" in the .json file. for check_url in machine_json.get("check_urls"): self._check_urls_per_machine[machine_id].append(check_url) diff --git a/plugins/FirmwareUpdateChecker/resources/machines.json b/plugins/FirmwareUpdateChecker/resources/machines.json index ee072f75c3..d9eaad0abf 100644 --- a/plugins/FirmwareUpdateChecker/resources/machines.json +++ b/plugins/FirmwareUpdateChecker/resources/machines.json @@ -11,8 +11,7 @@ "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", "http://software.ultimaker.com/releases/firmware/9066/stable/version.txt" ], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware", - "version_parser": "default" + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" }, { "id": 9511, @@ -22,15 +21,13 @@ "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", "http://software.ultimaker.com/releases/firmware/9511/stable/version.txt" ], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware", - "version_parser": "default" + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" }, { "id": 9051, "name": "ultimaker s5", "check_urls": ["http://software.ultimaker.com/releases/firmware/9051/stable/version.txt"], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware", - "version_parser": "default" + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" } ] } From 931143ceaabe02f88bd4f0eca7357ed494733c0c Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Sat, 13 Oct 2018 20:05:20 +0200 Subject: [PATCH 84/97] Added FirmwareUpdateCheckerMessage, so no variables have to be hidden in the action of a plain Message. --- .../FirmwareUpdateChecker.py | 11 ++++--- .../FirmwareUpdateCheckerJob.py | 19 ++---------- .../FirmwareUpdateCheckerLookup.py | 1 - .../FirmwareUpdateCheckerMessage.py | 31 +++++++++++++++++++ 4 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 61604ff78b..8c0ea1bea2 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -19,6 +19,7 @@ from cura.Settings.GlobalStack import GlobalStack from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine +from .FirmwareUpdateCheckerMessage import FirmwareUpdateCheckerMessage i18n_catalog = i18nCatalog("cura") @@ -40,20 +41,22 @@ class FirmwareUpdateChecker(Extension): # Partly initialize after creation, since we need our own path from the plugin-manager. self._download_url = None self._check_job = None - self._checked_printer_names = [] # type: Set[str] + self._checked_printer_names = set() # type: Set[str] self._lookups = None QtApplication.pluginsLoaded.connect(self._onPluginsLoaded) ## Callback for the message that is spawned when there is a new version. def _onActionTriggered(self, message, action): - download_url = self._lookups.getRedirectUserFor(int(action)) + if action == FirmwareUpdateCheckerMessage.STR_ACTION_DOWNLOAD: + machine_id = message.getMachineId() + download_url = self._lookups.getRedirectUserFor(machine_id) if download_url is not None: if QDesktopServices.openUrl(QUrl(download_url)): Logger.log("i", "Redirected browser to {0} to show newly available firmware.".format(download_url)) else: Logger.log("e", "Can't reach URL: {0}".format(download_url)) else: - Logger.log("e", "Can't find URL for {0}".format(action)) + Logger.log("e", "Can't find URL for {0}".format(machine_id)) def _onContainerAdded(self, container): # Only take care when a new GlobalStack was added @@ -88,7 +91,7 @@ class FirmwareUpdateChecker(Extension): container_name = container.definition.getName() if container_name in self._checked_printer_names: return - self._checked_printer_names.append(container_name) + self._checked_printer_names.add(container_name) self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, lookups = self._lookups, diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index a873f17d61..f39f4c8cea 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -10,9 +10,9 @@ from UM.Version import Version import urllib.request from urllib.error import URLError from typing import Dict -import codecs from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine +from .FirmwareUpdateCheckerMessage import FirmwareUpdateCheckerMessage from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") @@ -99,22 +99,7 @@ class FirmwareUpdateCheckerJob(Job): # notify the user when no new firmware version is available. if (checked_version != "") and (checked_version != current_version): Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE") - - message = Message(i18n_catalog.i18nc( - "@info Don't translate {machine_name}, since it gets replaced by a printer name!", - "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format( - machine_name = machine_name), - title = i18n_catalog.i18nc( - "@info:title The %s gets replaced with the printer name.", - "New %s firmware available") % machine_name) - - message.addAction(machine_id, - i18n_catalog.i18nc("@action:button", "How to update"), - "[no_icon]", - "[no_description]", - button_style = Message.ActionButtonStyle.LINK, - button_align = Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) - + message = FirmwareUpdateCheckerMessage(machine_id, machine_name) message.actionTriggered.connect(self._callback) message.show() else: diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index 4813e3ecbb..ff4e9ce73d 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -6,7 +6,6 @@ import json from typing import Callable, Dict, List, Optional from UM.Logger import Logger -from UM.Version import Version from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py new file mode 100644 index 0000000000..0f13796c29 --- /dev/null +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py @@ -0,0 +1,31 @@ + +from UM.i18n import i18nCatalog +from UM.Message import Message + +i18n_catalog = i18nCatalog("cura") + + +# Make a separate class, since we need an extra field: The machine-id that this message is about. +class FirmwareUpdateCheckerMessage(Message): + STR_ACTION_DOWNLOAD = "download" + + def __init__(self, machine_id: int, machine_name: str) -> None: + super().__init__(i18n_catalog.i18nc( + "@info Don't translate {machine_name}, since it gets replaced by a printer name!", + "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format( + machine_name=machine_name), + title=i18n_catalog.i18nc( + "@info:title The %s gets replaced with the printer name.", + "New %s firmware available") % machine_name) + + self._machine_id = machine_id + + self.addAction(self.STR_ACTION_DOWNLOAD, + i18n_catalog.i18nc("@action:button", "How to update"), + "[no_icon]", + "[no_description]", + button_style=Message.ActionButtonStyle.LINK, + button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) + + def getMachineId(self) -> int: + return self._machine_id From 2e3abbc9044c82e5dc858f52e34d027b0cbee10c Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Sat, 13 Oct 2018 21:55:33 +0200 Subject: [PATCH 85/97] Put the firmware-update meta-data in the 'normal' printer definitions and make the code handle that. --- .../FirmwareUpdateChecker.py | 31 +++------- .../FirmwareUpdateCheckerJob.py | 30 ++++++---- .../FirmwareUpdateCheckerLookup.py | 57 ++++++------------- .../FirmwareUpdateCheckerMessage.py | 6 +- .../resources/machines.json | 33 ----------- resources/definitions/ultimaker3.def.json | 11 +++- .../definitions/ultimaker3_extended.def.json | 11 +++- resources/definitions/ultimaker_s5.def.json | 7 ++- 8 files changed, 73 insertions(+), 113 deletions(-) delete mode 100644 plugins/FirmwareUpdateChecker/resources/machines.json diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 8c0ea1bea2..415931b7ec 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -10,15 +10,12 @@ from typing import Set from UM.Extension import Extension from UM.Application import Application from UM.Logger import Logger -from UM.PluginRegistry import PluginRegistry -from UM.Qt.QtApplication import QtApplication from UM.i18n import i18nCatalog from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob -from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine from .FirmwareUpdateCheckerMessage import FirmwareUpdateCheckerMessage i18n_catalog = i18nCatalog("cura") @@ -38,18 +35,14 @@ class FirmwareUpdateChecker(Extension): if Application.getInstance().getPreferences().getValue("info/automatic_update_check"): ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) - # Partly initialize after creation, since we need our own path from the plugin-manager. - self._download_url = None self._check_job = None self._checked_printer_names = set() # type: Set[str] - self._lookups = None - QtApplication.pluginsLoaded.connect(self._onPluginsLoaded) ## Callback for the message that is spawned when there is a new version. def _onActionTriggered(self, message, action): if action == FirmwareUpdateCheckerMessage.STR_ACTION_DOWNLOAD: machine_id = message.getMachineId() - download_url = self._lookups.getRedirectUserFor(machine_id) + download_url = message.getDownloadUrl() if download_url is not None: if QDesktopServices.openUrl(QUrl(download_url)): Logger.log("i", "Redirected browser to {0} to show newly available firmware.".format(download_url)) @@ -66,18 +59,6 @@ class FirmwareUpdateChecker(Extension): def _onJobFinished(self, *args, **kwargs): self._check_job = None - def _onPluginsLoaded(self): - if self._lookups is not None: - return - - self._lookups = FirmwareUpdateCheckerLookup(os.path.join(PluginRegistry.getInstance().getPluginPath( - "FirmwareUpdateChecker"), "resources/machines.json")) - - # Initialize the Preference called `latest_checked_firmware` that stores the last version - # checked for each printer. - for machine_id in self._lookups.getMachineIds(): - Application.getInstance().getPreferences().addPreference(getSettingsKeyForMachine(machine_id), "") - ## Connect with software.ultimaker.com, load latest.version and check version info. # If the version info is different from the current version, spawn a message to # allow the user to download it. @@ -85,16 +66,18 @@ class FirmwareUpdateChecker(Extension): # \param silent type(boolean) Suppresses messages other than "new version found" messages. # This is used when checking for a new firmware version at startup. def checkFirmwareVersion(self, container = None, silent = False): - if self._lookups is None: - self._onPluginsLoaded() - container_name = container.definition.getName() if container_name in self._checked_printer_names: return self._checked_printer_names.add(container_name) + metadata = container.definition.getMetaData().get("firmware_update_info") + if metadata is None: + Logger.log("i", "No machine with name {0} in list of firmware to check.".format(container_name)) + return + self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, - lookups = self._lookups, + machine_name = container_name, metadata = metadata, callback = self._onActionTriggered) self._check_job.start() self._check_job.finished.connect(self._onJobFinished) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index f39f4c8cea..2e15208336 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -9,7 +9,7 @@ from UM.Version import Version import urllib.request from urllib.error import URLError -from typing import Dict +from typing import Dict, Optional from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine from .FirmwareUpdateCheckerMessage import FirmwareUpdateCheckerMessage @@ -25,13 +25,15 @@ class FirmwareUpdateCheckerJob(Job): ZERO_VERSION = Version(STRING_ZERO_VERSION) EPSILON_VERSION = Version(STRING_EPSILON_VERSION) - def __init__(self, container, silent, lookups: FirmwareUpdateCheckerLookup, callback) -> None: + def __init__(self, container, silent, machine_name, metadata, callback) -> None: super().__init__() self._container = container self.silent = silent self._callback = callback - self._lookups = lookups + self._machine_name = machine_name + self._metadata = metadata + self._lookups = None # type:Optional[FirmwareUpdateCheckerLookup] self._headers = {} # type:Dict[str, str] # Don't set headers yet. def getUrlResponse(self, url: str) -> str: @@ -50,10 +52,12 @@ class FirmwareUpdateCheckerJob(Job): raw_str = response.split("\n", 1)[0].rstrip() return Version(raw_str) - def getCurrentVersionForMachine(self, machine_id: int) -> Version: + def getCurrentVersion(self) -> Version: max_version = self.ZERO_VERSION + if self._lookups is None: + return max_version - machine_urls = self._lookups.getCheckUrlsFor(machine_id) + machine_urls = self._lookups.getCheckUrls() if machine_urls is not None: for url in machine_urls: version = self.parseVersionResponse(self.getUrlResponse(url)) @@ -61,16 +65,20 @@ class FirmwareUpdateCheckerJob(Job): max_version = version if max_version < self.EPSILON_VERSION: - Logger.log("w", "MachineID {0} not handled!".format(repr(machine_id))) + Logger.log("w", "MachineID {0} not handled!".format(self._lookups.getMachineName())) return max_version def run(self): if self._lookups is None: - Logger.log("e", "Can not check for a new release. URL not set!") - return + self._lookups = FirmwareUpdateCheckerLookup(self._machine_name, self._metadata) try: + # Initialize a Preference that stores the last version checked for this printer. + Application.getInstance().getPreferences().addPreference( + getSettingsKeyForMachine(self._lookups.getMachineId()), "") + + # Get headers application_name = Application.getInstance().getApplicationName() application_version = Application.getInstance().getVersion() self._headers = {"User-Agent": "%s - %s" % (application_name, application_version)} @@ -79,11 +87,11 @@ class FirmwareUpdateCheckerJob(Job): machine_name = self._container.definition.getName() # If it is not None, then we compare between the checked_version and the current_version - machine_id = self._lookups.getMachineByName(machine_name.lower()) + machine_id = self._lookups.getMachineId() if machine_id is not None: Logger.log("i", "You have a(n) {0} in the printer list. Let's check the firmware!".format(machine_name)) - current_version = self.getCurrentVersionForMachine(machine_id) + current_version = self.getCurrentVersion() # If it is the first time the version is checked, the checked_version is "" setting_key_str = getSettingsKeyForMachine(machine_id) @@ -99,7 +107,7 @@ class FirmwareUpdateCheckerJob(Job): # notify the user when no new firmware version is available. if (checked_version != "") and (checked_version != current_version): Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE") - message = FirmwareUpdateCheckerMessage(machine_id, machine_name) + message = FirmwareUpdateCheckerMessage(machine_id, machine_name, self._lookups.getRedirectUserUrl()) message.actionTriggered.connect(self._callback) message.show() else: diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index ff4e9ce73d..a21ad3f0e5 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -1,11 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import json - -from typing import Callable, Dict, List, Optional - -from UM.Logger import Logger +from typing import List, Optional from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") @@ -17,44 +13,23 @@ def getSettingsKeyForMachine(machine_id: int) -> str: class FirmwareUpdateCheckerLookup: - def __init__(self, json_path) -> None: - # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve "raw" json. - with open(json_path, "r", encoding = "utf-8") as json_file: - machines_json = json.load(json_file).get("machines") - if machines_json is None: - Logger.log("e", "Missing or inaccessible: {0}".format(json_path)) - return - + def __init__(self, machine_name, machine_json) -> None: # Parse all the needed lookup-tables from the ".json" file(s) in the resources folder. - self._machine_ids = [] # type:List[int] - self._machine_per_name = {} # type:Dict[str, int] - self._parse_version_url_per_machine = {} # type:Dict[int, Callable] - self._check_urls_per_machine = {} # type:Dict[int, List[str]] - self._redirect_user_per_machine = {} # type:Dict[int, str] - try: - for machine_json in machines_json: - machine_id = machine_json.get("id") - machine_name = machine_json.get("name").lower() # Lower in case upper-case char are added to the json. - self._machine_ids.append(machine_id) - self._machine_per_name[machine_name] = machine_id - self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see "_comment" in the .json file. - for check_url in machine_json.get("check_urls"): - self._check_urls_per_machine[machine_id].append(check_url) - self._redirect_user_per_machine[machine_id] = machine_json.get("update_url") - except Exception as ex: - Logger.log("e", "Couldn't parse firmware-update-check lookup-lists from file because {0}.".format(ex)) + self._machine_id = machine_json.get("id") + self._machine_name = machine_name.lower() # Lower in-case upper-case chars are added to the original json. + self._check_urls = [] # type:List[str] + for check_url in machine_json.get("check_urls"): + self._check_urls.append(check_url) + self._redirect_user = machine_json.get("update_url") - def getMachineIds(self) -> List[int]: - return self._machine_ids + def getMachineId(self) -> Optional[int]: + return self._machine_id - def getMachineByName(self, machine_name: str) -> Optional[int]: - return self._machine_per_name.get(machine_name) + def getMachineName(self) -> Optional[int]: + return self._machine_name - def getParseVersionUrlFor(self, machine_id: int) -> Optional[Callable]: - return self._parse_version_url_per_machine.get(machine_id) + def getCheckUrls(self) -> Optional[List[str]]: + return self._check_urls - def getCheckUrlsFor(self, machine_id: int) -> Optional[List[str]]: - return self._check_urls_per_machine.get(machine_id) - - def getRedirectUserFor(self, machine_id: int) -> Optional[str]: - return self._redirect_user_per_machine.get(machine_id) + def getRedirectUserUrl(self) -> Optional[str]: + return self._redirect_user diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py index 0f13796c29..d509c432b4 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py @@ -9,7 +9,7 @@ i18n_catalog = i18nCatalog("cura") class FirmwareUpdateCheckerMessage(Message): STR_ACTION_DOWNLOAD = "download" - def __init__(self, machine_id: int, machine_name: str) -> None: + def __init__(self, machine_id: int, machine_name: str, download_url: str) -> None: super().__init__(i18n_catalog.i18nc( "@info Don't translate {machine_name}, since it gets replaced by a printer name!", "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format( @@ -19,6 +19,7 @@ class FirmwareUpdateCheckerMessage(Message): "New %s firmware available") % machine_name) self._machine_id = machine_id + self._download_url = download_url self.addAction(self.STR_ACTION_DOWNLOAD, i18n_catalog.i18nc("@action:button", "How to update"), @@ -29,3 +30,6 @@ class FirmwareUpdateCheckerMessage(Message): def getMachineId(self) -> int: return self._machine_id + + def getDownloadUrl(self) -> str: + return self._download_url diff --git a/plugins/FirmwareUpdateChecker/resources/machines.json b/plugins/FirmwareUpdateChecker/resources/machines.json deleted file mode 100644 index d9eaad0abf..0000000000 --- a/plugins/FirmwareUpdateChecker/resources/machines.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "_comment": "There are multiple 'check_urls', because sometimes an URL is about to be phased out, and it's useful to have a new 'future-proof' one at the ready.", - - "machines": - [ - { - "id": 9066, - "name": "ultimaker 3", - "check_urls": - [ - "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", - "http://software.ultimaker.com/releases/firmware/9066/stable/version.txt" - ], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" - }, - { - "id": 9511, - "name": "ultimaker 3 extended", - "check_urls": - [ - "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", - "http://software.ultimaker.com/releases/firmware/9511/stable/version.txt" - ], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" - }, - { - "id": 9051, - "name": "ultimaker s5", - "check_urls": ["http://software.ultimaker.com/releases/firmware/9051/stable/version.txt"], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" - } - ] -} diff --git a/resources/definitions/ultimaker3.def.json b/resources/definitions/ultimaker3.def.json index b1daa6b780..f5e31890f6 100644 --- a/resources/definitions/ultimaker3.def.json +++ b/resources/definitions/ultimaker3.def.json @@ -24,7 +24,16 @@ }, "first_start_actions": [ "DiscoverUM3Action" ], "supported_actions": [ "DiscoverUM3Action" ], - "supports_usb_connection": false + "supports_usb_connection": false, + "firmware_update_info": { + "id": 9066, + "check_urls": + [ + "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", + "http://software.ultimaker.com/releases/firmware/9066/stable/version.txt" + ], + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" + } }, diff --git a/resources/definitions/ultimaker3_extended.def.json b/resources/definitions/ultimaker3_extended.def.json index eb3cda9320..d13857e428 100644 --- a/resources/definitions/ultimaker3_extended.def.json +++ b/resources/definitions/ultimaker3_extended.def.json @@ -23,7 +23,16 @@ "1": "ultimaker3_extended_extruder_right" }, "first_start_actions": [ "DiscoverUM3Action" ], - "supported_actions": [ "DiscoverUM3Action" ] + "supported_actions": [ "DiscoverUM3Action" ], + "firmware_update_info": { + "id": 9511, + "check_urls": + [ + "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", + "http://software.ultimaker.com/releases/firmware/9511/stable/version.txt" + ], + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" + } }, "overrides": { diff --git a/resources/definitions/ultimaker_s5.def.json b/resources/definitions/ultimaker_s5.def.json index 2e634787af..6195933869 100644 --- a/resources/definitions/ultimaker_s5.def.json +++ b/resources/definitions/ultimaker_s5.def.json @@ -30,7 +30,12 @@ "first_start_actions": [ "DiscoverUM3Action" ], "supported_actions": [ "DiscoverUM3Action" ], "supports_usb_connection": false, - "weight": -1 + "weight": -1, + "firmware_update_info": { + "id": 9051, + "check_urls": ["http://software.ultimaker.com/releases/firmware/9051/stable/version.txt"], + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" + } }, "overrides": { From e747219bbec338736eb8ce683e15cf1cfbb77511 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 15 Oct 2018 01:30:36 +0200 Subject: [PATCH 86/97] Let Makerbot Replicator use Replicator's X3G variant --- resources/definitions/makerbotreplicator.def.json | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/definitions/makerbotreplicator.def.json b/resources/definitions/makerbotreplicator.def.json index 1770b7a979..3b02215e74 100644 --- a/resources/definitions/makerbotreplicator.def.json +++ b/resources/definitions/makerbotreplicator.def.json @@ -6,6 +6,7 @@ "visible": true, "author": "Ultimaker", "manufacturer": "MakerBot", + "machine_x3g_variant": "r1", "file_formats": "application/x3g", "platform_offset": [ 0, 0, 0], "machine_extruder_trains": From 73de7209477ea675475da5f6bd753fb6deeebba3 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 15 Oct 2018 09:27:53 +0200 Subject: [PATCH 87/97] Change material diameter to 1.75 for monoprice machines CURA-5817 --- .../extruders/monoprice_select_mini_v1_extruder_0.def.json | 2 +- .../extruders/monoprice_select_mini_v2_extruder_0.def.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/extruders/monoprice_select_mini_v1_extruder_0.def.json b/resources/extruders/monoprice_select_mini_v1_extruder_0.def.json index eef47c9b6f..e4a899d7af 100644 --- a/resources/extruders/monoprice_select_mini_v1_extruder_0.def.json +++ b/resources/extruders/monoprice_select_mini_v1_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/monoprice_select_mini_v2_extruder_0.def.json b/resources/extruders/monoprice_select_mini_v2_extruder_0.def.json index e0899304dd..b727cfce1f 100644 --- a/resources/extruders/monoprice_select_mini_v2_extruder_0.def.json +++ b/resources/extruders/monoprice_select_mini_v2_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } From 02efd7a1f5a312ea4124a45025ac2dc3bdac99da Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 15 Oct 2018 13:55:47 +0200 Subject: [PATCH 88/97] Correct some printers to use 1.75mm filament This should fix some underextrusion problems... Hmm. Sources: * https://alya3dp.com/pages/teknik-ozellikler * https://www.creality3d.cn/creality-cr-10-s4-3d-printer-p00098p1.html * https://www.creality3d.cn/creality-cr-10-s5-3d-printer-p00099p1.html * https://3dprint.com/3643/german-repraps-neo-3d-printer-now-available-in-the-us-uk/ * https://somosmaker.com/pegasus-impresora-3d/ * http://www.3dmaker.vn/3d-printer-3dmaker-starter/?lang=en# (assuming the filaments they sell on that website are compatible) * https://makezine.com/product-review/printrbot-play/ I could not find a source for the Deltabot, but got that information from here: https://github.com/Ultimaker/Cura/issues/4573 Contributes to issue CURA-5817. --- resources/extruders/alya3dp_extruder_0.def.json | 2 +- resources/extruders/creality_cr10s4_extruder_0.def.json | 2 +- resources/extruders/creality_cr10s5_extruder_0.def.json | 2 +- resources/extruders/deltabot_extruder_0.def.json | 2 +- resources/extruders/grr_neo_extruder_0.def.json | 2 +- resources/extruders/kupido_extruder_0.def.json | 2 +- resources/extruders/makeR_pegasus_extruder_0.def.json | 2 +- resources/extruders/maker_starter_extruder_0.def.json | 2 +- resources/extruders/printrbot_play_heated_extruder_0.def.json | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/extruders/alya3dp_extruder_0.def.json b/resources/extruders/alya3dp_extruder_0.def.json index e34db5dfbf..3676f01ad2 100644 --- a/resources/extruders/alya3dp_extruder_0.def.json +++ b/resources/extruders/alya3dp_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/creality_cr10s4_extruder_0.def.json b/resources/extruders/creality_cr10s4_extruder_0.def.json index 9afe1cee35..8a40c6431f 100644 --- a/resources/extruders/creality_cr10s4_extruder_0.def.json +++ b/resources/extruders/creality_cr10s4_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/creality_cr10s5_extruder_0.def.json b/resources/extruders/creality_cr10s5_extruder_0.def.json index fed86eb2b5..98b701ae2e 100644 --- a/resources/extruders/creality_cr10s5_extruder_0.def.json +++ b/resources/extruders/creality_cr10s5_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/deltabot_extruder_0.def.json b/resources/extruders/deltabot_extruder_0.def.json index 43fce74fa5..e13d6a6ee3 100644 --- a/resources/extruders/deltabot_extruder_0.def.json +++ b/resources/extruders/deltabot_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.5 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/grr_neo_extruder_0.def.json b/resources/extruders/grr_neo_extruder_0.def.json index 9fe86d9eed..6d76c90796 100644 --- a/resources/extruders/grr_neo_extruder_0.def.json +++ b/resources/extruders/grr_neo_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.5 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/kupido_extruder_0.def.json b/resources/extruders/kupido_extruder_0.def.json index d93395e667..ef988d4fde 100644 --- a/resources/extruders/kupido_extruder_0.def.json +++ b/resources/extruders/kupido_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/makeR_pegasus_extruder_0.def.json b/resources/extruders/makeR_pegasus_extruder_0.def.json index 8d2a98340a..e37891abde 100644 --- a/resources/extruders/makeR_pegasus_extruder_0.def.json +++ b/resources/extruders/makeR_pegasus_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/maker_starter_extruder_0.def.json b/resources/extruders/maker_starter_extruder_0.def.json index 5c60e536b7..ee94250248 100644 --- a/resources/extruders/maker_starter_extruder_0.def.json +++ b/resources/extruders/maker_starter_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/printrbot_play_heated_extruder_0.def.json b/resources/extruders/printrbot_play_heated_extruder_0.def.json index ba8bc5c34c..0a3eeb3d06 100644 --- a/resources/extruders/printrbot_play_heated_extruder_0.def.json +++ b/resources/extruders/printrbot_play_heated_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } From d7a7cf3003f7ebe55d45b41c1ce20717b5c7d865 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 15 Oct 2018 13:55:47 +0200 Subject: [PATCH 89/97] Correct some printers to use 1.75mm filament This should fix some underextrusion problems... Hmm. Sources: * https://alya3dp.com/pages/teknik-ozellikler * https://www.creality3d.cn/creality-cr-10-s4-3d-printer-p00098p1.html * https://www.creality3d.cn/creality-cr-10-s5-3d-printer-p00099p1.html * https://3dprint.com/3643/german-repraps-neo-3d-printer-now-available-in-the-us-uk/ * https://somosmaker.com/pegasus-impresora-3d/ * http://www.3dmaker.vn/3d-printer-3dmaker-starter/?lang=en# (assuming the filaments they sell on that website are compatible) * https://makezine.com/product-review/printrbot-play/ I could not find a source for the Deltabot, but got that information from here: https://github.com/Ultimaker/Cura/issues/4573 Contributes to issue CURA-5817. --- resources/extruders/alya3dp_extruder_0.def.json | 2 +- resources/extruders/creality_cr10s4_extruder_0.def.json | 2 +- resources/extruders/creality_cr10s5_extruder_0.def.json | 2 +- resources/extruders/deltabot_extruder_0.def.json | 2 +- resources/extruders/grr_neo_extruder_0.def.json | 2 +- resources/extruders/kupido_extruder_0.def.json | 2 +- resources/extruders/makeR_pegasus_extruder_0.def.json | 2 +- resources/extruders/maker_starter_extruder_0.def.json | 2 +- resources/extruders/printrbot_play_heated_extruder_0.def.json | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/extruders/alya3dp_extruder_0.def.json b/resources/extruders/alya3dp_extruder_0.def.json index e34db5dfbf..3676f01ad2 100644 --- a/resources/extruders/alya3dp_extruder_0.def.json +++ b/resources/extruders/alya3dp_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/creality_cr10s4_extruder_0.def.json b/resources/extruders/creality_cr10s4_extruder_0.def.json index 9afe1cee35..8a40c6431f 100644 --- a/resources/extruders/creality_cr10s4_extruder_0.def.json +++ b/resources/extruders/creality_cr10s4_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/creality_cr10s5_extruder_0.def.json b/resources/extruders/creality_cr10s5_extruder_0.def.json index fed86eb2b5..98b701ae2e 100644 --- a/resources/extruders/creality_cr10s5_extruder_0.def.json +++ b/resources/extruders/creality_cr10s5_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/deltabot_extruder_0.def.json b/resources/extruders/deltabot_extruder_0.def.json index 43fce74fa5..e13d6a6ee3 100644 --- a/resources/extruders/deltabot_extruder_0.def.json +++ b/resources/extruders/deltabot_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.5 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/grr_neo_extruder_0.def.json b/resources/extruders/grr_neo_extruder_0.def.json index 9fe86d9eed..6d76c90796 100644 --- a/resources/extruders/grr_neo_extruder_0.def.json +++ b/resources/extruders/grr_neo_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.5 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/kupido_extruder_0.def.json b/resources/extruders/kupido_extruder_0.def.json index d93395e667..ef988d4fde 100644 --- a/resources/extruders/kupido_extruder_0.def.json +++ b/resources/extruders/kupido_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/makeR_pegasus_extruder_0.def.json b/resources/extruders/makeR_pegasus_extruder_0.def.json index 8d2a98340a..e37891abde 100644 --- a/resources/extruders/makeR_pegasus_extruder_0.def.json +++ b/resources/extruders/makeR_pegasus_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/maker_starter_extruder_0.def.json b/resources/extruders/maker_starter_extruder_0.def.json index 5c60e536b7..ee94250248 100644 --- a/resources/extruders/maker_starter_extruder_0.def.json +++ b/resources/extruders/maker_starter_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/printrbot_play_heated_extruder_0.def.json b/resources/extruders/printrbot_play_heated_extruder_0.def.json index ba8bc5c34c..0a3eeb3d06 100644 --- a/resources/extruders/printrbot_play_heated_extruder_0.def.json +++ b/resources/extruders/printrbot_play_heated_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } From 56a383814b78bc6dc9d7349381a4a4b23d286edc Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 15 Oct 2018 14:48:18 +0200 Subject: [PATCH 90/97] Code style: Spaces around binary operators Contributes to issue CURA-5483. --- .../FirmwareUpdateCheckerMessage.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py index d509c432b4..fd56c101a0 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py @@ -1,3 +1,5 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. from UM.i18n import i18nCatalog from UM.Message import Message @@ -13,8 +15,8 @@ class FirmwareUpdateCheckerMessage(Message): super().__init__(i18n_catalog.i18nc( "@info Don't translate {machine_name}, since it gets replaced by a printer name!", "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format( - machine_name=machine_name), - title=i18n_catalog.i18nc( + machine_name = machine_name), + title = i18n_catalog.i18nc( "@info:title The %s gets replaced with the printer name.", "New %s firmware available") % machine_name) @@ -25,8 +27,8 @@ class FirmwareUpdateCheckerMessage(Message): i18n_catalog.i18nc("@action:button", "How to update"), "[no_icon]", "[no_description]", - button_style=Message.ActionButtonStyle.LINK, - button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) + button_style = Message.ActionButtonStyle.LINK, + button_align = Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) def getMachineId(self) -> int: return self._machine_id From 53dc28db891f1d49d4cd6662468fb8f68c272175 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 15 Oct 2018 15:12:42 +0200 Subject: [PATCH 91/97] Change URL of firmware update page for Ultimaker 3 and S5 I just got word of a new page to read up about the firmware update. Apparently we now have to link to this one. Contributes to issue CURA-5483. --- plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py | 2 +- resources/definitions/ultimaker3.def.json | 2 +- resources/definitions/ultimaker3_extended.def.json | 2 +- resources/definitions/ultimaker_s5.def.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 2e15208336..4c60b95824 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from UM.Application import Application diff --git a/resources/definitions/ultimaker3.def.json b/resources/definitions/ultimaker3.def.json index f5e31890f6..72756de2a5 100644 --- a/resources/definitions/ultimaker3.def.json +++ b/resources/definitions/ultimaker3.def.json @@ -32,7 +32,7 @@ "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", "http://software.ultimaker.com/releases/firmware/9066/stable/version.txt" ], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" + "update_url": "https://ultimaker.com/firmware" } }, diff --git a/resources/definitions/ultimaker3_extended.def.json b/resources/definitions/ultimaker3_extended.def.json index d13857e428..68f26969b7 100644 --- a/resources/definitions/ultimaker3_extended.def.json +++ b/resources/definitions/ultimaker3_extended.def.json @@ -31,7 +31,7 @@ "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", "http://software.ultimaker.com/releases/firmware/9511/stable/version.txt" ], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" + "update_url": "https://ultimaker.com/firmware" } }, diff --git a/resources/definitions/ultimaker_s5.def.json b/resources/definitions/ultimaker_s5.def.json index 6195933869..310765dbc3 100644 --- a/resources/definitions/ultimaker_s5.def.json +++ b/resources/definitions/ultimaker_s5.def.json @@ -34,7 +34,7 @@ "firmware_update_info": { "id": 9051, "check_urls": ["http://software.ultimaker.com/releases/firmware/9051/stable/version.txt"], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" + "update_url": "https://ultimaker.com/firmware" } }, From 6eb8b754903f3fe1db5baa55ad90305fda94de38 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 16 Oct 2018 11:31:33 +0200 Subject: [PATCH 92/97] Update typing and fixed the bug it exposes. --- cura/Settings/SettingInheritanceManager.py | 61 ++++++++++++++-------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/cura/Settings/SettingInheritanceManager.py b/cura/Settings/SettingInheritanceManager.py index 9cd24558b7..12b541c3d8 100644 --- a/cura/Settings/SettingInheritanceManager.py +++ b/cura/Settings/SettingInheritanceManager.py @@ -1,6 +1,6 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import List +from typing import List, Optional, TYPE_CHECKING from PyQt5.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal from UM.FlameProfiler import pyqtSlot @@ -20,13 +20,18 @@ from UM.Settings.SettingInstance import InstanceState from cura.Settings.ExtruderManager import ExtruderManager +if TYPE_CHECKING: + from cura.Settings.ExtruderStack import ExtruderStack + from UM.Settings.SettingDefinition import SettingDefinition + + class SettingInheritanceManager(QObject): - def __init__(self, parent = None): + def __init__(self, parent = None) -> None: super().__init__(parent) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) - self._global_container_stack = None - self._settings_with_inheritance_warning = [] - self._active_container_stack = None + self._global_container_stack = None # type: Optional[ContainerStack] + self._settings_with_inheritance_warning = [] # type: List[str] + self._active_container_stack = None # type: Optional[ExtruderStack] self._onGlobalContainerChanged() ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged) @@ -41,7 +46,9 @@ class SettingInheritanceManager(QObject): ## Get the keys of all children settings with an override. @pyqtSlot(str, result = "QStringList") - def getChildrenKeysWithOverride(self, key): + def getChildrenKeysWithOverride(self, key: str) -> List[str]: + if self._global_container_stack is None: + return [] definitions = self._global_container_stack.definition.findDefinitions(key=key) if not definitions: Logger.log("w", "Could not find definition for key [%s]", key) @@ -53,9 +60,11 @@ class SettingInheritanceManager(QObject): return result @pyqtSlot(str, str, result = "QStringList") - def getOverridesForExtruder(self, key, extruder_index): - result = [] + def getOverridesForExtruder(self, key: str, extruder_index: str) -> List[str]: + if self._global_container_stack is None: + return [] + result = [] # type: List[str] extruder_stack = ExtruderManager.getInstance().getExtruderStack(extruder_index) if not extruder_stack: Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index) @@ -73,16 +82,16 @@ class SettingInheritanceManager(QObject): return result @pyqtSlot(str) - def manualRemoveOverride(self, key): + def manualRemoveOverride(self, key: str) -> None: if key in self._settings_with_inheritance_warning: self._settings_with_inheritance_warning.remove(key) self.settingsWithIntheritanceChanged.emit() @pyqtSlot() - def forceUpdate(self): + def forceUpdate(self) -> None: self._update() - def _onActiveExtruderChanged(self): + def _onActiveExtruderChanged(self) -> None: new_active_stack = ExtruderManager.getInstance().getActiveExtruderStack() if not new_active_stack: self._active_container_stack = None @@ -94,13 +103,14 @@ class SettingInheritanceManager(QObject): self._active_container_stack.containersChanged.disconnect(self._onContainersChanged) self._active_container_stack = new_active_stack - self._active_container_stack.propertyChanged.connect(self._onPropertyChanged) - self._active_container_stack.containersChanged.connect(self._onContainersChanged) + if self._active_container_stack is not None: + self._active_container_stack.propertyChanged.connect(self._onPropertyChanged) + self._active_container_stack.containersChanged.connect(self._onContainersChanged) self._update() # Ensure that the settings_with_inheritance_warning list is populated. - def _onPropertyChanged(self, key, property_name): + def _onPropertyChanged(self, key: str, property_name: str) -> None: if (property_name == "value" or property_name == "enabled") and self._global_container_stack: - definitions = self._global_container_stack.definition.findDefinitions(key = key) + definitions = self._global_container_stack.definition.findDefinitions(key = key) # type: List["SettingDefinition"] if not definitions: return @@ -139,7 +149,7 @@ class SettingInheritanceManager(QObject): if settings_with_inheritance_warning_changed: self.settingsWithIntheritanceChanged.emit() - def _recursiveCheck(self, definition): + def _recursiveCheck(self, definition: "SettingDefinition") -> bool: for child in definition.children: if child.key in self._settings_with_inheritance_warning: return True @@ -149,7 +159,7 @@ class SettingInheritanceManager(QObject): return False @pyqtProperty("QVariantList", notify = settingsWithIntheritanceChanged) - def settingsWithInheritanceWarning(self): + def settingsWithInheritanceWarning(self) -> List[str]: return self._settings_with_inheritance_warning ## Check if a setting has an inheritance function that is overwritten @@ -157,9 +167,14 @@ class SettingInheritanceManager(QObject): has_setting_function = False if not stack: stack = self._active_container_stack - if not stack: #No active container stack yet! + if not stack: # No active container stack yet! return False - containers = [] # type: List[ContainerInterface] + + if self._active_container_stack is None: + return False + all_keys = self._active_container_stack.getAllKeys() + + containers = [] # type: List[ContainerInterface] ## Check if the setting has a user state. If not, it is never overwritten. has_user_state = stack.getProperty(key, "state") == InstanceState.User @@ -190,8 +205,8 @@ class SettingInheritanceManager(QObject): has_setting_function = isinstance(value, SettingFunction) if has_setting_function: for setting_key in value.getUsedSettingKeys(): - if setting_key in self._active_container_stack.getAllKeys(): - break # We found an actual setting. So has_setting_function can remain true + if setting_key in all_keys: + break # We found an actual setting. So has_setting_function can remain true else: # All of the setting_keys turned out to not be setting keys at all! # This can happen due enum keys also being marked as settings. @@ -205,7 +220,7 @@ class SettingInheritanceManager(QObject): break # There is a setting function somewhere, stop looking deeper. return has_setting_function and has_non_function_value - def _update(self): + def _update(self) -> None: self._settings_with_inheritance_warning = [] # Reset previous data. # Make sure that the GlobalStack is not None. sometimes the globalContainerChanged signal gets here late. @@ -226,7 +241,7 @@ class SettingInheritanceManager(QObject): # Notify others that things have changed. self.settingsWithIntheritanceChanged.emit() - def _onGlobalContainerChanged(self): + def _onGlobalContainerChanged(self) -> None: if self._global_container_stack: self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged) self._global_container_stack.containersChanged.disconnect(self._onContainersChanged) From 4a0808378b9a224cde179b95c8326343b33a5202 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 16 Oct 2018 13:23:35 +0200 Subject: [PATCH 93/97] Allow whitespaces in the job name. Fixes #4530. --- resources/qml/JobSpecs.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/JobSpecs.qml b/resources/qml/JobSpecs.qml index 31ca84d66e..1a5b604886 100644 --- a/resources/qml/JobSpecs.qml +++ b/resources/qml/JobSpecs.qml @@ -86,7 +86,7 @@ Item { printJobTextfield.focus = false; } validator: RegExpValidator { - regExp: /^[^\\ \/ \*\?\|\[\]]*$/ + regExp: /^[^\\\/\*\?\|\[\]]*$/ } style: TextFieldStyle{ textColor: UM.Theme.getColor("text_scene"); From 25000e8a6b9232bebdb4565463dcd41e347aeb4c Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 16 Oct 2018 13:05:15 +0200 Subject: [PATCH 94/97] Fix typo's. [CURA-5760] Feature support brim. --- resources/definitions/fdmprinter.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 22491417ab..138e1adcc5 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -4613,7 +4613,7 @@ "brim_replaces_support": { "label": "Brim Replaces Support", - "description": "Enforce brim to be printed around the model even if that space would otherwise be occupied by support. This replaces some regions fo the first layer of supprot by brim regions.", + "description": "Enforce brim to be printed around the model even if that space would otherwise be occupied by support. This replaces some regions of the first layer of support by brim regions.", "type": "bool", "default_value": true, "enabled": "resolveOrValue('adhesion_type') == 'brim' and support_enable", From 20fa7f4dd8c55c32547c5157f5d7b9f94ea16af9 Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Tue, 16 Oct 2018 16:47:05 +0200 Subject: [PATCH 95/97] Display retractions lines for the loaded Gcode files CURA-5769 --- plugins/GCodeReader/FlavorParser.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py index eb19853748..1dc20d5602 100644 --- a/plugins/GCodeReader/FlavorParser.py +++ b/plugins/GCodeReader/FlavorParser.py @@ -44,6 +44,7 @@ class FlavorParser: self._extruder_offsets = {} # type: Dict[int, List[float]] # Offsets for multi extruders. key is index, value is [x-offset, y-offset] self._current_layer_thickness = 0.2 # default self._filament_diameter = 2.85 # default + self._previous_extrusion_value = 0 # keep track of the filament retractions CuraApplication.getInstance().getPreferences().addPreference("gcodereader/show_caution", True) @@ -182,6 +183,7 @@ class FlavorParser: new_extrusion_value = params.e if self._is_absolute_extrusion else e[self._extruder_number] + params.e if new_extrusion_value > e[self._extruder_number]: path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], self._layer_type]) # extrusion + self._previous_extrusion_value = new_extrusion_value else: path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) # retraction e[self._extruder_number] = new_extrusion_value @@ -191,6 +193,12 @@ class FlavorParser: if z > self._previous_z and (z - self._previous_z < 1.5): self._current_layer_thickness = z - self._previous_z # allow a tiny overlap self._previous_z = z + elif self._previous_extrusion_value > e[self._extruder_number]: + path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) + + # This case only for initial start, for the first coordinate in GCode + elif e[self._extruder_number] == 0 and self._previous_extrusion_value == 0: + path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) else: path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveCombingType]) return self._position(x, y, z, f, e) @@ -235,6 +243,7 @@ class FlavorParser: position.e) def processGCode(self, G: int, line: str, position: Position, path: List[List[Union[float, int]]]) -> Position: + self.previous_extrusion_value = 0 func = getattr(self, "_gCode%s" % G, None) line = line.split(";", 1)[0] # Remove comments (if any) if func is not None: From a7be605b9d3c1cd52bb7323373237f7c7554411d Mon Sep 17 00:00:00 2001 From: alekseisasin Date: Wed, 17 Oct 2018 09:50:22 +0200 Subject: [PATCH 96/97] Typing error in CI CURA-5769 --- plugins/GCodeReader/FlavorParser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py index 1dc20d5602..9ba1deb410 100644 --- a/plugins/GCodeReader/FlavorParser.py +++ b/plugins/GCodeReader/FlavorParser.py @@ -44,7 +44,7 @@ class FlavorParser: self._extruder_offsets = {} # type: Dict[int, List[float]] # Offsets for multi extruders. key is index, value is [x-offset, y-offset] self._current_layer_thickness = 0.2 # default self._filament_diameter = 2.85 # default - self._previous_extrusion_value = 0 # keep track of the filament retractions + self._previous_extrusion_value = 0.0 # keep track of the filament retractions CuraApplication.getInstance().getPreferences().addPreference("gcodereader/show_caution", True) @@ -243,7 +243,7 @@ class FlavorParser: position.e) def processGCode(self, G: int, line: str, position: Position, path: List[List[Union[float, int]]]) -> Position: - self.previous_extrusion_value = 0 + self._previous_extrusion_value = 0.0 func = getattr(self, "_gCode%s" % G, None) line = line.split(";", 1)[0] # Remove comments (if any) if func is not None: From e9e95b85e74ed4c01f9b94955ef560adff6e33d4 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 17 Oct 2018 10:51:28 +0200 Subject: [PATCH 97/97] Remove support_roof_enable to True in Tevo Black Widow quality profiles, since the support interface is already enabled. Fixes #4587. --- resources/quality/tevo_blackwidow/tevo_blackwidow_draft.inst.cfg | 1 - resources/quality/tevo_blackwidow/tevo_blackwidow_high.inst.cfg | 1 - .../quality/tevo_blackwidow/tevo_blackwidow_normal.inst.cfg | 1 - 3 files changed, 3 deletions(-) diff --git a/resources/quality/tevo_blackwidow/tevo_blackwidow_draft.inst.cfg b/resources/quality/tevo_blackwidow/tevo_blackwidow_draft.inst.cfg index b059b3c65f..be83533e0b 100644 --- a/resources/quality/tevo_blackwidow/tevo_blackwidow_draft.inst.cfg +++ b/resources/quality/tevo_blackwidow/tevo_blackwidow_draft.inst.cfg @@ -25,7 +25,6 @@ support_angle = 60 support_enable = True support_interface_enable = True support_pattern = triangles -support_roof_enable = True support_type = everywhere support_use_towers = False support_xy_distance = 0.7 diff --git a/resources/quality/tevo_blackwidow/tevo_blackwidow_high.inst.cfg b/resources/quality/tevo_blackwidow/tevo_blackwidow_high.inst.cfg index 6a6c605c00..5ca8a6e4ef 100644 --- a/resources/quality/tevo_blackwidow/tevo_blackwidow_high.inst.cfg +++ b/resources/quality/tevo_blackwidow/tevo_blackwidow_high.inst.cfg @@ -25,7 +25,6 @@ support_angle = 60 support_enable = True support_interface_enable = True support_pattern = triangles -support_roof_enable = True support_type = everywhere support_use_towers = False support_xy_distance = 0.7 diff --git a/resources/quality/tevo_blackwidow/tevo_blackwidow_normal.inst.cfg b/resources/quality/tevo_blackwidow/tevo_blackwidow_normal.inst.cfg index 7cba03853f..f542952fab 100644 --- a/resources/quality/tevo_blackwidow/tevo_blackwidow_normal.inst.cfg +++ b/resources/quality/tevo_blackwidow/tevo_blackwidow_normal.inst.cfg @@ -25,7 +25,6 @@ support_angle = 60 support_enable = True support_interface_enable = True support_pattern = triangles -support_roof_enable = True support_type = everywhere support_use_towers = False support_xy_distance = 0.7