From a55cf0678e46c89150179ae5a4c9c921f95c8f41 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Jun 2018 11:18:58 +0200 Subject: [PATCH 01/83] Add missing function types The class is typed now. There's some bugs though. Contributes to issue CURA-5330. --- .../NetworkedPrinterOutputDevice.py | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py index 86af368a86..ff9fe4477a 100644 --- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py +++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py @@ -2,17 +2,18 @@ # Cura is released under the terms of the LGPLv3 or higher. from UM.Application import Application +from UM.FileHandler.FileHandler import FileHandler #For typing. from UM.Logger import Logger +from UM.Scene.SceneNode import SceneNode #For typing. from cura.CuraApplication import CuraApplication from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState -from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply -from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, pyqtSignal, QUrl, QCoreApplication +from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply, QAuthenticator +from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QUrl, QCoreApplication from time import time -from typing import Callable, Any, Optional, Dict, Tuple +from typing import Any, Callable, Dict, List, Optional from enum import IntEnum -from typing import List import os # To get the username import gzip @@ -28,7 +29,7 @@ class AuthState(IntEnum): class NetworkedPrinterOutputDevice(PrinterOutputDevice): authenticationStateChanged = pyqtSignal() - def __init__(self, device_id, address: str, properties, parent = None) -> None: + def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], parent = None) -> None: super().__init__(device_id = device_id, parent = parent) self._manager = None # type: QNetworkAccessManager self._last_manager_create_time = None # type: float @@ -68,16 +69,16 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): self._printer_type = value break - def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs) -> None: + def requestWrite(self, nodes: List[SceneNode], file_name: str = None, filter_by_machine: bool = False, file_handler: FileHandler = None, **kwargs: Dict[str, Any]) -> None: raise NotImplementedError("requestWrite needs to be implemented") - def setAuthenticationState(self, authentication_state) -> None: + def setAuthenticationState(self, authentication_state: AuthState) -> None: if self._authentication_state != authentication_state: self._authentication_state = authentication_state self.authenticationStateChanged.emit() - @pyqtProperty(int, notify=authenticationStateChanged) - def authenticationState(self) -> int: + @pyqtProperty(int, notify = authenticationStateChanged) + def authenticationState(self) -> AuthState: return self._authentication_state def _compressDataAndNotifyQt(self, data_to_append: str) -> bytes: @@ -154,7 +155,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): return True - def _createEmptyRequest(self, target, content_type: Optional[str] = "application/json") -> QNetworkRequest: + def _createEmptyRequest(self, target: str, content_type: Optional[str] = "application/json") -> QNetworkRequest: url = QUrl("http://" + self._address + self._api_prefix + target) request = QNetworkRequest(url) if content_type is not None: @@ -162,7 +163,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent) return request - def _createFormPart(self, content_header, data, content_type = None) -> QHttpPart: + def _createFormPart(self, content_header: str, data: str, content_type: Optional[str] = None) -> QHttpPart: part = QHttpPart() if not content_header.startswith("form-data;"): @@ -188,33 +189,33 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): if reply in self._kept_alive_multiparts: del self._kept_alive_multiparts[reply] - def put(self, target: str, data: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]]) -> None: + def put(self, target: str, data: str, on_finished: Optional[Callable[[Any, QNetworkReply], None]]) -> None: if self._manager is None: self._createNetworkManager() request = self._createEmptyRequest(target) self._last_request_time = time() reply = self._manager.put(request, data.encode()) - self._registerOnFinishedCallback(reply, onFinished) + self._registerOnFinishedCallback(reply, on_finished) - def get(self, target: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]]) -> None: + def get(self, target: str, on_finished: Optional[Callable[[Any, QNetworkReply], None]]) -> None: if self._manager is None: self._createNetworkManager() request = self._createEmptyRequest(target) self._last_request_time = time() reply = self._manager.get(request) - self._registerOnFinishedCallback(reply, onFinished) + self._registerOnFinishedCallback(reply, on_finished) - def post(self, target: str, data: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None) -> None: + def post(self, target: str, data: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]], on_progress: Callable = None) -> None: if self._manager is None: self._createNetworkManager() request = self._createEmptyRequest(target) self._last_request_time = time() reply = self._manager.post(request, data) - if onProgress is not None: - reply.uploadProgress.connect(onProgress) + if on_progress is not None: + reply.uploadProgress.connect(on_progress) self._registerOnFinishedCallback(reply, onFinished) - def postFormWithParts(self, target:str, parts: List[QHttpPart], onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None) -> None: + def postFormWithParts(self, target:str, parts: List[QHttpPart], on_finished: Optional[Callable[[Any, QNetworkReply], None]], on_progress: Callable = None) -> None: if self._manager is None: self._createNetworkManager() request = self._createEmptyRequest(target, content_type=None) @@ -228,20 +229,20 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): self._kept_alive_multiparts[reply] = multi_post_part - if onProgress is not None: - reply.uploadProgress.connect(onProgress) - self._registerOnFinishedCallback(reply, onFinished) + if on_progress is not None: + reply.uploadProgress.connect(on_progress) + self._registerOnFinishedCallback(reply, on_finished) return reply - def postForm(self, target: str, header_data: str, body_data: bytes, onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None) -> None: + def postForm(self, target: str, header_data: str, body_data: bytes, on_finished: Optional[Callable[[Any, QNetworkReply], None]], on_progress: Callable = None) -> None: post_part = QHttpPart() post_part.setHeader(QNetworkRequest.ContentDispositionHeader, header_data) post_part.setBody(body_data) - self.postFormWithParts(target, [post_part], onFinished, onProgress) + self.postFormWithParts(target, [post_part], on_finished, on_progress) - def _onAuthenticationRequired(self, reply, authenticator) -> None: + def _onAuthenticationRequired(self, reply: QNetworkReply, authenticator: QAuthenticator) -> None: Logger.log("w", "Request to {url} required authentication, which was not implemented".format(url = reply.url().toString())) def _createNetworkManager(self) -> None: @@ -258,9 +259,9 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): machine_manager = CuraApplication.getInstance().getMachineManager() machine_manager.checkCorrectGroupName(self.getId(), self.name) - def _registerOnFinishedCallback(self, reply: QNetworkReply, onFinished: Optional[Callable[[Any, QNetworkReply], None]]) -> None: - if onFinished is not None: - self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished + def _registerOnFinishedCallback(self, reply: QNetworkReply, on_finished: Optional[Callable[[Any, QNetworkReply], None]]) -> None: + if on_finished is not None: + self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = on_finished def __handleOnFinished(self, reply: QNetworkReply) -> None: # Due to garbage collection, we need to cache certain bits of post operations. @@ -297,30 +298,30 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): ## Get the unique key of this machine # \return key String containing the key of the machine. - @pyqtProperty(str, constant=True) + @pyqtProperty(str, constant = True) def key(self) -> str: return self._id ## The IP address of the printer. - @pyqtProperty(str, constant=True) + @pyqtProperty(str, constant = True) def address(self) -> str: return self._properties.get(b"address", b"").decode("utf-8") ## Name of the printer (as returned from the ZeroConf properties) - @pyqtProperty(str, constant=True) + @pyqtProperty(str, constant = True) def name(self) -> str: return self._properties.get(b"name", b"").decode("utf-8") ## Firmware version (as returned from the ZeroConf properties) - @pyqtProperty(str, constant=True) + @pyqtProperty(str, constant = True) def firmwareVersion(self) -> str: return self._properties.get(b"firmware_version", b"").decode("utf-8") - @pyqtProperty(str, constant=True) + @pyqtProperty(str, constant = True) def printerType(self) -> str: return self._printer_type - ## IPadress of this printer - @pyqtProperty(str, constant=True) + ## IP adress of this printer + @pyqtProperty(str, constant = True) def ipAddress(self) -> str: return self._address From a946a8aaedc79400a7ea446a6ff72287824d87c3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Jun 2018 11:44:08 +0200 Subject: [PATCH 02/83] Add function typing for PrinterOutputDevice This causes a lot of typing errors to surface. We'll fix this later though, when we get to it. Contributes to issue CURA-5330. --- cura/PrinterOutputDevice.py | 113 ++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py index 98ba4a19a8..ab727d723d 100644 --- a/cura/PrinterOutputDevice.py +++ b/cura/PrinterOutputDevice.py @@ -1,17 +1,20 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. + from UM.Decorators import deprecated from UM.i18n import i18nCatalog from UM.OutputDevice.OutputDevice import OutputDevice -from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal, QVariant +from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal 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.Application import Application from enum import IntEnum # For the connection state tracking. -from typing import List, Optional +from typing import Callable, List, Optional MYPY = False if MYPY: @@ -20,6 +23,16 @@ if MYPY: i18n_catalog = i18nCatalog("cura") + +## The current processing state of the backend. +class ConnectionState(IntEnum): + closed = 0 + connecting = 1 + connected = 2 + busy = 3 + error = 4 + + ## Printer output device adds extra interface options on top of output device. # # The assumption is made the printer is a FDM printer. @@ -47,38 +60,37 @@ class PrinterOutputDevice(QObject, OutputDevice): # Signal to indicate that the configuration of one of the printers has changed. uniqueConfigurationsChanged = pyqtSignal() - def __init__(self, device_id, parent = None): + def __init__(self, device_id: str, parent: QObject = None) -> None: super().__init__(device_id = device_id, parent = parent) self._printers = [] # type: List[PrinterOutputModel] self._unique_configurations = [] # type: List[ConfigurationModel] - self._monitor_view_qml_path = "" - self._monitor_component = None - self._monitor_item = None + self._monitor_view_qml_path = "" #type: str + self._monitor_component = None #type: Optional[QObject] + self._monitor_item = None #type: Optional[QObject] - self._control_view_qml_path = "" - self._control_component = None - self._control_item = None + self._control_view_qml_path = "" #type: str + self._control_component = None #type: Optional[QObject] + self._control_item = None #type: Optional[QObject] - self._qml_context = None - self._accepts_commands = False + self._accepts_commands = False #type: bool - self._update_timer = QTimer() + self._update_timer = QTimer() #type: QTimer self._update_timer.setInterval(2000) # TODO; Add preference for update interval self._update_timer.setSingleShot(False) self._update_timer.timeout.connect(self._update) - self._connection_state = ConnectionState.closed + self._connection_state = ConnectionState.closed #type: ConnectionState - self._firmware_name = None - self._address = "" - self._connection_text = "" + self._firmware_name = None #type: Optional[str] + self._address = "" #type: str + self._connection_text = "" #type: str self.printersChanged.connect(self._onPrintersChanged) Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._updateUniqueConfigurations) @pyqtProperty(str, notify = connectionTextChanged) - def address(self): + def address(self) -> str: return self._address def setConnectionText(self, connection_text): @@ -87,36 +99,36 @@ class PrinterOutputDevice(QObject, OutputDevice): self.connectionTextChanged.emit() @pyqtProperty(str, constant=True) - def connectionText(self): + def connectionText(self) -> str: return self._connection_text - def materialHotendChangedMessage(self, callback): + def materialHotendChangedMessage(self, callback: Callable[[int], None]) -> None: Logger.log("w", "materialHotendChangedMessage needs to be implemented, returning 'Yes'") callback(QMessageBox.Yes) - def isConnected(self): + def isConnected(self) -> bool: return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error - def setConnectionState(self, connection_state): + def setConnectionState(self, connection_state: ConnectionState) -> None: if self._connection_state != connection_state: self._connection_state = connection_state self.connectionStateChanged.emit(self._id) @pyqtProperty(str, notify = connectionStateChanged) - def connectionState(self): + def connectionState(self) -> ConnectionState: return self._connection_state - def _update(self): + def _update(self) -> None: pass - def _getPrinterByKey(self, key) -> Optional["PrinterOutputModel"]: + def _getPrinterByKey(self, key: str) -> Optional["PrinterOutputModel"]: for printer in self._printers: if printer.key == key: return printer return None - def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs): + 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) @@ -126,11 +138,11 @@ class PrinterOutputDevice(QObject, OutputDevice): return None @pyqtProperty("QVariantList", notify = printersChanged) - def printers(self): + def printers(self) -> List["PrinterOutputModel"]: return self._printers - @pyqtProperty(QObject, constant=True) - def monitorItem(self): + @pyqtProperty(QObject, constant = True) + def monitorItem(self) -> QObject: # Note that we specifically only check if the monitor component is created. # It could be that it failed to actually create the qml item! If we check if the item was created, it will try to # create the item (and fail) every time. @@ -138,19 +150,19 @@ class PrinterOutputDevice(QObject, OutputDevice): self._createMonitorViewFromQML() return self._monitor_item - @pyqtProperty(QObject, constant=True) - def controlItem(self): + @pyqtProperty(QObject, constant = True) + def controlItem(self) -> QObject: if not self._control_component: self._createControlViewFromQML() return self._control_item - def _createControlViewFromQML(self): + def _createControlViewFromQML(self) -> None: if not self._control_view_qml_path: return if self._control_item is None: self._control_item = Application.getInstance().createQmlComponent(self._control_view_qml_path, {"OutputDevice": self}) - def _createMonitorViewFromQML(self): + def _createMonitorViewFromQML(self) -> None: if not self._monitor_view_qml_path: return @@ -158,29 +170,29 @@ class PrinterOutputDevice(QObject, OutputDevice): self._monitor_item = Application.getInstance().createQmlComponent(self._monitor_view_qml_path, {"OutputDevice": self}) ## Attempt to establish connection - def connect(self): + def connect(self) -> None: self.setConnectionState(ConnectionState.connecting) self._update_timer.start() ## Attempt to close the connection - def close(self): + def close(self) -> None: self._update_timer.stop() self.setConnectionState(ConnectionState.closed) ## Ensure that close gets called when object is destroyed - def __del__(self): + def __del__(self) -> None: self.close() - @pyqtProperty(bool, notify=acceptsCommandsChanged) - def acceptsCommands(self): + @pyqtProperty(bool, notify = acceptsCommandsChanged) + def acceptsCommands(self) -> bool: return self._accepts_commands @deprecated("Please use the protected function instead", "3.2") - def setAcceptsCommands(self, accepts_commands): + def setAcceptsCommands(self, accepts_commands: bool) -> None: self._setAcceptsCommands(accepts_commands) ## Set a flag to signal the UI that the printer is not (yet) ready to receive commands - def _setAcceptsCommands(self, accepts_commands): + def _setAcceptsCommands(self, accepts_commands: bool) -> None: if self._accepts_commands != accepts_commands: self._accepts_commands = accepts_commands @@ -188,15 +200,15 @@ class PrinterOutputDevice(QObject, OutputDevice): # Returns the unique configurations of the printers within this output device @pyqtProperty("QVariantList", notify = uniqueConfigurationsChanged) - def uniqueConfigurations(self): + def uniqueConfigurations(self) -> List[ConfigurationModel]: return self._unique_configurations - def _updateUniqueConfigurations(self): + def _updateUniqueConfigurations(self) -> None: self._unique_configurations = list(set([printer.printerConfiguration for printer in self._printers if printer.printerConfiguration is not None])) self._unique_configurations.sort(key = lambda k: k.printerType) self.uniqueConfigurationsChanged.emit() - def _onPrintersChanged(self): + def _onPrintersChanged(self) -> None: for printer in self._printers: printer.configurationChanged.connect(self._updateUniqueConfigurations) @@ -205,21 +217,12 @@ class PrinterOutputDevice(QObject, OutputDevice): ## Set the device firmware name # - # \param name \type{str} The name of the firmware. - def _setFirmwareName(self, name): + # \param name The name of the firmware. + def _setFirmwareName(self, name: str) -> None: self._firmware_name = name ## Get the name of device firmware # # This name can be used to define device type - def getFirmwareName(self): - return self._firmware_name - - -## The current processing state of the backend. -class ConnectionState(IntEnum): - closed = 0 - connecting = 1 - connected = 2 - busy = 3 - error = 4 + def getFirmwareName(self) -> str: + return self._firmware_name \ No newline at end of file From 8ed3bd29cb38d1427b9eb4200dde038ec26a073d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Jun 2018 11:47:04 +0200 Subject: [PATCH 03/83] Don't return any boolean in _update Nothing listens to that return value, and its parent classes say that this must return None. Contributes to issue CURA-5330. --- cura/PrinterOutput/NetworkedPrinterOutputDevice.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py index ff9fe4477a..74719851c4 100644 --- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py +++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py @@ -10,7 +10,7 @@ from cura.CuraApplication import CuraApplication from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply, QAuthenticator -from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QUrl, QCoreApplication +from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QCoreApplication from time import time from typing import Any, Callable, Dict, List, Optional from enum import IntEnum @@ -29,7 +29,7 @@ class AuthState(IntEnum): class NetworkedPrinterOutputDevice(PrinterOutputDevice): authenticationStateChanged = pyqtSignal() - def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], parent = None) -> None: + def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], parent: QObject = None) -> None: super().__init__(device_id = device_id, parent = parent) self._manager = None # type: QNetworkAccessManager self._last_manager_create_time = None # type: float @@ -69,7 +69,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): self._printer_type = value break - def requestWrite(self, nodes: List[SceneNode], file_name: str = None, filter_by_machine: bool = False, file_handler: FileHandler = None, **kwargs: Dict[str, Any]) -> 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") def setAuthenticationState(self, authentication_state: AuthState) -> None: @@ -123,7 +123,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): self._compressing_gcode = False return b"".join(file_data_bytes_list) - def _update(self) -> bool: + def _update(self) -> None: if self._last_response_time: time_since_last_response = time() - self._last_response_time else: @@ -153,8 +153,6 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): self.setConnectionState(self._connection_state_before_timeout) self._connection_state_before_timeout = None - return True - def _createEmptyRequest(self, target: str, content_type: Optional[str] = "application/json") -> QNetworkRequest: url = QUrl("http://" + self._address + self._api_prefix + target) request = QNetworkRequest(url) From b3f73594bfd5427df15d5cec5eaab867f42c056e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Jun 2018 11:53:01 +0200 Subject: [PATCH 04/83] Fix types of on_finished callback It only has a QNetworkReply argument. Contributes to issue CURA-5330. --- cura/PrinterOutput/NetworkedPrinterOutputDevice.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py index 74719851c4..4b4cb602c4 100644 --- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py +++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py @@ -187,7 +187,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): if reply in self._kept_alive_multiparts: del self._kept_alive_multiparts[reply] - def put(self, target: str, data: str, on_finished: Optional[Callable[[Any, QNetworkReply], None]]) -> None: + def put(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None: if self._manager is None: self._createNetworkManager() request = self._createEmptyRequest(target) @@ -195,7 +195,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): reply = self._manager.put(request, data.encode()) self._registerOnFinishedCallback(reply, on_finished) - def get(self, target: str, on_finished: Optional[Callable[[Any, QNetworkReply], None]]) -> None: + def get(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None: if self._manager is None: self._createNetworkManager() request = self._createEmptyRequest(target) @@ -203,7 +203,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): reply = self._manager.get(request) self._registerOnFinishedCallback(reply, on_finished) - def post(self, target: str, data: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]], on_progress: Callable = None) -> None: + def post(self, target: str, data: str, onFinished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None: if self._manager is None: self._createNetworkManager() request = self._createEmptyRequest(target) @@ -213,7 +213,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): reply.uploadProgress.connect(on_progress) self._registerOnFinishedCallback(reply, onFinished) - def postFormWithParts(self, target:str, parts: List[QHttpPart], on_finished: Optional[Callable[[Any, QNetworkReply], None]], on_progress: Callable = None) -> None: + def postFormWithParts(self, target:str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None: if self._manager is None: self._createNetworkManager() request = self._createEmptyRequest(target, content_type=None) @@ -233,7 +233,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): return reply - def postForm(self, target: str, header_data: str, body_data: bytes, on_finished: Optional[Callable[[Any, QNetworkReply], None]], on_progress: Callable = None) -> None: + def postForm(self, target: str, header_data: str, body_data: bytes, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None: post_part = QHttpPart() post_part.setHeader(QNetworkRequest.ContentDispositionHeader, header_data) post_part.setBody(body_data) @@ -257,7 +257,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): machine_manager = CuraApplication.getInstance().getMachineManager() machine_manager.checkCorrectGroupName(self.getId(), self.name) - def _registerOnFinishedCallback(self, reply: QNetworkReply, on_finished: Optional[Callable[[Any, QNetworkReply], None]]) -> None: + def _registerOnFinishedCallback(self, reply: QNetworkReply, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None: if on_finished is not None: self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = on_finished From a98b4fe35c8c8c1b1010c01834003024aecc650b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Jun 2018 12:48:29 +0200 Subject: [PATCH 05/83] Fix unknown model My IDE didn't indicate this because it is imported fine, but only if MYPY. Contributes to issue CURA-5330. --- cura/PrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py index ab727d723d..bdac80e61c 100644 --- a/cura/PrinterOutputDevice.py +++ b/cura/PrinterOutputDevice.py @@ -200,7 +200,7 @@ class PrinterOutputDevice(QObject, OutputDevice): # Returns the unique configurations of the printers within this output device @pyqtProperty("QVariantList", notify = uniqueConfigurationsChanged) - def uniqueConfigurations(self) -> List[ConfigurationModel]: + def uniqueConfigurations(self) -> List["ConfigurationModel"]: return self._unique_configurations def _updateUniqueConfigurations(self) -> None: From e77592d7189e727c006c5ad1ce92cdd4496555fe Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Jun 2018 13:01:41 +0200 Subject: [PATCH 06/83] Add missing typing for CuraContainerStack This was already largely done, but not 100%. Contributes to issue CURA-5330. --- cura/Settings/CuraContainerStack.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index e1f89eb725..7d87507442 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.py @@ -1,15 +1,12 @@ -# 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.path - -from typing import Any, Optional - +from typing import Any, List, Optional from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject -from UM.FlameProfiler import pyqtSlot from UM.Application import Application from UM.Decorators import override +from UM.FlameProfiler import pyqtSlot from UM.Logger import Logger from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError from UM.Settings.InstanceContainer import InstanceContainer @@ -42,16 +39,16 @@ class CuraContainerStack(ContainerStack): def __init__(self, container_id: str): super().__init__(container_id) - self._container_registry = ContainerRegistry.getInstance() + self._container_registry = ContainerRegistry.getInstance() #type: ContainerRegistry - self._empty_instance_container = self._container_registry.getEmptyInstanceContainer() + self._empty_instance_container = self._container_registry.getEmptyInstanceContainer() #type: InstanceContainer - self._empty_quality_changes = self._container_registry.findInstanceContainers(id = "empty_quality_changes")[0] - self._empty_quality = self._container_registry.findInstanceContainers(id = "empty_quality")[0] - self._empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0] - self._empty_variant = self._container_registry.findInstanceContainers(id = "empty_variant")[0] + self._empty_quality_changes = self._container_registry.findInstanceContainers(id = "empty_quality_changes")[0] #type: InstanceContainer + self._empty_quality = self._container_registry.findInstanceContainers(id = "empty_quality")[0] #type: InstanceContainer + self._empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0] #type: InstanceContainer + self._empty_variant = self._container_registry.findInstanceContainers(id = "empty_variant")[0] #type: InstanceContainer - self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))] + self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))] #type: List[ContainerInterface] self._containers[_ContainerIndexes.QualityChanges] = self._empty_quality_changes self._containers[_ContainerIndexes.Quality] = self._empty_quality self._containers[_ContainerIndexes.Material] = self._empty_material @@ -94,7 +91,7 @@ class CuraContainerStack(ContainerStack): ## Set the quality container. # # \param new_quality The new quality container. It is expected to have a "type" metadata entry with the value "quality". - def setQuality(self, new_quality: InstanceContainer, postpone_emit = False) -> None: + def setQuality(self, new_quality: InstanceContainer, postpone_emit: bool = False) -> None: self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit) ## Get the quality container. @@ -107,7 +104,7 @@ class CuraContainerStack(ContainerStack): ## Set the material container. # # \param new_material The new material container. It is expected to have a "type" metadata entry with the value "material". - def setMaterial(self, new_material: InstanceContainer, postpone_emit = False) -> None: + def setMaterial(self, new_material: InstanceContainer, postpone_emit: bool = False) -> None: self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit) ## Get the material container. From a6ffbbde8f0fe5e4accff49f2cc5e1264215cbf1 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Jun 2018 13:08:55 +0200 Subject: [PATCH 07/83] Add missing typing Contributes to issue CURA-5330. --- cura/Settings/GlobalStack.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 6d300954c2..6552e43073 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -1,29 +1,30 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from collections import defaultdict import threading -from typing import Any, Dict, Optional - +from typing import Any, Dict, Optional, Set, TYPE_CHECKING from PyQt5.QtCore import pyqtProperty -from UM.Application import Application from UM.Decorators import override - from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase from UM.Settings.ContainerStack import ContainerStack from UM.Settings.SettingInstance import InstanceState from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.Interfaces import PropertyEvaluationContext from UM.Logger import Logger +import cura.CuraApplication from . import Exceptions from .CuraContainerStack import CuraContainerStack +if TYPE_CHECKING: + from cura.Settings.ExtruderStack import ExtruderStack + ## Represents the Global or Machine stack and its related containers. # class GlobalStack(CuraContainerStack): - def __init__(self, container_id: str): + def __init__(self, container_id: str) -> None: super().__init__(container_id) self.addMetaDataEntry("type", "machine") # For backward compatibility @@ -34,7 +35,7 @@ class GlobalStack(CuraContainerStack): # and if so, to bypass the resolve to prevent an infinite recursion that would occur # if the resolve function tried to access the same property it is a resolve for. # Per thread we have our own resolving_settings, or strange things sometimes occur. - self._resolving_settings = defaultdict(set) # keys are thread names + self._resolving_settings = defaultdict(set) #type: Dict[str, Set[str]] # keys are thread names ## Get the list of extruders of this stack. # @@ -94,6 +95,7 @@ class GlobalStack(CuraContainerStack): context.pushContainer(self) # Handle the "resolve" property. + #TODO: Why the hell does this involve threading? if self._shouldResolve(key, property_name, context): current_thread = threading.current_thread() self._resolving_settings[current_thread.name].add(key) @@ -106,7 +108,7 @@ class GlobalStack(CuraContainerStack): limit_to_extruder = super().getProperty(key, "limit_to_extruder", context) if limit_to_extruder is not None: if limit_to_extruder == -1: - limit_to_extruder = int(Application.getInstance().getMachineManager().defaultExtruderPosition) + limit_to_extruder = int(cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition) limit_to_extruder = str(limit_to_extruder) if limit_to_extruder is not None and limit_to_extruder != "-1" and limit_to_extruder in self._extruders: if super().getProperty(key, "settable_per_extruder", context): @@ -155,7 +157,7 @@ class GlobalStack(CuraContainerStack): ## Perform some sanity checks on the global stack # Sanity check for extruders; they must have positions 0 and up to machine_extruder_count - 1 - def isValid(self): + def isValid(self) -> bool: container_registry = ContainerRegistry.getInstance() extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = self.getId()) From e38228ac246bea8b8b5e0e87f30eccac2a1a6e3c Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Jun 2018 13:32:59 +0200 Subject: [PATCH 08/83] Remove unused target_container parameter It's not compatible with ContainerInterface anyway. Contributes to issue CURA-5330. --- cura/Settings/CuraContainerStack.py | 16 ++++++---------- tests/Settings/TestExtruderStack.py | 24 +----------------------- tests/Settings/TestGlobalStack.py | 25 +------------------------ 3 files changed, 8 insertions(+), 57 deletions(-) diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index 7d87507442..7d1730ee2b 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.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 Any, List, Optional +from typing import Any, List, Optional, Union from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject from UM.Application import Application @@ -36,7 +36,7 @@ from . import Exceptions # This also means that operations on the stack that modifies the container ordering is prohibited and # will raise an exception. class CuraContainerStack(ContainerStack): - def __init__(self, container_id: str): + def __init__(self, container_id: str) -> None: super().__init__(container_id) self._container_registry = ContainerRegistry.getInstance() #type: ContainerRegistry @@ -48,7 +48,7 @@ class CuraContainerStack(ContainerStack): self._empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0] #type: InstanceContainer self._empty_variant = self._container_registry.findInstanceContainers(id = "empty_variant")[0] #type: InstanceContainer - self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))] #type: List[ContainerInterface] + self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))] #type: List[Union[InstanceContainer, DefinitionContainer]] self._containers[_ContainerIndexes.QualityChanges] = self._empty_quality_changes self._containers[_ContainerIndexes.Quality] = self._empty_quality self._containers[_ContainerIndexes.Material] = self._empty_material @@ -186,13 +186,9 @@ class CuraContainerStack(ContainerStack): # \param key The key of the setting to set. # \param property_name The name of the property to set. # \param new_value The new value to set the property to. - # \param target_container The type of the container to set the property of. Defaults to "user". - def setProperty(self, key: str, property_name: str, new_value: Any, target_container: str = "user") -> None: - container_index = _ContainerIndexes.TypeIndexMap.get(target_container, -1) - if container_index != -1: - self._containers[container_index].setProperty(key, property_name, new_value) - else: - raise IndexError("Invalid target container {type}".format(type = target_container)) + def setProperty(self, key: str, property_name: str, new_value: Any) -> None: + container_index = _ContainerIndexes.UserChanges + self._containers[container_index].setProperty(key, property_name, new_value) ## Overridden from ContainerStack # diff --git a/tests/Settings/TestExtruderStack.py b/tests/Settings/TestExtruderStack.py index ce829da4b3..b418ae4306 100644 --- a/tests/Settings/TestExtruderStack.py +++ b/tests/Settings/TestExtruderStack.py @@ -340,26 +340,4 @@ def test_setPropertyUser(key, property, value, extruder_stack): extruder_stack.setProperty(key, property, value) #The actual test. - extruder_stack.userChanges.setProperty.assert_called_once_with(key, property, value) #Make sure that the user container gets a setProperty call. - -## Tests setting properties on specific containers on the global stack. -@pytest.mark.parametrize("target_container, stack_variable", [ - ("user", "userChanges"), - ("quality_changes", "qualityChanges"), - ("quality", "quality"), - ("material", "material"), - ("variant", "variant") -]) -def test_setPropertyOtherContainers(target_container, stack_variable, extruder_stack): - #Other parameters that don't need to be varied. - key = "layer_height" - property = "value" - value = 0.1337 - #A mock container in the right spot. - container = unittest.mock.MagicMock() - container.getMetaDataEntry = unittest.mock.MagicMock(return_value = target_container) - setattr(extruder_stack, stack_variable, container) #For instance, set global_stack.qualityChanges = container. - - extruder_stack.setProperty(key, property, value, target_container = target_container) #The actual test. - - getattr(extruder_stack, stack_variable).setProperty.assert_called_once_with(key, property, value) #Make sure that the proper container gets a setProperty call. + extruder_stack.userChanges.setProperty.assert_called_once_with(key, property, value) #Make sure that the user container gets a setProperty call. \ No newline at end of file diff --git a/tests/Settings/TestGlobalStack.py b/tests/Settings/TestGlobalStack.py index 6bf10dd8c1..05c7cf1677 100755 --- a/tests/Settings/TestGlobalStack.py +++ b/tests/Settings/TestGlobalStack.py @@ -481,27 +481,4 @@ def test_setPropertyUser(key, property, value, global_stack): global_stack.setProperty(key, property, value) #The actual test. - global_stack.userChanges.setProperty.assert_called_once_with(key, property, value) #Make sure that the user container gets a setProperty call. - -## Tests setting properties on specific containers on the global stack. -@pytest.mark.parametrize("target_container, stack_variable", [ - ("user", "userChanges"), - ("quality_changes", "qualityChanges"), - ("quality", "quality"), - ("material", "material"), - ("variant", "variant"), - ("definition_changes", "definitionChanges") -]) -def test_setPropertyOtherContainers(target_container, stack_variable, global_stack): - #Other parameters that don't need to be varied. - key = "layer_height" - property = "value" - value = 0.1337 - #A mock container in the right spot. - container = unittest.mock.MagicMock() - container.getMetaDataEntry = unittest.mock.MagicMock(return_value = target_container) - setattr(global_stack, stack_variable, container) #For instance, set global_stack.qualityChanges = container. - - global_stack.setProperty(key, property, value, target_container = target_container) #The actual test. - - getattr(global_stack, stack_variable).setProperty.assert_called_once_with(key, property, value) #Make sure that the proper container gets a setProperty call. + global_stack.userChanges.setProperty.assert_called_once_with(key, property, value) #Make sure that the user container gets a setProperty call. \ No newline at end of file From b920b9de4f92e7ab5d87dd1f43415f3ae584a0f4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Jun 2018 13:34:41 +0200 Subject: [PATCH 09/83] Fix type of setProperty And properly pass on the extra parameters. Contributes to issue CURA-5330. --- cura/Settings/CuraContainerStack.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index 7d1730ee2b..f9041fc86e 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.py @@ -186,9 +186,9 @@ class CuraContainerStack(ContainerStack): # \param key The key of the setting to set. # \param property_name The name of the property to set. # \param new_value The new value to set the property to. - def setProperty(self, key: str, property_name: str, new_value: Any) -> None: + def setProperty(self, key: str, property_name: str, property_value: Any, container: "ContainerInterface" = None, set_from_cache: bool = False) -> None: container_index = _ContainerIndexes.UserChanges - self._containers[container_index].setProperty(key, property_name, new_value) + self._containers[container_index].setProperty(key, property_name, property_value, container, set_from_cache) ## Overridden from ContainerStack # From ff4a214c24424dc207409392e118ed2c1cdfb1ab Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Jun 2018 14:52:20 +0200 Subject: [PATCH 10/83] Add function typing This makes MyPy discover a lot of typing errors because it now starts analysing all of these functions. Contributes to issue CURA-5330. --- cura/Settings/MachineManager.py | 82 +++++++++++++++++---------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 3ee14ca85b..80498639f4 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1,10 +1,9 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import collections import time -#Type hinting. -from typing import List, Dict, TYPE_CHECKING, Optional +from typing import Any, List, Dict, TYPE_CHECKING, Optional from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator @@ -24,6 +23,9 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.SettingFunction import SettingFunction from UM.Signal import postponeSignals, CompressTechnique +from cura.Machines.ContainerNode import ContainerNode #For typing. +from cura.Machines.QualityChangesGroup import QualityChangesGroup #For typing. +from cura.Machines.QualityGroup import QualityGroup #For typing. from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch from cura.PrinterOutputDevice import PrinterOutputDevice from cura.PrinterOutput.ConfigurationModel import ConfigurationModel @@ -40,10 +42,12 @@ catalog = i18nCatalog("cura") if TYPE_CHECKING: from cura.Settings.CuraContainerStack import CuraContainerStack from cura.Settings.GlobalStack import GlobalStack + from cura.Machines.MaterialManager import MaterialManager + from cura.Machines.QualityManager import QualityManager + from cura.Machines.VariantManager import VariantManager class MachineManager(QObject): - - def __init__(self, parent = None): + def __init__(self, parent: QObject = None): super().__init__(parent) self._active_container_stack = None # type: Optional[ExtruderManager] @@ -57,12 +61,12 @@ class MachineManager(QObject): self.machine_extruder_material_update_dict = collections.defaultdict(list) - self._instance_container_timer = QTimer() + self._instance_container_timer = QTimer() #type: QTimer self._instance_container_timer.setInterval(250) self._instance_container_timer.setSingleShot(True) self._instance_container_timer.timeout.connect(self.__emitChangedSignals) - self._application = Application.getInstance() + self._application = Application.getInstance() #type: Application self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged) self._application.getContainerRegistry().containerLoadComplete.connect(self._onContainersChanged) @@ -74,14 +78,14 @@ class MachineManager(QObject): self.globalContainerChanged.connect(self.activeQualityChangesGroupChanged) self.globalContainerChanged.connect(self.activeQualityGroupChanged) - self._stacks_have_errors = None # type:Optional[bool] + self._stacks_have_errors = None # type: Optional[bool] - self._empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() - self._empty_definition_changes_container = ContainerRegistry.getInstance().findContainers(id = "empty_definition_changes")[0] - self._empty_variant_container = ContainerRegistry.getInstance().findContainers(id = "empty_variant")[0] - self._empty_material_container = ContainerRegistry.getInstance().findContainers(id = "empty_material")[0] - self._empty_quality_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0] - self._empty_quality_changes_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality_changes")[0] + self._empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() #type: InstanceContainer + self._empty_definition_changes_container = ContainerRegistry.getInstance().findContainers(id = "empty_definition_changes")[0] #type: InstanceContainer + self._empty_variant_container = ContainerRegistry.getInstance().findContainers(id = "empty_variant")[0] #type: InstanceContainer + self._empty_material_container = ContainerRegistry.getInstance().findContainers(id = "empty_material")[0] #type: InstanceContainer + self._empty_quality_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0] #type: InstanceContainer + self._empty_quality_changes_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality_changes")[0] #type: InstanceContainer self._onGlobalContainerChanged() @@ -99,8 +103,6 @@ class MachineManager(QObject): self._application.getPreferences().addPreference("cura/active_machine", "") - self._global_event_keys = set() - self._printer_output_devices = [] # type: List[PrinterOutputDevice] self._application.getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged) # There might already be some output devices by the time the signal is connected @@ -116,15 +118,15 @@ class MachineManager(QObject): self._material_incompatible_message = Message(catalog.i18nc("@info:status", "The selected material is incompatible with the selected machine or configuration."), - title = catalog.i18nc("@info:title", "Incompatible Material")) + title = catalog.i18nc("@info:title", "Incompatible Material")) #type: Message - containers = ContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) + containers = ContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) #type: List[InstanceContainer] if containers: containers[0].nameChanged.connect(self._onMaterialNameChanged) - self._material_manager = self._application.getMaterialManager() - self._variant_manager = self._application.getVariantManager() - self._quality_manager = self._application.getQualityManager() + self._material_manager = self._application.getMaterialManager() #type: MaterialManager + self._variant_manager = self._application.getVariantManager() #type: VariantManager + self._quality_manager = self._application.getQualityManager() #type: QualityManager # When the materials lookup table gets updated, it can mean that a material has its name changed, which should # be reflected on the GUI. This signal emission makes sure that it happens. @@ -1030,7 +1032,7 @@ class MachineManager(QObject): self.activeQualityGroupChanged.emit() self.activeQualityChangesGroupChanged.emit() - def _setQualityGroup(self, quality_group, empty_quality_changes: bool = True) -> None: + def _setQualityGroup(self, quality_group: Optional[QualityGroup], empty_quality_changes: bool = True) -> None: if quality_group is None: self._setEmptyQuality() return @@ -1059,14 +1061,14 @@ class MachineManager(QObject): self.activeQualityGroupChanged.emit() self.activeQualityChangesGroupChanged.emit() - def _fixQualityChangesGroupToNotSupported(self, quality_changes_group): + def _fixQualityChangesGroupToNotSupported(self, quality_changes_group: QualityChangesGroup) -> None: nodes = [quality_changes_group.node_for_global] + list(quality_changes_group.nodes_for_extruders.values()) containers = [n.getContainer() for n in nodes if n is not None] for container in containers: container.setMetaDataEntry("quality_type", "not_supported") quality_changes_group.quality_type = "not_supported" - def _setQualityChangesGroup(self, quality_changes_group): + def _setQualityChangesGroup(self, quality_changes_group: QualityChangesGroup) -> None: if self._global_container_stack is None: return #Can't change that. quality_type = quality_changes_group.quality_type @@ -1110,18 +1112,18 @@ class MachineManager(QObject): self.activeQualityGroupChanged.emit() self.activeQualityChangesGroupChanged.emit() - def _setVariantNode(self, position, container_node): + def _setVariantNode(self, position: str, container_node: ContainerNode) -> None: if container_node.getContainer() is None: return self._global_container_stack.extruders[position].variant = container_node.getContainer() self.activeVariantChanged.emit() - def _setGlobalVariant(self, container_node): + def _setGlobalVariant(self, container_node: ContainerNode) -> None: self._global_container_stack.variant = container_node.getContainer() if not self._global_container_stack.variant: self._global_container_stack.variant = self._application.empty_variant_container - def _setMaterial(self, position, container_node = None): + def _setMaterial(self, position: str, container_node: ContainerNode = None) -> None: if container_node and container_node.getContainer(): self._global_container_stack.extruders[position].material = container_node.getContainer() root_material_id = container_node.metadata["base_file"] @@ -1133,7 +1135,7 @@ class MachineManager(QObject): self._current_root_material_id[position] = root_material_id self.rootMaterialChanged.emit() - def activeMaterialsCompatible(self): + def activeMaterialsCompatible(self) -> bool: # check material - variant compatibility if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)): for position, extruder in self._global_container_stack.extruders.items(): @@ -1144,7 +1146,7 @@ class MachineManager(QObject): return True ## Update current quality type and machine after setting material - def _updateQualityWithMaterial(self, *args): + def _updateQualityWithMaterial(self, *args: Any) -> None: if self._global_container_stack is None: return Logger.log("i", "Updating quality/quality_changes due to material change") @@ -1183,7 +1185,7 @@ class MachineManager(QObject): current_quality_type, quality_type) self._setQualityGroup(candidate_quality_groups[quality_type], empty_quality_changes = True) - def _updateMaterialWithVariant(self, position: Optional[str]): + def _updateMaterialWithVariant(self, position: Optional[str]) -> None: if self._global_container_stack is None: return if position is None: @@ -1226,7 +1228,7 @@ class MachineManager(QObject): ## Given a printer definition name, select the right machine instance. In case it doesn't exist, create a new # instance with the same network key. @pyqtSlot(str) - def switchPrinterType(self, machine_name: str): + def switchPrinterType(self, machine_name: str) -> None: # Don't switch if the user tries to change to the same type of printer if self.activeMachineDefinitionName == machine_name: return @@ -1309,7 +1311,7 @@ class MachineManager(QObject): return bool(containers) @pyqtSlot("QVariant") - def setGlobalVariant(self, container_node): + def setGlobalVariant(self, container_node: ContainerNode) -> None: self.blurSettings.emit() with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self._setGlobalVariant(container_node) @@ -1317,7 +1319,7 @@ class MachineManager(QObject): self._updateQualityWithMaterial() @pyqtSlot(str, str) - def setMaterialById(self, position, root_material_id): + def setMaterialById(self, position: str, root_material_id: str) -> None: machine_definition_id = self._global_container_stack.definition.id position = str(position) extruder_stack = self._global_container_stack.extruders[position] @@ -1345,7 +1347,7 @@ class MachineManager(QObject): self.setVariant(position, variant_node) @pyqtSlot(str, "QVariant") - def setVariant(self, position: str, container_node): + def setVariant(self, position: str, container_node: ContainerNode) -> None: position = str(position) self.blurSettings.emit() with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): @@ -1367,7 +1369,7 @@ class MachineManager(QObject): self.setQualityGroup(quality_group) @pyqtSlot(QObject) - def setQualityGroup(self, quality_group, no_dialog = False): + def setQualityGroup(self, quality_group: QualityGroup, no_dialog: bool = False) -> None: self.blurSettings.emit() with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self._setQualityGroup(quality_group) @@ -1377,11 +1379,11 @@ class MachineManager(QObject): self._application.discardOrKeepProfileChanges() @pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged) - def activeQualityGroup(self): + def activeQualityGroup(self) -> QualityGroup: return self._current_quality_group @pyqtSlot(QObject) - def setQualityChangesGroup(self, quality_changes_group, no_dialog = False): + def setQualityChangesGroup(self, quality_changes_group: QualityChangesGroup, no_dialog: bool = False) -> None: self.blurSettings.emit() with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self._setQualityChangesGroup(quality_changes_group) @@ -1391,18 +1393,18 @@ class MachineManager(QObject): self._application.discardOrKeepProfileChanges() @pyqtSlot() - def resetToUseDefaultQuality(self): + def resetToUseDefaultQuality(self) -> None: with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self._setQualityGroup(self._current_quality_group) for stack in [self._global_container_stack] + list(self._global_container_stack.extruders.values()): stack.userChanges.clear() @pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged) - def activeQualityChangesGroup(self): + def activeQualityChangesGroup(self) -> QualityChangesGroup: return self._current_quality_changes_group @pyqtProperty(str, notify = activeQualityGroupChanged) - def activeQualityOrQualityChangesName(self): + def activeQualityOrQualityChangesName(self) -> str: name = self._empty_quality_container.getName() if self._current_quality_changes_group: name = self._current_quality_changes_group.name From c3bac474ab7a7e91b428110d11dfb3b38def2d5a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Jun 2018 16:33:32 +0200 Subject: [PATCH 11/83] Use CuraContainerRegistry instead of ContainerRegistry Because we're calling functions of CuraContainerRegistry. Contributes to issue CURA-5330. --- cura/Settings/CuraContainerRegistry.py | 8 ++++- cura/Settings/MachineManager.py | 49 +++++++++++++------------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 6d8ed7c037..86f4a58cde 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -5,7 +5,7 @@ import os import re import configparser -from typing import Optional +from typing import cast, Optional from PyQt5.QtWidgets import QMessageBox @@ -731,3 +731,9 @@ class CuraContainerRegistry(ContainerRegistry): extruder_stack.setNextStack(machines[0]) else: Logger.log("w", "Could not find machine {machine} for extruder {extruder}", machine = extruder_stack.getMetaDataEntry("machine"), extruder = extruder_stack.getId()) + + #Override just for the type. + @override + @classmethod + def getInstance(cls, *args, **kwargs) -> "CuraContainerRegistry": + return cast(CuraContainerRegistry, super().getInstance(*args, **kwargs)) \ No newline at end of file diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 80498639f4..c5d6242c14 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -14,15 +14,13 @@ from UM.Signal import Signal from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer from UM.FlameProfiler import pyqtSlot from UM import Util - -from UM.Application import Application from UM.Logger import Logger from UM.Message import Message -from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.SettingFunction import SettingFunction from UM.Signal import postponeSignals, CompressTechnique +import cura.CuraApplication from cura.Machines.ContainerNode import ContainerNode #For typing. from cura.Machines.QualityChangesGroup import QualityChangesGroup #For typing. from cura.Machines.QualityGroup import QualityGroup #For typing. @@ -31,6 +29,7 @@ from cura.PrinterOutputDevice import PrinterOutputDevice from cura.PrinterOutput.ConfigurationModel import ConfigurationModel from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel +from cura.Settings.CuraContainerRegistry import CuraContainerRegistry from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderStack import ExtruderStack @@ -47,7 +46,7 @@ if TYPE_CHECKING: from cura.Machines.VariantManager import VariantManager class MachineManager(QObject): - def __init__(self, parent: QObject = None): + def __init__(self, parent: QObject = None) -> None: super().__init__(parent) self._active_container_stack = None # type: Optional[ExtruderManager] @@ -59,14 +58,14 @@ class MachineManager(QObject): self._default_extruder_position = "0" # to be updated when extruders are switched on and off - self.machine_extruder_material_update_dict = collections.defaultdict(list) + self.machine_extruder_material_update_dict = collections.defaultdict(list) #type: Dict[str, List[Callable[[], None]]] self._instance_container_timer = QTimer() #type: QTimer self._instance_container_timer.setInterval(250) self._instance_container_timer.setSingleShot(True) self._instance_container_timer.timeout.connect(self.__emitChangedSignals) - self._application = Application.getInstance() #type: Application + self._application = cura.CuraApplication.CuraApplication.getInstance() #type: cura.CuraApplication.CuraApplication self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged) self._application.getContainerRegistry().containerLoadComplete.connect(self._onContainersChanged) @@ -120,7 +119,7 @@ class MachineManager(QObject): "The selected material is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Incompatible Material")) #type: Message - containers = ContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) #type: List[InstanceContainer] + containers = CuraContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) #type: List[InstanceContainer] if containers: containers[0].nameChanged.connect(self._onMaterialNameChanged) @@ -166,7 +165,7 @@ class MachineManager(QObject): def setInitialActiveMachine(self) -> None: active_machine_id = self._application.getPreferences().getValue("cura/active_machine") - if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacksMetadata(id = active_machine_id): + if active_machine_id != "" and CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = active_machine_id): # An active machine was saved, so restore it. self.setActiveMachine(active_machine_id) @@ -217,7 +216,7 @@ class MachineManager(QObject): @pyqtProperty(int, constant=True) def totalNumberOfSettings(self) -> int: - return len(ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0].getAllKeys()) + return len(CuraContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0].getAllKeys()) def _onGlobalContainerChanged(self) -> None: if self._global_container_stack: @@ -352,7 +351,7 @@ class MachineManager(QObject): def setActiveMachine(self, stack_id: str) -> None: self.blurSettings.emit() # Ensure no-one has focus. - container_registry = ContainerRegistry.getInstance() + container_registry = CuraContainerRegistry.getInstance() containers = container_registry.findContainerStacks(id = stack_id) if not containers: @@ -378,7 +377,7 @@ class MachineManager(QObject): # \param metadata_filter \type{dict} list of metadata keys and values used for filtering @staticmethod def getMachine(definition_id: str, metadata_filter: Dict[str, str] = None) -> Optional["GlobalStack"]: - machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) + machines = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) for machine in machines: if machine.definition.getId() == definition_id: return machine @@ -622,7 +621,7 @@ class MachineManager(QObject): ## Check if a container is read_only @pyqtSlot(str, result = bool) def isReadOnly(self, container_id: str) -> bool: - return ContainerRegistry.getInstance().isReadOnly(container_id) + return CuraContainerRegistry.getInstance().isReadOnly(container_id) ## Copy the value of the setting of the current extruder to all other extruders as well as the global container. @pyqtSlot(str) @@ -692,7 +691,7 @@ class MachineManager(QObject): @pyqtSlot(str, str) def renameMachine(self, machine_id: str, new_name: str) -> None: - container_registry = ContainerRegistry.getInstance() + container_registry = CuraContainerRegistry.getInstance() machine_stack = container_registry.findContainerStacks(id = machine_id) if machine_stack: new_name = container_registry.createUniqueName("machine", machine_stack[0].getName(), new_name, machine_stack[0].definition.getName()) @@ -706,23 +705,23 @@ class MachineManager(QObject): # activate a new machine before removing a machine because this is safer if activate_new_machine: - machine_stacks = ContainerRegistry.getInstance().findContainerStacksMetadata(type = "machine") + machine_stacks = CuraContainerRegistry.getInstance().findContainerStacksMetadata(type = "machine") other_machine_stacks = [s for s in machine_stacks if s["id"] != machine_id] if other_machine_stacks: self.setActiveMachine(other_machine_stacks[0]["id"]) - metadata = ContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0] + metadata = CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0] network_key = metadata["um_network_key"] if "um_network_key" in metadata else None ExtruderManager.getInstance().removeMachineExtruders(machine_id) - containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id) + containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id) for container in containers: - ContainerRegistry.getInstance().removeContainer(container["id"]) - ContainerRegistry.getInstance().removeContainer(machine_id) + CuraContainerRegistry.getInstance().removeContainer(container["id"]) + CuraContainerRegistry.getInstance().removeContainer(machine_id) # If the printer that is being removed is a network printer, the hidden printers have to be also removed if network_key: metadata_filter = {"um_network_key": network_key} - hidden_containers = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) + hidden_containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) if hidden_containers: # This reuses the method and remove all printers recursively self.removeMachine(hidden_containers[0].getId()) @@ -790,10 +789,10 @@ class MachineManager(QObject): ## Get the Definition ID of a machine (specified by ID) # \param machine_id string machine id to get the definition ID of - # \returns DefinitionID (string) if found, None otherwise + # \returns DefinitionID if found, None otherwise @pyqtSlot(str, result = str) def getDefinitionByMachineId(self, machine_id: str) -> str: - containers = ContainerRegistry.getInstance().findContainerStacks(id = machine_id) + containers = CuraContainerRegistry.getInstance().findContainerStacks(id = machine_id) if containers: return containers[0].definition.getId() @@ -1233,7 +1232,7 @@ class MachineManager(QObject): if self.activeMachineDefinitionName == machine_name: return # Get the definition id corresponding to this machine name - machine_definition_id = ContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId() + machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId() # Try to find a machine with the same network key new_machine = self.getMachine(machine_definition_id, metadata_filter = {"um_network_key": self.activeMachineNetworkKey}) # If there is no machine, then create a new one and set it to the non-hidden instance @@ -1287,7 +1286,7 @@ class MachineManager(QObject): ## Find all container stacks that has the pair 'key = value' in its metadata and replaces the value with 'new_value' def replaceContainersMetadata(self, key: str, value: str, new_value: str) -> None: - machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine") + machines = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine") for machine in machines: if machine.getMetaDataEntry(key) == value: machine.setMetaDataEntry(key, new_value) @@ -1300,14 +1299,14 @@ class MachineManager(QObject): # Check if the connect_group_name is correct. If not, update all the containers connected to the same printer if self.activeMachineNetworkGroupName != group_name: metadata_filter = {"um_network_key": self.activeMachineNetworkKey} - hidden_containers = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) + hidden_containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) for container in hidden_containers: container.setMetaDataEntry("connect_group_name", group_name) ## This method checks if there is an instance connected to the given network_key def existNetworkInstances(self, network_key: str) -> bool: metadata_filter = {"um_network_key": network_key} - containers = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) + containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) return bool(containers) @pyqtSlot("QVariant") From e4a0345fe43030ff479a7077586b66a3119094e6 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Jun 2018 16:40:57 +0200 Subject: [PATCH 12/83] Fix more minor typing mistakes Contributes to issue CURA-5330. --- cura/Settings/MachineManager.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index c5d6242c14..86e5b85edf 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -3,7 +3,7 @@ import collections import time -from typing import Any, List, Dict, TYPE_CHECKING, Optional +from typing import Any, Callable, List, Dict, TYPE_CHECKING, Optional from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator @@ -79,12 +79,12 @@ class MachineManager(QObject): self._stacks_have_errors = None # type: Optional[bool] - self._empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() #type: InstanceContainer - self._empty_definition_changes_container = ContainerRegistry.getInstance().findContainers(id = "empty_definition_changes")[0] #type: InstanceContainer - self._empty_variant_container = ContainerRegistry.getInstance().findContainers(id = "empty_variant")[0] #type: InstanceContainer - self._empty_material_container = ContainerRegistry.getInstance().findContainers(id = "empty_material")[0] #type: InstanceContainer - self._empty_quality_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0] #type: InstanceContainer - self._empty_quality_changes_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality_changes")[0] #type: InstanceContainer + self._empty_container = CuraContainerRegistry.getInstance().getEmptyInstanceContainer() #type: InstanceContainer + self._empty_definition_changes_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_definition_changes")[0] #type: InstanceContainer + self._empty_variant_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_variant")[0] #type: InstanceContainer + self._empty_material_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_material")[0] #type: InstanceContainer + self._empty_quality_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_quality")[0] #type: InstanceContainer + self._empty_quality_changes_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_quality_changes")[0] #type: InstanceContainer self._onGlobalContainerChanged() @@ -791,10 +791,11 @@ class MachineManager(QObject): # \param machine_id string machine id to get the definition ID of # \returns DefinitionID if found, None otherwise @pyqtSlot(str, result = str) - def getDefinitionByMachineId(self, machine_id: str) -> str: + def getDefinitionByMachineId(self, machine_id: str) -> Optional[str]: containers = CuraContainerRegistry.getInstance().findContainerStacks(id = machine_id) if containers: return containers[0].definition.getId() + return None def getIncompatibleSettingsOnEnabledExtruders(self, container: InstanceContainer) -> List[str]: extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value") @@ -856,7 +857,7 @@ class MachineManager(QObject): # Check to see if any objects are set to print with an extruder that will no longer exist root_node = self._application.getController().getScene().getRoot() - for node in DepthFirstIterator(root_node): + for node in DepthFirstIterator(root_node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if node.getMeshData(): extruder_nr = node.callDecoration("getActiveExtruderPosition") @@ -871,7 +872,7 @@ class MachineManager(QObject): global_user_container = self._global_container_stack.userChanges # Make sure extruder_stacks exists - extruder_stacks = [] + extruder_stacks = [] #type: List[ExtruderStack] if previous_extruder_count == 1: extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() From da285a33d5731c59e261920557789c9807376526 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Jun 2018 16:47:46 +0200 Subject: [PATCH 13/83] Fix types Added the missing ones and fixed the broken ones. Contributes to issue CURA-5330. --- cura/CuraActions.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/cura/CuraActions.py b/cura/CuraActions.py index 8544438f3a..d0f36bc45a 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.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 PyQt5.QtCore import QObject, QUrl @@ -6,7 +6,6 @@ from PyQt5.QtGui import QDesktopServices from UM.FlameProfiler import pyqtSlot from UM.Event import CallFunctionEvent -from UM.Application import Application from UM.Math.Vector import Vector from UM.Scene.Selection import Selection from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator @@ -14,6 +13,7 @@ from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation from UM.Operations.TranslateOperation import TranslateOperation +from cura.CuraApplication import CuraApplication from cura.Operations.SetParentOperation import SetParentOperation from cura.MultiplyObjectsJob import MultiplyObjectsJob from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation @@ -25,26 +25,26 @@ from UM.Logger import Logger class CuraActions(QObject): - def __init__(self, parent = None): + def __init__(self, parent: QObject = None) -> None: super().__init__(parent) @pyqtSlot() - def openDocumentation(self): + def openDocumentation(self) -> None: # Starting a web browser from a signal handler connected to a menu will crash on windows. # So instead, defer the call to the next run of the event loop, since that does work. # Note that weirdly enough, only signal handlers that open a web browser fail like that. event = CallFunctionEvent(self._openUrl, [QUrl("http://ultimaker.com/en/support/software")], {}) - Application.getInstance().functionEvent(event) + CuraApplication.getInstance().functionEvent(event) @pyqtSlot() - def openBugReportPage(self): + def openBugReportPage(self) -> None: event = CallFunctionEvent(self._openUrl, [QUrl("http://github.com/Ultimaker/Cura/issues")], {}) - Application.getInstance().functionEvent(event) + CuraApplication.getInstance().functionEvent(event) ## Reset camera position and direction to default @pyqtSlot() def homeCamera(self) -> None: - scene = Application.getInstance().getController().getScene() + scene = CuraApplication.getInstance().getController().getScene() camera = scene.getActiveCamera() camera.setPosition(Vector(-80, 250, 700)) camera.setPerspective(True) @@ -72,14 +72,14 @@ class CuraActions(QObject): # \param count The number of times to multiply the selection. @pyqtSlot(int) def multiplySelection(self, count: int) -> None: - min_offset = Application.getInstance().getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors + min_offset = CuraApplication.getInstance().getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, min_offset = max(min_offset, 8)) job.start() ## Delete all selected objects. @pyqtSlot() def deleteSelection(self) -> None: - if not Application.getInstance().getController().getToolsEnabled(): + if not CuraApplication.getInstance().getController().getToolsEnabled(): return removed_group_nodes = [] @@ -96,7 +96,7 @@ class CuraActions(QObject): op.addOperation(RemoveSceneNodeOperation(group_node)) # Reset the print information - Application.getInstance().getController().getScene().sceneChanged.emit(node) + CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node) op.push() @@ -111,7 +111,7 @@ class CuraActions(QObject): for node in Selection.getAllSelectedObjects(): # If the node is a group, apply the active extruder to all children of the group. if node.callDecoration("isGroup"): - for grouped_node in BreadthFirstIterator(node): + for grouped_node in BreadthFirstIterator(node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if grouped_node.callDecoration("getActiveExtruder") == extruder_id: continue @@ -143,7 +143,7 @@ class CuraActions(QObject): Logger.log("d", "Setting build plate number... %d" % build_plate_nr) operation = GroupedOperation() - root = Application.getInstance().getController().getScene().getRoot() + root = CuraApplication.getInstance().getController().getScene().getRoot() nodes_to_change = [] for node in Selection.getAllSelectedObjects(): @@ -151,7 +151,7 @@ class CuraActions(QObject): while parent_node.getParent() != root: parent_node = parent_node.getParent() - for single_node in BreadthFirstIterator(parent_node): + for single_node in BreadthFirstIterator(parent_node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. nodes_to_change.append(single_node) if not nodes_to_change: @@ -164,5 +164,5 @@ class CuraActions(QObject): Selection.clear() - def _openUrl(self, url): + def _openUrl(self, url: QUrl) -> None: QDesktopServices.openUrl(url) From 84d69fcd9746b6c216bbd28d57b561a89eb67c66 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Jun 2018 16:50:58 +0200 Subject: [PATCH 14/83] Add missing type for locally created list MyPy isn't clever enough to figure this out. ...but I am. Contributes to issue CURA-5330. --- cura/CuraActions.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cura/CuraActions.py b/cura/CuraActions.py index d0f36bc45a..dd892946d5 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -3,9 +3,10 @@ from PyQt5.QtCore import QObject, QUrl from PyQt5.QtGui import QDesktopServices -from UM.FlameProfiler import pyqtSlot +from typing import List, TYPE_CHECKING from UM.Event import CallFunctionEvent +from UM.FlameProfiler import pyqtSlot from UM.Math.Vector import Vector from UM.Scene.Selection import Selection from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator @@ -23,6 +24,8 @@ from cura.Operations.SetBuildPlateNumberOperation import SetBuildPlateNumberOper from UM.Logger import Logger +if TYPE_CHECKING: + from UM.Scene.SceneNode import SceneNode class CuraActions(QObject): def __init__(self, parent: QObject = None) -> None: @@ -82,7 +85,7 @@ class CuraActions(QObject): if not CuraApplication.getInstance().getController().getToolsEnabled(): return - removed_group_nodes = [] + removed_group_nodes = [] #type: List[SceneNode] op = GroupedOperation() nodes = Selection.getAllSelectedObjects() for node in nodes: From 5a5d07865baedb9897daed31c6d1867546afe16c Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 4 Jun 2018 09:01:30 +0200 Subject: [PATCH 15/83] Fix circular imports with CuraApplication Contributes to issue CURA-5330. --- cura/CuraActions.py | 16 ++++++++-------- cura/Settings/CuraContainerRegistry.py | 18 +++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/cura/CuraActions.py b/cura/CuraActions.py index dd892946d5..1ddc41717e 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -14,7 +14,7 @@ from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation from UM.Operations.TranslateOperation import TranslateOperation -from cura.CuraApplication import CuraApplication +import cura.CuraApplication from cura.Operations.SetParentOperation import SetParentOperation from cura.MultiplyObjectsJob import MultiplyObjectsJob from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation @@ -37,17 +37,17 @@ class CuraActions(QObject): # So instead, defer the call to the next run of the event loop, since that does work. # Note that weirdly enough, only signal handlers that open a web browser fail like that. event = CallFunctionEvent(self._openUrl, [QUrl("http://ultimaker.com/en/support/software")], {}) - CuraApplication.getInstance().functionEvent(event) + cura.CuraApplication.CuraApplication.getInstance().functionEvent(event) @pyqtSlot() def openBugReportPage(self) -> None: event = CallFunctionEvent(self._openUrl, [QUrl("http://github.com/Ultimaker/Cura/issues")], {}) - CuraApplication.getInstance().functionEvent(event) + cura.CuraApplication.CuraApplication.getInstance().functionEvent(event) ## Reset camera position and direction to default @pyqtSlot() def homeCamera(self) -> None: - scene = CuraApplication.getInstance().getController().getScene() + scene = cura.CuraApplication.CuraApplication.getInstance().getController().getScene() camera = scene.getActiveCamera() camera.setPosition(Vector(-80, 250, 700)) camera.setPerspective(True) @@ -75,14 +75,14 @@ class CuraActions(QObject): # \param count The number of times to multiply the selection. @pyqtSlot(int) def multiplySelection(self, count: int) -> None: - min_offset = CuraApplication.getInstance().getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors + min_offset = cura.CuraApplication.CuraApplication.getInstance().getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, min_offset = max(min_offset, 8)) job.start() ## Delete all selected objects. @pyqtSlot() def deleteSelection(self) -> None: - if not CuraApplication.getInstance().getController().getToolsEnabled(): + if not cura.CuraApplication.CuraApplication.getInstance().getController().getToolsEnabled(): return removed_group_nodes = [] #type: List[SceneNode] @@ -99,7 +99,7 @@ class CuraActions(QObject): op.addOperation(RemoveSceneNodeOperation(group_node)) # Reset the print information - CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node) + cura.CuraApplication.CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node) op.push() @@ -146,7 +146,7 @@ class CuraActions(QObject): Logger.log("d", "Setting build plate number... %d" % build_plate_nr) operation = GroupedOperation() - root = CuraApplication.getInstance().getController().getScene().getRoot() + root = cura.CuraApplication.CuraApplication.getInstance().getController().getScene().getRoot() nodes_to_change = [] for node in Selection.getAllSelectedObjects(): diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 86f4a58cde..05a41c81fd 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -26,7 +26,7 @@ from UM.Resources import Resources from . import ExtruderStack from . import GlobalStack -from cura.CuraApplication import CuraApplication +import cura.CuraApplication from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch from cura.ReaderWriters.ProfileReader import NoProfileException @@ -57,7 +57,7 @@ class CuraContainerRegistry(ContainerRegistry): if isinstance(container, InstanceContainer) and type(container) != type(self.getEmptyInstanceContainer()): # Check against setting version of the definition. - required_setting_version = CuraApplication.SettingVersion + required_setting_version = cura.CuraApplication.CuraApplication.SettingVersion actual_setting_version = int(container.getMetaDataEntry("setting_version", default = 0)) if required_setting_version != actual_setting_version: Logger.log("w", "Instance container {container_id} is outdated. Its setting version is {actual_setting_version} but it should be {required_setting_version}.".format(container_id = container.getId(), actual_setting_version = actual_setting_version, required_setting_version = required_setting_version)) @@ -260,7 +260,7 @@ class CuraContainerRegistry(ContainerRegistry): profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1)) profile = InstanceContainer(profile_id) profile.setName(quality_name) - profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) + profile.addMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.SettingVersion) profile.addMetaDataEntry("type", "quality_changes") profile.addMetaDataEntry("definition", expected_machine_definition) profile.addMetaDataEntry("quality_type", quality_type) @@ -362,7 +362,7 @@ class CuraContainerRegistry(ContainerRegistry): # Check to make sure the imported profile actually makes sense in context of the current configuration. # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as # successfully imported but then fail to show up. - quality_manager = CuraApplication.getInstance()._quality_manager + quality_manager = cura.CuraApplication.CuraApplication.getInstance()._quality_manager quality_group_dict = quality_manager.getQualityGroupsForMachineDefinition(global_stack) if quality_type not in quality_group_dict: return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type) @@ -465,7 +465,7 @@ class CuraContainerRegistry(ContainerRegistry): def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id, new_global_quality_changes = None, create_new_ids = True): new_extruder_id = extruder_id - application = CuraApplication.getInstance() + application = cura.CuraApplication.CuraApplication.getInstance() extruder_definitions = self.findDefinitionContainers(id = new_extruder_id) if not extruder_definitions: @@ -485,7 +485,7 @@ class CuraContainerRegistry(ContainerRegistry): definition_changes_name = definition_changes_id definition_changes = InstanceContainer(definition_changes_id, parent = application) definition_changes.setName(definition_changes_name) - definition_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) + definition_changes.addMetaDataEntry("setting_version", application.SettingVersion) definition_changes.addMetaDataEntry("type", "definition_changes") definition_changes.addMetaDataEntry("definition", extruder_definition.getId()) @@ -514,7 +514,7 @@ class CuraContainerRegistry(ContainerRegistry): user_container.setName(user_container_name) user_container.addMetaDataEntry("type", "user") user_container.addMetaDataEntry("machine", machine.getId()) - user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) + user_container.addMetaDataEntry("setting_version", application.SettingVersion) user_container.setDefinition(machine.definition.getId()) user_container.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position")) @@ -587,7 +587,7 @@ class CuraContainerRegistry(ContainerRegistry): extruder_quality_changes_container = InstanceContainer(container_id, parent = application) extruder_quality_changes_container.setName(container_name) extruder_quality_changes_container.addMetaDataEntry("type", "quality_changes") - extruder_quality_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) + extruder_quality_changes_container.addMetaDataEntry("setting_version", application.SettingVersion) extruder_quality_changes_container.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position")) extruder_quality_changes_container.addMetaDataEntry("quality_type", machine_quality_changes.getMetaDataEntry("quality_type")) extruder_quality_changes_container.setDefinition(machine_quality_changes.getDefinition().getId()) @@ -675,7 +675,7 @@ class CuraContainerRegistry(ContainerRegistry): return extruder_stack def _findQualityChangesContainerInCuraFolder(self, name): - quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityChangesInstanceContainer) + quality_changes_dir = Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.QualityChangesInstanceContainer) instance_container = None From f544e3d5c07279040f00528ed98503023213cef7 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 4 Jun 2018 09:02:57 +0200 Subject: [PATCH 16/83] Fix @override decorator They need a parameter and they need to be the last decorator. Contributes to issue CURA-5330. --- cura/Settings/CuraContainerRegistry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 05a41c81fd..9300fa5fef 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -733,7 +733,7 @@ class CuraContainerRegistry(ContainerRegistry): Logger.log("w", "Could not find machine {machine} for extruder {extruder}", machine = extruder_stack.getMetaDataEntry("machine"), extruder = extruder_stack.getId()) #Override just for the type. - @override @classmethod + @override(ContainerRegistry) def getInstance(cls, *args, **kwargs) -> "CuraContainerRegistry": return cast(CuraContainerRegistry, super().getInstance(*args, **kwargs)) \ No newline at end of file From 9fb4511f5265803809e34159aae4f8c06f6affe8 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 4 Jun 2018 09:43:13 +0200 Subject: [PATCH 17/83] Add function typing This causes MyPy to discover errors in this file. Contributes to issue CURA-5330. --- cura/Scene/CuraSceneNode.py | 38 +++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/cura/Scene/CuraSceneNode.py b/cura/Scene/CuraSceneNode.py index 428a59f554..734348f6c6 100644 --- a/cura/Scene/CuraSceneNode.py +++ b/cura/Scene/CuraSceneNode.py @@ -1,39 +1,42 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. + from copy import deepcopy -from typing import List +from typing import Dict, List, Optional from UM.Application import Application from UM.Math.AxisAlignedBox import AxisAlignedBox +from UM.Math.Polygon import Polygon #For typing. from UM.Scene.SceneNode import SceneNode -from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator - +from cura.CuraApplication import CuraApplication #To get the build plate. +from cura.Settings.ExtruderStack import ExtruderStack #For typing. +from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator #For per-object settings. ## Scene nodes that are models are only seen when selecting the corresponding build plate # Note that many other nodes can just be UM SceneNode objects. class CuraSceneNode(SceneNode): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - if "no_setting_override" not in kwargs: - self.addDecorator(SettingOverrideDecorator()) # now we always have a getActiveExtruderPosition, unless explicitly disabled + def __init__(self, parent: Optional["SceneNode"] = None, visible: bool = True, name: str = "", no_setting_override: bool = False) -> None: + super().__init__(parent = parent, visible = visible, name = name) + if not no_setting_override: + self.addDecorator(SettingOverrideDecorator()) self._outside_buildarea = False - def setOutsideBuildArea(self, new_value): + def setOutsideBuildArea(self, new_value: bool) -> None: self._outside_buildarea = new_value - def isOutsideBuildArea(self): + def isOutsideBuildArea(self) -> bool: return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0 - def isVisible(self): - return super().isVisible() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getMultiBuildPlateModel().activeBuildPlate + def isVisible(self) -> bool: + return super().isVisible() and self.callDecoration("getBuildPlateNumber") == CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate def isSelectable(self) -> bool: - return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getMultiBuildPlateModel().activeBuildPlate + return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate ## Get the extruder used to print this node. If there is no active node, then the extruder in position zero is returned # TODO The best way to do it is by adding the setActiveExtruder decorator to every node when is loaded - def getPrintingExtruder(self): + def getPrintingExtruder(self) -> Optional[ExtruderStack]: global_container_stack = Application.getInstance().getGlobalContainerStack() per_mesh_stack = self.callDecoration("getStack") extruders = list(global_container_stack.extruders.values()) @@ -79,7 +82,7 @@ class CuraSceneNode(SceneNode): ] ## Return if the provided bbox collides with the bbox of this scene node - def collidesWithBbox(self, check_bbox): + def collidesWithBbox(self, check_bbox: AxisAlignedBox) -> bool: bbox = self.getBoundingBox() # Mark the node as outside the build volume if the bounding box test fails. @@ -89,7 +92,7 @@ class CuraSceneNode(SceneNode): return False ## Return if any area collides with the convex hull of this scene node - def collidesWithArea(self, areas): + def collidesWithArea(self, areas: List[Polygon]) -> bool: convex_hull = self.callDecoration("getConvexHull") if convex_hull: if not convex_hull.isValid(): @@ -104,8 +107,7 @@ class CuraSceneNode(SceneNode): return False ## Override of SceneNode._calculateAABB to exclude non-printing-meshes from bounding box - def _calculateAABB(self): - aabb = None + def _calculateAABB(self) -> None: if self._mesh_data: aabb = self._mesh_data.getExtents(self.getWorldTransformation()) else: # If there is no mesh_data, use a boundingbox that encompasses the local (0,0,0) @@ -123,7 +125,7 @@ class CuraSceneNode(SceneNode): self._aabb = aabb ## Taken from SceneNode, but replaced SceneNode with CuraSceneNode - def __deepcopy__(self, memo): + def __deepcopy__(self, memo: Dict[int, object]) -> "CuraSceneNode": copy = CuraSceneNode(no_setting_override = True) # Setting override will be added later copy.setTransformation(self.getLocalTransformation()) copy.setMeshData(self._mesh_data) From aff341eac95563f1e35994feadcacb418cc35d34 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 4 Jun 2018 09:48:30 +0200 Subject: [PATCH 18/83] Add casts for result of deepcopy Because deepcopy should return the same object as what is provided as input. Contributes to issue CURA-5330. --- cura/Scene/CuraSceneNode.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/cura/Scene/CuraSceneNode.py b/cura/Scene/CuraSceneNode.py index 734348f6c6..d634be1aae 100644 --- a/cura/Scene/CuraSceneNode.py +++ b/cura/Scene/CuraSceneNode.py @@ -2,12 +2,13 @@ # Cura is released under the terms of the LGPLv3 or higher. from copy import deepcopy -from typing import Dict, List, Optional +from typing import cast, Dict, List, Optional from UM.Application import Application from UM.Math.AxisAlignedBox import AxisAlignedBox from UM.Math.Polygon import Polygon #For typing. from UM.Scene.SceneNode import SceneNode +from UM.Scene.SceneNodeDecorator import SceneNodeDecorator #To cast the deepcopy of every decorator back to SceneNodeDecorator. from cura.CuraApplication import CuraApplication #To get the build plate. from cura.Settings.ExtruderStack import ExtruderStack #For typing. @@ -129,14 +130,14 @@ class CuraSceneNode(SceneNode): copy = CuraSceneNode(no_setting_override = True) # Setting override will be added later copy.setTransformation(self.getLocalTransformation()) copy.setMeshData(self._mesh_data) - copy.setVisible(deepcopy(self._visible, memo)) - copy._selectable = deepcopy(self._selectable, memo) - copy._name = deepcopy(self._name, memo) + copy.setVisible(cast(bool, deepcopy(self._visible, memo))) + copy._selectable = cast(bool, deepcopy(self._selectable, memo)) + copy._name = cast(str, deepcopy(self._name, memo)) for decorator in self._decorators: - copy.addDecorator(deepcopy(decorator, memo)) + copy.addDecorator(cast(SceneNodeDecorator, deepcopy(decorator, memo))) for child in self._children: - copy.addChild(deepcopy(child, memo)) + copy.addChild(cast(SceneNode, deepcopy(child, memo))) self.calculateBoundingBoxMesh() return copy From 8ba592b59309a6de8cecedf33f2115e794fbac58 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 4 Jun 2018 09:48:50 +0200 Subject: [PATCH 19/83] Fix circular import with CuraApplication again Contributes to issue CURA-5330. --- cura/Scene/CuraSceneNode.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/Scene/CuraSceneNode.py b/cura/Scene/CuraSceneNode.py index d634be1aae..4a367f886c 100644 --- a/cura/Scene/CuraSceneNode.py +++ b/cura/Scene/CuraSceneNode.py @@ -10,7 +10,7 @@ from UM.Math.Polygon import Polygon #For typing. from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNodeDecorator import SceneNodeDecorator #To cast the deepcopy of every decorator back to SceneNodeDecorator. -from cura.CuraApplication import CuraApplication #To get the build plate. +import cura.CuraApplication #To get the build plate. from cura.Settings.ExtruderStack import ExtruderStack #For typing. from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator #For per-object settings. @@ -30,10 +30,10 @@ class CuraSceneNode(SceneNode): return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0 def isVisible(self) -> bool: - return super().isVisible() and self.callDecoration("getBuildPlateNumber") == CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate + return super().isVisible() and self.callDecoration("getBuildPlateNumber") == cura.CuraApplication.CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate def isSelectable(self) -> bool: - return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate + return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == cura.CuraApplication.CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate ## Get the extruder used to print this node. If there is no active node, then the extruder in position zero is returned # TODO The best way to do it is by adding the setActiveExtruder decorator to every node when is loaded From 49c684a741d9b06193020f74c79d0ebe9fa667b2 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 4 Jun 2018 09:51:34 +0200 Subject: [PATCH 20/83] Add function typing Contributes to issue CURA-5330. --- cura/Settings/ExtruderStack.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py index b3f7d529a2..9794cd1b3d 100644 --- a/cura/Settings/ExtruderStack.py +++ b/cura/Settings/ExtruderStack.py @@ -1,7 +1,7 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Any, TYPE_CHECKING, Optional +from typing import Any, Dict, TYPE_CHECKING, Optional from PyQt5.QtCore import pyqtProperty, pyqtSignal @@ -25,7 +25,7 @@ if TYPE_CHECKING: # # class ExtruderStack(CuraContainerStack): - def __init__(self, container_id: str): + def __init__(self, container_id: str) -> None: super().__init__(container_id) self.addMetaDataEntry("type", "extruder_train") # For backward compatibility @@ -50,14 +50,14 @@ class ExtruderStack(CuraContainerStack): def getNextStack(self) -> Optional["GlobalStack"]: return super().getNextStack() - def setEnabled(self, enabled): + def setEnabled(self, enabled: bool) -> None: if "enabled" not in self._metadata: self.addMetaDataEntry("enabled", "True") self.setMetaDataEntry("enabled", str(enabled)) self.enabledChanged.emit() @pyqtProperty(bool, notify = enabledChanged) - def isEnabled(self): + def isEnabled(self) -> bool: return parseBool(self.getMetaDataEntry("enabled", "True")) @classmethod @@ -142,7 +142,7 @@ class ExtruderStack(CuraContainerStack): if stacks: self.setNextStack(stacks[0]) - def _onPropertiesChanged(self, key, properties): + def _onPropertiesChanged(self, key: str, properties: Dict[str, Any]) -> None: # When there is a setting that is not settable per extruder that depends on a value from a setting that is, # we do not always get properly informed that we should re-evaluate the setting. So make sure to indicate # something changed for those settings. From 51f24bc8c8a779791e764589aacf63bdb77adc9a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 4 Jun 2018 09:53:20 +0200 Subject: [PATCH 21/83] Call getMachineManager on CuraApplication Because Application doesn't have it. Its child class has. Contributes to issue CURA-5330. --- cura/Settings/ExtruderStack.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py index 9794cd1b3d..4445563e00 100644 --- a/cura/Settings/ExtruderStack.py +++ b/cura/Settings/ExtruderStack.py @@ -5,7 +5,6 @@ from typing import Any, Dict, TYPE_CHECKING, Optional from PyQt5.QtCore import pyqtProperty, pyqtSignal -from UM.Application import Application from UM.Decorators import override from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase from UM.Settings.ContainerStack import ContainerStack @@ -13,6 +12,8 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext from UM.Util import parseBool +import cura.CuraApplication + from . import Exceptions from .CuraContainerStack import CuraContainerStack, _ContainerIndexes from .ExtruderManager import ExtruderManager @@ -113,7 +114,7 @@ class ExtruderStack(CuraContainerStack): limit_to_extruder = super().getProperty(key, "limit_to_extruder", context) if limit_to_extruder is not None: if limit_to_extruder == -1: - limit_to_extruder = int(Application.getInstance().getMachineManager().defaultExtruderPosition) + limit_to_extruder = int(cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition) limit_to_extruder = str(limit_to_extruder) if (limit_to_extruder is not None and limit_to_extruder != "-1") and self.getMetaDataEntry("position") != str(limit_to_extruder): if str(limit_to_extruder) in self.getNextStack().extruders: From e5d1a53dd679885e7ca2a613ef7302f7d7b9c7da Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 4 Jun 2018 09:55:17 +0200 Subject: [PATCH 22/83] Add missing return type Contributes to issue CURA-5330. --- cura/Settings/PerObjectContainerStack.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cura/Settings/PerObjectContainerStack.py b/cura/Settings/PerObjectContainerStack.py index 33111cbed7..6a4e638279 100644 --- a/cura/Settings/PerObjectContainerStack.py +++ b/cura/Settings/PerObjectContainerStack.py @@ -1,3 +1,6 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + from typing import Any, Optional from UM.Application import Application @@ -9,7 +12,6 @@ from .CuraContainerStack import CuraContainerStack class PerObjectContainerStack(CuraContainerStack): - @override(CuraContainerStack) def getProperty(self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None) -> Any: if context is None: @@ -53,7 +55,7 @@ class PerObjectContainerStack(CuraContainerStack): return result @override(CuraContainerStack) - def setNextStack(self, stack: CuraContainerStack): + def setNextStack(self, stack: CuraContainerStack) -> None: super().setNextStack(stack) # trigger signal to re-evaluate all default settings From e1ecbdf8fef7639a962619a5d8e6caccec969365 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 8 Jun 2018 15:46:24 +0200 Subject: [PATCH 23/83] CURA-5330 Add typing in QualityGroup --- cura/Machines/QualityGroup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cura/Machines/QualityGroup.py b/cura/Machines/QualityGroup.py index 02096cfb36..b1c564fadf 100644 --- a/cura/Machines/QualityGroup.py +++ b/cura/Machines/QualityGroup.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 Dict, Optional, List +from typing import Dict, Optional, List, Set from PyQt5.QtCore import QObject, pyqtSlot @@ -21,7 +21,7 @@ from PyQt5.QtCore import QObject, pyqtSlot # class QualityGroup(QObject): - def __init__(self, name: str, quality_type: str, parent = None): + def __init__(self, name: str, quality_type: str, parent = None) -> None: super().__init__(parent) self.name = name self.node_for_global = None # type: Optional["QualityGroup"] @@ -33,8 +33,8 @@ class QualityGroup(QObject): def getName(self) -> str: return self.name - def getAllKeys(self) -> set: - result = set() + def getAllKeys(self) -> Set[str]: + result = set() #type: Set[str] for node in [self.node_for_global] + list(self.nodes_for_extruders.values()): if node is None: continue From ace7e89ea0f1e563bd4102d56252061a8ece4277 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 8 Jun 2018 15:48:47 +0200 Subject: [PATCH 24/83] CURA-5330 Add typing to PrintJobOutputModel --- cura/PrinterOutput/PrintJobOutputModel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/PrinterOutput/PrintJobOutputModel.py b/cura/PrinterOutput/PrintJobOutputModel.py index 92376ad1dd..f46ec6706c 100644 --- a/cura/PrinterOutput/PrintJobOutputModel.py +++ b/cura/PrinterOutput/PrintJobOutputModel.py @@ -18,7 +18,7 @@ class PrintJobOutputModel(QObject): assignedPrinterChanged = pyqtSignal() ownerChanged = pyqtSignal() - def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None): + def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None) -> None: super().__init__(parent) self._output_controller = output_controller self._state = "" From fcdb1aef52724ab4a7c6e14b0eb8761196fc2b53 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 8 Jun 2018 15:53:33 +0200 Subject: [PATCH 25/83] CURA-5330 Add typing in ExtruderOutputModel --- cura/PrinterOutput/ExtruderOutputModel.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cura/PrinterOutput/ExtruderOutputModel.py b/cura/PrinterOutput/ExtruderOutputModel.py index 75b9cc98ac..0726662c6c 100644 --- a/cura/PrinterOutput/ExtruderOutputModel.py +++ b/cura/PrinterOutput/ExtruderOutputModel.py @@ -4,10 +4,9 @@ from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel -from typing import Optional +from typing import Optional, TYPE_CHECKING -MYPY = False -if MYPY: +if TYPE_CHECKING: from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel @@ -20,12 +19,12 @@ class ExtruderOutputModel(QObject): extruderConfigurationChanged = pyqtSignal() isPreheatingChanged = pyqtSignal() - def __init__(self, printer: "PrinterOutputModel", position, parent=None): + def __init__(self, printer: "PrinterOutputModel", position, parent=None) -> None: super().__init__(parent) self._printer = printer self._position = position - self._target_hotend_temperature = 0 - self._hotend_temperature = 0 + self._target_hotend_temperature = 0 # type: float + self._hotend_temperature = 0 # type: float self._hotend_id = "" self._active_material = None # type: Optional[MaterialOutputModel] self._extruder_configuration = ExtruderConfigurationModel() @@ -47,7 +46,7 @@ class ExtruderOutputModel(QObject): return False @pyqtProperty(QObject, notify = activeMaterialChanged) - def activeMaterial(self) -> "MaterialOutputModel": + def activeMaterial(self) -> Optional["MaterialOutputModel"]: return self._active_material def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]): From 1982c9b84c1f57104af4a779d1164ac0bd5798b1 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 8 Jun 2018 15:54:28 +0200 Subject: [PATCH 26/83] CURA-5330 Change TYPE_CHECKING flag --- cura/PrinterOutput/PrintJobOutputModel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/PrinterOutput/PrintJobOutputModel.py b/cura/PrinterOutput/PrintJobOutputModel.py index f46ec6706c..b77600f85c 100644 --- a/cura/PrinterOutput/PrintJobOutputModel.py +++ b/cura/PrinterOutput/PrintJobOutputModel.py @@ -2,9 +2,9 @@ # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot -from typing import Optional -MYPY = False -if MYPY: +from typing import Optional, TYPE_CHECKING + +if TYPE_CHECKING: from cura.PrinterOutput.PrinterOutputController import PrinterOutputController from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel From 51462b20c31c2f6ca39b69836ba7445351519921 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 8 Jun 2018 15:56:20 +0200 Subject: [PATCH 27/83] CURA-5330 Add typing to PrinterOutputModel constructor --- cura/PrinterOutput/PrinterOutputModel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index 928a882c8c..6fafa368bb 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -27,7 +27,7 @@ class PrinterOutputModel(QObject): cameraChanged = pyqtSignal() configurationChanged = pyqtSignal() - def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = ""): + def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = "") -> None: super().__init__(parent) self._bed_temperature = -1 # Use -1 for no heated bed. self._target_bed_temperature = 0 From adafea73cc9f5eafab74cb54b1493ed5215f7c68 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 8 Jun 2018 16:19:20 +0200 Subject: [PATCH 28/83] Ignore missing imports We don't want to be type checking inside those files. Contributes to issue CURA-5330. --- run_mypy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_mypy.py b/run_mypy.py index a72c555b8a..2a2c72dbbe 100644 --- a/run_mypy.py +++ b/run_mypy.py @@ -44,7 +44,7 @@ def main(): for mod in mods: print("------------- Checking module {mod}".format(**locals())) - result = subprocess.run([sys.executable, mypyModule, "-p", mod]) + result = subprocess.run([sys.executable, mypyModule, "-p", mod, "--ignore-missing-imports"]) if result.returncode != 0: print("\nModule {mod} failed checking. :(".format(**locals())) return 1 From b9d47f60922670442b3e049a9d635c8e17c636ec Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 8 Jun 2018 16:31:32 +0200 Subject: [PATCH 29/83] Add function typing This allows MyPy to detect mistakes in the typing of these functions. Contributes to issue CURA-5330. --- cura/SingleInstance.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cura/SingleInstance.py b/cura/SingleInstance.py index a664204d79..134c85fd8c 100644 --- a/cura/SingleInstance.py +++ b/cura/SingleInstance.py @@ -7,12 +7,12 @@ from typing import List, Optional from PyQt5.QtNetwork import QLocalServer, QLocalSocket +from UM.Application import Application #For typing. from UM.Logger import Logger class SingleInstance: - - def __init__(self, application, files_to_open: Optional[List[str]]): + def __init__(self, application: Application, files_to_open: Optional[List[str]]) -> None: self._application = application self._files_to_open = files_to_open @@ -64,14 +64,14 @@ class SingleInstance: self._single_instance_server.newConnection.connect(self._onClientConnected) self._single_instance_server.listen("ultimaker-cura") - def _onClientConnected(self): + def _onClientConnected(self) -> None: Logger.log("i", "New connection recevied on our single-instance server") connection = self._single_instance_server.nextPendingConnection() if connection is not None: connection.readyRead.connect(lambda c = connection: self.__readCommands(c)) - def __readCommands(self, connection): + def __readCommands(self, connection: QLocalSocket) -> None: line = connection.readLine() while len(line) != 0: # There is also a .canReadLine() try: From a6815e7c61a7ab83cb15a0aa215c3229dbd4c246 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 8 Jun 2018 16:31:43 +0200 Subject: [PATCH 30/83] CURA-5330 Add typing to Backup --- cura/Backups/Backup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cura/Backups/Backup.py b/cura/Backups/Backup.py index c4fe720b2b..6c1f02dc01 100644 --- a/cura/Backups/Backup.py +++ b/cura/Backups/Backup.py @@ -29,11 +29,11 @@ class Backup: # Re-use translation catalog. catalog = i18nCatalog("cura") - def __init__(self, zip_file: bytes = None, meta_data: dict = None): + def __init__(self, zip_file: bytes = None, meta_data: dict = None) -> None: self.zip_file = zip_file # type: Optional[bytes] self.meta_data = meta_data # type: Optional[dict] - def makeFromCurrent(self) -> (bool, Optional[str]): + def makeFromCurrent(self) -> None: """ Create a backup from the current user config folder. """ @@ -57,6 +57,8 @@ class Backup: # Create an empty buffer and write the archive to it. buffer = io.BytesIO() archive = self._makeArchive(buffer, version_data_dir) + if archive is None: + return files = archive.namelist() # Count the metadata items. We do this in a rather naive way at the moment. From 8c2ba64f32871a5f3dd2faed60c83623e8c09579 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 8 Jun 2018 16:47:52 +0200 Subject: [PATCH 31/83] CURA-5330 Add typing to NetworkedPrinterOutputDevice --- cura/PrinterOutput/NetworkedPrinterOutputDevice.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py index 40dd709f03..9348061695 100644 --- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py +++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py @@ -32,12 +32,12 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], parent: QObject = None) -> None: super().__init__(device_id = device_id, parent = parent) self._manager = None # type: QNetworkAccessManager - self._last_manager_create_time = None # type: float + self._last_manager_create_time = None # type: Optional[float] self._recreate_network_manager_time = 30 self._timeout_time = 10 # After how many seconds of no response should a timeout occur? - self._last_response_time = None # type: float - self._last_request_time = None # type: float + self._last_response_time = None # type: Optional[float] + self._last_request_time = None # type: Optional[float] self._api_prefix = "" self._address = address @@ -146,12 +146,13 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): if time_since_last_response > self._recreate_network_manager_time: if self._last_manager_create_time is None: self._createNetworkManager() - if time() - self._last_manager_create_time > self._recreate_network_manager_time: + elif time() - self._last_manager_create_time > self._recreate_network_manager_time: self._createNetworkManager() elif self._connection_state == ConnectionState.closed: # Go out of timeout. - self.setConnectionState(self._connection_state_before_timeout) - self._connection_state_before_timeout = None + if self._connection_state_before_timeout is not None: # sanity check, but it should never be None here + self.setConnectionState(self._connection_state_before_timeout) + self._connection_state_before_timeout = None def _createEmptyRequest(self, target: str, content_type: Optional[str] = "application/json") -> QNetworkRequest: url = QUrl("http://" + self._address + self._api_prefix + target) From 7f34d7615e48e9a1fb7f2feec9fb549d62ae0523 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 8 Jun 2018 16:56:52 +0200 Subject: [PATCH 32/83] CURA-5330 Add typing to CuraSceneNode --- cura/Scene/CuraSceneNode.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cura/Scene/CuraSceneNode.py b/cura/Scene/CuraSceneNode.py index b6f9daaa67..259c273329 100644 --- a/cura/Scene/CuraSceneNode.py +++ b/cura/Scene/CuraSceneNode.py @@ -39,6 +39,9 @@ class CuraSceneNode(SceneNode): # TODO The best way to do it is by adding the setActiveExtruder decorator to every node when is loaded def getPrintingExtruder(self) -> Optional[ExtruderStack]: global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack is None: + return None + per_mesh_stack = self.callDecoration("getStack") extruders = list(global_container_stack.extruders.values()) @@ -85,10 +88,10 @@ class CuraSceneNode(SceneNode): ## Return if the provided bbox collides with the bbox of this scene node def collidesWithBbox(self, check_bbox: AxisAlignedBox) -> bool: bbox = self.getBoundingBox() - - # Mark the node as outside the build volume if the bounding box test fails. - if check_bbox.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection: - return True + if bbox is not None: + # Mark the node as outside the build volume if the bounding box test fails. + if check_bbox.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection: + return True return False From 2dfedf3ae4d7c1414d19694d2441606947187041 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 8 Jun 2018 17:02:59 +0200 Subject: [PATCH 33/83] CURA-5330 Add typing in CuraContainerRegistry and QualityProfilesDropDownMenuModel --- cura/Machines/Models/QualityProfilesDropDownMenuModel.py | 2 +- cura/Settings/CuraContainerRegistry.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py index d8c4b668cf..62e8c638e3 100644 --- a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py +++ b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py @@ -83,7 +83,7 @@ class QualityProfilesDropDownMenuModel(ListModel): self.setItems(item_list) - def _fetchLayerHeight(self, quality_group: "QualityGroup"): + def _fetchLayerHeight(self, quality_group: "QualityGroup") -> float: global_stack = self._machine_manager.activeMachine if not self._layer_height_unit: unit = global_stack.definition.getProperty("layer_height", "unit") diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 916caadfe9..6cbb3036f8 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -356,6 +356,8 @@ class CuraContainerRegistry(ContainerRegistry): return catalog.i18nc("@info:status", "Profile is missing a quality type.") global_stack = Application.getInstance().getGlobalContainerStack() + if global_stack is None: + return None definition_id = getMachineDefinitionIDForQualitySearch(global_stack.definition) profile.setDefinition(definition_id) From 2e174e75fa83a6e128f6bb660f8add2bf333ea18 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 12 Jun 2018 16:28:39 +0200 Subject: [PATCH 34/83] CURA-5330 Add typing checks to the MachineManager --- cura/Machines/ContainerNode.py | 10 ++++- cura/Settings/MachineManager.py | 80 ++++++++++++++++++++++++--------- 2 files changed, 68 insertions(+), 22 deletions(-) diff --git a/cura/Machines/ContainerNode.py b/cura/Machines/ContainerNode.py index 44e2d6875d..ff2fe0216e 100644 --- a/cura/Machines/ContainerNode.py +++ b/cura/Machines/ContainerNode.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 Optional +from typing import Optional, Any, Dict from collections import OrderedDict @@ -23,11 +23,17 @@ from UM.Settings.InstanceContainer import InstanceContainer class ContainerNode: __slots__ = ("metadata", "container", "children_map") - def __init__(self, metadata: Optional[dict] = None): + def __init__(self, metadata: Optional[Dict[str, Any]] = None): self.metadata = metadata self.container = None self.children_map = OrderedDict() + ## Get an entry value from the metadata + def getMetaDataEntry(self, entry: str, default: Any = None) -> Any: + if self.metadata is None: + return default + return self.metadata.get(entry, default) + def getChildNode(self, child_key: str) -> Optional["ContainerNode"]: return self.children_map.get(child_key) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index a4aeb10449..2df7ce1f15 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -53,8 +53,8 @@ class MachineManager(QObject): self._global_container_stack = None # type: Optional[GlobalStack] self._current_root_material_id = {} # type: Dict[str, str] - self._current_quality_group = None - self._current_quality_changes_group = None + self._current_quality_group = None # type: Optional[QualityGroup] + self._current_quality_changes_group = None # type: Optional[QualityChangesGroup] self._default_extruder_position = "0" # to be updated when extruders are switched on and off @@ -626,6 +626,8 @@ class MachineManager(QObject): ## Copy the value of the setting of the current extruder to all other extruders as well as the global container. @pyqtSlot(str) def copyValueToExtruders(self, key: str) -> None: + if self._active_container_stack is None or self._global_container_stack is None: + return new_value = self._active_container_stack.getProperty(key, "value") extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())] @@ -637,6 +639,8 @@ class MachineManager(QObject): ## Copy the value of all manually changed settings of the current extruder to all other extruders. @pyqtSlot() def copyAllValuesToExtruders(self) -> None: + if self._active_container_stack is None or self._global_container_stack is None: + return extruder_stacks = list(self._global_container_stack.extruders.values()) for extruder_stack in extruder_stacks: if extruder_stack != self._active_container_stack: @@ -807,6 +811,8 @@ class MachineManager(QObject): return None def getIncompatibleSettingsOnEnabledExtruders(self, container: InstanceContainer) -> List[str]: + if self._global_container_stack is None: + return [] extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value") result = [] # type: List[str] for setting_instance in container.findInstances(): @@ -831,6 +837,8 @@ class MachineManager(QObject): ## Update extruder number to a valid value when the number of extruders are changed, or when an extruder is changed def correctExtruderSettings(self) -> None: + if self._global_container_stack is None: + return for setting_key in self.getIncompatibleSettingsOnEnabledExtruders(self._global_container_stack.userChanges): self._global_container_stack.userChanges.removeInstance(setting_key) add_user_changes = self.getIncompatibleSettingsOnEnabledExtruders(self._global_container_stack.qualityChanges) @@ -848,6 +856,8 @@ class MachineManager(QObject): ## Set the amount of extruders on the active machine (global stack) # \param extruder_count int the number of extruders to set def setActiveMachineExtruderCount(self, extruder_count: int) -> None: + if self._global_container_stack is None: + return extruder_manager = self._application.getExtruderManager() definition_changes_container = self._global_container_stack.definitionChanges @@ -909,6 +919,8 @@ class MachineManager(QObject): return extruder def updateDefaultExtruder(self) -> None: + if self._global_container_stack is None: + return extruder_items = sorted(self._global_container_stack.extruders.items()) old_position = self._default_extruder_position new_default_position = "0" @@ -921,6 +933,8 @@ class MachineManager(QObject): self.extruderChanged.emit() def updateNumberExtrudersEnabled(self) -> None: + if self._global_container_stack is None: + return definition_changes_container = self._global_container_stack.definitionChanges machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value") extruder_count = 0 @@ -933,6 +947,8 @@ class MachineManager(QObject): @pyqtProperty(int, notify = numberExtrudersEnabledChanged) def numberExtrudersEnabled(self) -> int: + if self._global_container_stack is None: + return 1 return self._global_container_stack.definitionChanges.getProperty("extruders_enabled_count", "value") @pyqtProperty(str, notify = extruderChanged) @@ -942,6 +958,8 @@ class MachineManager(QObject): ## This will fire the propertiesChanged for all settings so they will be updated in the front-end @pyqtSlot() def forceUpdateAllSettings(self) -> None: + if self._global_container_stack is None: + return with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): property_names = ["value", "resolve", "validationState"] for container in [self._global_container_stack] + list(self._global_container_stack.extruders.values()): @@ -951,8 +969,9 @@ class MachineManager(QObject): @pyqtSlot(int, bool) def setExtruderEnabled(self, position: int, enabled: bool) -> None: extruder = self.getExtruder(position) - if not extruder: + if not extruder or self._global_container_stack is None: Logger.log("w", "Could not find extruder on position %s", position) + return extruder.setEnabled(enabled) self.updateDefaultExtruder() @@ -988,6 +1007,8 @@ class MachineManager(QObject): @pyqtSlot(str, str, str) def setSettingForAllExtruders(self, setting_name: str, property_name: str, property_value: str) -> None: + if self._global_container_stack is None: + return for key, extruder in self._global_container_stack.extruders.items(): container = extruder.userChanges container.setProperty(setting_name, property_name, property_value) @@ -996,6 +1017,8 @@ class MachineManager(QObject): # \param setting_name The ID of the setting to reset. @pyqtSlot(str) def resetSettingForAllExtruders(self, setting_name: str) -> None: + if self._global_container_stack is None: + return for key, extruder in self._global_container_stack.extruders.items(): container = extruder.userChanges container.removeInstance(setting_name) @@ -1038,6 +1061,8 @@ class MachineManager(QObject): # for all stacks in the currently active machine. # def _setEmptyQuality(self) -> None: + if self._global_container_stack is None: + return self._current_quality_group = None self._current_quality_changes_group = None self._global_container_stack.quality = self._empty_quality_container @@ -1050,11 +1075,13 @@ class MachineManager(QObject): self.activeQualityChangesGroupChanged.emit() def _setQualityGroup(self, quality_group: Optional[QualityGroup], empty_quality_changes: bool = True) -> None: + if self._global_container_stack is None: + return if quality_group is None: self._setEmptyQuality() return - if quality_group.node_for_global.getContainer() is None: + if quality_group.node_for_global is None or quality_group.node_for_global.getContainer() is None: return for node in quality_group.nodes_for_extruders.values(): if node.getContainer() is None: @@ -1130,20 +1157,24 @@ class MachineManager(QObject): self.activeQualityChangesGroupChanged.emit() def _setVariantNode(self, position: str, container_node: ContainerNode) -> None: - if container_node.getContainer() is None: + if container_node.getContainer() is None or self._global_container_stack is None: return self._global_container_stack.extruders[position].variant = container_node.getContainer() self.activeVariantChanged.emit() def _setGlobalVariant(self, container_node: ContainerNode) -> None: + if self._global_container_stack is None: + return self._global_container_stack.variant = container_node.getContainer() if not self._global_container_stack.variant: self._global_container_stack.variant = self._application.empty_variant_container def _setMaterial(self, position: str, container_node: ContainerNode = None) -> None: + if self._global_container_stack is None: + return if container_node and container_node.getContainer(): self._global_container_stack.extruders[position].material = container_node.getContainer() - root_material_id = container_node.metadata["base_file"] + root_material_id = container_node.getMetaDataEntry("base_file", None) else: self._global_container_stack.extruders[position].material = self._empty_material_container root_material_id = None @@ -1154,12 +1185,13 @@ class MachineManager(QObject): def activeMaterialsCompatible(self) -> bool: # check material - variant compatibility - if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)): - for position, extruder in self._global_container_stack.extruders.items(): - if extruder.isEnabled and not extruder.material.getMetaDataEntry("compatible"): - return False - if not extruder.material.getMetaDataEntry("compatible"): - return False + if self._global_container_stack is not None: + if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)): + for position, extruder in self._global_container_stack.extruders.items(): + if extruder.isEnabled and not extruder.material.getMetaDataEntry("compatible"): + return False + if not extruder.material.getMetaDataEntry("compatible"): + return False return True ## Update current quality type and machine after setting material @@ -1210,8 +1242,8 @@ class MachineManager(QObject): else: position_list = [position] - for position in position_list: - extruder = self._global_container_stack.extruders[position] + for position_item in position_list: + extruder = self._global_container_stack.extruders[position_item] current_material_base_name = extruder.material.getMetaDataEntry("base_file") current_variant_name = None @@ -1229,25 +1261,25 @@ class MachineManager(QObject): material_diameter) if not candidate_materials: - self._setMaterial(position, container_node = None) + self._setMaterial(position_item, container_node = None) continue if current_material_base_name in candidate_materials: new_material = candidate_materials[current_material_base_name] - self._setMaterial(position, new_material) + self._setMaterial(position_item, new_material) continue # The current material is not available, find the preferred one material_node = self._material_manager.getDefaultMaterial(self._global_container_stack, current_variant_name) if material_node is not None: - self._setMaterial(position, material_node) + self._setMaterial(position_item, material_node) ## Given a printer definition name, select the right machine instance. In case it doesn't exist, create a new # instance with the same network key. @pyqtSlot(str) def switchPrinterType(self, machine_name: str) -> None: # Don't switch if the user tries to change to the same type of printer - if self.activeMachineDefinitionName == machine_name: + if self._global_container_stack is None or self.self.activeMachineDefinitionName == machine_name: return # Get the definition id corresponding to this machine name machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId() @@ -1272,6 +1304,8 @@ class MachineManager(QObject): @pyqtSlot(QObject) def applyRemoteConfiguration(self, configuration: ConfigurationModel) -> None: + if self._global_container_stack is None: + return self.blurSettings.emit() with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self.switchPrinterType(configuration.printerType) @@ -1339,6 +1373,8 @@ class MachineManager(QObject): @pyqtSlot(str, str) def setMaterialById(self, position: str, root_material_id: str) -> None: + if self._global_container_stack is None: + return machine_definition_id = self._global_container_stack.definition.id position = str(position) extruder_stack = self._global_container_stack.extruders[position] @@ -1361,6 +1397,8 @@ class MachineManager(QObject): @pyqtSlot(str, str) def setVariantByName(self, position: str, variant_name: str) -> None: + if self._global_container_stack is None: + return machine_definition_id = self._global_container_stack.definition.id variant_node = self._variant_manager.getVariantNode(machine_definition_id, variant_name) self.setVariant(position, variant_node) @@ -1398,7 +1436,7 @@ class MachineManager(QObject): self._application.discardOrKeepProfileChanges() @pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged) - def activeQualityGroup(self) -> QualityGroup: + def activeQualityGroup(self) -> Optional[QualityGroup]: return self._current_quality_group @pyqtSlot(QObject) @@ -1413,13 +1451,15 @@ class MachineManager(QObject): @pyqtSlot() def resetToUseDefaultQuality(self) -> None: + if self._global_container_stack is None: + return with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self._setQualityGroup(self._current_quality_group) for stack in [self._global_container_stack] + list(self._global_container_stack.extruders.values()): stack.userChanges.clear() @pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged) - def activeQualityChangesGroup(self) -> QualityChangesGroup: + def activeQualityChangesGroup(self) -> Optional[QualityChangesGroup]: return self._current_quality_changes_group @pyqtProperty(str, notify = activeQualityGroupChanged) From 5cd464c5de7a33745fd71f02fdd9dc98c5837b28 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 12 Jun 2018 16:35:46 +0200 Subject: [PATCH 35/83] CURA-5330 Fix some typing issues in QualityProfilesDropDownMenuModel and SettingInheritanceManager. --- cura/Machines/Models/QualityProfilesDropDownMenuModel.py | 2 ++ cura/Settings/SettingInheritanceManager.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py index 62e8c638e3..ec8236e887 100644 --- a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py +++ b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py @@ -94,6 +94,8 @@ class QualityProfilesDropDownMenuModel(ListModel): default_layer_height = global_stack.definition.getProperty("layer_height", "value") # Get layer_height from the quality profile for the GlobalStack + if quality_group.node_for_global is None: + return float(default_layer_height) container = quality_group.node_for_global.getContainer() layer_height = default_layer_height diff --git a/cura/Settings/SettingInheritanceManager.py b/cura/Settings/SettingInheritanceManager.py index 7cbfbcfda1..6d4176ebf1 100644 --- a/cura/Settings/SettingInheritanceManager.py +++ b/cura/Settings/SettingInheritanceManager.py @@ -1,5 +1,6 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from typing import List from PyQt5.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal from UM.FlameProfiler import pyqtSlot @@ -13,6 +14,7 @@ from UM.Logger import Logger # speed settings. If all the children of print_speed have a single value override, changing the speed won't # actually do anything, as only the 'leaf' settings are used by the engine. from UM.Settings.ContainerStack import ContainerStack +from UM.Settings.Interfaces import ContainerInterface from UM.Settings.SettingFunction import SettingFunction from UM.Settings.SettingInstance import InstanceState @@ -157,7 +159,7 @@ class SettingInheritanceManager(QObject): stack = self._active_container_stack if not stack: #No active container stack yet! return False - containers = [] + 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 From e6d3fd548dd4e1186beb8f57f6e51d9e863dc17f Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 12 Jun 2018 16:52:10 +0200 Subject: [PATCH 36/83] CURA-5330 Fix some typing in the MachineManager --- cura/Machines/MaterialManager.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index ff666f392d..49ea10cf03 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -4,6 +4,7 @@ from collections import defaultdict, OrderedDict import copy import uuid +from typing import Dict from typing import Optional, TYPE_CHECKING from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot @@ -263,7 +264,7 @@ class MaterialManager(QObject): # Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup. # def getAvailableMaterials(self, machine_definition: "DefinitionContainer", extruder_variant_name: Optional[str], - diameter: float) -> dict: + diameter: float) -> Dict[str, MaterialNode]: # round the diameter to get the approximate diameter rounded_diameter = str(round(diameter)) if rounded_diameter not in self._diameter_machine_variant_material_map: @@ -288,7 +289,7 @@ class MaterialManager(QObject): # 3. generic material (for fdmprinter) machine_exclude_materials = machine_definition.getMetaDataEntry("exclude_materials", []) - material_id_metadata_dict = dict() + material_id_metadata_dict = dict() # type: Dict[str, MaterialNode] for node in nodes_to_check: if node is not None: # Only exclude the materials that are explicitly specified in the "exclude_materials" field. @@ -434,7 +435,7 @@ class MaterialManager(QObject): nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list for node in nodes_to_remove: - self._container_registry.removeContainer(node.metadata["id"]) + self._container_registry.removeContainer(node.getMetaDataEntry("id", "")) # # Methods for GUI @@ -445,22 +446,27 @@ class MaterialManager(QObject): # @pyqtSlot("QVariant", str) def setMaterialName(self, material_node: "MaterialNode", name: str): - root_material_id = material_node.metadata["base_file"] + root_material_id = material_node.getMetaDataEntry("base_file") + if root_material_id is None: + return if self._container_registry.isReadOnly(root_material_id): Logger.log("w", "Cannot set name of read-only container %s.", root_material_id) return material_group = self.getMaterialGroup(root_material_id) if material_group: - material_group.root_material_node.getContainer().setName(name) + container = material_group.root_material_node.getContainer() + if container: + container.setName(name) # # Removes the given material. # @pyqtSlot("QVariant") def removeMaterial(self, material_node: "MaterialNode"): - root_material_id = material_node.metadata["base_file"] - self.removeMaterialByRootId(root_material_id) + root_material_id = material_node.getMetaDataEntry("base_file") + if root_material_id is not None: + self.removeMaterialByRootId(root_material_id) # # Creates a duplicate of a material, which has the same GUID and base_file metadata. @@ -539,6 +545,10 @@ class MaterialManager(QObject): root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter) material_group = self.getMaterialGroup(root_material_id) + if not material_group: # This should never happen + Logger.log("w", "Cannot get the material group of %s.", root_material_id) + return "" + # Create a new ID & container to hold the data. new_id = self._container_registry.uniqueName("custom_material") new_metadata = {"name": catalog.i18nc("@label", "Custom Material"), From 698c72e190c250e334f3f0a5d23efc5916df7974 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 12 Jun 2018 17:24:20 +0200 Subject: [PATCH 37/83] CURA-5330 Fix typing in ExtruderManager and ContainerManager --- cura/Settings/ContainerManager.py | 2 +- cura/Settings/ExtruderManager.py | 64 +++++++++++++++++-------------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index ea2821ce25..35550d5cdc 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -459,7 +459,7 @@ class ContainerManager(QObject): container_list = [n.getContainer() for n in quality_changes_group.getAllNodes() if n.getContainer() is not None] self._container_registry.exportQualityProfile(container_list, path, file_type) - __instance = None + __instance = None # type: ContainerManager @classmethod def getInstance(cls, *args, **kwargs) -> "ContainerManager": diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 0f8cb9ae23..051e467a41 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -16,7 +16,7 @@ from UM.Settings.SettingInstance import SettingInstance from UM.Settings.ContainerStack import ContainerStack from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext -from typing import Optional, List, TYPE_CHECKING, Union +from typing import Optional, List, TYPE_CHECKING, Union, Dict if TYPE_CHECKING: from cura.Settings.ExtruderStack import ExtruderStack @@ -43,7 +43,6 @@ class ExtruderManager(QObject): self._selected_object_extruders = [] self._addCurrentMachineExtruders() - #Application.getInstance().globalContainerStackChanged.connect(self._globalContainerStackChanged) Selection.selectionChanged.connect(self.resetSelectedObjectExtruders) ## Signal to notify other components when the list of extruders for a machine definition changes. @@ -60,42 +59,47 @@ class ExtruderManager(QObject): # \return The unique ID of the currently active extruder stack. @pyqtProperty(str, notify = activeExtruderChanged) def activeExtruderStackId(self) -> Optional[str]: - if not Application.getInstance().getGlobalContainerStack(): + if not self._application.getGlobalContainerStack(): return None # No active machine, so no active extruder. try: - return self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][str(self._active_extruder_index)].getId() + return self._extruder_trains[self._application.getGlobalContainerStack().getId()][str(self._active_extruder_index)].getId() 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): - if not Application.getInstance().getGlobalContainerStack(): + if not self._application.getGlobalContainerStack(): return 0 # No active machine, so no extruders. try: - return len(self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]) + 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): + def extruderIds(self) -> Dict[str, str]: extruder_stack_ids = {} - global_stack_id = Application.getInstance().getGlobalContainerStack().getId() + global_container_stack = self._application.getGlobalContainerStack() + if global_container_stack: + global_stack_id = global_container_stack.getId() - if global_stack_id in self._extruder_trains: - for position in self._extruder_trains[global_stack_id]: - extruder_stack_ids[position] = self._extruder_trains[global_stack_id][position].getId() + if global_stack_id in self._extruder_trains: + for position in self._extruder_trains[global_stack_id]: + extruder_stack_ids[position] = self._extruder_trains[global_stack_id][position].getId() return extruder_stack_ids @pyqtSlot(str, result = str) def getQualityChangesIdByExtruderStackId(self, extruder_stack_id: str) -> str: - for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]: - extruder = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position] - if extruder.getId() == extruder_stack_id: - return extruder.qualityChanges.getId() + global_container_stack = self._application.getGlobalContainerStack() + if global_container_stack is not None: + for position in self._extruder_trains[global_container_stack.getId()]: + extruder = self._extruder_trains[global_container_stack.getId()][position] + if extruder.getId() == extruder_stack_id: + return extruder.qualityChanges.getId() + return "" ## Changes the active extruder by index. # @@ -141,7 +145,7 @@ class ExtruderManager(QObject): selected_nodes.append(node) # Then, figure out which nodes are used by those selected nodes. - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = self._application.getGlobalContainerStack() current_extruder_trains = self._extruder_trains.get(global_stack.getId()) for node in selected_nodes: extruder = node.callDecoration("getActiveExtruder") @@ -164,7 +168,7 @@ class ExtruderManager(QObject): @pyqtSlot(result = QObject) def getActiveExtruderStack(self) -> Optional["ExtruderStack"]: - global_container_stack = Application.getInstance().getGlobalContainerStack() + global_container_stack = self._application.getGlobalContainerStack() if global_container_stack: if global_container_stack.getId() in self._extruder_trains: @@ -175,7 +179,7 @@ class ExtruderManager(QObject): ## Get an extruder stack by index def getExtruderStack(self, index) -> Optional["ExtruderStack"]: - global_container_stack = Application.getInstance().getGlobalContainerStack() + global_container_stack = self._application.getGlobalContainerStack() if global_container_stack: if global_container_stack.getId() in self._extruder_trains: if str(index) in self._extruder_trains[global_container_stack.getId()]: @@ -186,7 +190,9 @@ class ExtruderManager(QObject): def getExtruderStacks(self) -> List["ExtruderStack"]: result = [] for i in range(self.extruderCount): - result.append(self.getExtruderStack(i)) + stack = self.getExtruderStack(i) + if stack: + result.append(stack) return result def registerExtruder(self, extruder_train, machine_id): @@ -252,7 +258,7 @@ class ExtruderManager(QObject): support_bottom_enabled = False support_roof_enabled = False - scene_root = Application.getInstance().getController().getScene().getRoot() + scene_root = self._application.getController().getScene().getRoot() # If no extruders are registered in the extruder manager yet, return an empty array if len(self.extruderIds) == 0: @@ -301,10 +307,10 @@ class ExtruderManager(QObject): # The platform adhesion extruder. Not used if using none. if global_stack.getProperty("adhesion_type", "value") != "none": - extruder_nr = str(global_stack.getProperty("adhesion_extruder_nr", "value")) - if extruder_nr == "-1": - extruder_nr = Application.getInstance().getMachineManager().defaultExtruderPosition - used_extruder_stack_ids.add(self.extruderIds[extruder_nr]) + extruder_str_nr = str(global_stack.getProperty("adhesion_extruder_nr", "value")) + if extruder_str_nr == "-1": + extruder_str_nr = self._application.getMachineManager().defaultExtruderPosition + used_extruder_stack_ids.add(self.extruderIds[extruder_str_nr]) try: return [container_registry.findContainerStacks(id = stack_id)[0] for stack_id in used_extruder_stack_ids] @@ -335,7 +341,7 @@ class ExtruderManager(QObject): # The first element is the global container stack, followed by any extruder stacks. # \return \type{List[ContainerStack]} def getActiveGlobalAndExtruderStacks(self) -> Optional[List[Union["ExtruderStack", "GlobalStack"]]]: - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = self._application.getGlobalContainerStack() if not global_stack: return None @@ -347,7 +353,7 @@ class ExtruderManager(QObject): # # \return \type{List[ContainerStack]} a list of def getActiveExtruderStacks(self) -> List["ExtruderStack"]: - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = self._application.getGlobalContainerStack() if not global_stack: return [] @@ -559,7 +565,7 @@ class ExtruderManager(QObject): ## Updates the material container to a material that matches the material diameter set for the printer def updateMaterialForDiameter(self, extruder_position: int, global_stack = None): if not global_stack: - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = self._application.getGlobalContainerStack() if not global_stack: return @@ -611,7 +617,7 @@ class ExtruderManager(QObject): if has_material_variants: search_criteria["variant"] = extruder_stack.variant.getId() - container_registry = Application.getInstance().getContainerRegistry() + container_registry = self._application.getContainerRegistry() empty_material = container_registry.findInstanceContainers(id = "empty_material")[0] if old_material == empty_material: @@ -736,7 +742,7 @@ class ExtruderManager(QObject): return resolved_value - __instance = None + __instance = None # type: ExtruderManager @classmethod def getInstance(cls, *args, **kwargs) -> "ExtruderManager": From 3a8756efa5494890d61bb806c9d9d4b5684482f6 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 13 Jun 2018 08:53:01 +0200 Subject: [PATCH 38/83] CURA-5330 Fix typing in the Arranger --- cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py | 9 +++++---- cura/Arranging/ArrangeObjectsJob.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py b/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py index 6abc553a4b..8bbc2bf132 100644 --- a/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py +++ b/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py @@ -20,14 +20,14 @@ from typing import List ## Do arrangements on multiple build plates (aka builtiplexer) class ArrangeArray: - def __init__(self, x: int, y: int, fixed_nodes: List[SceneNode]): + def __init__(self, x: int, y: int, fixed_nodes: List[SceneNode]) -> None: self._x = x self._y = y self._fixed_nodes = fixed_nodes self._count = 0 self._first_empty = None self._has_empty = False - self._arrange = [] + self._arrange = [] # type: List[Arrange] def _update_first_empty(self): for i, a in enumerate(self._arrange): @@ -48,16 +48,17 @@ class ArrangeArray: return self._count def get(self, index): + print(self._arrange) return self._arrange[index] def getFirstEmpty(self): - if not self._is_empty: + if not self._has_empty: self.add() return self._arrange[self._first_empty] class ArrangeObjectsAllBuildPlatesJob(Job): - def __init__(self, nodes: List[SceneNode], min_offset = 8): + def __init__(self, nodes: List[SceneNode], min_offset = 8) -> None: super().__init__() self._nodes = nodes self._min_offset = min_offset diff --git a/cura/Arranging/ArrangeObjectsJob.py b/cura/Arranging/ArrangeObjectsJob.py index 5e982582fd..ce11556b5b 100644 --- a/cura/Arranging/ArrangeObjectsJob.py +++ b/cura/Arranging/ArrangeObjectsJob.py @@ -20,7 +20,7 @@ from typing import List class ArrangeObjectsJob(Job): - def __init__(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode], min_offset = 8): + def __init__(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode], min_offset = 8) -> None: super().__init__() self._nodes = nodes self._fixed_nodes = fixed_nodes From 221cd3e73e164ceef1ed036e3374eaa39e9b7dcb Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 13 Jun 2018 09:06:05 +0200 Subject: [PATCH 39/83] CURA-5330 Fix typing in SingleInstance --- cura/SingleInstance.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cura/SingleInstance.py b/cura/SingleInstance.py index a664204d79..ce8b112f3c 100644 --- a/cura/SingleInstance.py +++ b/cura/SingleInstance.py @@ -12,7 +12,7 @@ from UM.Logger import Logger class SingleInstance: - def __init__(self, application, files_to_open: Optional[List[str]]): + def __init__(self, application, files_to_open: Optional[List[str]]) -> None: self._application = application self._files_to_open = files_to_open @@ -61,8 +61,11 @@ class SingleInstance: def startServer(self) -> None: self._single_instance_server = QLocalServer() - self._single_instance_server.newConnection.connect(self._onClientConnected) - self._single_instance_server.listen("ultimaker-cura") + if self._single_instance_server: + self._single_instance_server.newConnection.connect(self._onClientConnected) + self._single_instance_server.listen("ultimaker-cura") + else: + Logger.log("e", "Single instance server was not created.") def _onClientConnected(self): Logger.log("i", "New connection recevied on our single-instance server") From f860b9c99e2888486061f4cbdef530cb16fa9c8b Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 13 Jun 2018 09:07:34 +0200 Subject: [PATCH 40/83] CURA-5330 Fix typing in GenericOutputController --- cura/PrinterOutput/GenericOutputController.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/PrinterOutput/GenericOutputController.py b/cura/PrinterOutput/GenericOutputController.py index dd1448a329..3e7793b61c 100644 --- a/cura/PrinterOutput/GenericOutputController.py +++ b/cura/PrinterOutput/GenericOutputController.py @@ -1,6 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. - +from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel from cura.PrinterOutput.PrinterOutputController import PrinterOutputController from PyQt5.QtCore import QTimer @@ -121,7 +121,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): position = extruder.getPosition() number_of_extruders = len(extruder.getPrinter().extruders) if position >= number_of_extruders: @@ -139,7 +139,7 @@ class GenericOutputController(PrinterOutputController): self._preheat_hotends.add(extruder) extruder.updateIsPreheating(True) - def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"): + def cancelPreheatHotend(self, extruder: ExtruderOutputModel): self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), temperature=0) if extruder in self._preheat_hotends: extruder.updateIsPreheating(False) From 6e364f08958466f20144271cc3fc58e30723d3d2 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 13 Jun 2018 10:55:57 +0200 Subject: [PATCH 41/83] CURA-5330 Fix typing and code-style in the ContainerNode and all the related children, and QualityGroup and its children. Also fix the related calls in the managers. --- cura/Machines/ContainerNode.py | 6 +++--- cura/Machines/MaterialGroup.py | 2 +- cura/Machines/MaterialNode.py | 8 +++----- .../Models/QualityProfilesDropDownMenuModel.py | 2 +- cura/Machines/QualityChangesGroup.py | 11 ++++++++--- cura/Machines/QualityGroup.py | 12 +++++++----- cura/Machines/QualityManager.py | 16 +++++++++------- cura/Machines/QualityNode.py | 6 +++--- cura/PrinterOutput/GenericOutputController.py | 12 +++++++----- cura/Settings/MachineManager.py | 3 ++- 10 files changed, 44 insertions(+), 34 deletions(-) diff --git a/cura/Machines/ContainerNode.py b/cura/Machines/ContainerNode.py index ff2fe0216e..14739eedd8 100644 --- a/cura/Machines/ContainerNode.py +++ b/cura/Machines/ContainerNode.py @@ -23,10 +23,10 @@ from UM.Settings.InstanceContainer import InstanceContainer class ContainerNode: __slots__ = ("metadata", "container", "children_map") - def __init__(self, metadata: Optional[Dict[str, Any]] = None): + def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None: self.metadata = metadata self.container = None - self.children_map = OrderedDict() + self.children_map = OrderedDict() #type: OrderedDict[str, ContainerNode] ## Get an entry value from the metadata def getMetaDataEntry(self, entry: str, default: Any = None) -> Any: @@ -56,4 +56,4 @@ class ContainerNode: return self.container def __str__(self) -> str: - return "%s[%s]" % (self.__class__.__name__, self.metadata.get("id")) + return "%s[%s]" % (self.__class__.__name__, self.getMetaDataEntry("id")) diff --git a/cura/Machines/MaterialGroup.py b/cura/Machines/MaterialGroup.py index 93c8a227a8..eea9e41d62 100644 --- a/cura/Machines/MaterialGroup.py +++ b/cura/Machines/MaterialGroup.py @@ -18,7 +18,7 @@ from cura.Machines.MaterialNode import MaterialNode #For type checking. class MaterialGroup: __slots__ = ("name", "is_read_only", "root_material_node", "derived_material_node_list") - def __init__(self, name: str, root_material_node: MaterialNode): + def __init__(self, name: str, root_material_node: MaterialNode) -> None: self.name = name self.is_read_only = False self.root_material_node = root_material_node diff --git a/cura/Machines/MaterialNode.py b/cura/Machines/MaterialNode.py index fde11186c2..04423d7b2c 100644 --- a/cura/Machines/MaterialNode.py +++ b/cura/Machines/MaterialNode.py @@ -1,7 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. - -from typing import Optional +from typing import Optional, Dict from .ContainerNode import ContainerNode @@ -15,7 +14,6 @@ from .ContainerNode import ContainerNode class MaterialNode(ContainerNode): __slots__ = ("material_map", "children_map") - def __init__(self, metadata: Optional[dict] = None): + def __init__(self, metadata: Optional[dict] = None) -> None: super().__init__(metadata = metadata) - self.material_map = {} # material_root_id -> material_node - self.children_map = {} # mapping for the child nodes + self.material_map = {} # type: Dict[str, MaterialNode] # material_root_id -> material_node diff --git a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py index ec8236e887..02de23b10a 100644 --- a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py +++ b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py @@ -99,7 +99,7 @@ class QualityProfilesDropDownMenuModel(ListModel): container = quality_group.node_for_global.getContainer() layer_height = default_layer_height - if container.hasProperty("layer_height", "value"): + if container and container.hasProperty("layer_height", "value"): layer_height = container.getProperty("layer_height", "value") else: # Look for layer_height in the GlobalStack from material -> definition diff --git a/cura/Machines/QualityChangesGroup.py b/cura/Machines/QualityChangesGroup.py index 5ff5af3657..2d0e655ed8 100644 --- a/cura/Machines/QualityChangesGroup.py +++ b/cura/Machines/QualityChangesGroup.py @@ -1,22 +1,27 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from typing import TYPE_CHECKING + from UM.Application import Application from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from .QualityGroup import QualityGroup +if TYPE_CHECKING: + from cura.Machines.QualityNode import QualityNode + class QualityChangesGroup(QualityGroup): - def __init__(self, name: str, quality_type: str, parent = None): + def __init__(self, name: str, quality_type: str, parent = None) -> None: super().__init__(name, quality_type, parent) self._container_registry = Application.getInstance().getContainerRegistry() def addNode(self, node: "QualityNode"): - extruder_position = node.metadata.get("position") + extruder_position = node.getMetaDataEntry("position") if extruder_position is None and self.node_for_global is not None or extruder_position in self.nodes_for_extruders: #We would be overwriting another node. - ConfigurationErrorMessage.getInstance().addFaultyContainers(node.metadata["id"]) + ConfigurationErrorMessage.getInstance().addFaultyContainers(node.getMetaDataEntry("id")) return if extruder_position is None: #Then we're a global quality changes profile. diff --git a/cura/Machines/QualityGroup.py b/cura/Machines/QualityGroup.py index b1c564fadf..90ef63af51 100644 --- a/cura/Machines/QualityGroup.py +++ b/cura/Machines/QualityGroup.py @@ -4,7 +4,7 @@ from typing import Dict, Optional, List, Set from PyQt5.QtCore import QObject, pyqtSlot - +from cura.Machines.ContainerNode import ContainerNode # # A QualityGroup represents a group of containers that must be applied to each ContainerStack when it's used. @@ -24,8 +24,8 @@ class QualityGroup(QObject): def __init__(self, name: str, quality_type: str, parent = None) -> None: super().__init__(parent) self.name = name - self.node_for_global = None # type: Optional["QualityGroup"] - self.nodes_for_extruders = {} # type: Dict[int, "QualityGroup"] + self.node_for_global = None # type: Optional[ContainerNode] + self.nodes_for_extruders = {} # type: Dict[int, ContainerNode] self.quality_type = quality_type self.is_available = False @@ -38,10 +38,12 @@ class QualityGroup(QObject): for node in [self.node_for_global] + list(self.nodes_for_extruders.values()): if node is None: continue - result.update(node.getContainer().getAllKeys()) + container = node.getContainer() + if container: + result.update(container.getAllKeys()) return result - def getAllNodes(self) -> List["QualityGroup"]: + def getAllNodes(self) -> List[ContainerNode]: result = [] if self.node_for_global is not None: result.append(self.node_for_global) diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index cb0a2f5922..8033057f77 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.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, Optional +from typing import TYPE_CHECKING, Optional, cast from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot @@ -90,7 +90,7 @@ class QualityManager(QObject): if definition_id not in self._machine_variant_material_quality_type_to_quality_dict: self._machine_variant_material_quality_type_to_quality_dict[definition_id] = QualityNode() - machine_node = self._machine_variant_material_quality_type_to_quality_dict[definition_id] + machine_node = cast(QualityNode, self._machine_variant_material_quality_type_to_quality_dict[definition_id]) if is_global_quality: # For global qualities, save data in the machine node @@ -102,7 +102,7 @@ class QualityManager(QObject): # too. if variant_name not in machine_node.children_map: machine_node.children_map[variant_name] = QualityNode() - variant_node = machine_node.children_map[variant_name] + variant_node = cast(QualityNode, machine_node.children_map[variant_name]) if root_material_id is None: # If only variant_name is specified but material is not, add the quality/quality_changes metadata @@ -114,7 +114,7 @@ class QualityManager(QObject): # material node. if root_material_id not in variant_node.children_map: variant_node.children_map[root_material_id] = QualityNode() - material_node = variant_node.children_map[root_material_id] + material_node = cast(QualityNode, variant_node.children_map[root_material_id]) material_node.addQualityMetadata(quality_type, metadata) @@ -123,7 +123,7 @@ class QualityManager(QObject): if root_material_id is not None: if root_material_id not in machine_node.children_map: machine_node.children_map[root_material_id] = QualityNode() - material_node = machine_node.children_map[root_material_id] + material_node = cast(QualityNode, machine_node.children_map[root_material_id]) material_node.addQualityMetadata(quality_type, metadata) @@ -351,7 +351,7 @@ class QualityManager(QObject): def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup"): Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name) for node in quality_changes_group.getAllNodes(): - self._container_registry.removeContainer(node.metadata["id"]) + self._container_registry.removeContainer(node.getMetaDataEntry("id")) # # Rename a set of quality changes containers. Returns the new name. @@ -365,7 +365,9 @@ class QualityManager(QObject): new_name = self._container_registry.uniqueName(new_name) for node in quality_changes_group.getAllNodes(): - node.getContainer().setName(new_name) + container = node.getContainer() + if container: + container.setName(new_name) quality_changes_group.name = new_name diff --git a/cura/Machines/QualityNode.py b/cura/Machines/QualityNode.py index a30e219da3..922227b022 100644 --- a/cura/Machines/QualityNode.py +++ b/cura/Machines/QualityNode.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 Optional +from typing import Optional, Dict from .ContainerNode import ContainerNode from .QualityChangesGroup import QualityChangesGroup @@ -12,9 +12,9 @@ from .QualityChangesGroup import QualityChangesGroup # class QualityNode(ContainerNode): - def __init__(self, metadata: Optional[dict] = None): + def __init__(self, metadata: Optional[dict] = None) -> None: super().__init__(metadata = metadata) - self.quality_type_map = {} # quality_type -> QualityNode for InstanceContainer + self.quality_type_map = {} # type: Dict[str, QualityNode] # quality_type -> QualityNode for InstanceContainer def addQualityMetadata(self, quality_type: str, metadata: dict): if quality_type not in self.quality_type_map: diff --git a/cura/PrinterOutput/GenericOutputController.py b/cura/PrinterOutput/GenericOutputController.py index 3e7793b61c..e6310e5bff 100644 --- a/cura/PrinterOutput/GenericOutputController.py +++ b/cura/PrinterOutput/GenericOutputController.py @@ -1,13 +1,15 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel + +from typing import TYPE_CHECKING + from cura.PrinterOutput.PrinterOutputController import PrinterOutputController from PyQt5.QtCore import QTimer -MYPY = False -if MYPY: +if TYPE_CHECKING: from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel + from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel class GenericOutputController(PrinterOutputController): @@ -121,7 +123,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): position = extruder.getPosition() number_of_extruders = len(extruder.getPrinter().extruders) if position >= number_of_extruders: @@ -139,7 +141,7 @@ class GenericOutputController(PrinterOutputController): self._preheat_hotends.add(extruder) extruder.updateIsPreheating(True) - def cancelPreheatHotend(self, extruder: ExtruderOutputModel): + def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"): self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), temperature=0) if extruder in self._preheat_hotends: extruder.updateIsPreheating(False) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 2df7ce1f15..8f521f559a 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1109,7 +1109,8 @@ class MachineManager(QObject): nodes = [quality_changes_group.node_for_global] + list(quality_changes_group.nodes_for_extruders.values()) containers = [n.getContainer() for n in nodes if n is not None] for container in containers: - container.setMetaDataEntry("quality_type", "not_supported") + if container: + container.setMetaDataEntry("quality_type", "not_supported") quality_changes_group.quality_type = "not_supported" def _setQualityChangesGroup(self, quality_changes_group: QualityChangesGroup) -> None: From f1b0c6238f302b86b87d18a8eb1131dd9d9bef53 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 13 Jun 2018 11:01:30 +0200 Subject: [PATCH 42/83] CURA-5330 Add return type to some constructors --- cura/PickingPass.py | 2 +- cura/PreviewPass.py | 2 +- cura/PrinterOutputDevice.py | 2 +- cura/Scene/CuraSceneController.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cura/PickingPass.py b/cura/PickingPass.py index 2a1abe8f63..40a0aaf949 100644 --- a/cura/PickingPass.py +++ b/cura/PickingPass.py @@ -16,7 +16,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator # # Note that in order to increase precision, the 24 bit depth value is encoded into all three of the R,G & B channels class PickingPass(RenderPass): - def __init__(self, width: int, height: int): + def __init__(self, width: int, height: int) -> None: super().__init__("picking", width, height) self._renderer = Application.getInstance().getRenderer() diff --git a/cura/PreviewPass.py b/cura/PreviewPass.py index 436e2719b7..511d379f4b 100644 --- a/cura/PreviewPass.py +++ b/cura/PreviewPass.py @@ -33,7 +33,7 @@ def prettier_color(color_list): # # This is useful to get a preview image of a scene taken from a different location as the active camera. class PreviewPass(RenderPass): - def __init__(self, width: int, height: int): + def __init__(self, width: int, height: int) -> None: super().__init__("preview", width, height, 0) self._camera = None # type: Optional[Camera] diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py index bdac80e61c..ae749ba0dc 100644 --- a/cura/PrinterOutputDevice.py +++ b/cura/PrinterOutputDevice.py @@ -224,5 +224,5 @@ class PrinterOutputDevice(QObject, OutputDevice): ## Get the name of device firmware # # This name can be used to define device type - def getFirmwareName(self) -> str: + def getFirmwareName(self) -> Optional[str]: return self._firmware_name \ No newline at end of file diff --git a/cura/Scene/CuraSceneController.py b/cura/Scene/CuraSceneController.py index 749c5257a2..4b19271538 100644 --- a/cura/Scene/CuraSceneController.py +++ b/cura/Scene/CuraSceneController.py @@ -16,7 +16,7 @@ from UM.Signal import Signal class CuraSceneController(QObject): activeBuildPlateChanged = Signal() - def __init__(self, objects_model: ObjectsModel, multi_build_plate_model: MultiBuildPlateModel): + def __init__(self, objects_model: ObjectsModel, multi_build_plate_model: MultiBuildPlateModel) -> None: super().__init__() self._objects_model = objects_model From d03b0e610baf1ec49d48348fd3a1498c8ddb38df Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 13 Jun 2018 11:56:53 +0200 Subject: [PATCH 43/83] CURA-5330 Fix typing in the PreviewPass --- cura/PreviewPass.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/cura/PreviewPass.py b/cura/PreviewPass.py index 511d379f4b..22f50890a3 100644 --- a/cura/PreviewPass.py +++ b/cura/PreviewPass.py @@ -53,20 +53,23 @@ class PreviewPass(RenderPass): def render(self) -> None: if not self._shader: self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "overhang.shader")) - self._shader.setUniformValue("u_overhangAngle", 1.0) - self._shader.setUniformValue("u_ambientColor", [0.1, 0.1, 0.1, 1.0]) - self._shader.setUniformValue("u_specularColor", [0.6, 0.6, 0.6, 1.0]) - self._shader.setUniformValue("u_shininess", 20.0) + if self._shader: + self._shader.setUniformValue("u_overhangAngle", 1.0) + self._shader.setUniformValue("u_ambientColor", [0.1, 0.1, 0.1, 1.0]) + self._shader.setUniformValue("u_specularColor", [0.6, 0.6, 0.6, 1.0]) + self._shader.setUniformValue("u_shininess", 20.0) if not self._non_printing_shader: - self._non_printing_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader")) - self._non_printing_shader.setUniformValue("u_diffuseColor", [0.5, 0.5, 0.5, 0.5]) - self._non_printing_shader.setUniformValue("u_opacity", 0.6) + if self._non_printing_shader: + self._non_printing_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader")) + self._non_printing_shader.setUniformValue("u_diffuseColor", [0.5, 0.5, 0.5, 0.5]) + self._non_printing_shader.setUniformValue("u_opacity", 0.6) if not self._support_mesh_shader: self._support_mesh_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader")) - self._support_mesh_shader.setUniformValue("u_vertical_stripes", True) - self._support_mesh_shader.setUniformValue("u_width", 5.0) + if self._support_mesh_shader: + self._support_mesh_shader.setUniformValue("u_vertical_stripes", True) + self._support_mesh_shader.setUniformValue("u_width", 5.0) self._gl.glClearColor(0.0, 0.0, 0.0, 0.0) self._gl.glClear(self._gl.GL_COLOR_BUFFER_BIT | self._gl.GL_DEPTH_BUFFER_BIT) From 803d945fb68ce054ba78fd3fb1c62a73bab1ca78 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 13 Jun 2018 13:35:17 +0200 Subject: [PATCH 44/83] CURA-5330 Add assertions when the networkmanager is not None after creating it. --- cura/PrinterOutput/NetworkedPrinterOutputDevice.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py index 9348061695..376d8cbdc8 100644 --- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py +++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py @@ -148,6 +148,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): self._createNetworkManager() elif 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: # Go out of timeout. if self._connection_state_before_timeout is not None: # sanity check, but it should never be None here @@ -191,6 +192,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): def put(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None: if self._manager is None: self._createNetworkManager() + assert(self._manager is not None) request = self._createEmptyRequest(target) self._last_request_time = time() reply = self._manager.put(request, data.encode()) @@ -199,6 +201,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): def get(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None: if self._manager is None: self._createNetworkManager() + assert(self._manager is not None) request = self._createEmptyRequest(target) self._last_request_time = time() reply = self._manager.get(request) @@ -207,6 +210,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): def post(self, target: str, data: str, onFinished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None: if self._manager is None: self._createNetworkManager() + assert(self._manager is not None) request = self._createEmptyRequest(target) self._last_request_time = time() reply = self._manager.post(request, data) @@ -217,6 +221,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): def postFormWithParts(self, target:str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None: if self._manager is None: self._createNetworkManager() + assert(self._manager is not None) request = self._createEmptyRequest(target, content_type=None) multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType) for part in parts: From f700a395475abecca2f3a3df2bbda6f4571347ce Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 13 Jun 2018 16:10:11 +0200 Subject: [PATCH 45/83] CURA-5330 Fix type checking in ContainerNodes --- cura/Machines/ContainerNode.py | 7 +++++-- cura/Machines/MaterialGroup.py | 2 +- cura/Machines/QualityNode.py | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cura/Machines/ContainerNode.py b/cura/Machines/ContainerNode.py index 14739eedd8..944579e354 100644 --- a/cura/Machines/ContainerNode.py +++ b/cura/Machines/ContainerNode.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 Optional, Any, Dict +from typing import Optional, Any, Dict, Union, TYPE_CHECKING from collections import OrderedDict @@ -9,6 +9,9 @@ from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.Logger import Logger from UM.Settings.InstanceContainer import InstanceContainer +if TYPE_CHECKING: + from cura.Machines.QualityGroup import QualityGroup + ## # A metadata / container combination. Use getContainer() to get the container corresponding to the metadata. @@ -26,7 +29,7 @@ class ContainerNode: def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None: self.metadata = metadata self.container = None - self.children_map = OrderedDict() #type: OrderedDict[str, ContainerNode] + self.children_map = OrderedDict() #type: OrderedDict[str, Union[QualityGroup, ContainerNode]] ## Get an entry value from the metadata def getMetaDataEntry(self, entry: str, default: Any = None) -> Any: diff --git a/cura/Machines/MaterialGroup.py b/cura/Machines/MaterialGroup.py index eea9e41d62..b57e0e1808 100644 --- a/cura/Machines/MaterialGroup.py +++ b/cura/Machines/MaterialGroup.py @@ -21,7 +21,7 @@ class MaterialGroup: def __init__(self, name: str, root_material_node: MaterialNode) -> None: self.name = name self.is_read_only = False - self.root_material_node = root_material_node + self.root_material_node = root_material_node # type: MaterialNode self.derived_material_node_list = [] #type: List[MaterialNode] def __str__(self) -> str: diff --git a/cura/Machines/QualityNode.py b/cura/Machines/QualityNode.py index 922227b022..f384ee7825 100644 --- a/cura/Machines/QualityNode.py +++ b/cura/Machines/QualityNode.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 Optional, Dict +from typing import Optional, Dict, cast from .ContainerNode import ContainerNode from .QualityChangesGroup import QualityChangesGroup @@ -32,4 +32,4 @@ class QualityNode(ContainerNode): if name not in quality_type_node.children_map: quality_type_node.children_map[name] = QualityChangesGroup(name, quality_type) quality_changes_group = quality_type_node.children_map[name] - quality_changes_group.addNode(QualityNode(metadata)) + cast(QualityChangesGroup, quality_changes_group).addNode(QualityNode(metadata)) From f2768fd761c3aef98f0546d0a8daf927e823a27b Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 13 Jun 2018 16:35:48 +0200 Subject: [PATCH 46/83] CURA-5330 Fix typing in the Backups --- cura/API/Backups.py | 4 +++- cura/Backups/BackupsManager.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cura/API/Backups.py b/cura/API/Backups.py index a2423bd798..5964557264 100644 --- a/cura/API/Backups.py +++ b/cura/API/Backups.py @@ -1,5 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from typing import Tuple, Optional + from cura.Backups.BackupsManager import BackupsManager @@ -17,7 +19,7 @@ class Backups: ## Create a new back-up using the BackupsManager. # \return Tuple containing a ZIP file with the back-up data and a dict # with metadata about the back-up. - def createBackup(self) -> (bytes, dict): + def createBackup(self) -> Tuple[Optional[bytes], Optional[dict]]: return self.manager.createBackup() ## Restore a back-up using the BackupsManager. diff --git a/cura/Backups/BackupsManager.py b/cura/Backups/BackupsManager.py index 850b0a2edc..bc560a8dd9 100644 --- a/cura/Backups/BackupsManager.py +++ b/cura/Backups/BackupsManager.py @@ -1,6 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Optional +from typing import Optional, Tuple from UM.Logger import Logger from cura.Backups.Backup import Backup @@ -18,7 +18,7 @@ class BackupsManager: ## Get a back-up of the current configuration. # \return A tuple containing a ZipFile (the actual back-up) and a dict # containing some metadata (like version). - def createBackup(self) -> (Optional[bytes], Optional[dict]): + def createBackup(self) -> Tuple[Optional[bytes], Optional[dict]]: self._disableAutoSave() backup = Backup() backup.makeFromCurrent() From eca23e5b5dcd33ecaca745bd8da2ee2ff37e93aa Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 13 Jun 2018 16:48:08 +0200 Subject: [PATCH 47/83] CURA-5330 Fix typing in 3MFReader plugin --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 28 ++++++++++----------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index ecccfc77ac..3df3d9d4a2 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -4,7 +4,7 @@ from configparser import ConfigParser import zipfile import os -from typing import List, Tuple +from typing import Dict, List, Tuple import xml.etree.ElementTree as ET @@ -38,7 +38,7 @@ i18n_catalog = i18nCatalog("cura") class ContainerInfo: - def __init__(self, file_name: str, serialized: str, parser: ConfigParser): + def __init__(self, file_name: str, serialized: str, parser: ConfigParser) -> None: self.file_name = file_name self.serialized = serialized self.parser = parser @@ -47,14 +47,14 @@ class ContainerInfo: class QualityChangesInfo: - def __init__(self): + def __init__(self) -> None: self.name = None self.global_info = None - self.extruder_info_dict = {} + self.extruder_info_dict = {} # type: Dict[str, ContainerInfo] class MachineInfo: - def __init__(self): + def __init__(self) -> None: self.container_id = None self.name = None self.definition_id = None @@ -66,11 +66,11 @@ class MachineInfo: self.definition_changes_info = None self.user_changes_info = None - self.extruder_info_dict = {} + self.extruder_info_dict = {} # type: Dict[str, ExtruderInfo] class ExtruderInfo: - def __init__(self): + def __init__(self) -> None: self.position = None self.enabled = True self.variant_info = None @@ -82,7 +82,7 @@ class ExtruderInfo: ## Base implementation for reading 3MF workspace files. class ThreeMFWorkspaceReader(WorkspaceReader): - def __init__(self): + def __init__(self) -> None: super().__init__() MimeTypeDatabase.addMimeType( @@ -112,28 +112,26 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # - variant self._ignored_instance_container_types = {"quality", "variant"} - self._resolve_strategies = {} + self._resolve_strategies = {} # type: Dict[str, str] - self._id_mapping = {} + self._id_mapping = {} # type: Dict[str, str] # In Cura 2.5 and 2.6, the empty profiles used to have those long names self._old_empty_profile_id_dict = {"empty_%s" % k: "empty" for k in ["material", "variant"]} self._is_same_machine_type = False - self._old_new_materials = {} - self._materials_to_select = {} + self._old_new_materials = {} # type: Dict[str, str] self._machine_info = None def _clearState(self): self._is_same_machine_type = False self._id_mapping = {} self._old_new_materials = {} - self._materials_to_select = {} self._machine_info = None ## Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results. # This has nothing to do with speed, but with getting consistent new naming for instances & objects. - def getNewId(self, old_id): + def getNewId(self, old_id: str): if old_id not in self._id_mapping: self._id_mapping[old_id] = self._container_registry.uniqueName(old_id) return self._id_mapping[old_id] @@ -671,7 +669,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): else: material_container = materials[0] old_material_root_id = material_container.getMetaDataEntry("base_file") - if not self._container_registry.isReadOnly(old_material_root_id): # Only create new materials if they are not read only. + if old_material_root_id is not None and not self._container_registry.isReadOnly(old_material_root_id): # Only create new materials if they are not read only. to_deserialize_material = True if self._resolve_strategies["material"] == "override": From 51888d86fec087354644d1f7d015f589852d4b02 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 13 Jun 2018 17:13:33 +0200 Subject: [PATCH 48/83] CURA-5330 Fix typing check --- cura/SingleInstance.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cura/SingleInstance.py b/cura/SingleInstance.py index dca0d3237c..b3d4f096fd 100644 --- a/cura/SingleInstance.py +++ b/cura/SingleInstance.py @@ -69,7 +69,9 @@ class SingleInstance: def _onClientConnected(self) -> None: Logger.log("i", "New connection recevied on our single-instance server") - connection = self._single_instance_server.nextPendingConnection() + connection = None + if self._single_instance_server: + connection = self._single_instance_server.nextPendingConnection() if connection is not None: connection.readyRead.connect(lambda c = connection: self.__readCommands(c)) From 0a21ce44b01030f7927fe1fe57434a122eb3ad3c Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 13 Jun 2018 17:36:04 +0200 Subject: [PATCH 49/83] CURA-5330 Fix code style and typing in GCodeReader plugin --- plugins/GCodeReader/FlavorParser.py | 31 +++++++++++++++-------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py index 696b3b180b..5da1a79fb7 100644 --- a/plugins/GCodeReader/FlavorParser.py +++ b/plugins/GCodeReader/FlavorParser.py @@ -23,9 +23,9 @@ import numpy import math import re from typing import Dict, List, NamedTuple, Optional, Union -from collections import namedtuple -Position = NamedTuple("Position", [("x", float), ("y", float), ("z", float), ("f", float), ("e", float)]) +PositionOptional = NamedTuple("Position", [("x", Optional[float]), ("y", Optional[float]), ("z", Optional[float]), ("f", Optional[float]), ("e", Optional[float])]) +Position = NamedTuple("Position", [("x", float), ("y", float), ("z", float), ("f", float), ("e", List[float])]) ## This parser is intended to interpret the common firmware codes among all the # different flavors @@ -40,9 +40,9 @@ class FlavorParser: self._clearValues() self._scene_node = None # X, Y, Z position, F feedrate and E extruder values are stored - self._position = namedtuple('Position', ['x', 'y', 'z', 'f', 'e']) + self._position = Position self._is_layers_in_file = False # Does the Gcode have the layers comment? - self._extruder_offsets = {} # Offsets for multi extruders. key is index, value is [x-offset, y-offset] + 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 @@ -50,10 +50,10 @@ class FlavorParser: def _clearValues(self) -> None: self._extruder_number = 0 - self._extrusion_length_offset = [0] + self._extrusion_length_offset = [0] # type: List[float] self._layer_type = LayerPolygon.Inset0Type self._layer_number = 0 - self._previous_z = 0 + self._previous_z = 0 # type: float self._layer_data_builder = LayerDataBuilder.LayerDataBuilder() self._is_absolute_positioning = True # It can be absolute (G90) or relative (G91) self._is_absolute_extrusion = True # It can become absolute (M82, default) or relative (M83) @@ -77,14 +77,14 @@ class FlavorParser: def _getInt(self, line: str, code: str) -> Optional[int]: value = self._getValue(line, code) try: - return int(value) + return int(value) # type: ignore except: return None def _getFloat(self, line: str, code: str) -> Optional[float]: value = self._getValue(line, code) try: - return float(value) + return float(value) # type: ignore except: return None @@ -169,7 +169,7 @@ class FlavorParser: return 0.35 return line_width - def _gCode0(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position: + def _gCode0(self, position: Position, params: PositionOptional, path: List[List[Union[float, int]]]) -> Position: x, y, z, f, e = position if self._is_absolute_positioning: @@ -205,7 +205,7 @@ class FlavorParser: _gCode1 = _gCode0 ## Home the head. - def _gCode28(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position: + def _gCode28(self, position: Position, params: PositionOptional, path: List[List[Union[float, int]]]) -> Position: return self._position( params.x if params.x is not None else position.x, params.y if params.y is not None else position.y, @@ -214,20 +214,20 @@ class FlavorParser: position.e) ## Set the absolute positioning - def _gCode90(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position: + def _gCode90(self, position: Position, params: PositionOptional, path: List[List[Union[float, int]]]) -> Position: self._is_absolute_positioning = True self._is_absolute_extrusion = True return position ## Set the relative positioning - def _gCode91(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position: + def _gCode91(self, position: Position, params: PositionOptional, path: List[List[Union[float, int]]]) -> Position: self._is_absolute_positioning = False self._is_absolute_extrusion = False return position ## Reset the current position to the values specified. # For example: G92 X10 will set the X to 10 without any physical motion. - def _gCode92(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position: + def _gCode92(self, position: Position, params: PositionOptional, path: List[List[Union[float, int]]]) -> Position: if params.e is not None: # Sometimes a G92 E0 is introduced in the middle of the GCode so we need to keep those offsets for calculate the line_width self._extrusion_length_offset[self._extruder_number] += position.e[self._extruder_number] - params.e @@ -260,7 +260,7 @@ class FlavorParser: f = float(item[1:]) / 60 if item[0] == "E": e = float(item[1:]) - params = self._position(x, y, z, f, e) + params = PositionOptional(x, y, z, f, e) return func(position, params, path) return position @@ -322,12 +322,13 @@ class FlavorParser: lifetime=0, title = catalog.i18nc("@info:title", "G-code Details")) + assert(self._message is not None) # use for typing purposes self._message.setProgress(0) self._message.show() Logger.log("d", "Parsing Gcode...") - current_position = self._position(0, 0, 0, 0, [0]) + current_position = Position(0, 0, 0, 0, [0]) current_path = [] min_layer_number = 0 negative_layers = 0 From eb65a11e183304b59d6947ef79801713edfb6200 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 13 Jun 2018 18:22:27 +0200 Subject: [PATCH 50/83] CURA-5330 Fix code style and typing in Toolbox plugin --- plugins/Toolbox/__init__.py | 2 +- plugins/Toolbox/src/Toolbox.py | 89 ++++++++++++++++++---------------- 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/plugins/Toolbox/__init__.py b/plugins/Toolbox/__init__.py index 2f8e192764..70c00ed07c 100644 --- a/plugins/Toolbox/__init__.py +++ b/plugins/Toolbox/__init__.py @@ -9,4 +9,4 @@ def getMetaData(): def register(app): - return {"extension": Toolbox.Toolbox()} + return {"extension": Toolbox.Toolbox(app)} diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 2f26e21d65..d0515010ce 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -1,11 +1,12 @@ # Copyright (c) 2018 Ultimaker B.V. # Toolbox is released under the terms of the LGPLv3 or higher. -from typing import Dict, Optional, Union, Any +from typing import Dict, Optional, Union, Any, cast import json import os import tempfile import platform +from typing import List from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply @@ -14,6 +15,7 @@ from UM.Application import Application from UM.Logger import Logger from UM.PluginRegistry import PluginRegistry from UM.Extension import Extension +from UM.Qt.ListModel import ListModel from UM.i18n import i18nCatalog from UM.Version import Version @@ -31,40 +33,38 @@ class Toolbox(QObject, Extension): DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" DEFAULT_CLOUD_API_VERSION = 1 - def __init__(self, parent=None) -> None: + def __init__(self, application: Application, parent=None) -> None: super().__init__(parent) - self._application = Application.getInstance() - self._package_manager = None - self._plugin_registry = Application.getInstance().getPluginRegistry() + self._application = application - self._sdk_version = None - self._cloud_api_version = None - self._cloud_api_root = None - self._api_url = None + self._sdk_version = None # type: Optional[int] + self._cloud_api_version = None # type: Optional[int] + self._cloud_api_root = None # type: Optional[str] + self._api_url = None # type: Optional[str] # Network: self._get_packages_request = None self._get_showcase_request = None self._download_request = None self._download_reply = None - self._download_progress = 0 + self._download_progress = 0 # type: float self._is_downloading = False self._network_manager = None self._request_header = [ b"User-Agent", str.encode( "%s/%s (%s %s)" % ( - Application.getInstance().getApplicationName(), - Application.getInstance().getVersion(), + self._application.getApplicationName(), + self._application.getVersion(), platform.system(), platform.machine(), ) ) ] - self._request_urls = {} - self._to_update = [] # Package_ids that are waiting to be updated - self._old_plugin_ids = [] + self._request_urls = {} # type: Dict[str, QUrl] + self._to_update = [] # type: List[str] # Package_ids that are waiting to be updated + self._old_plugin_ids = [] # type: List[str] # Data: self._metadata = { @@ -76,7 +76,7 @@ class Toolbox(QObject, Extension): "materials_showcase": [], "materials_available": [], "materials_installed": [] - } + } # type: Dict[str, List[Any]] # Models: self._models = { @@ -88,7 +88,7 @@ class Toolbox(QObject, Extension): "materials_showcase": AuthorsModel(self), "materials_available": PackagesModel(self), "materials_installed": PackagesModel(self) - } + } # type: Dict[str, ListModel] # These properties are for keeping track of the UI state: # ---------------------------------------------------------------------- @@ -103,7 +103,7 @@ class Toolbox(QObject, Extension): # Active package refers to which package is currently being downloaded, # installed, or otherwise modified. - self._active_package = None + self._active_package = None # type: Optional[Dict[str, Any]] self._dialog = None self._restart_required = False @@ -114,7 +114,7 @@ class Toolbox(QObject, Extension): self._license_dialog_plugin_file_location = "" self._restart_dialog_message = "" - Application.getInstance().initializationFinished.connect(self._onAppInitialized) + self._application.initializationFinished.connect(self._onAppInitialized) @@ -156,7 +156,8 @@ class Toolbox(QObject, Extension): # This is a plugin, so most of the components required are not ready when # this is initialized. Therefore, we wait until the application is ready. def _onAppInitialized(self) -> None: - self._package_manager = Application.getInstance().getPackageManager() + self._plugin_registry = self._application.getPluginRegistry() + self._package_manager = self._application.getPackageManager() self._sdk_version = self._getSDKVersion() self._cloud_api_version = self._getCloudAPIVersion() self._cloud_api_root = self._getCloudAPIRoot() @@ -178,31 +179,31 @@ class Toolbox(QObject, Extension): def _getCloudAPIRoot(self) -> str: if not hasattr(cura, "CuraVersion"): return self.DEFAULT_CLOUD_API_ROOT - if not hasattr(cura.CuraVersion, "CuraCloudAPIRoot"): + if not hasattr(cura.CuraVersion, "CuraCloudAPIRoot"): # type: ignore return self.DEFAULT_CLOUD_API_ROOT - if not cura.CuraVersion.CuraCloudAPIRoot: + if not cura.CuraVersion.CuraCloudAPIRoot: # type: ignore return self.DEFAULT_CLOUD_API_ROOT - return cura.CuraVersion.CuraCloudAPIRoot + return cura.CuraVersion.CuraCloudAPIRoot # type: ignore # Get the cloud API version from CuraVersion def _getCloudAPIVersion(self) -> int: if not hasattr(cura, "CuraVersion"): return self.DEFAULT_CLOUD_API_VERSION - if not hasattr(cura.CuraVersion, "CuraCloudAPIVersion"): + if not hasattr(cura.CuraVersion, "CuraCloudAPIVersion"): # type: ignore return self.DEFAULT_CLOUD_API_VERSION - if not cura.CuraVersion.CuraCloudAPIVersion: + if not cura.CuraVersion.CuraCloudAPIVersion: # type: ignore return self.DEFAULT_CLOUD_API_VERSION - return cura.CuraVersion.CuraCloudAPIVersion + return cura.CuraVersion.CuraCloudAPIVersion # type: ignore # Get the packages version depending on Cura version settings. def _getSDKVersion(self) -> int: if not hasattr(cura, "CuraVersion"): return self._plugin_registry.APIVersion - if not hasattr(cura.CuraVersion, "CuraSDKVersion"): + if not hasattr(cura.CuraVersion, "CuraSDKVersion"): # type: ignore return self._plugin_registry.APIVersion - if not cura.CuraVersion.CuraSDKVersion: + if not cura.CuraVersion.CuraSDKVersion: # type: ignore return self._plugin_registry.APIVersion - return cura.CuraVersion.CuraSDKVersion + return cura.CuraVersion.CuraSDKVersion # type: ignore @pyqtSlot() def browsePackages(self) -> None: @@ -213,6 +214,7 @@ class Toolbox(QObject, Extension): self._network_manager.finished.disconnect(self._onRequestFinished) self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccessibleChanged) self._network_manager = QNetworkAccessManager() + assert(self._network_manager is not None) self._network_manager.finished.connect(self._onRequestFinished) self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccessibleChanged) @@ -235,11 +237,11 @@ class Toolbox(QObject, Extension): def _createDialog(self, qml_name: str) -> Optional[QObject]: Logger.log("d", "Toolbox: Creating dialog [%s].", qml_name) path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "resources", "qml", qml_name) - dialog = Application.getInstance().createQmlComponent(path, {"toolbox": self}) + dialog = self._application.createQmlComponent(path, {"toolbox": self}) return dialog - def _convertPluginMetadata(self, plugin: dict) -> dict: + def _convertPluginMetadata(self, plugin: Dict[str, Any]) -> Dict[str, Any]: formatted = { "package_id": plugin["id"], "package_type": "plugin", @@ -265,7 +267,7 @@ class Toolbox(QObject, Extension): scheduled_to_remove_package_ids = self._package_manager.getToRemovePackageIDs() self._old_plugin_ids = [] - self._old_plugin_metadata = [] + self._old_plugin_metadata = [] # type: List[Dict[str, Any]] for plugin_id in old_plugin_ids: # Neither the installed packages nor the packages that are scheduled to remove are old plugins @@ -354,7 +356,7 @@ class Toolbox(QObject, Extension): @pyqtSlot() def restart(self): - CuraApplication.getInstance().windowClosed() + cast(CuraApplication, self._application).windowClosed() def getRemotePackage(self, package_id: str) -> Optional[Dict]: # TODO: make the lookup in a dict, not a loop. canUpdate is called for every item. @@ -432,7 +434,8 @@ class Toolbox(QObject, Extension): Logger.log("i", "Toolbox: Requesting %s metadata from server.", type) request = QNetworkRequest(self._request_urls[type]) request.setRawHeader(*self._request_header) - self._network_manager.get(request) + if self._network_manager: + self._network_manager.get(request) @pyqtSlot(str) def startDownload(self, url: str) -> None: @@ -441,15 +444,15 @@ class Toolbox(QObject, Extension): self._download_request = QNetworkRequest(url) if hasattr(QNetworkRequest, "FollowRedirectsAttribute"): # Patch for Qt 5.6-5.8 - self._download_request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True) + cast(QNetworkRequest, self._download_request).setAttribute(QNetworkRequest.FollowRedirectsAttribute, True) if hasattr(QNetworkRequest, "RedirectPolicyAttribute"): # Patch for Qt 5.9+ - self._download_request.setAttribute(QNetworkRequest.RedirectPolicyAttribute, True) - self._download_request.setRawHeader(*self._request_header) - self._download_reply = self._network_manager.get(self._download_request) + cast(QNetworkRequest, self._download_request).setAttribute(QNetworkRequest.RedirectPolicyAttribute, True) + cast(QNetworkRequest, self._download_request).setRawHeader(*self._request_header) + self._download_reply = cast(QNetworkAccessManager, self._network_manager).get(self._download_request) self.setDownloadProgress(0) self.setIsDownloading(True) - self._download_reply.downloadProgress.connect(self._onDownloadProgress) + cast(QNetworkReply, self._download_reply).downloadProgress.connect(self._onDownloadProgress) @pyqtSlot() def cancelDownload(self) -> None: @@ -551,12 +554,12 @@ class Toolbox(QObject, Extension): self.setDownloadProgress(new_progress) if bytes_sent == bytes_total: self.setIsDownloading(False) - self._download_reply.downloadProgress.disconnect(self._onDownloadProgress) + cast(QNetworkReply, self._download_reply).downloadProgress.disconnect(self._onDownloadProgress) # Must not delete the temporary file on Windows self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False) file_path = self._temp_plugin_file.name # Write first and close, otherwise on Windows, it cannot read the file - self._temp_plugin_file.write(self._download_reply.readAll()) + self._temp_plugin_file.write(cast(QNetworkReply, self._download_reply).readAll()) self._temp_plugin_file.close() self._onDownloadComplete(file_path) @@ -577,13 +580,13 @@ class Toolbox(QObject, Extension): # Getter & Setters for Properties: # -------------------------------------------------------------------------- - def setDownloadProgress(self, progress: Union[int, float]) -> None: + def setDownloadProgress(self, progress: float) -> None: if progress != self._download_progress: self._download_progress = progress self.onDownloadProgressChanged.emit() @pyqtProperty(int, fset = setDownloadProgress, notify = onDownloadProgressChanged) - def downloadProgress(self) -> int: + def downloadProgress(self) -> float: return self._download_progress def setIsDownloading(self, is_downloading: bool) -> None: From eac3c759cdd89f0c490c5bc38f86301fd0b0c43f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 13 Jun 2018 17:21:22 +0200 Subject: [PATCH 51/83] Cast each container in their stack to actual types We know for sure that these containers have those types. We'll accept the risk here that this assumption was wrong. Contributes to issue CURA-5330. --- cura/Settings/CuraContainerStack.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index f9041fc86e..ba97f232a3 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.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 Any, List, Optional, Union +from typing import Any, cast, List, Optional, Union from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject from UM.Application import Application @@ -73,7 +73,7 @@ class CuraContainerStack(ContainerStack): # \return The user changes container. Should always be a valid container, but can be equal to the empty InstanceContainer. @pyqtProperty(InstanceContainer, fset = setUserChanges, notify = pyqtContainersChanged) def userChanges(self) -> InstanceContainer: - return self._containers[_ContainerIndexes.UserChanges] + return cast(InstanceContainer, self._containers[_ContainerIndexes.UserChanges]) ## Set the quality changes container. # @@ -86,7 +86,7 @@ class CuraContainerStack(ContainerStack): # \return The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer. @pyqtProperty(InstanceContainer, fset = setQualityChanges, notify = pyqtContainersChanged) def qualityChanges(self) -> InstanceContainer: - return self._containers[_ContainerIndexes.QualityChanges] + return cast(InstanceContainer, self._containers[_ContainerIndexes.QualityChanges]) ## Set the quality container. # @@ -99,7 +99,7 @@ class CuraContainerStack(ContainerStack): # \return The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer. @pyqtProperty(InstanceContainer, fset = setQuality, notify = pyqtContainersChanged) def quality(self) -> InstanceContainer: - return self._containers[_ContainerIndexes.Quality] + return cast(InstanceContainer, self._containers[_ContainerIndexes.Quality]) ## Set the material container. # @@ -112,7 +112,7 @@ class CuraContainerStack(ContainerStack): # \return The material container. Should always be a valid container, but can be equal to the empty InstanceContainer. @pyqtProperty(InstanceContainer, fset = setMaterial, notify = pyqtContainersChanged) def material(self) -> InstanceContainer: - return self._containers[_ContainerIndexes.Material] + return cast(InstanceContainer, self._containers[_ContainerIndexes.Material]) ## Set the variant container. # @@ -125,7 +125,7 @@ class CuraContainerStack(ContainerStack): # \return The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer. @pyqtProperty(InstanceContainer, fset = setVariant, notify = pyqtContainersChanged) def variant(self) -> InstanceContainer: - return self._containers[_ContainerIndexes.Variant] + return cast(InstanceContainer, self._containers[_ContainerIndexes.Variant]) ## Set the definition changes container. # @@ -138,7 +138,7 @@ class CuraContainerStack(ContainerStack): # \return The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer. @pyqtProperty(InstanceContainer, fset = setDefinitionChanges, notify = pyqtContainersChanged) def definitionChanges(self) -> InstanceContainer: - return self._containers[_ContainerIndexes.DefinitionChanges] + return cast(InstanceContainer, self._containers[_ContainerIndexes.DefinitionChanges]) ## Set the definition container. # @@ -151,7 +151,7 @@ class CuraContainerStack(ContainerStack): # \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer. @pyqtProperty(QObject, fset = setDefinition, notify = pyqtContainersChanged) def definition(self) -> DefinitionContainer: - return self._containers[_ContainerIndexes.Definition] + return cast(DefinitionContainer, self._containers[_ContainerIndexes.Definition]) @override(ContainerStack) def getBottom(self) -> "DefinitionContainer": From b331736cb2188d4102ae11c6a5057b9f7fa51cd0 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 13 Jun 2018 17:22:17 +0200 Subject: [PATCH 52/83] Let _findInstanceContainerDefinitionId work with DefinitionContainerInterface But where it needs the .id field it needs to ignore this type because this works with getattr weirdness. Contributes to issue CURA-5330. --- cura/Settings/CuraContainerStack.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index ba97f232a3..0cee696a8d 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.py @@ -303,15 +303,15 @@ class CuraContainerStack(ContainerStack): # # \return The ID of the definition container to use when searching for instance containers. @classmethod - def _findInstanceContainerDefinitionId(cls, machine_definition: DefinitionContainer) -> str: + def _findInstanceContainerDefinitionId(cls, machine_definition: DefinitionContainerInterface) -> str: quality_definition = machine_definition.getMetaDataEntry("quality_definition") if not quality_definition: - return machine_definition.id + return machine_definition.id #type: ignore definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition) if not definitions: - Logger.log("w", "Unable to find parent definition {parent} for machine {machine}", parent = quality_definition, machine = machine_definition.id) - return machine_definition.id + Logger.log("w", "Unable to find parent definition {parent} for machine {machine}", parent = quality_definition, machine = machine_definition.id) #type: ignore + return machine_definition.id #type: ignore return cls._findInstanceContainerDefinitionId(definitions[0]) From 7eba8685744c5513f9ee59e85ada8a729d801e80 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Thu, 14 Jun 2018 15:41:12 +0200 Subject: [PATCH 53/83] CURA-5330 Fix typing and code style in the UM3NetworkPrinting plugin --- .../NetworkedPrinterOutputDevice.py | 4 +- .../ClusterUM3OutputDevice.py | 48 +++++++++++-------- .../LegacyUM3OutputDevice.py | 8 +++- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py index 376d8cbdc8..d4dd2a5cc2 100644 --- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py +++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py @@ -207,7 +207,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): reply = self._manager.get(request) self._registerOnFinishedCallback(reply, on_finished) - def post(self, target: str, data: str, onFinished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None: + def post(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None: if self._manager is None: self._createNetworkManager() assert(self._manager is not None) @@ -216,7 +216,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): reply = self._manager.post(request, data) if on_progress is not None: reply.uploadProgress.connect(on_progress) - self._registerOnFinishedCallback(reply, onFinished) + self._registerOnFinishedCallback(reply, on_finished) def postFormWithParts(self, target:str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None: if self._manager is None: diff --git a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py index c54ced6b13..197debb58b 100644 --- a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py @@ -1,6 +1,11 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from typing import Generator +from typing import Set +from typing import Tuple +from typing import Union +from UM.FileHandler.FileHandler import FileHandler from UM.FileHandler.FileWriter import FileWriter #To choose based on the output file mode (text vs. binary). from UM.FileHandler.WriteFileJob import WriteFileJob #To call the file writer asynchronously. from UM.Logger import Logger @@ -44,15 +49,15 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): # Inheritance doesn't seem to work. Tying them together does work, but i'm open for better suggestions. clusterPrintersChanged = pyqtSignal() - def __init__(self, device_id, address, properties, parent = None): + def __init__(self, device_id, address, properties, parent = None) -> None: super().__init__(device_id = device_id, address = address, properties=properties, parent = parent) self._api_prefix = "/cluster-api/v1/" self._number_of_extruders = 2 - self._dummy_lambdas = set() + self._dummy_lambdas = set() # type: Set[Tuple[str, Dict, Union[io.StringIO, io.BytesIO]]] - self._print_jobs = [] + self._print_jobs = [] # type: List[PrintJobOutputModel] self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterMonitorItem.qml") self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterControlItem.qml") @@ -80,15 +85,15 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network")) - self._printer_uuid_to_unique_name_mapping = {} + self._printer_uuid_to_unique_name_mapping = {} # type: Dict[str, str] - self._finished_jobs = [] + self._finished_jobs = [] # type: List[PrintJobOutputModel] self._cluster_size = int(properties.get(b"cluster_size", 0)) self._latest_reply_handler = None - def requestWrite(self, nodes: List[SceneNode], file_name=None, filter_by_machine=False, file_handler=None, **kwargs): + def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None: self.writeStarted.emit(self) #Formats supported by this application (file types that we can actually write). @@ -168,6 +173,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._error_message = Message( i18n_catalog.i18nc("@info:status", "Sending new jobs (temporarily) blocked, still sending the previous print job.")) + + assert(self._error_message is not None) self._error_message.show() yield #Wait on the user to select a target printer. yield #Wait for the write job to be finished. @@ -179,15 +186,17 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): target_printer = yield #Potentially wait on the user to select a target printer. # Using buffering greatly reduces the write time for many lines of gcode + + stream = io.BytesIO() # type: Union[io.BytesIO, io.StringIO]# Binary mode. if preferred_format["mode"] == FileWriter.OutputMode.TextMode: stream = io.StringIO() - else: #Binary mode. - stream = io.BytesIO() job = WriteFileJob(writer, stream, nodes, preferred_format["mode"]) self._write_job_progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1, title = i18n_catalog.i18nc("@info:title", "Sending Data"), use_inactivity_timer = False) + + assert(self._write_job_progress_message is not None) # use for typing purposes self._write_job_progress_message.show() self._dummy_lambdas = (target_printer, preferred_format, stream) @@ -254,7 +263,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get # timeout responses if this happens. self._last_response_time = time() - if new_progress > self._progress_message.getProgress(): + if self._progress_message and new_progress > self._progress_message.getProgress(): self._progress_message.show() # Ensure that the message is visible. self._progress_message.setProgress(bytes_sent / bytes_total * 100) @@ -271,13 +280,15 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._success_message.actionTriggered.connect(self._successMessageActionTriggered) self._success_message.show() else: - self._progress_message.setProgress(0) - self._progress_message.hide() + if self._progress_message is not None: + self._progress_message.setProgress(0) + self._progress_message.hide() def _progressMessageActionTriggered(self, message_id: Optional[str]=None, action_id: Optional[str]=None) -> None: if action_id == "Abort": Logger.log("d", "User aborted sending print to remote.") - self._progress_message.hide() + if self._progress_message is not None: + self._progress_message.hide() self._compressing_gcode = False self._sending_gcode = False Application.getInstance().getController().setActiveStage("PrepareStage") @@ -315,8 +326,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is not None and print_job.state != "queued"] @pyqtProperty("QVariantList", notify=clusterPrintersChanged) - def connectedPrintersTypeCount(self) -> List[PrinterOutputModel]: - printer_count = {} + def connectedPrintersTypeCount(self) -> List[Dict[str, str]]: + printer_count = {} # type: Dict[str, int] for printer in self._printers: if printer.type in printer_count: printer_count[printer.type] += 1 @@ -324,7 +335,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): printer_count[printer.type] = 1 result = [] for machine_type in printer_count: - result.append({"machine_type": machine_type, "count": printer_count[machine_type]}) + result.append({"machine_type": machine_type, "count": str(printer_count[machine_type])}) return result @pyqtSlot(int, result=str) @@ -367,10 +378,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._finished_jobs = finished_jobs def _update(self) -> None: - if not super()._update(): - return - self.get("printers/", onFinished=self._onGetPrintersDataFinished) - self.get("print_jobs/", onFinished=self._onGetPrintJobsFinished) + super()._update() + self.get("printers/", on_finished=self._onGetPrintersDataFinished) + self.get("print_jobs/", on_finished=self._onGetPrintJobsFinished) def _onGetPrintJobsFinished(self, reply: QNetworkReply) -> None: if not checkValidGetReply(reply): diff --git a/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py b/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py index 42f00beceb..0111ad5e4f 100644 --- a/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py @@ -1,3 +1,7 @@ +from typing import List, Optional + +from UM.FileHandler.FileHandler import FileHandler +from UM.Scene.SceneNode import SceneNode from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel @@ -39,7 +43,7 @@ i18n_catalog = i18nCatalog("cura") # 4. At this point the machine either has the state Authenticated or AuthenticationDenied. # 5. As a final step, we verify the authentication, as this forces the QT manager to setup the authenticator. class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): - def __init__(self, device_id, address: str, properties, parent = None): + def __init__(self, device_id, address: str, properties, parent = None) -> None: super().__init__(device_id = device_id, address = address, properties = properties, parent = parent) self._api_prefix = "/api/v1/" self._number_of_extruders = 2 @@ -168,7 +172,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): # NotImplementedError. We can simply ignore these. pass - def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs): + def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None: if not self.activePrinter: # No active printer. Unable to write return From a184fad8eecf713cd14fc6e54b77c3c36da56da9 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Thu, 14 Jun 2018 15:57:30 +0200 Subject: [PATCH 54/83] CURA-5330 Add typing to the USBPrinting plugin --- plugins/USBPrinting/USBPrinterOutputDevice.py | 17 ++++++++--------- .../USBPrinterOutputDeviceManager.py | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 00eb2f0b25..d61cf03337 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -22,7 +22,7 @@ from threading import Thread, Event from time import time, sleep from queue import Queue from enum import IntEnum -from typing import Union, Optional, List +from typing import Union, Optional, List, cast import re import functools # Used for reduce @@ -35,7 +35,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): firmwareProgressChanged = pyqtSignal() firmwareUpdateStateChanged = pyqtSignal() - def __init__(self, serial_port: str, baud_rate: Optional[int] = None): + def __init__(self, serial_port: str, baud_rate: Optional[int] = None) -> None: super().__init__(serial_port) self.setName(catalog.i18nc("@item:inmenu", "USB printing")) self.setShortDescription(catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print via USB")) @@ -68,7 +68,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._is_printing = False # A print is being sent. ## Set when print is started in order to check running time. - self._print_start_time = None # type: Optional[int] + self._print_start_time = None # type: Optional[float] self._print_estimated_time = None # type: Optional[int] self._accepts_commands = True @@ -83,7 +83,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self.setConnectionText(catalog.i18nc("@info:status", "Connected via USB")) # Queue for commands that need to be sent. - self._command_queue = Queue() + self._command_queue = Queue() # type: Queue # Event to indicate that an "ok" was received from the printer after sending a command. self._command_received = Event() self._command_received.set() @@ -277,13 +277,12 @@ class USBPrinterOutputDevice(PrinterOutputDevice): if self._serial is None or self._connection_state != ConnectionState.connected: return - if type(command == str): - command = command.encode() - if not command.endswith(b"\n"): - command += b"\n" + new_command = cast(bytes, command) if type(command) is bytes else cast(str, command).encode() # type: bytes + if not new_command.endswith(b"\n"): + new_command += b"\n" try: self._command_received.clear() - self._serial.write(command) + self._serial.write(new_command) except SerialTimeoutException: Logger.log("w", "Timeout when sending command to printer via USB.") self._command_received.set() diff --git a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py index abf3b9ece2..2ee85187ee 100644 --- a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py +++ b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py @@ -179,7 +179,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin): return list(base_list) - __instance = None + __instance = None # type: USBPrinterOutputDeviceManager @classmethod def getInstance(cls, *args, **kwargs) -> "USBPrinterOutputDeviceManager": From dc4556647a4e7a5fd5c0c5809fa8af863d50d770 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Thu, 14 Jun 2018 16:49:26 +0200 Subject: [PATCH 55/83] CURA-5330 Fix code style in XmlMaterialProfile plugin --- .../XmlMaterialProfile/XmlMaterialProfile.py | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 9d456c833d..e1e17b20ea 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -6,8 +6,10 @@ import io import json #To parse the product-to-id mapping file. import os.path #To find the product-to-id mapping. import sys -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, cast import xml.etree.ElementTree as ET +from typing import Dict +from typing import Iterator from UM.Resources import Resources from UM.Logger import Logger @@ -132,7 +134,7 @@ class XmlMaterialProfile(InstanceContainer): "version": self.CurrentFdmMaterialVersion}) ## Begin Metadata Block - builder.start("metadata") + builder.start("metadata") # type: ignore metadata = copy.deepcopy(self.getMetaData()) # setting_version is derived from the "version" tag in the schema, so don't serialize it into a file @@ -156,21 +158,21 @@ class XmlMaterialProfile(InstanceContainer): metadata.pop("name", "") ## Begin Name Block - builder.start("name") + builder.start("name") # type: ignore - builder.start("brand") + builder.start("brand") # type: ignore builder.data(metadata.pop("brand", "")) builder.end("brand") - builder.start("material") + builder.start("material") # type: ignore builder.data(metadata.pop("material", "")) builder.end("material") - builder.start("color") + builder.start("color") # type: ignore builder.data(metadata.pop("color_name", "")) builder.end("color") - builder.start("label") + builder.start("label") # type: ignore builder.data(self.getName()) builder.end("label") @@ -178,7 +180,7 @@ class XmlMaterialProfile(InstanceContainer): ## End Name Block for key, value in metadata.items(): - builder.start(key) + builder.start(key) # type: ignore if value is not None: #Nones get handled well by the builder. #Otherwise the builder always expects a string. #Deserialize expects the stringified version. @@ -190,10 +192,10 @@ class XmlMaterialProfile(InstanceContainer): ## End Metadata Block ## Begin Properties Block - builder.start("properties") + builder.start("properties") # type: ignore for key, value in properties.items(): - builder.start(key) + builder.start(key) # type: ignore builder.data(value) builder.end(key) @@ -201,14 +203,14 @@ class XmlMaterialProfile(InstanceContainer): ## End Properties Block ## Begin Settings Block - builder.start("settings") + builder.start("settings") # type: ignore if self.getMetaDataEntry("definition") == "fdmprinter": for instance in self.findInstances(): self._addSettingElement(builder, instance) - machine_container_map = {} - machine_variant_map = {} + machine_container_map = {} # type: Dict[str, InstanceContainer] + machine_variant_map = {} # type: Dict[str, Dict[str, Any]] variant_manager = CuraApplication.getInstance().getVariantManager() @@ -248,7 +250,7 @@ class XmlMaterialProfile(InstanceContainer): product = product_name break - builder.start("machine") + builder.start("machine") # type: ignore builder.start("machine_identifier", { "manufacturer": container.getMetaDataEntry("machine_manufacturer", definition_metadata.get("manufacturer", "Unknown")), @@ -264,7 +266,7 @@ class XmlMaterialProfile(InstanceContainer): self._addSettingElement(builder, instance) # Find all hotend sub-profiles corresponding to this material and machine and add them to this profile. - buildplate_dict = {} + buildplate_dict = {} # type: Dict[str, Any] for variant_name, variant_dict in machine_variant_map[definition_id].items(): variant_type = variant_dict["variant_node"].metadata["hardware_type"] from cura.Machines.VariantManager import VariantType @@ -812,11 +814,14 @@ class XmlMaterialProfile(InstanceContainer): if label is not None and label.text is not None: base_metadata["name"] = label.text else: - base_metadata["name"] = cls._profile_name(material.text, color.text) + if material is not None and color is not None: + base_metadata["name"] = cls._profile_name(material.text, color.text) + else: + base_metadata["name"] = "Unknown Material" - base_metadata["brand"] = brand.text if brand.text is not None else "Unknown Brand" - base_metadata["material"] = material.text if material.text is not None else "Unknown Type" - base_metadata["color_name"] = color.text if color.text is not None else "Unknown Color" + base_metadata["brand"] = brand.text if brand is not None and brand.text is not None else "Unknown Brand" + base_metadata["material"] = material.text if material is not None and material.text is not None else "Unknown Type" + base_metadata["color_name"] = color.text if color is not None and color.text is not None else "Unknown Color" continue #Setting_version is derived from the "version" tag in the schema earlier, so don't set it here. @@ -836,13 +841,13 @@ class XmlMaterialProfile(InstanceContainer): tag_name = _tag_without_namespace(entry) property_values[tag_name] = entry.text - base_metadata["approximate_diameter"] = str(round(float(property_values.get("diameter", 2.85)))) # In mm + base_metadata["approximate_diameter"] = str(round(float(cast(float, property_values.get("diameter", 2.85))))) # In mm base_metadata["properties"] = property_values base_metadata["definition"] = "fdmprinter" compatible_entries = data.iterfind("./um:settings/um:setting[@key='hardware compatible']", cls.__namespaces) try: - common_compatibility = cls._parseCompatibleValue(next(compatible_entries).text) + common_compatibility = cls._parseCompatibleValue(next(compatible_entries).text) # type: ignore except StopIteration: #No 'hardware compatible' setting. common_compatibility = True base_metadata["compatible"] = common_compatibility @@ -856,7 +861,8 @@ class XmlMaterialProfile(InstanceContainer): for entry in machine.iterfind("./um:setting", cls.__namespaces): key = entry.get("key") if key == "hardware compatible": - machine_compatibility = cls._parseCompatibleValue(entry.text) + if entry.text is not None: + machine_compatibility = cls._parseCompatibleValue(entry.text) for identifier in machine.iterfind("./um:machine_identifier", cls.__namespaces): machine_id_list = product_id_map.get(identifier.get("product"), []) @@ -891,7 +897,7 @@ class XmlMaterialProfile(InstanceContainer): result_metadata.append(new_material_metadata) buildplates = machine.iterfind("./um:buildplate", cls.__namespaces) - buildplate_map = {} + buildplate_map = {} # type: Dict[str, Dict[str, bool]] buildplate_map["buildplate_compatible"] = {} buildplate_map["buildplate_recommended"] = {} for buildplate in buildplates: @@ -912,10 +918,11 @@ class XmlMaterialProfile(InstanceContainer): buildplate_recommended = True for entry in settings: key = entry.get("key") - if key == "hardware compatible": - buildplate_compatibility = cls._parseCompatibleValue(entry.text) - elif key == "hardware recommended": - buildplate_recommended = cls._parseCompatibleValue(entry.text) + if entry.text is not None: + if key == "hardware compatible": + buildplate_compatibility = cls._parseCompatibleValue(entry.text) + elif key == "hardware recommended": + buildplate_recommended = cls._parseCompatibleValue(entry.text) buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_compatibility buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_recommended @@ -929,7 +936,8 @@ class XmlMaterialProfile(InstanceContainer): for entry in hotend.iterfind("./um:setting", cls.__namespaces): key = entry.get("key") if key == "hardware compatible": - hotend_compatibility = cls._parseCompatibleValue(entry.text) + if entry.text is not None: + hotend_compatibility = cls._parseCompatibleValue(entry.text) new_hotend_specific_material_id = container_id + "_" + machine_id + "_" + hotend_name.replace(" ", "_") From b309e93767456a48b8b2f2cd48b200d7a3e8fe92 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Thu, 14 Jun 2018 16:54:22 +0200 Subject: [PATCH 56/83] CURA-5330 Fix typing in the VersionUpgrade plugin --- .../VersionUpgrade21to22/Profile.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py b/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py index becf29c242..161edcb67c 100644 --- a/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py +++ b/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py @@ -44,20 +44,18 @@ class Profile: # Parse the general section. self._name = parser.get("general", "name") - self._type = parser.get("general", "type", fallback = None) + self._type = parser.get("general", "type") + self._weight = None if "weight" in parser["general"]: self._weight = int(parser.get("general", "weight")) - else: - self._weight = None - self._machine_type_id = parser.get("general", "machine_type", fallback = None) - self._machine_variant_name = parser.get("general", "machine_variant", fallback = None) - self._machine_instance_name = parser.get("general", "machine_instance", fallback = None) + self._machine_type_id = parser.get("general", "machine_type") + self._machine_variant_name = parser.get("general", "machine_variant") + self._machine_instance_name = parser.get("general", "machine_instance") + self._material_name = None if "material" in parser["general"]: #Note: Material name is unused in this upgrade. self._material_name = parser.get("general", "material") elif self._type == "material": - self._material_name = parser.get("general", "name", fallback = None) - else: - self._material_name = None + self._material_name = parser.get("general", "name") # Parse the settings. self._settings = {} # type: Dict[str,str] From 01a645e726737f3dbe769e9d3b028f6be6a4428e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 10:28:07 +0200 Subject: [PATCH 57/83] Use only CuraApplication It was complaining that getMachineManager doesn't exist, because that is in CuraApplication instead of UM.Application. I removed all references to UM.Application so that the imports are a bit simpler. Contributes to issue CURA-5330. --- cura/PrinterOutput/NetworkedPrinterOutputDevice.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py index d4dd2a5cc2..e319614bc7 100644 --- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py +++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py @@ -1,7 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from UM.Application import Application from UM.FileHandler.FileHandler import FileHandler #For typing. from UM.Logger import Logger from UM.Scene.SceneNode import SceneNode #For typing. @@ -42,7 +41,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): self._api_prefix = "" self._address = address self._properties = properties - self._user_agent = "%s/%s " % (Application.getInstance().getApplicationName(), Application.getInstance().getVersion()) + self._user_agent = "%s/%s " % (CuraApplication.getInstance().getApplicationName(), CuraApplication.getInstance().getVersion()) self._onFinishedCallbacks = {} # type: Dict[str, Callable[[QNetworkReply], None]] self._authentication_state = AuthState.NotAuthenticated @@ -261,7 +260,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): self._manager.authenticationRequired.connect(self._onAuthenticationRequired) if self._properties.get(b"temporary", b"false") != b"true": - Application.getInstance().getMachineManager().checkCorrectGroupName(self.getId(), self.name) + CuraApplication.getInstance().getMachineManager().checkCorrectGroupName(self.getId(), self.name) def _registerOnFinishedCallback(self, reply: QNetworkReply, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None: if on_finished is not None: From 4418cf3aac9045dfe4df19596b5fe35847db238d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 10:59:41 +0200 Subject: [PATCH 58/83] Prevent accessing private _instances variable We can get at this via the getProperty function. Contributes to issue CURA-5330. --- cura/Settings/PerObjectContainerStack.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/Settings/PerObjectContainerStack.py b/cura/Settings/PerObjectContainerStack.py index 6a4e638279..9f4576317f 100644 --- a/cura/Settings/PerObjectContainerStack.py +++ b/cura/Settings/PerObjectContainerStack.py @@ -22,7 +22,7 @@ class PerObjectContainerStack(CuraContainerStack): # Return the user defined value if present, otherwise, evaluate the value according to the default routine. if self.getContainer(0).hasProperty(key, property_name): - if self.getContainer(0)._instances[key].state == InstanceState.User: + if self.getContainer(0).getProperty(key, "state") == InstanceState.User: result = super().getProperty(key, property_name, context) context.popContainer() return result @@ -59,9 +59,9 @@ class PerObjectContainerStack(CuraContainerStack): super().setNextStack(stack) # trigger signal to re-evaluate all default settings - for key, instance in self.getContainer(0)._instances.items(): + for key in self.getContainer(0).getAllKeys(): # only evaluate default settings - if instance.state != InstanceState.Default: + if self.getContainer(0).getProperty(key, "state") != InstanceState.Default: continue self._collectPropertyChanges(key, "value") From c3d4d5eba7b97ad45d5699644b963f54a3552dd3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 11:02:15 +0200 Subject: [PATCH 59/83] Ignore MyPy errors caused by DepthFirstIterator not being detected as iterator MyPy is wrong in this case. Contributes to issue CURA-5330. --- cura/Settings/ExtruderManager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 84294240f5..c734123bb4 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.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 PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt. @@ -136,7 +136,7 @@ class ExtruderManager(QObject): selected_nodes = [] for node in Selection.getAllSelectedObjects(): if node.callDecoration("isGroup"): - for grouped_node in BreadthFirstIterator(node): + for grouped_node in BreadthFirstIterator(node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if grouped_node.callDecoration("isGroup"): continue @@ -265,7 +265,7 @@ class ExtruderManager(QObject): return [] # Get the extruders of all printable meshes in the scene - meshes = [node for node in DepthFirstIterator(scene_root) if isinstance(node, SceneNode) and node.isSelectable()] + meshes = [node for node in DepthFirstIterator(scene_root) if isinstance(node, SceneNode) and node.isSelectable()] #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. for mesh in meshes: extruder_stack_id = mesh.callDecoration("getActiveExtruder") if not extruder_stack_id: From 6e663ac6f57b01affc2d76fe44009235f54a2f49 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 11:15:31 +0200 Subject: [PATCH 60/83] Don't call getMachineManager on Uranium's application It is a function of CuraApplication. Contributes to issue CURA-5330. --- cura/Settings/ExtruderManager.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index c734123bb4..a773bccc7e 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -4,7 +4,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt. from UM.FlameProfiler import pyqtSlot -from UM.Application import Application # To get the global container stack to find the current machine. +import cura.CuraApplication #To get the global container stack to find the current machine. from UM.Logger import Logger from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.SceneNode import SceneNode @@ -36,7 +36,7 @@ class ExtruderManager(QObject): super().__init__(parent) - self._application = Application.getInstance() + self._application = cura.CuraApplication.CuraApplication.getInstance() self._extruder_trains = {} # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders. self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack @@ -477,7 +477,7 @@ class ExtruderManager(QObject): # If no extruder has the value, the list will contain the global value. @staticmethod def getExtruderValues(key): - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() result = [] for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): @@ -512,7 +512,7 @@ class ExtruderManager(QObject): # If no extruder has the value, the list will contain the global value. @staticmethod def getDefaultExtruderValues(key): - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = 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"] = { @@ -545,7 +545,7 @@ class ExtruderManager(QObject): ## Return the default extruder position from the machine manager @staticmethod def getDefaultExtruderPosition() -> str: - return Application.getInstance().getMachineManager().defaultExtruderPosition + return cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition ## Get all extruder values for a certain setting. # @@ -570,7 +570,7 @@ class ExtruderManager(QObject): @staticmethod def getExtruderValue(extruder_index, key): if extruder_index == -1: - extruder_index = int(Application.getInstance().getMachineManager().defaultExtruderPosition) + extruder_index = int(cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition) extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index) if extruder: @@ -579,7 +579,7 @@ class ExtruderManager(QObject): value = value(extruder) else: # Just a value from global. - value = Application.getInstance().getGlobalContainerStack().getProperty(key, "value") + value = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack().getProperty(key, "value") return value @@ -608,7 +608,7 @@ class ExtruderManager(QObject): if isinstance(value, SettingFunction): value = value(extruder, context = context) else: # Just a value from global. - value = Application.getInstance().getGlobalContainerStack().getProperty(key, "value", context = context) + value = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack().getProperty(key, "value", context = context) return value @@ -621,7 +621,7 @@ class ExtruderManager(QObject): # \return The effective value @staticmethod def getResolveOrValue(key): - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() resolved_value = global_stack.getProperty(key, "value") return resolved_value @@ -635,7 +635,7 @@ class ExtruderManager(QObject): # \return The effective value @staticmethod def getDefaultResolveOrValue(key): - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = 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"] = { From eda0d34fd9b7465a94c616c0f9904e19928c698e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 11:21:59 +0200 Subject: [PATCH 61/83] Call from QtApplication instead of Application Because we're using createQmlComponent which is only in QtApplication. Contributes to issue CURA-5330. --- cura/PrinterOutputDevice.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py index ae749ba0dc..67f02415e4 100644 --- a/cura/PrinterOutputDevice.py +++ b/cura/PrinterOutputDevice.py @@ -11,7 +11,7 @@ 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.Application import Application +from UM.Qt.QtApplication import QtApplication from enum import IntEnum # For the connection state tracking. from typing import Callable, List, Optional @@ -87,7 +87,7 @@ class PrinterOutputDevice(QObject, OutputDevice): self._address = "" #type: str self._connection_text = "" #type: str self.printersChanged.connect(self._onPrintersChanged) - Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._updateUniqueConfigurations) + QtApplication.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._updateUniqueConfigurations) @pyqtProperty(str, notify = connectionTextChanged) def address(self) -> str: @@ -160,14 +160,14 @@ class PrinterOutputDevice(QObject, OutputDevice): if not self._control_view_qml_path: return if self._control_item is None: - self._control_item = Application.getInstance().createQmlComponent(self._control_view_qml_path, {"OutputDevice": self}) + self._control_item = QtApplication.getInstance().createQmlComponent(self._control_view_qml_path, {"OutputDevice": self}) def _createMonitorViewFromQML(self) -> None: if not self._monitor_view_qml_path: return if self._monitor_item is None: - self._monitor_item = Application.getInstance().createQmlComponent(self._monitor_view_qml_path, {"OutputDevice": self}) + self._monitor_item = QtApplication.getInstance().createQmlComponent(self._monitor_view_qml_path, {"OutputDevice": self}) ## Attempt to establish connection def connect(self) -> None: From 105f6d4271e0421d44d7907681f3041443853ba3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 11:25:16 +0200 Subject: [PATCH 62/83] Fix type of self._shader MyPy was thinking that this variable has type None, which is nonsense because no variable should always have type None. Contributes to issue CURA-5330. --- cura/PickingPass.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cura/PickingPass.py b/cura/PickingPass.py index 40a0aaf949..7512b4fc3e 100644 --- a/cura/PickingPass.py +++ b/cura/PickingPass.py @@ -1,5 +1,8 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. + +from typing import Optional, TYPE_CHECKING + from UM.Application import Application from UM.Math.Vector import Vector from UM.Resources import Resources @@ -10,6 +13,8 @@ from UM.View.RenderBatch import RenderBatch from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator +if TYPE_CHECKING: + from UM.View.GL.ShaderProgram import ShaderProgram ## A RenderPass subclass that renders a the distance of selectable objects from the active camera to a texture. # The texture is used to map a 2d location (eg the mouse location) to a world space position @@ -21,7 +26,7 @@ class PickingPass(RenderPass): self._renderer = Application.getInstance().getRenderer() - self._shader = None + self._shader = None #type: Optional[ShaderProgram] self._scene = Application.getInstance().getController().getScene() def render(self) -> None: From 7d9816738323b7523222bc59c46922f73d9afa8b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 11:30:49 +0200 Subject: [PATCH 63/83] Ignore type errors arising from misinterpretation of DepthFirstIterator type MyPy requests that this needs to have a __next__ function, but actually Python calls the __iter__ function first and then calls the __next__ function on the result of that. MyPy is wrong here. Contributes to issue CURA-5330. --- cura/PickingPass.py | 2 +- cura/PreviewPass.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cura/PickingPass.py b/cura/PickingPass.py index 7512b4fc3e..8859bc9f5b 100644 --- a/cura/PickingPass.py +++ b/cura/PickingPass.py @@ -42,7 +42,7 @@ class PickingPass(RenderPass): batch = RenderBatch(self._shader) # Fill up the batch with objects that can be sliced. ` - for node in DepthFirstIterator(self._scene.getRoot()): + for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible(): batch.addItem(node.getWorldTransformation(), node.getMeshData()) diff --git a/cura/PreviewPass.py b/cura/PreviewPass.py index 22f50890a3..e9f6a3a548 100644 --- a/cura/PreviewPass.py +++ b/cura/PreviewPass.py @@ -1,5 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. + from UM.Application import Application from UM.Resources import Resources @@ -78,8 +79,8 @@ class PreviewPass(RenderPass): batch = RenderBatch(self._shader) batch_support_mesh = RenderBatch(self._support_mesh_shader) - # Fill up the batch with objects that can be sliced. ` - for node in DepthFirstIterator(self._scene.getRoot()): + # Fill up the batch with objects that can be sliced. + for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible(): per_mesh_stack = node.callDecoration("getStack") if node.callDecoration("isNonThumbnailVisibleMesh"): From ab32d64c01245f0b1d644746315691f5520d3cf1 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 11:34:35 +0200 Subject: [PATCH 64/83] Add types for ShaderPrograms Otherwise it'll think that this variable can only have None in it. Contributes to issue CURA-5330. --- cura/PreviewPass.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cura/PreviewPass.py b/cura/PreviewPass.py index e9f6a3a548..befb52ee5e 100644 --- a/cura/PreviewPass.py +++ b/cura/PreviewPass.py @@ -1,6 +1,8 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from typing import Optional, TYPE_CHECKING + from UM.Application import Application from UM.Resources import Resources @@ -11,7 +13,8 @@ from UM.View.RenderBatch import RenderBatch from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator -from typing import Optional +if TYPE_CHECKING: + from UM.View.GL.ShaderProgram import ShaderProgram MYPY = False if MYPY: @@ -41,9 +44,9 @@ class PreviewPass(RenderPass): self._renderer = Application.getInstance().getRenderer() - self._shader = None - self._non_printing_shader = None - self._support_mesh_shader = None + self._shader = None #type: Optional[ShaderProgram] + self._non_printing_shader = None #type: Optional[ShaderProgram] + self._support_mesh_shader = None #type: Optional[ShaderProgram] self._scene = Application.getInstance().getController().getScene() # Set the camera to be used by this render pass From b9727a33c6c4c4218891561184f3cff87c1cd5cb Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 11:38:11 +0200 Subject: [PATCH 65/83] Fix type of QtRenderer Because we're calling getWindowSize on it so it must be the QtRenderer, not the Renderer. Contributes to issue CURA-5330. --- cura/PickingPass.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/PickingPass.py b/cura/PickingPass.py index 8859bc9f5b..bfe465ff39 100644 --- a/cura/PickingPass.py +++ b/cura/PickingPass.py @@ -3,7 +3,7 @@ from typing import Optional, TYPE_CHECKING -from UM.Application import Application +from UM.Qt.QtApplication import QtApplication from UM.Math.Vector import Vector from UM.Resources import Resources @@ -24,10 +24,10 @@ class PickingPass(RenderPass): def __init__(self, width: int, height: int) -> None: super().__init__("picking", width, height) - self._renderer = Application.getInstance().getRenderer() + self._renderer = QtApplication.getInstance().getRenderer() self._shader = None #type: Optional[ShaderProgram] - self._scene = Application.getInstance().getController().getScene() + self._scene = QtApplication.getInstance().getController().getScene() def render(self) -> None: if not self._shader: From 75e5a185d959d83607c4e8512acba9a9c4c06865 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 13:07:55 +0200 Subject: [PATCH 66/83] Don't unnecessarily cast to a set The InstanceContainer version of getAllKeys was first casting to a list but I removed that, because everywhere where we were using it we were casting it directly to a set. Contributes to issue CURA-5330. --- cura/Settings/SimpleModeSettingsManager.py | 8 ++++---- plugins/3MFWriter/ThreeMFWriter.py | 2 +- plugins/CuraEngineBackend/StartSliceJob.py | 2 +- plugins/GCodeWriter/GCodeWriter.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cura/Settings/SimpleModeSettingsManager.py b/cura/Settings/SimpleModeSettingsManager.py index a337d8b04e..fce43243bd 100644 --- a/cura/Settings/SimpleModeSettingsManager.py +++ b/cura/Settings/SimpleModeSettingsManager.py @@ -39,12 +39,12 @@ class SimpleModeSettingsManager(QObject): global_stack = self._machine_manager.activeMachine # check user settings in the global stack - user_setting_keys.update(set(global_stack.userChanges.getAllKeys())) + user_setting_keys.update(global_stack.userChanges.getAllKeys()) # check user settings in the extruder stacks if global_stack.extruders: for extruder_stack in global_stack.extruders.values(): - user_setting_keys.update(set(extruder_stack.userChanges.getAllKeys())) + user_setting_keys.update(extruder_stack.userChanges.getAllKeys()) # remove settings that are visible in recommended (we don't show the reset button for those) for skip_key in self.__ignored_custom_setting_keys: @@ -70,12 +70,12 @@ class SimpleModeSettingsManager(QObject): global_stack = self._machine_manager.activeMachine # check quality changes settings in the global stack - quality_changes_keys.update(set(global_stack.qualityChanges.getAllKeys())) + quality_changes_keys.update(global_stack.qualityChanges.getAllKeys()) # check quality changes settings in the extruder stacks if global_stack.extruders: for extruder_stack in global_stack.extruders.values(): - quality_changes_keys.update(set(extruder_stack.qualityChanges.getAllKeys())) + quality_changes_keys.update(extruder_stack.qualityChanges.getAllKeys()) # check if the qualityChanges container is not empty (meaning it is a user created profile) has_quality_changes = len(quality_changes_keys) > 0 diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py index ff6333763a..8d54f475d6 100644 --- a/plugins/3MFWriter/ThreeMFWriter.py +++ b/plugins/3MFWriter/ThreeMFWriter.py @@ -91,7 +91,7 @@ class ThreeMFWriter(MeshWriter): # Handle per object settings (if any) stack = um_node.callDecoration("getStack") if stack is not None: - changed_setting_keys = set(stack.getTop().getAllKeys()) + changed_setting_keys = stack.getTop().getAllKeys() # Ensure that we save the extruder used for this object in a multi-extrusion setup if stack.getProperty("machine_extruder_count", "value") > 1: diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index b704ca0d2f..1a51f9201f 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -427,7 +427,7 @@ class StartSliceJob(Job): # Check all settings for relations, so we can also calculate the correct values for dependent settings. top_of_stack = stack.getTop() # Cache for efficiency. - changed_setting_keys = set(top_of_stack.getAllKeys()) + changed_setting_keys = top_of_stack.getAllKeys() # Add all relations to changed settings as well. for key in top_of_stack.getAllKeys(): diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index c01d48be4c..d334c66dbe 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -140,7 +140,7 @@ class GCodeWriter(MeshWriter): serialized = flat_global_container.serialize() data = {"global_quality": serialized} - all_setting_keys = set(flat_global_container.getAllKeys()) + all_setting_keys = flat_global_container.getAllKeys() for extruder in sorted(stack.extruders.values(), key = lambda k: int(k.getMetaDataEntry("position"))): extruder_quality = extruder.qualityChanges if extruder_quality.getId() == "empty_quality_changes": @@ -167,7 +167,7 @@ class GCodeWriter(MeshWriter): extruder_serialized = flat_extruder_quality.serialize() data.setdefault("extruder_quality", []).append(extruder_serialized) - all_setting_keys.update(set(flat_extruder_quality.getAllKeys())) + all_setting_keys.update(flat_extruder_quality.getAllKeys()) # Check if there is any profiles if not all_setting_keys: From fe43219e347fac95e872ec06ae684471d609feb7 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 13:14:56 +0200 Subject: [PATCH 67/83] Fix minor type mistakes Contributes to issue CURA-5330. --- cura/SingleInstance.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/SingleInstance.py b/cura/SingleInstance.py index b3d4f096fd..8109123df5 100644 --- a/cura/SingleInstance.py +++ b/cura/SingleInstance.py @@ -7,12 +7,12 @@ from typing import List, Optional from PyQt5.QtNetwork import QLocalServer, QLocalSocket -from UM.Application import Application #For typing. +from UM.Qt.QtApplication import QtApplication #For typing. from UM.Logger import Logger class SingleInstance: - def __init__(self, application: Application, files_to_open: Optional[List[str]]) -> None: + def __init__(self, application: QtApplication, files_to_open: Optional[List[str]]) -> None: self._application = application self._files_to_open = files_to_open @@ -69,7 +69,7 @@ class SingleInstance: def _onClientConnected(self) -> None: Logger.log("i", "New connection recevied on our single-instance server") - connection = None + connection = None #type: Optional[QLocalSocket] if self._single_instance_server: connection = self._single_instance_server.nextPendingConnection() From 1789a8f33e3f939612f061a340a50b0625883fd3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 13:20:20 +0200 Subject: [PATCH 68/83] Fix type of SelectionPass Contributes to issue CURA-5330. --- cura/CuraApplication.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 9db53c0836..3dc0ef02d7 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -2,7 +2,6 @@ # Cura is released under the terms of the LGPLv3 or higher. import copy -import json import os import sys import time @@ -14,7 +13,8 @@ from PyQt5.QtGui import QColor, QIcon from PyQt5.QtWidgets import QMessageBox from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType -from UM.Qt.QtApplication import QtApplication +from typing import cast, TYPE_CHECKING + from UM.Scene.SceneNode import SceneNode from UM.Scene.Camera import Camera from UM.Math.Vector import Vector @@ -28,6 +28,8 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Mesh.ReadMeshJob import ReadMeshJob from UM.Logger import Logger from UM.Preferences import Preferences +from UM.Qt.QtApplication import QtApplication #The class we're inheriting from. +from UM.View.SelectionPass import SelectionPass #For typing. from UM.Scene.Selection import Selection from UM.Scene.GroupDecorator import GroupDecorator from UM.Settings.ContainerStack import ContainerStack @@ -109,8 +111,7 @@ from UM.FlameProfiler import pyqtSlot numpy.seterr(all = "ignore") -MYPY = False -if not MYPY: +if TYPE_CHECKING: try: from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode except ImportError: @@ -1719,7 +1720,7 @@ class CuraApplication(QtApplication): def _onContextMenuRequested(self, x: float, y: float) -> None: # Ensure we select the object if we request a context menu over an object without having a selection. if not Selection.hasSelection(): - node = self.getController().getScene().findObject(self.getRenderer().getRenderPass("selection").getIdAtPosition(x, y)) + node = cast(SelectionPass, self.getController().getScene().findObject(self.getRenderer().getRenderPass("selection"))).getIdAtPosition(x, y) if node: while(node.getParent() and node.getParent().callDecoration("isGroup")): node = node.getParent() From 7a33a8f2121c240b26da8668290e035c90401789 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 13:28:55 +0200 Subject: [PATCH 69/83] Always import CuraVersion I don't know why there was this check around it because it is always necessary to start the application. Contributes to issue CURA-5330. --- cura/CuraApplication.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 3dc0ef02d7..c13239d9dd 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -111,13 +111,12 @@ from UM.FlameProfiler import pyqtSlot numpy.seterr(all = "ignore") -if TYPE_CHECKING: - try: - from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode - except ImportError: - CuraVersion = "master" # [CodeStyle: Reflecting imported value] - CuraBuildType = "" - CuraDebugMode = False +try: + from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode +except ImportError: + CuraVersion = "master" # [CodeStyle: Reflecting imported value] + CuraBuildType = "" + CuraDebugMode = False class CuraApplication(QtApplication): From a021820031ee0764c749ebf8a54261fbfdd50c3b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 13:34:39 +0200 Subject: [PATCH 70/83] Fix call to getIdAtPosition I fixed the typing in the wrong way here. Sorry. Contributes to issue CURA-5330. --- cura/CuraApplication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index c13239d9dd..3acc2af8f0 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1719,7 +1719,7 @@ class CuraApplication(QtApplication): def _onContextMenuRequested(self, x: float, y: float) -> None: # Ensure we select the object if we request a context menu over an object without having a selection. if not Selection.hasSelection(): - node = cast(SelectionPass, self.getController().getScene().findObject(self.getRenderer().getRenderPass("selection"))).getIdAtPosition(x, y) + node = self.getController().getScene().findObject(cast(SelectionPass, self.getRenderer().getRenderPass("selection")).getIdAtPosition(x, y)) if node: while(node.getParent() and node.getParent().callDecoration("isGroup")): node = node.getParent() From 8256788ed89439e22990e123b99fa251e0fe17cd Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 13:40:43 +0200 Subject: [PATCH 71/83] Use CuraApplication instead of Application Because we need to call getExtruderManager which is only in CuraApplication. Contributes to issue CURA-5330. --- .../CuraEngineBackend/CuraEngineBackend.py | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 3e66edc203..21b23ee617 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -1,8 +1,7 @@ -# 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.Backend.Backend import Backend, BackendState -from UM.Application import Application from UM.Scene.SceneNode import SceneNode from UM.Signal import Signal from UM.Logger import Logger @@ -15,6 +14,7 @@ from UM.Qt.Duration import DurationFormat from PyQt5.QtCore import QObject, pyqtSlot from collections import defaultdict +from cura.CuraApplication import CuraApplication from cura.Settings.ExtruderManager import ExtruderManager from . import ProcessSlicedLayersJob from . import StartSliceJob @@ -48,8 +48,8 @@ class CuraEngineBackend(QObject, Backend): if Platform.isWindows(): executable_name += ".exe" default_engine_location = executable_name - if os.path.exists(os.path.join(Application.getInstallPrefix(), "bin", executable_name)): - default_engine_location = os.path.join(Application.getInstallPrefix(), "bin", executable_name) + if os.path.exists(os.path.join(CuraApplication.getInstallPrefix(), "bin", executable_name)): + default_engine_location = os.path.join(CuraApplication.getInstallPrefix(), "bin", executable_name) if hasattr(sys, "frozen"): default_engine_location = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), executable_name) if Platform.isLinux() and not default_engine_location: @@ -61,7 +61,7 @@ class CuraEngineBackend(QObject, Backend): default_engine_location = execpath break - self._application = Application.getInstance() + self._application = CuraApplication.getInstance() self._multi_build_plate_model = None self._machine_error_checker = None @@ -71,7 +71,7 @@ class CuraEngineBackend(QObject, Backend): Logger.log("i", "Found CuraEngine at: %s", default_engine_location) default_engine_location = os.path.abspath(default_engine_location) - Application.getInstance().getPreferences().addPreference("backend/location", default_engine_location) + self._application.getPreferences().addPreference("backend/location", default_engine_location) # Workaround to disable layer view processing if layer view is not active. self._layer_view_active = False @@ -120,7 +120,7 @@ class CuraEngineBackend(QObject, Backend): self._slice_start_time = None self._is_disabled = False - Application.getInstance().getPreferences().addPreference("general/auto_slice", False) + self._application.getPreferences().addPreference("general/auto_slice", False) self._use_timer = False # When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired. @@ -130,7 +130,7 @@ class CuraEngineBackend(QObject, Backend): self._change_timer.setSingleShot(True) self._change_timer.setInterval(500) self.determineAutoSlicing() - Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged) + self._application.getPreferences().preferenceChanged.connect(self._onPreferencesChanged) self._application.initializationFinished.connect(self.initialize) @@ -169,7 +169,7 @@ class CuraEngineBackend(QObject, Backend): # \return list of commands and args / parameters. def getEngineCommand(self): json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json") - return [Application.getInstance().getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""] + return [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""] ## Emitted when we get a message containing print duration and material amount. # This also implies the slicing has finished. @@ -221,7 +221,7 @@ class CuraEngineBackend(QObject, Backend): self._scene.gcode_dict = {} # see if we really have to slice - active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate + active_build_plate = self._application.getMultiBuildPlateModel().activeBuildPlate build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0) Logger.log("d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced) num_objects = self._numObjectsPerBuildPlate() @@ -236,8 +236,8 @@ class CuraEngineBackend(QObject, Backend): self.slice() return - if Application.getInstance().getPrintInformation() and build_plate_to_be_sliced == active_build_plate: - Application.getInstance().getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced) + if self._application.getPrintInformation() and build_plate_to_be_sliced == active_build_plate: + self._application.getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced) if self._process is None: self._createSocket() @@ -274,7 +274,7 @@ class CuraEngineBackend(QObject, Backend): self.processingProgress.emit(0) Logger.log("d", "Attempting to kill the engine process") - if Application.getInstance().getUseExternalBackend(): + if self._application.getUseExternalBackend(): return if self._process is not None: @@ -309,7 +309,7 @@ class CuraEngineBackend(QObject, Backend): return if job.getResult() == StartSliceJob.StartJobResult.MaterialIncompatible: - if Application.getInstance().platformActivity: + if self._application.platformActivity: self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current material as it is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Unable to slice")) self._error_message.show() @@ -320,7 +320,7 @@ class CuraEngineBackend(QObject, Backend): return if job.getResult() == StartSliceJob.StartJobResult.SettingError: - if Application.getInstance().platformActivity: + if self._application.platformActivity: extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())) error_keys = [] for extruder in extruders: @@ -350,7 +350,7 @@ class CuraEngineBackend(QObject, Backend): elif job.getResult() == StartSliceJob.StartJobResult.ObjectSettingError: errors = {} - for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()): + for node in DepthFirstIterator(self._application.getController().getScene().getRoot()): stack = node.callDecoration("getStack") if not stack: continue @@ -370,7 +370,7 @@ class CuraEngineBackend(QObject, Backend): return if job.getResult() == StartSliceJob.StartJobResult.BuildPlateError: - if Application.getInstance().platformActivity: + if self._application.platformActivity: self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid."), title = catalog.i18nc("@info:title", "Unable to slice")) self._error_message.show() @@ -388,7 +388,7 @@ class CuraEngineBackend(QObject, Backend): return if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice: - if Application.getInstance().platformActivity: + if self._application.platformActivity: self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."), title = catalog.i18nc("@info:title", "Unable to slice")) self._error_message.show() @@ -415,7 +415,7 @@ class CuraEngineBackend(QObject, Backend): enable_timer = True self._is_disabled = False - if not Application.getInstance().getPreferences().getValue("general/auto_slice"): + if not self._application.getPreferences().getValue("general/auto_slice"): enable_timer = False for node in DepthFirstIterator(self._scene.getRoot()): if node.callDecoration("isBlockSlicing"): @@ -507,7 +507,7 @@ class CuraEngineBackend(QObject, Backend): # # \param error The exception that occurred. def _onSocketError(self, error): - if Application.getInstance().isShuttingDown(): + if self._application.isShuttingDown(): return super()._onSocketError(error) @@ -528,7 +528,7 @@ class CuraEngineBackend(QObject, Backend): node.getParent().removeChild(node) def markSliceAll(self): - for build_plate_number in range(Application.getInstance().getMultiBuildPlateModel().maxBuildPlate + 1): + for build_plate_number in range(self._application.getMultiBuildPlateModel().maxBuildPlate + 1): if build_plate_number not in self._build_plates_to_be_sliced: self._build_plates_to_be_sliced.append(build_plate_number) @@ -606,11 +606,11 @@ class CuraEngineBackend(QObject, Backend): gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] for index, line in enumerate(gcode_list): - replaced = line.replace("{print_time}", str(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601))) - replaced = replaced.replace("{filament_amount}", str(Application.getInstance().getPrintInformation().materialLengths)) - replaced = replaced.replace("{filament_weight}", str(Application.getInstance().getPrintInformation().materialWeights)) - replaced = replaced.replace("{filament_cost}", str(Application.getInstance().getPrintInformation().materialCosts)) - replaced = replaced.replace("{jobname}", str(Application.getInstance().getPrintInformation().jobName)) + replaced = line.replace("{print_time}", str(self._application.getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601))) + replaced = replaced.replace("{filament_amount}", str(self._application.getPrintInformation().materialLengths)) + replaced = replaced.replace("{filament_weight}", str(self._application.getPrintInformation().materialWeights)) + replaced = replaced.replace("{filament_cost}", str(self._application.getPrintInformation().materialCosts)) + replaced = replaced.replace("{jobname}", str(self._application.getPrintInformation().jobName)) gcode_list[index] = replaced @@ -619,7 +619,7 @@ class CuraEngineBackend(QObject, Backend): Logger.log("d", "Number of models per buildplate: %s", dict(self._numObjectsPerBuildPlate())) # See if we need to process the sliced layers job. - active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate + active_build_plate = self._application.getMultiBuildPlateModel().activeBuildPlate if ( self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()) and @@ -745,10 +745,9 @@ class CuraEngineBackend(QObject, Backend): ## Called when the user changes the active view mode. def _onActiveViewChanged(self): - application = Application.getInstance() - view = application.getController().getActiveView() + view = self._application.getController().getActiveView() if view: - active_build_plate = application.getMultiBuildPlateModel().activeBuildPlate + active_build_plate = self._application.getMultiBuildPlateModel().activeBuildPlate if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet. self._layer_view_active = True # There is data and we're not slicing at the moment @@ -783,7 +782,7 @@ class CuraEngineBackend(QObject, Backend): extruder.propertyChanged.disconnect(self._onSettingChanged) extruder.containersChanged.disconnect(self._onChanged) - self._global_container_stack = Application.getInstance().getGlobalContainerStack() + self._global_container_stack = self._application.getGlobalContainerStack() if self._global_container_stack: self._global_container_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed. From 4c3d28709be900dde9a10568a4b297cc9224affe Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 14:07:33 +0200 Subject: [PATCH 72/83] Add typing for all functions and fields Lots of typing so that MyPy can then check whether the functions are safe to call. Contributes to issue CURA-5330. --- .../CuraEngineBackend/CuraEngineBackend.py | 163 +++++++++--------- 1 file changed, 85 insertions(+), 78 deletions(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 21b23ee617..188de1b388 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -1,6 +1,13 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from collections import defaultdict +import os +from PyQt5.QtCore import QObject, QTimer, pyqtSlot +import sys +from time import time +from typing import Any, Dict, List, Optional, Set, TYPE_CHECKING + from UM.Backend.Backend import Backend, BackendState from UM.Scene.SceneNode import SceneNode from UM.Signal import Signal @@ -9,30 +16,29 @@ from UM.Message import Message from UM.PluginRegistry import PluginRegistry from UM.Resources import Resources from UM.Platform import Platform -from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Qt.Duration import DurationFormat -from PyQt5.QtCore import QObject, pyqtSlot +from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator +from UM.Settings.SettingInstance import SettingInstance #For typing. +from UM.Tool import Tool #For typing. -from collections import defaultdict from cura.CuraApplication import CuraApplication from cura.Settings.ExtruderManager import ExtruderManager from . import ProcessSlicedLayersJob from . import StartSliceJob -import os -import sys -from time import time - -from PyQt5.QtCore import QTimer - import Arcus +if TYPE_CHECKING: + from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel + from cura.Machines.MachineErrorChecker import MachineErrorChecker + from UM.Scene.Scene import Scene + from UM.Settings.ContainerStack import ContainerStack + from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") class CuraEngineBackend(QObject, Backend): - backendError = Signal() ## Starts the back-end plug-in. @@ -40,8 +46,9 @@ class CuraEngineBackend(QObject, Backend): # This registers all the signal listeners and prepares for communication # with the back-end in general. # CuraEngineBackend is exposed to qml as well. - def __init__(self, parent = None): - super().__init__(parent = parent) + def __init__(self, parent = None) -> None: + super(QObject, self).__init__(parent = parent) + super(Backend, self).__init__() # Find out where the engine is located, and how it is called. # This depends on how Cura is packaged and which OS we are running on. executable_name = "CuraEngine" @@ -61,9 +68,9 @@ class CuraEngineBackend(QObject, Backend): default_engine_location = execpath break - self._application = CuraApplication.getInstance() - self._multi_build_plate_model = None - self._machine_error_checker = None + self._application = CuraApplication.getInstance() #type: CuraApplication + self._multi_build_plate_model = None #type: MultiBuildPlateModel + self._machine_error_checker = None #type: MachineErrorChecker if not default_engine_location: raise EnvironmentError("Could not find CuraEngine") @@ -74,13 +81,13 @@ class CuraEngineBackend(QObject, Backend): self._application.getPreferences().addPreference("backend/location", default_engine_location) # Workaround to disable layer view processing if layer view is not active. - self._layer_view_active = False + self._layer_view_active = False #type: bool self._onActiveViewChanged() - self._stored_layer_data = [] - self._stored_optimized_layer_data = {} # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob + self._stored_layer_data = [] #type: List[Arcus.PythonMessage] + self._stored_optimized_layer_data = {} #type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob - self._scene = self._application.getController().getScene() + self._scene = self._application.getController().getScene() #type: Scene self._scene.sceneChanged.connect(self._onSceneChanged) # Triggers for auto-slicing. Auto-slicing is triggered as follows: @@ -91,7 +98,7 @@ class CuraEngineBackend(QObject, Backend): # If there is an error check, stop the auto-slicing timer, and only wait for the error check to be finished # to start the auto-slicing timer again. # - self._global_container_stack = None + self._global_container_stack = None #type: Optional[ContainerStack] # Listeners for receiving messages from the back-end. self._message_handlers["cura.proto.Layer"] = self._onLayerMessage @@ -102,31 +109,31 @@ class CuraEngineBackend(QObject, Backend): self._message_handlers["cura.proto.PrintTimeMaterialEstimates"] = self._onPrintTimeMaterialEstimates self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage - self._start_slice_job = None - self._start_slice_job_build_plate = None - self._slicing = False # Are we currently slicing? - self._restart = False # Back-end is currently restarting? - self._tool_active = False # If a tool is active, some tasks do not have to do anything - self._always_restart = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness. - self._process_layers_job = None # The currently active job to process layers, or None if it is not processing layers. - self._build_plates_to_be_sliced = [] # what needs slicing? - self._engine_is_fresh = True # Is the newly started engine used before or not? + self._start_slice_job = None #type: Optional[StartSliceJob] + self._start_slice_job_build_plate = None #type: Optional[StartSliceJob] + self._slicing = False #type: bool # Are we currently slicing? + self._restart = False #type: bool # Back-end is currently restarting? + self._tool_active = False #type: bool # If a tool is active, some tasks do not have to do anything + self._always_restart = True #type: bool # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness. + self._process_layers_job = None #type: Optional[ProcessSlicedLayersJob] # The currently active job to process layers, or None if it is not processing layers. + self._build_plates_to_be_sliced = [] #type: List[int] # what needs slicing? + self._engine_is_fresh = True #type: bool # Is the newly started engine used before or not? - self._backend_log_max_lines = 20000 # Maximum number of lines to buffer - self._error_message = None # Pop-up message that shows errors. - self._last_num_objects = defaultdict(int) # Count number of objects to see if there is something changed - self._postponed_scene_change_sources = [] # scene change is postponed (by a tool) + self._backend_log_max_lines = 20000 #type: int # Maximum number of lines to buffer + self._error_message = None #type: Message # Pop-up message that shows errors. + self._last_num_objects = defaultdict(int) #type: Dict[int, int] # Count number of objects to see if there is something changed + self._postponed_scene_change_sources = [] #type: List[SceneNode] # scene change is postponed (by a tool) - self._slice_start_time = None - self._is_disabled = False + self._slice_start_time = None #type: Optional[float] + self._is_disabled = False #type: bool self._application.getPreferences().addPreference("general/auto_slice", False) - self._use_timer = False + self._use_timer = False #type: bool # When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired. # This timer will group them up, and only slice for the last setting changed signal. # TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction. - self._change_timer = QTimer() + self._change_timer = QTimer() #type: QTimer self._change_timer.setSingleShot(True) self._change_timer.setInterval(500) self.determineAutoSlicing() @@ -134,7 +141,7 @@ class CuraEngineBackend(QObject, Backend): self._application.initializationFinished.connect(self.initialize) - def initialize(self): + def initialize(self) -> None: self._multi_build_plate_model = self._application.getMultiBuildPlateModel() self._application.getController().activeViewChanged.connect(self._onActiveViewChanged) @@ -160,14 +167,14 @@ class CuraEngineBackend(QObject, Backend): # # This function should terminate the engine process. # Called when closing the application. - def close(self): + def close(self) -> None: # Terminate CuraEngine if it is still running at this point self._terminate() ## Get the command that is used to call the engine. # This is useful for debugging and used to actually start the engine. # \return list of commands and args / parameters. - def getEngineCommand(self): + def getEngineCommand(self) -> List[str]: json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json") return [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""] @@ -184,7 +191,7 @@ class CuraEngineBackend(QObject, Backend): slicingCancelled = Signal() @pyqtSlot() - def stopSlicing(self): + def stopSlicing(self) -> None: self.backendStateChange.emit(BackendState.NotStarted) if self._slicing: # We were already slicing. Stop the old job. self._terminate() @@ -200,12 +207,12 @@ class CuraEngineBackend(QObject, Backend): ## Manually triggers a reslice @pyqtSlot() - def forceSlice(self): + def forceSlice(self) -> None: self.markSliceAll() self.slice() ## Perform a slice of the scene. - def slice(self): + def slice(self) -> None: Logger.log("d", "Starting to slice...") self._slice_start_time = time() if not self._build_plates_to_be_sliced: @@ -262,7 +269,7 @@ class CuraEngineBackend(QObject, Backend): ## Terminate the engine process. # Start the engine process by calling _createSocket() - def _terminate(self): + def _terminate(self) -> None: self._slicing = False self._stored_layer_data = [] if self._start_slice_job_build_plate in self._stored_optimized_layer_data: @@ -295,7 +302,7 @@ class CuraEngineBackend(QObject, Backend): # bootstrapping of a slice job. # # \param job The start slice job that was just finished. - def _onStartSliceCompleted(self, job): + def _onStartSliceCompleted(self, job: StartSliceJob) -> None: if self._error_message: self._error_message.hide() @@ -411,7 +418,7 @@ class CuraEngineBackend(QObject, Backend): # It disables when # - preference auto slice is off # - decorator isBlockSlicing is found (used in g-code reader) - def determineAutoSlicing(self): + def determineAutoSlicing(self) -> bool: enable_timer = True self._is_disabled = False @@ -437,7 +444,7 @@ class CuraEngineBackend(QObject, Backend): return False ## Return a dict with number of objects per build plate - def _numObjectsPerBuildPlate(self): + def _numObjectsPerBuildPlate(self) -> Dict[int, int]: num_objects = defaultdict(int) for node in DepthFirstIterator(self._scene.getRoot()): # Only count sliceable objects @@ -451,7 +458,7 @@ class CuraEngineBackend(QObject, Backend): # This should start a slice if the scene is now ready to slice. # # \param source The scene node that was changed. - def _onSceneChanged(self, source): + def _onSceneChanged(self, source: SceneNode) -> None: if not isinstance(source, SceneNode): return @@ -506,7 +513,7 @@ class CuraEngineBackend(QObject, Backend): ## Called when an error occurs in the socket connection towards the engine. # # \param error The exception that occurred. - def _onSocketError(self, error): + def _onSocketError(self, error: Arcus.Error) -> None: if self._application.isShuttingDown(): return @@ -521,19 +528,19 @@ class CuraEngineBackend(QObject, Backend): Logger.log("w", "A socket error caused the connection to be reset") ## Remove old layer data (if any) - def _clearLayerData(self, build_plate_numbers = set()): + def _clearLayerData(self, build_plate_numbers: Set = None) -> None: for node in DepthFirstIterator(self._scene.getRoot()): if node.callDecoration("getLayerData"): if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers: node.getParent().removeChild(node) - def markSliceAll(self): + def markSliceAll(self) -> None: for build_plate_number in range(self._application.getMultiBuildPlateModel().maxBuildPlate + 1): if build_plate_number not in self._build_plates_to_be_sliced: self._build_plates_to_be_sliced.append(build_plate_number) ## Convenient function: mark everything to slice, emit state and clear layer data - def needsSlicing(self): + def needsSlicing(self) -> None: self.stopSlicing() self.markSliceAll() self.processingProgress.emit(0.0) @@ -545,7 +552,7 @@ class CuraEngineBackend(QObject, Backend): ## A setting has changed, so check if we must reslice. # \param instance The setting instance that has changed. # \param property The property of the setting instance that has changed. - def _onSettingChanged(self, instance, property): + def _onSettingChanged(self, instance: SettingInstance, property: str) -> None: if property == "value": # Only reslice if the value has changed. self.needsSlicing() self._onChanged() @@ -554,7 +561,7 @@ class CuraEngineBackend(QObject, Backend): if self._use_timer: self._change_timer.stop() - def _onStackErrorCheckFinished(self): + def _onStackErrorCheckFinished(self) -> None: self.determineAutoSlicing() if self._is_disabled: return @@ -566,13 +573,13 @@ class CuraEngineBackend(QObject, Backend): ## Called when a sliced layer data message is received from the engine. # # \param message The protobuf message containing sliced layer data. - def _onLayerMessage(self, message): + def _onLayerMessage(self, message: Arcus.PythonMessage) -> None: self._stored_layer_data.append(message) ## Called when an optimized sliced layer data message is received from the engine. # # \param message The protobuf message containing sliced layer data. - def _onOptimizedLayerMessage(self, message): + def _onOptimizedLayerMessage(self, message: Arcus.PythonMessage) -> None: if self._start_slice_job_build_plate not in self._stored_optimized_layer_data: self._stored_optimized_layer_data[self._start_slice_job_build_plate] = [] self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message) @@ -580,11 +587,11 @@ class CuraEngineBackend(QObject, Backend): ## Called when a progress message is received from the engine. # # \param message The protobuf message containing the slicing progress. - def _onProgressMessage(self, message): + def _onProgressMessage(self, message: Arcus.PythonMessage) -> None: self.processingProgress.emit(message.amount) self.backendStateChange.emit(BackendState.Processing) - def _invokeSlice(self): + def _invokeSlice(self) -> None: if self._use_timer: # if the error check is scheduled, wait for the error check finish signal to trigger auto-slice, # otherwise business as usual @@ -600,7 +607,7 @@ class CuraEngineBackend(QObject, Backend): ## Called when the engine sends a message that slicing is finished. # # \param message The protobuf message signalling that slicing is finished. - def _onSlicingFinishedMessage(self, message): + def _onSlicingFinishedMessage(self, message: Arcus.PythonMessage) -> None: self.backendStateChange.emit(BackendState.Done) self.processingProgress.emit(1.0) @@ -641,25 +648,25 @@ class CuraEngineBackend(QObject, Backend): ## Called when a g-code message is received from the engine. # # \param message The protobuf message containing g-code, encoded as UTF-8. - def _onGCodeLayerMessage(self, message): + def _onGCodeLayerMessage(self, message: Arcus.PythonMessage) -> None: self._scene.gcode_dict[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace")) ## Called when a g-code prefix message is received from the engine. # # \param message The protobuf message containing the g-code prefix, # encoded as UTF-8. - def _onGCodePrefixMessage(self, message): + def _onGCodePrefixMessage(self, message: Arcus.PythonMessage) -> None: self._scene.gcode_dict[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace")) ## Creates a new socket connection. - def _createSocket(self): + def _createSocket(self) -> None: super()._createSocket(os.path.abspath(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "Cura.proto"))) self._engine_is_fresh = True ## Called when anything has changed to the stuff that needs to be sliced. # # This indicates that we should probably re-slice soon. - def _onChanged(self, *args, **kwargs): + def _onChanged(self, *args: Any, **kwargs: Any) -> None: self.needsSlicing() if self._use_timer: # if the error check is scheduled, wait for the error check finish signal to trigger auto-slice, @@ -677,7 +684,7 @@ class CuraEngineBackend(QObject, Backend): # # \param message The protobuf message containing the print time per feature and # material amount per extruder - def _onPrintTimeMaterialEstimates(self, message): + def _onPrintTimeMaterialEstimates(self, message: Arcus.PythonMessage) -> None: material_amounts = [] for index in range(message.repeatedMessageCount("materialEstimates")): material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount) @@ -688,7 +695,7 @@ class CuraEngineBackend(QObject, Backend): ## Called for parsing message to retrieve estimated time per feature # # \param message The protobuf message containing the print time per feature - def _parseMessagePrintTimes(self, message): + def _parseMessagePrintTimes(self, message: Arcus.PythonMessage) -> Dict[str, float]: result = { "inset_0": message.time_inset_0, "inset_x": message.time_inset_x, @@ -705,7 +712,7 @@ class CuraEngineBackend(QObject, Backend): return result ## Called when the back-end connects to the front-end. - def _onBackendConnected(self): + def _onBackendConnected(self) -> None: if self._restart: self._restart = False self._onChanged() @@ -716,7 +723,7 @@ class CuraEngineBackend(QObject, Backend): # continuously slicing while the user is dragging some tool handle. # # \param tool The tool that the user is using. - def _onToolOperationStarted(self, tool): + def _onToolOperationStarted(self, tool: Tool) -> None: self._tool_active = True # Do not react on scene change self.disableTimer() # Restart engine as soon as possible, we know we want to slice afterwards @@ -729,7 +736,7 @@ class CuraEngineBackend(QObject, Backend): # This indicates that we can safely start slicing again. # # \param tool The tool that the user was using. - def _onToolOperationStopped(self, tool): + def _onToolOperationStopped(self, tool: Tool) -> None: self._tool_active = False # React on scene change again self.determineAutoSlicing() # Switch timer on if appropriate # Process all the postponed scene changes @@ -737,14 +744,14 @@ class CuraEngineBackend(QObject, Backend): source = self._postponed_scene_change_sources.pop(0) self._onSceneChanged(source) - def _startProcessSlicedLayersJob(self, build_plate_number): + def _startProcessSlicedLayersJob(self, build_plate_number: int) -> None: self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data[build_plate_number]) self._process_layers_job.setBuildPlate(build_plate_number) self._process_layers_job.finished.connect(self._onProcessLayersFinished) self._process_layers_job.start() ## Called when the user changes the active view mode. - def _onActiveViewChanged(self): + def _onActiveViewChanged(self) -> None: view = self._application.getController().getActiveView() if view: active_build_plate = self._application.getMultiBuildPlateModel().activeBuildPlate @@ -765,14 +772,14 @@ class CuraEngineBackend(QObject, Backend): ## Called when the back-end self-terminates. # # We should reset our state and start listening for new connections. - def _onBackendQuit(self): + def _onBackendQuit(self) -> None: if not self._restart: if self._process: Logger.log("d", "Backend quit with return code %s. Resetting process and socket.", self._process.wait()) self._process = None ## Called when the global container stack changes - def _onGlobalStackChanged(self): + def _onGlobalStackChanged(self) -> None: if self._global_container_stack: self._global_container_stack.propertyChanged.disconnect(self._onSettingChanged) self._global_container_stack.containersChanged.disconnect(self._onChanged) @@ -793,26 +800,26 @@ class CuraEngineBackend(QObject, Backend): extruder.containersChanged.connect(self._onChanged) self._onChanged() - def _onProcessLayersFinished(self, job): + def _onProcessLayersFinished(self, job: ProcessSlicedLayersJob) -> None: del self._stored_optimized_layer_data[job.getBuildPlate()] self._process_layers_job = None Logger.log("d", "See if there is more to slice(2)...") self._invokeSlice() ## Connect slice function to timer. - def enableTimer(self): + def enableTimer(self) -> None: if not self._use_timer: self._change_timer.timeout.connect(self.slice) self._use_timer = True ## Disconnect slice function from timer. # This means that slicing will not be triggered automatically - def disableTimer(self): + def disableTimer(self) -> None: if self._use_timer: self._use_timer = False self._change_timer.timeout.disconnect(self.slice) - def _onPreferencesChanged(self, preference): + def _onPreferencesChanged(self, preference: str) -> None: if preference != "general/auto_slice": return auto_slice = self.determineAutoSlicing() @@ -820,11 +827,11 @@ class CuraEngineBackend(QObject, Backend): self._change_timer.start() ## Tickle the backend so in case of auto slicing, it starts the timer. - def tickle(self): + def tickle(self) -> None: if self._use_timer: self._change_timer.start() - def _extruderChanged(self): + def _extruderChanged(self) -> None: for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1): if build_plate_number not in self._build_plates_to_be_sliced: self._build_plates_to_be_sliced.append(build_plate_number) From 0d52b03716b0c8dba1b8b491f0dc1f6b7de9a586 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 14:25:25 +0200 Subject: [PATCH 73/83] Fix the wrong types in this class These mistakes were found by MyPy. Contributes to issue CURA-5330. --- .../CuraEngineBackend/CuraEngineBackend.py | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 188de1b388..23175d24a7 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -6,7 +6,7 @@ import os from PyQt5.QtCore import QObject, QTimer, pyqtSlot import sys from time import time -from typing import Any, Dict, List, Optional, Set, TYPE_CHECKING +from typing import Any, cast, Dict, List, Optional, Set, TYPE_CHECKING from UM.Backend.Backend import Backend, BackendState from UM.Scene.SceneNode import SceneNode @@ -18,13 +18,14 @@ from UM.Resources import Resources from UM.Platform import Platform from UM.Qt.Duration import DurationFormat from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator +from UM.Settings.DefinitionContainerInterface import DefinitionContainerInterface from UM.Settings.SettingInstance import SettingInstance #For typing. from UM.Tool import Tool #For typing. from cura.CuraApplication import CuraApplication from cura.Settings.ExtruderManager import ExtruderManager -from . import ProcessSlicedLayersJob -from . import StartSliceJob +from .ProcessSlicedLayersJob import ProcessSlicedLayersJob +from .StartSliceJob import StartSliceJob, StartJobResult import Arcus @@ -46,9 +47,8 @@ class CuraEngineBackend(QObject, Backend): # This registers all the signal listeners and prepares for communication # with the back-end in general. # CuraEngineBackend is exposed to qml as well. - def __init__(self, parent = None) -> None: - super(QObject, self).__init__(parent = parent) - super(Backend, self).__init__() + def __init__(self) -> None: + super().__init__() # Find out where the engine is located, and how it is called. # This depends on how Cura is packaged and which OS we are running on. executable_name = "CuraEngine" @@ -110,7 +110,7 @@ class CuraEngineBackend(QObject, Backend): self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage self._start_slice_job = None #type: Optional[StartSliceJob] - self._start_slice_job_build_plate = None #type: Optional[StartSliceJob] + self._start_slice_job_build_plate = None #type: Optional[int] self._slicing = False #type: bool # Are we currently slicing? self._restart = False #type: bool # Back-end is currently restarting? self._tool_active = False #type: bool # If a tool is active, some tasks do not have to do anything @@ -197,7 +197,7 @@ class CuraEngineBackend(QObject, Backend): self._terminate() self._createSocket() - if self._process_layers_job: # We were processing layers. Stop that, the layers are going to change soon. + if self._process_layers_job is not None: # We were processing layers. Stop that, the layers are going to change soon. Logger.log("d", "Aborting process layers job...") self._process_layers_job.abort() self._process_layers_job = None @@ -225,7 +225,7 @@ class CuraEngineBackend(QObject, Backend): return if not hasattr(self._scene, "gcode_dict"): - self._scene.gcode_dict = {} + self._scene.gcode_dict = {} #type: ignore #Because we are creating the missing attribute here. # see if we really have to slice active_build_plate = self._application.getMultiBuildPlateModel().activeBuildPlate @@ -237,7 +237,7 @@ class CuraEngineBackend(QObject, Backend): self._stored_optimized_layer_data[build_plate_to_be_sliced] = [] if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0: - self._scene.gcode_dict[build_plate_to_be_sliced] = [] + self._scene.gcode_dict[build_plate_to_be_sliced] = [] #type: ignore #Because we created this attribute above. Logger.log("d", "Build plate %s has no objects to be sliced, skipping", build_plate_to_be_sliced) if self._build_plates_to_be_sliced: self.slice() @@ -254,14 +254,14 @@ class CuraEngineBackend(QObject, Backend): self.processingProgress.emit(0.0) self.backendStateChange.emit(BackendState.NotStarted) - self._scene.gcode_dict[build_plate_to_be_sliced] = [] #[] indexed by build plate number + self._scene.gcode_dict[build_plate_to_be_sliced] = [] #type: ignore #[] indexed by build plate number self._slicing = True self.slicingStarted.emit() self.determineAutoSlicing() # Switch timer on or off if appropriate slice_message = self._socket.createMessage("cura.proto.Slice") - self._start_slice_job = StartSliceJob.StartSliceJob(slice_message) + self._start_slice_job = StartSliceJob(slice_message) self._start_slice_job_build_plate = build_plate_to_be_sliced self._start_slice_job.setBuildPlate(self._start_slice_job_build_plate) self._start_slice_job.start() @@ -310,12 +310,12 @@ class CuraEngineBackend(QObject, Backend): if self._start_slice_job is job: self._start_slice_job = None - if job.isCancelled() or job.getError() or job.getResult() == StartSliceJob.StartJobResult.Error: + if job.isCancelled() or job.getError() or job.getResult() == StartJobResult.Error: self.backendStateChange.emit(BackendState.Error) self.backendError.emit(job) return - if job.getResult() == StartSliceJob.StartJobResult.MaterialIncompatible: + if job.getResult() == StartJobResult.MaterialIncompatible: if self._application.platformActivity: self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current material as it is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Unable to slice")) @@ -326,10 +326,10 @@ class CuraEngineBackend(QObject, Backend): self.backendStateChange.emit(BackendState.NotStarted) return - if job.getResult() == StartSliceJob.StartJobResult.SettingError: + if job.getResult() == StartJobResult.SettingError: if self._application.platformActivity: extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())) - error_keys = [] + error_keys = [] #type: List[str] for extruder in extruders: error_keys.extend(extruder.getErrorKeys()) if not extruders: @@ -337,7 +337,7 @@ class CuraEngineBackend(QObject, Backend): error_labels = set() for key in error_keys: for stack in [self._global_container_stack] + extruders: #Search all container stacks for the definition of this setting. Some are only in an extruder stack. - definitions = stack.getBottom().findDefinitions(key = key) + definitions = cast(DefinitionContainerInterface, stack.getBottom()).findDefinitions(key = key) if definitions: break #Found it! No need to continue search. else: #No stack has a definition for this setting. @@ -345,8 +345,7 @@ class CuraEngineBackend(QObject, Backend): continue error_labels.add(definitions[0].label) - error_labels = ", ".join(error_labels) - self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}").format(error_labels), + self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}").format(", ".join(error_labels)), title = catalog.i18nc("@info:title", "Unable to slice")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) @@ -355,28 +354,27 @@ class CuraEngineBackend(QObject, Backend): self.backendStateChange.emit(BackendState.NotStarted) return - elif job.getResult() == StartSliceJob.StartJobResult.ObjectSettingError: + elif job.getResult() == StartJobResult.ObjectSettingError: errors = {} - for node in DepthFirstIterator(self._application.getController().getScene().getRoot()): + for node in DepthFirstIterator(self._application.getController().getScene().getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. stack = node.callDecoration("getStack") if not stack: continue for key in stack.getErrorKeys(): - definition = self._global_container_stack.getBottom().findDefinitions(key = key) + definition = cast(DefinitionContainerInterface, self._global_container_stack.getBottom()).findDefinitions(key = key) if not definition: Logger.log("e", "When checking settings for errors, unable to find definition for key {key} in per-object stack.".format(key = key)) continue definition = definition[0] errors[key] = definition.label - error_labels = ", ".join(errors.values()) - self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}").format(error_labels = error_labels), + self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}").format(error_labels = ", ".join(errors.values())), title = catalog.i18nc("@info:title", "Unable to slice")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) self.backendError.emit(job) return - if job.getResult() == StartSliceJob.StartJobResult.BuildPlateError: + if job.getResult() == StartJobResult.BuildPlateError: if self._application.platformActivity: self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid."), title = catalog.i18nc("@info:title", "Unable to slice")) @@ -386,7 +384,7 @@ class CuraEngineBackend(QObject, Backend): else: self.backendStateChange.emit(BackendState.NotStarted) - if job.getResult() == StartSliceJob.StartJobResult.ObjectsWithDisabledExtruder: + if job.getResult() == StartJobResult.ObjectsWithDisabledExtruder: self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because there are objects associated with disabled Extruder %s." % job.getMessage()), title = catalog.i18nc("@info:title", "Unable to slice")) self._error_message.show() @@ -394,7 +392,7 @@ class CuraEngineBackend(QObject, Backend): self.backendError.emit(job) return - if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice: + if job.getResult() == StartJobResult.NothingToSlice: if self._application.platformActivity: self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."), title = catalog.i18nc("@info:title", "Unable to slice")) @@ -424,14 +422,14 @@ class CuraEngineBackend(QObject, Backend): if not self._application.getPreferences().getValue("general/auto_slice"): enable_timer = False - for node in DepthFirstIterator(self._scene.getRoot()): + for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if node.callDecoration("isBlockSlicing"): enable_timer = False self.backendStateChange.emit(BackendState.Disabled) self._is_disabled = True gcode_list = node.callDecoration("getGCodeList") if gcode_list is not None: - self._scene.gcode_dict[node.callDecoration("getBuildPlateNumber")] = gcode_list + self._scene.gcode_dict[node.callDecoration("getBuildPlateNumber")] = gcode_list #type: ignore #Because we generate this attribute dynamically. if self._use_timer == enable_timer: return self._use_timer @@ -445,8 +443,8 @@ class CuraEngineBackend(QObject, Backend): ## Return a dict with number of objects per build plate def _numObjectsPerBuildPlate(self) -> Dict[int, int]: - num_objects = defaultdict(int) - for node in DepthFirstIterator(self._scene.getRoot()): + num_objects = defaultdict(int) #type: Dict[int, int] + for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. # Only count sliceable objects if node.callDecoration("isSliceable"): build_plate_number = node.callDecoration("getBuildPlateNumber") @@ -529,7 +527,7 @@ class CuraEngineBackend(QObject, Backend): ## Remove old layer data (if any) def _clearLayerData(self, build_plate_numbers: Set = None) -> None: - for node in DepthFirstIterator(self._scene.getRoot()): + for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if node.callDecoration("getLayerData"): if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers: node.getParent().removeChild(node) @@ -611,7 +609,7 @@ class CuraEngineBackend(QObject, Backend): self.backendStateChange.emit(BackendState.Done) self.processingProgress.emit(1.0) - gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] + gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically. for index, line in enumerate(gcode_list): replaced = line.replace("{print_time}", str(self._application.getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601))) replaced = replaced.replace("{filament_amount}", str(self._application.getPrintInformation().materialLengths)) @@ -649,18 +647,20 @@ class CuraEngineBackend(QObject, Backend): # # \param message The protobuf message containing g-code, encoded as UTF-8. def _onGCodeLayerMessage(self, message: Arcus.PythonMessage) -> None: - self._scene.gcode_dict[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace")) + self._scene.gcode_dict[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically. ## Called when a g-code prefix message is received from the engine. # # \param message The protobuf message containing the g-code prefix, # encoded as UTF-8. def _onGCodePrefixMessage(self, message: Arcus.PythonMessage) -> None: - self._scene.gcode_dict[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace")) + self._scene.gcode_dict[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically. ## Creates a new socket connection. - def _createSocket(self) -> None: - super()._createSocket(os.path.abspath(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "Cura.proto"))) + def _createSocket(self, protocol_file: str = None) -> None: + if not protocol_file: + protocol_file = os.path.abspath(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "Cura.proto")) + super()._createSocket(protocol_file) self._engine_is_fresh = True ## Called when anything has changed to the stuff that needs to be sliced. @@ -745,7 +745,7 @@ class CuraEngineBackend(QObject, Backend): self._onSceneChanged(source) def _startProcessSlicedLayersJob(self, build_plate_number: int) -> None: - self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data[build_plate_number]) + self._process_layers_job = ProcessSlicedLayersJob(self._stored_optimized_layer_data[build_plate_number]) self._process_layers_job.setBuildPlate(build_plate_number) self._process_layers_job.finished.connect(self._onProcessLayersFinished) self._process_layers_job.start() From 2b33a1f1b682e9b242833d0ddf5f45fc06443138 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 14:31:29 +0200 Subject: [PATCH 74/83] Fix broken import with DefinitionContainerInterface And the ensuing type error that can then be found. Contributes to issue CURA-5330. --- plugins/CuraEngineBackend/CuraEngineBackend.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 23175d24a7..e7dca2ae3e 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -18,7 +18,7 @@ from UM.Resources import Resources from UM.Platform import Platform from UM.Qt.Duration import DurationFormat from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator -from UM.Settings.DefinitionContainerInterface import DefinitionContainerInterface +from UM.Settings.Interfaces import DefinitionContainerInterface from UM.Settings.SettingInstance import SettingInstance #For typing. from UM.Tool import Tool #For typing. @@ -365,8 +365,7 @@ class CuraEngineBackend(QObject, Backend): if not definition: Logger.log("e", "When checking settings for errors, unable to find definition for key {key} in per-object stack.".format(key = key)) continue - definition = definition[0] - errors[key] = definition.label + errors[key] = definition[0].label self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}").format(error_labels = ", ".join(errors.values())), title = catalog.i18nc("@info:title", "Unable to slice")) self._error_message.show() From e957f7b650f9af432089e222685e52ae0bc64958 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 15:07:08 +0200 Subject: [PATCH 75/83] Fix typing errors Contributes to issue CURA-5330. --- plugins/CuraEngineBackend/StartSliceJob.py | 90 +++++++++++----------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 1a51f9201f..2041c45ef5 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -1,21 +1,25 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import numpy from string import Formatter from enum import IntEnum import time +from typing import Any, Dict, List, Optional, Set import re +import Arcus #For typing. from UM.Job import Job -from UM.Application import Application from UM.Logger import Logger +from UM.Settings.ContainerStack import ContainerStack #For typing. +from UM.Settings.SettingRelation import SettingRelation #For typing. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator - +from UM.Scene.Scene import Scene #For typing. from UM.Settings.Validator import ValidatorState from UM.Settings.SettingRelation import RelationType +from cura.CuraApplication import CuraApplication from cura.Scene.CuraSceneNode import CuraSceneNode from cura.OneAtATimeIterator import OneAtATimeIterator from cura.Settings.ExtruderManager import ExtruderManager @@ -35,19 +39,19 @@ class StartJobResult(IntEnum): ObjectsWithDisabledExtruder = 8 -## Formatter class that handles token expansion in start/end gcod +## Formatter class that handles token expansion in start/end gcode class GcodeStartEndFormatter(Formatter): - def get_value(self, key, args, kwargs): # [CodeStyle: get_value is an overridden function from the Formatter class] + def get_value(self, key: str, *args: str, **kwargs) -> 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 if isinstance(key, str): try: - extruder_nr = kwargs["default_extruder_nr"] + extruder_nr = int(kwargs["default_extruder_nr"]) except ValueError: extruder_nr = -1 - key_fragments = [fragment.strip() for fragment in key.split(',')] + key_fragments = [fragment.strip() for fragment in key.split(",")] if len(key_fragments) == 2: try: extruder_nr = int(key_fragments[1]) @@ -74,25 +78,25 @@ class GcodeStartEndFormatter(Formatter): ## Job class that builds up the message of scene data to send to CuraEngine. class StartSliceJob(Job): - def __init__(self, slice_message): + def __init__(self, slice_message: Arcus.PythonMessage) -> None: super().__init__() - self._scene = Application.getInstance().getController().getScene() - self._slice_message = slice_message - self._is_cancelled = False - self._build_plate_number = None + self._scene = CuraApplication.getInstance().getController().getScene() #type: Scene + self._slice_message = slice_message #type: Arcus.PythonMessage + self._is_cancelled = False #type: bool + self._build_plate_number = None #type: Optional[int] - self._all_extruders_settings = None # cache for all setting values from all stacks (global & extruder) for the current machine + self._all_extruders_settings = None #type: Optional[Dict[str, Any]] # cache for all setting values from all stacks (global & extruder) for the current machine - def getSliceMessage(self): + def getSliceMessage(self) -> Arcus.PythonMessage: return self._slice_message - def setBuildPlate(self, build_plate_number): + def setBuildPlate(self, build_plate_number: int) -> None: self._build_plate_number = build_plate_number ## Check if a stack has any errors. ## returns true if it has errors, false otherwise. - def _checkStackForErrors(self, stack): + def _checkStackForErrors(self, stack: ContainerStack) -> bool: if stack is None: return False @@ -105,28 +109,28 @@ class StartSliceJob(Job): return False ## Runs the job that initiates the slicing. - def run(self): + def run(self) -> None: if self._build_plate_number is None: self.setResult(StartJobResult.Error) return - stack = Application.getInstance().getGlobalContainerStack() + stack = CuraApplication.getInstance().getGlobalContainerStack() if not stack: self.setResult(StartJobResult.Error) return # Don't slice if there is a setting with an error value. - if Application.getInstance().getMachineManager().stacksHaveErrors: + if CuraApplication.getInstance().getMachineManager().stacksHaveErrors: self.setResult(StartJobResult.SettingError) return - if Application.getInstance().getBuildVolume().hasErrors(): + if CuraApplication.getInstance().getBuildVolume().hasErrors(): self.setResult(StartJobResult.BuildPlateError) return # Don't slice if the buildplate or the nozzle type is incompatible with the materials - if not Application.getInstance().getMachineManager().variantBuildplateCompatible and \ - not Application.getInstance().getMachineManager().variantBuildplateUsable: + if not CuraApplication.getInstance().getMachineManager().variantBuildplateCompatible and \ + not CuraApplication.getInstance().getMachineManager().variantBuildplateUsable: self.setResult(StartJobResult.MaterialIncompatible) return @@ -141,7 +145,7 @@ class StartSliceJob(Job): # Don't slice if there is a per object setting with an error value. - for node in DepthFirstIterator(self._scene.getRoot()): + for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if not isinstance(node, CuraSceneNode) or not node.isSelectable(): continue @@ -151,7 +155,7 @@ class StartSliceJob(Job): with self._scene.getSceneLock(): # Remove old layer data. - for node in DepthFirstIterator(self._scene.getRoot()): + for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if node.callDecoration("getLayerData") and node.callDecoration("getBuildPlateNumber") == self._build_plate_number: node.getParent().removeChild(node) break @@ -159,7 +163,7 @@ class StartSliceJob(Job): # Get the objects in their groups to print. object_groups = [] if stack.getProperty("print_sequence", "value") == "one_at_a_time": - for node in OneAtATimeIterator(self._scene.getRoot()): + for node in OneAtATimeIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. temp_list = [] # Node can't be printed, so don't bother sending it. @@ -185,7 +189,7 @@ class StartSliceJob(Job): else: temp_list = [] has_printing_mesh = False - for node in DepthFirstIterator(self._scene.getRoot()): + for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if node.callDecoration("isSliceable") and node.getMeshData() and node.getMeshData().getVertices() is not None: per_object_stack = node.callDecoration("getStack") is_non_printing_mesh = False @@ -212,12 +216,12 @@ class StartSliceJob(Job): if temp_list: object_groups.append(temp_list) - extruders_enabled = {position: stack.isEnabled for position, stack in Application.getInstance().getGlobalContainerStack().extruders.items()} + extruders_enabled = {position: stack.isEnabled for position, stack in CuraApplication.getInstance().getGlobalContainerStack().extruders.items()} filtered_object_groups = [] has_model_with_disabled_extruders = False associated_siabled_extruders = set() for group in object_groups: - stack = Application.getInstance().getGlobalContainerStack() + stack = CuraApplication.getInstance().getGlobalContainerStack() skip_group = False for node in group: extruder_position = node.callDecoration("getActiveExtruderPosition") @@ -231,7 +235,7 @@ class StartSliceJob(Job): if has_model_with_disabled_extruders: self.setResult(StartJobResult.ObjectsWithDisabledExtruder) - associated_siabled_extruders = [str(c) for c in sorted([int(p) + 1 for p in associated_siabled_extruders])] + associated_siabled_extruders = {str(c) for c in sorted([int(p) + 1 for p in associated_siabled_extruders])} self.setMessage(", ".join(associated_siabled_extruders)) return @@ -284,11 +288,11 @@ class StartSliceJob(Job): self.setResult(StartJobResult.Finished) - def cancel(self): + def cancel(self) -> None: super().cancel() self._is_cancelled = True - def isCancelled(self): + def isCancelled(self) -> bool: return self._is_cancelled ## Creates a dictionary of tokens to replace in g-code pieces. @@ -298,7 +302,7 @@ class StartSliceJob(Job): # with. # \return A dictionary of replacement tokens to the values they should be # replaced with. - def _buildReplacementTokens(self, stack) -> dict: + def _buildReplacementTokens(self, stack: ContainerStack) -> Dict[str, Any]: result = {} for key in stack.getAllKeys(): value = stack.getProperty(key, "value") @@ -311,7 +315,7 @@ class StartSliceJob(Job): result["date"] = time.strftime("%d-%m-%Y") result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))] - initial_extruder_stack = Application.getInstance().getExtruderManager().getUsedExtruderStacks()[0] + initial_extruder_stack = CuraApplication.getInstance().getExtruderManager().getUsedExtruderStacks()[0] initial_extruder_nr = initial_extruder_stack.getProperty("extruder_nr", "value") result["initial_extruder_nr"] = initial_extruder_nr @@ -320,9 +324,9 @@ class StartSliceJob(Job): ## Replace setting tokens in a piece of g-code. # \param value A piece of g-code to replace tokens in. # \param default_extruder_nr Stack nr to use when no stack nr is specified, defaults to the global stack - def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1): + def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str: if not self._all_extruders_settings: - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = CuraApplication.getInstance().getGlobalContainerStack() # NB: keys must be strings for the string formatter self._all_extruders_settings = { @@ -344,7 +348,7 @@ class StartSliceJob(Job): return str(value) ## Create extruder message from stack - def _buildExtruderMessage(self, stack): + def _buildExtruderMessage(self, stack: ContainerStack) -> None: message = self._slice_message.addRepeatedMessage("extruders") message.id = int(stack.getMetaDataEntry("position")) @@ -371,7 +375,7 @@ class StartSliceJob(Job): # # The settings are taken from the global stack. This does not include any # per-extruder settings or per-object settings. - def _buildGlobalSettingsMessage(self, stack): + def _buildGlobalSettingsMessage(self, stack: ContainerStack) -> None: settings = self._buildReplacementTokens(stack) # Pre-compute material material_bed_temp_prepend and material_print_temp_prepend @@ -385,7 +389,7 @@ class StartSliceJob(Job): # Replace the setting tokens in start and end g-code. # Use values from the first used extruder by default so we get the expected temperatures - initial_extruder_stack = Application.getInstance().getExtruderManager().getUsedExtruderStacks()[0] + initial_extruder_stack = CuraApplication.getInstance().getExtruderManager().getUsedExtruderStacks()[0] initial_extruder_nr = initial_extruder_stack.getProperty("extruder_nr", "value") settings["machine_start_gcode"] = self._expandGcodeTokens(settings["machine_start_gcode"], initial_extruder_nr) @@ -406,7 +410,7 @@ class StartSliceJob(Job): # # \param stack The global stack with all settings, from which to read the # limit_to_extruder property. - def _buildGlobalInheritsStackMessage(self, stack): + def _buildGlobalInheritsStackMessage(self, stack: ContainerStack) -> None: for key in stack.getAllKeys(): extruder_position = int(round(float(stack.getProperty(key, "limit_to_extruder")))) if extruder_position >= 0: # Set to a specific extruder. @@ -416,9 +420,9 @@ class StartSliceJob(Job): Job.yieldThread() ## Check if a node has per object settings and ensure that they are set correctly in the message - # \param node \type{SceneNode} Node to check. + # \param node Node to check. # \param message object_lists message to put the per object settings in - def _handlePerObjectSettings(self, node, message): + def _handlePerObjectSettings(self, node: CuraSceneNode, message: Arcus.PythonMessage): stack = node.callDecoration("getStack") # Check if the node has a stack attached to it and the stack has any settings in the top container. @@ -456,9 +460,9 @@ class StartSliceJob(Job): Job.yieldThread() ## Recursive function to put all settings that require each other for value changes in a list - # \param relations_set \type{set} Set of keys (strings) of settings that are influenced + # \param relations_set Set of keys of settings that are influenced # \param relations list of relation objects that need to be checked. - def _addRelations(self, relations_set, relations): + def _addRelations(self, relations_set: Set[str], relations: List[SettingRelation]): for relation in filter(lambda r: r.role == "value" or r.role == "limit_to_extruder", relations): if relation.type == RelationType.RequiresTarget: continue From 10f22f9c2572902edb6e0eea49ba58e1e241c7fd Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 15:09:19 +0200 Subject: [PATCH 76/83] Cast result of getPluginObject to the plug-in type it implements We know for sure what the type of that plug-in is, only we can't import that type because it's a plug-in. We can (and did) import the plug-in type object MeshWriter though. Contributes to issue CURA-5330. --- plugins/GCodeGzWriter/GCodeGzWriter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/GCodeGzWriter/GCodeGzWriter.py b/plugins/GCodeGzWriter/GCodeGzWriter.py index 06fafb5995..6ddecdb0bd 100644 --- a/plugins/GCodeGzWriter/GCodeGzWriter.py +++ b/plugins/GCodeGzWriter/GCodeGzWriter.py @@ -3,7 +3,7 @@ import gzip from io import StringIO, BufferedIOBase #To write the g-code to a temporary buffer, and for typing. -from typing import List +from typing import cast, List from UM.Logger import Logger from UM.Mesh.MeshWriter import MeshWriter #The class we're extending/implementing. @@ -32,7 +32,7 @@ class GCodeGzWriter(MeshWriter): #Get the g-code from the g-code writer. gcode_textio = StringIO() #We have to convert the g-code into bytes. - success = PluginRegistry.getInstance().getPluginObject("GCodeWriter").write(gcode_textio, None) + success = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter")).write(gcode_textio, None) if not success: #Writing the g-code failed. Then I can also not write the gzipped g-code. return False From 9821793b46428b299e2b1daacf7cf4e553350148 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 15:16:55 +0200 Subject: [PATCH 77/83] Don't override boundingbox of SceneNode Whatever was intended here, it didn't work. Because when a method is called it is called on the class of the object. Methods are not attributes of instances but attributes of the class. The attribute of the class was never changed, so this had no effect. Contributes to issue CURA-5330. --- plugins/GCodeReader/FlavorParser.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py index 5da1a79fb7..de6469516e 100644 --- a/plugins/GCodeReader/FlavorParser.py +++ b/plugins/GCodeReader/FlavorParser.py @@ -92,10 +92,6 @@ class FlavorParser: if message == self._message: self._cancelled = True - @staticmethod - def _getNullBoundingBox() -> AxisAlignedBox: - return AxisAlignedBox(minimum=Vector(0, 0, 0), maximum=Vector(10, 10, 10)) - def _createPolygon(self, layer_thickness: float, path: List[List[Union[float, int]]], extruder_offsets: List[float]) -> bool: countvalid = 0 for point in path: @@ -294,9 +290,6 @@ class FlavorParser: self._filament_diameter = global_stack.extruders[str(self._extruder_number)].getProperty("material_diameter", "value") scene_node = CuraSceneNode() - # Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no - # real data to calculate it from. - scene_node.getBoundingBox = self._getNullBoundingBox gcode_list = [] self._is_layers_in_file = False From 797d05947cfa6020416b93f5b38e5ed079d4a8c0 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 15:20:41 +0200 Subject: [PATCH 78/83] Fix type errors Contributes to issue CURA-5330. --- plugins/GCodeReader/FlavorParser.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py index de6469516e..05f40b41e7 100644 --- a/plugins/GCodeReader/FlavorParser.py +++ b/plugins/GCodeReader/FlavorParser.py @@ -1,11 +1,9 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from UM.Application import Application from UM.Backend import Backend from UM.Job import Job from UM.Logger import Logger -from UM.Math.AxisAlignedBox import AxisAlignedBox from UM.Math.Vector import Vector from UM.Message import Message from cura.Scene.CuraSceneNode import CuraSceneNode @@ -13,7 +11,8 @@ from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") -from cura import LayerDataBuilder +from cura.CuraApplication import CuraApplication +from cura.LayerDataBuilder import LayerDataBuilder from cura.LayerDataDecorator import LayerDataDecorator from cura.LayerPolygon import LayerPolygon from cura.Scene.GCodeListDecorator import GCodeListDecorator @@ -32,7 +31,7 @@ Position = NamedTuple("Position", [("x", float), ("y", float), ("z", float), ("f class FlavorParser: def __init__(self) -> None: - Application.getInstance().hideMessageSignal.connect(self._onHideMessage) + CuraApplication.getInstance().hideMessageSignal.connect(self._onHideMessage) self._cancelled = False self._message = None self._layer_number = 0 @@ -46,7 +45,7 @@ class FlavorParser: self._current_layer_thickness = 0.2 # default self._filament_diameter = 2.85 # default - Application.getInstance().getPreferences().addPreference("gcodereader/show_caution", True) + CuraApplication.getInstance().getPreferences().addPreference("gcodereader/show_caution", True) def _clearValues(self) -> None: self._extruder_number = 0 @@ -54,7 +53,7 @@ class FlavorParser: self._layer_type = LayerPolygon.Inset0Type self._layer_number = 0 self._previous_z = 0 # type: float - self._layer_data_builder = LayerDataBuilder.LayerDataBuilder() + self._layer_data_builder = LayerDataBuilder() self._is_absolute_positioning = True # It can be absolute (G90) or relative (G91) self._is_absolute_extrusion = True # It can become absolute (M82, default) or relative (M83) @@ -286,7 +285,7 @@ class FlavorParser: Logger.log("d", "Preparing to load GCode") self._cancelled = False # We obtain the filament diameter from the selected extruder to calculate line widths - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = CuraApplication.getInstance().getGlobalContainerStack() self._filament_diameter = global_stack.extruders[str(self._extruder_number)].getProperty("material_diameter", "value") scene_node = CuraSceneNode() @@ -322,7 +321,7 @@ class FlavorParser: Logger.log("d", "Parsing Gcode...") current_position = Position(0, 0, 0, 0, [0]) - current_path = [] + current_path = [] #type: List[List[float]] min_layer_number = 0 negative_layers = 0 previous_layer = 0 @@ -437,9 +436,9 @@ class FlavorParser: scene_node.addDecorator(gcode_list_decorator) # gcode_dict stores gcode_lists for a number of build plates. - active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate + active_build_plate_id = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate gcode_dict = {active_build_plate_id: gcode_list} - Application.getInstance().getController().getScene().gcode_dict = gcode_dict + CuraApplication.getInstance().getController().getScene().gcode_dict = gcode_dict #type: ignore #Because gcode_dict is generated dynamically. Logger.log("d", "Finished parsing Gcode") self._message.hide() @@ -447,7 +446,7 @@ class FlavorParser: if self._layer_number == 0: Logger.log("w", "File doesn't contain any valid layers") - settings = Application.getInstance().getGlobalContainerStack() + settings = CuraApplication.getInstance().getGlobalContainerStack() if not settings.getProperty("machine_center_is_zero", "value"): machine_width = settings.getProperty("machine_width", "value") machine_depth = settings.getProperty("machine_depth", "value") @@ -455,7 +454,7 @@ class FlavorParser: Logger.log("d", "GCode loading finished") - if Application.getInstance().getPreferences().getValue("gcodereader/show_caution"): + if CuraApplication.getInstance().getPreferences().getValue("gcodereader/show_caution"): caution_message = Message(catalog.i18nc( "@info:generic", "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."), @@ -464,7 +463,7 @@ class FlavorParser: caution_message.show() # The "save/print" button's state is bound to the backend state. - backend = Application.getInstance().getBackend() + backend = CuraApplication.getInstance().getBackend() backend.backendStateChange.emit(Backend.BackendState.Disabled) return scene_node From cfd0bde6b15c0095ec40e1eea231166af1e1fa2d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 15:32:44 +0200 Subject: [PATCH 79/83] Use CuraApplication instead of Application Because UM.Application has no function getMultiBuildPlateModel. Contributes to issue CURA-5330. --- plugins/SupportEraser/SupportEraser.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/plugins/SupportEraser/SupportEraser.py b/plugins/SupportEraser/SupportEraser.py index 06d9fc3707..92b42f9abc 100644 --- a/plugins/SupportEraser/SupportEraser.py +++ b/plugins/SupportEraser/SupportEraser.py @@ -1,22 +1,17 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import os -import os.path - from PyQt5.QtCore import Qt, QTimer from PyQt5.QtWidgets import QApplication from UM.Math.Vector import Vector from UM.Tool import Tool -from UM.Application import Application from UM.Event import Event, MouseEvent - from UM.Mesh.MeshBuilder import MeshBuilder from UM.Scene.Selection import Selection -from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator -from cura.Scene.CuraSceneNode import CuraSceneNode +from cura.CuraApplication import CuraApplication +from cura.Scene.CuraSceneNode import CuraSceneNode from cura.PickingPass import PickingPass from UM.Operations.GroupedOperation import GroupedOperation @@ -26,8 +21,6 @@ from cura.Operations.SetParentOperation import SetParentOperation from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator from cura.Scene.BuildPlateDecorator import BuildPlateDecorator -from UM.Scene.GroupDecorator import GroupDecorator -from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator from UM.Settings.SettingInstance import SettingInstance @@ -38,7 +31,7 @@ class SupportEraser(Tool): self._controller = self.getController() self._selection_pass = None - Application.getInstance().globalContainerStackChanged.connect(self._updateEnabled) + CuraApplication.getInstance().globalContainerStackChanged.connect(self._updateEnabled) # Note: if the selection is cleared with this tool active, there is no way to switch to # another tool than to reselect an object (by clicking it) because the tool buttons in the @@ -106,7 +99,7 @@ class SupportEraser(Tool): mesh.addCube(10,10,10) node.setMeshData(mesh.build()) - active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate + active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate node.addDecorator(BuildPlateDecorator(active_build_plate)) node.addDecorator(SliceableObjectDecorator()) @@ -126,7 +119,7 @@ class SupportEraser(Tool): op.push() node.setPosition(position, CuraSceneNode.TransformSpace.World) - Application.getInstance().getController().getScene().sceneChanged.emit(node) + CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node) def _removeEraserMesh(self, node: CuraSceneNode): parent = node.getParent() @@ -139,16 +132,16 @@ class SupportEraser(Tool): if parent and not Selection.isSelected(parent): Selection.add(parent) - Application.getInstance().getController().getScene().sceneChanged.emit(node) + CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node) def _updateEnabled(self): plugin_enabled = False - global_container_stack = Application.getInstance().getGlobalContainerStack() + global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() if global_container_stack: plugin_enabled = global_container_stack.getProperty("anti_overhang_mesh", "enabled") - Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, plugin_enabled) + CuraApplication.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, plugin_enabled) def _onSelectionChanged(self): # When selection is passed from one object to another object, first the selection is cleared From b07db74011333b090b35c30064a61a32f3c59453 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 15:44:29 +0200 Subject: [PATCH 80/83] Fix more typing errors It never ends. Contributes to issue CURA-5330. --- plugins/Toolbox/src/Toolbox.py | 49 +++++++++++++++------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index d0515010ce..0d0060e48c 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -11,7 +11,6 @@ from typing import List from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply -from UM.Application import Application from UM.Logger import Logger from UM.PluginRegistry import PluginRegistry from UM.Extension import Extension @@ -29,14 +28,13 @@ i18n_catalog = i18nCatalog("cura") ## The Toolbox class is responsible of communicating with the server through the API class Toolbox(QObject, Extension): + DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" #type: str + DEFAULT_CLOUD_API_VERSION = 1 #type: int - DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" - DEFAULT_CLOUD_API_VERSION = 1 + def __init__(self, application: CuraApplication) -> None: + super().__init__() - def __init__(self, application: Application, parent=None) -> None: - super().__init__(parent) - - self._application = application + self._application = application #type: CuraApplication self._sdk_version = None # type: Optional[int] self._cloud_api_version = None # type: Optional[int] @@ -44,13 +42,11 @@ class Toolbox(QObject, Extension): self._api_url = None # type: Optional[str] # Network: - self._get_packages_request = None - self._get_showcase_request = None - self._download_request = None - self._download_reply = None - self._download_progress = 0 # type: float - self._is_downloading = False - self._network_manager = None + self._download_request = None #type: Optional[QNetworkRequest] + self._download_reply = None #type: Optional[QNetworkReply] + self._download_progress = 0 #type: float + self._is_downloading = False #type: bool + self._network_manager = None #type: Optional[QNetworkAccessManager] self._request_header = [ b"User-Agent", str.encode( @@ -95,24 +91,24 @@ class Toolbox(QObject, Extension): # View category defines which filter to use, and therefore effectively # which category is currently being displayed. For example, possible # values include "plugin" or "material", but also "installed". - self._view_category = "plugin" + self._view_category = "plugin" #type: str # View page defines which type of page layout to use. For example, # possible values include "overview", "detail" or "author". - self._view_page = "loading" + self._view_page = "loading" #type: str # Active package refers to which package is currently being downloaded, # installed, or otherwise modified. self._active_package = None # type: Optional[Dict[str, Any]] - self._dialog = None - self._restart_required = False + self._dialog = None #type: Optional[QObject] + self._restart_required = False #type: bool # variables for the license agreement dialog - self._license_dialog_plugin_name = "" - self._license_dialog_license_content = "" - self._license_dialog_plugin_file_location = "" - self._restart_dialog_message = "" + self._license_dialog_plugin_name = "" #type: str + self._license_dialog_license_content = "" #type: str + self._license_dialog_plugin_file_location = "" #type: str + self._restart_dialog_message = "" #type: str self._application.initializationFinished.connect(self._onAppInitialized) @@ -210,11 +206,10 @@ class Toolbox(QObject, Extension): # Create the network manager: # This was formerly its own function but really had no reason to be as # it was never called more than once ever. - if self._network_manager: + if self._network_manager is not None: self._network_manager.finished.disconnect(self._onRequestFinished) self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccessibleChanged) self._network_manager = QNetworkAccessManager() - assert(self._network_manager is not None) self._network_manager.finished.connect(self._onRequestFinished) self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccessibleChanged) @@ -259,7 +254,6 @@ class Toolbox(QObject, Extension): @pyqtSlot() def _updateInstalledModels(self) -> None: - # This is moved here to avoid code duplication and so that after installing plugins they get removed from the # list of old plugins old_plugin_ids = self._plugin_registry.getInstalledPlugins() @@ -355,8 +349,8 @@ class Toolbox(QObject, Extension): return self._restart_required @pyqtSlot() - def restart(self): - cast(CuraApplication, self._application).windowClosed() + def restart(self) -> None: + self._application.windowClosed() def getRemotePackage(self, package_id: str) -> Optional[Dict]: # TODO: make the lookup in a dict, not a loop. canUpdate is called for every item. @@ -478,7 +472,6 @@ class Toolbox(QObject, Extension): self.resetDownload() def _onRequestFinished(self, reply: QNetworkReply) -> None: - if reply.error() == QNetworkReply.TimeoutError: Logger.log("w", "Got a timeout.") self.setViewPage("errored") From e717abf49967ee269a9d001d63a6c1a766864719 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 16:53:45 +0200 Subject: [PATCH 81/83] Fix typing related to Network Printing Contributes to issue CURA-5330. --- .../NetworkedPrinterOutputDevice.py | 10 +- .../ClusterUM3OutputDevice.py | 116 +++++++++--------- .../UM3NetworkPrinting/DiscoverUM3Action.py | 54 ++++---- .../LegacyUM3OutputDevice.py | 34 ++--- 4 files changed, 104 insertions(+), 110 deletions(-) diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py index e319614bc7..017fd0f0ed 100644 --- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py +++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py @@ -30,7 +30,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], parent: QObject = None) -> None: super().__init__(device_id = device_id, parent = parent) - self._manager = None # type: QNetworkAccessManager + self._manager = None # type: Optional[QNetworkAccessManager] self._last_manager_create_time = None # type: Optional[float] self._recreate_network_manager_time = 30 self._timeout_time = 10 # After how many seconds of no response should a timeout occur? @@ -162,7 +162,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent) return request - def _createFormPart(self, content_header: str, data: str, content_type: Optional[str] = None) -> QHttpPart: + def _createFormPart(self, content_header: str, data: bytes, content_type: Optional[str] = None) -> QHttpPart: part = QHttpPart() if not content_header.startswith("form-data;"): @@ -191,7 +191,6 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): def put(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None: if self._manager is None: self._createNetworkManager() - assert(self._manager is not None) request = self._createEmptyRequest(target) self._last_request_time = time() reply = self._manager.put(request, data.encode()) @@ -200,7 +199,6 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): def get(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None: if self._manager is None: self._createNetworkManager() - assert(self._manager is not None) request = self._createEmptyRequest(target) self._last_request_time = time() reply = self._manager.get(request) @@ -209,7 +207,6 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): def post(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None: if self._manager is None: self._createNetworkManager() - assert(self._manager is not None) request = self._createEmptyRequest(target) self._last_request_time = time() reply = self._manager.post(request, data) @@ -217,10 +214,9 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): reply.uploadProgress.connect(on_progress) self._registerOnFinishedCallback(reply, on_finished) - def postFormWithParts(self, target:str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None: + def postFormWithParts(self, target:str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> QNetworkReply: if self._manager is None: self._createNetworkManager() - assert(self._manager is not None) request = self._createEmptyRequest(target, content_type=None) multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType) for part in parts: diff --git a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py index 197debb58b..495bbe1315 100644 --- a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py @@ -1,15 +1,12 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Generator -from typing import Set -from typing import Tuple -from typing import Union + +from typing import Any, cast, Set, Tuple, Union from UM.FileHandler.FileHandler import FileHandler from UM.FileHandler.FileWriter import FileWriter #To choose based on the output file mode (text vs. binary). from UM.FileHandler.WriteFileJob import WriteFileJob #To call the file writer asynchronously. from UM.Logger import Logger -from UM.Application import Application from UM.Settings.ContainerRegistry import ContainerRegistry from UM.i18n import i18nCatalog from UM.Message import Message @@ -18,6 +15,7 @@ from UM.OutputDevice import OutputDeviceError #To show that something went wrong from UM.Scene.SceneNode import SceneNode #For typing. from UM.Version import Version #To check against firmware versions for support. +from cura.CuraApplication import CuraApplication from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel @@ -30,7 +28,7 @@ from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply from PyQt5.QtGui import QDesktopServices from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject -from time import time, sleep +from time import time from datetime import datetime from typing import Optional, Dict, List @@ -55,7 +53,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._number_of_extruders = 2 - self._dummy_lambdas = set() # type: Set[Tuple[str, Dict, Union[io.StringIO, io.BytesIO]]] + self._dummy_lambdas = ("", {}, io.BytesIO()) #type: Tuple[str, Dict, Union[io.StringIO, io.BytesIO]] self._print_jobs = [] # type: List[PrintJobOutputModel] @@ -65,18 +63,18 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): # See comments about this hack with the clusterPrintersChanged signal self.printersChanged.connect(self.clusterPrintersChanged) - self._accepts_commands = True + self._accepts_commands = True #type: bool # Cluster does not have authentication, so default to authenticated self._authentication_state = AuthState.Authenticated - self._error_message = None - self._write_job_progress_message = None - self._progress_message = None + self._error_message = None #type: Optional[Message] + self._write_job_progress_message = None #type: Optional[Message] + self._progress_message = None #type: Optional[Message] self._active_printer = None # type: Optional[PrinterOutputModel] - self._printer_selection_dialog = None + self._printer_selection_dialog = None #type: QObject self.setPriority(3) # Make sure the output device gets selected above local file output self.setName(self._id) @@ -91,7 +89,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._cluster_size = int(properties.get(b"cluster_size", 0)) - self._latest_reply_handler = None + self._latest_reply_handler = None #type: Optional[QNetworkReply] def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None: self.writeStarted.emit(self) @@ -100,10 +98,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): if file_handler: file_formats = file_handler.getSupportedFileTypesWrite() else: - file_formats = Application.getInstance().getMeshFileHandler().getSupportedFileTypesWrite() + file_formats = CuraApplication.getInstance().getMeshFileHandler().getSupportedFileTypesWrite() #Create a list from the supported file formats string. - machine_file_formats = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("file_formats").split(";") + machine_file_formats = CuraApplication.getInstance().getGlobalContainerStack().getMetaDataEntry("file_formats").split(";") machine_file_formats = [file_type.strip() for file_type in machine_file_formats] #Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format. if "application/x-ufp" not in machine_file_formats and self.printerType == "ultimaker3" and Version(self.firmwareVersion) >= Version("4.4"): @@ -120,9 +118,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): #Just take the first file format available. if file_handler is not None: - writer = file_handler.getWriterByMimeType(preferred_format["mime_type"]) + writer = file_handler.getWriterByMimeType(cast(str, preferred_format["mime_type"])) else: - writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType(preferred_format["mime_type"]) + writer = CuraApplication.getInstance().getMeshFileHandler().getWriterByMimeType(cast(str, preferred_format["mime_type"])) #This function pauses with the yield, waiting on instructions on which printer it needs to print with. self._sending_job = self._sendPrintJob(writer, preferred_format, nodes) @@ -138,7 +136,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): def _spawnPrinterSelectionDialog(self): if self._printer_selection_dialog is None: path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PrintWindow.qml") - self._printer_selection_dialog = Application.getInstance().createQmlComponent(path, {"OutputDevice": self}) + self._printer_selection_dialog = CuraApplication.getInstance().createQmlComponent(path, {"OutputDevice": self}) if self._printer_selection_dialog is not None: self._printer_selection_dialog.show() @@ -173,8 +171,6 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._error_message = Message( i18n_catalog.i18nc("@info:status", "Sending new jobs (temporarily) blocked, still sending the previous print job.")) - - assert(self._error_message is not None) self._error_message.show() yield #Wait on the user to select a target printer. yield #Wait for the write job to be finished. @@ -195,8 +191,6 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._write_job_progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1, title = i18n_catalog.i18nc("@info:title", "Sending Data"), use_inactivity_timer = False) - - assert(self._write_job_progress_message is not None) # use for typing purposes self._write_job_progress_message.show() self._dummy_lambdas = (target_printer, preferred_format, stream) @@ -207,9 +201,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): yield True #Return that we had success! yield #To prevent having to catch the StopIteration exception. - from cura.Utils.Threading import call_on_qt_thread - - def _sendPrintJobWaitOnWriteJobFinished(self, job): + def _sendPrintJobWaitOnWriteJobFinished(self, job: WriteFileJob) -> None: self._write_job_progress_message.hide() self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1, @@ -230,34 +222,35 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): # Add user name to the print_job parts.append(self._createFormPart("name=owner", bytes(self._getUserName(), "utf-8"), "text/plain")) - file_name = Application.getInstance().getPrintInformation().jobName + "." + preferred_format["extension"] + file_name = CuraApplication.getInstance().getPrintInformation().jobName + "." + preferred_format["extension"] output = stream.getvalue() #Either str or bytes depending on the output mode. if isinstance(stream, io.StringIO): - output = output.encode("utf-8") + output = cast(str, output).encode("utf-8") + output = cast(bytes, output) parts.append(self._createFormPart("name=\"file\"; filename=\"%s\"" % file_name, output)) - self._latest_reply_handler = self.postFormWithParts("print_jobs/", parts, onFinished=self._onPostPrintJobFinished, onProgress=self._onUploadPrintJobProgress) + self._latest_reply_handler = self.postFormWithParts("print_jobs/", parts, on_finished = self._onPostPrintJobFinished, on_progress = self._onUploadPrintJobProgress) - @pyqtProperty(QObject, notify=activePrinterChanged) + @pyqtProperty(QObject, notify = activePrinterChanged) def activePrinter(self) -> Optional[PrinterOutputModel]: return self._active_printer @pyqtSlot(QObject) - def setActivePrinter(self, printer: Optional[PrinterOutputModel]): + def setActivePrinter(self, printer: Optional[PrinterOutputModel]) -> None: if self._active_printer != printer: if self._active_printer and self._active_printer.camera: self._active_printer.camera.stop() self._active_printer = printer self.activePrinterChanged.emit() - def _onPostPrintJobFinished(self, reply): + def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None: self._progress_message.hide() self._compressing_gcode = False self._sending_gcode = False - def _onUploadPrintJobProgress(self, bytes_sent:int, bytes_total:int): + def _onUploadPrintJobProgress(self, bytes_sent: int, bytes_total: int) -> None: if bytes_total > 0: new_progress = bytes_sent / bytes_total * 100 # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get @@ -284,14 +277,14 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._progress_message.setProgress(0) self._progress_message.hide() - def _progressMessageActionTriggered(self, message_id: Optional[str]=None, action_id: Optional[str]=None) -> None: + def _progressMessageActionTriggered(self, message_id: Optional[str] = None, action_id: Optional[str] = None) -> None: if action_id == "Abort": Logger.log("d", "User aborted sending print to remote.") if self._progress_message is not None: self._progress_message.hide() self._compressing_gcode = False self._sending_gcode = False - Application.getInstance().getController().setActiveStage("PrepareStage") + CuraApplication.getInstance().getController().setActiveStage("PrepareStage") # After compressing the sliced model Cura sends data to printer, to stop receiving updates from the request # the "reply" should be disconnected @@ -299,9 +292,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._latest_reply_handler.disconnect() self._latest_reply_handler = None - def _successMessageActionTriggered(self, message_id: Optional[str]=None, action_id: Optional[str]=None) -> None: + def _successMessageActionTriggered(self, message_id: Optional[str] = None, action_id: Optional[str] = None) -> None: if action_id == "View": - Application.getInstance().getController().setActiveStage("MonitorStage") + CuraApplication.getInstance().getController().setActiveStage("MonitorStage") @pyqtSlot() def openPrintJobControlPanel(self) -> None: @@ -313,19 +306,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): Logger.log("d", "Opening printer control panel...") QDesktopServices.openUrl(QUrl("http://" + self._address + "/printers")) - @pyqtProperty("QVariantList", notify=printJobsChanged) - def printJobs(self)-> List[PrintJobOutputModel] : + @pyqtProperty("QVariantList", notify = printJobsChanged) + def printJobs(self)-> List[PrintJobOutputModel]: return self._print_jobs - @pyqtProperty("QVariantList", notify=printJobsChanged) + @pyqtProperty("QVariantList", notify = printJobsChanged) def queuedPrintJobs(self) -> List[PrintJobOutputModel]: return [print_job for print_job in self._print_jobs if print_job.state == "queued"] - @pyqtProperty("QVariantList", notify=printJobsChanged) + @pyqtProperty("QVariantList", notify = printJobsChanged) def activePrintJobs(self) -> List[PrintJobOutputModel]: return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is not None and print_job.state != "queued"] - @pyqtProperty("QVariantList", notify=clusterPrintersChanged) + @pyqtProperty("QVariantList", notify = clusterPrintersChanged) def connectedPrintersTypeCount(self) -> List[Dict[str, str]]: printer_count = {} # type: Dict[str, int] for printer in self._printers: @@ -338,17 +331,17 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): result.append({"machine_type": machine_type, "count": str(printer_count[machine_type])}) return result - @pyqtSlot(int, result=str) + @pyqtSlot(int, result = str) def formatDuration(self, seconds: int) -> str: return Duration(seconds).getDisplayString(DurationFormat.Format.Short) - @pyqtSlot(int, result=str) + @pyqtSlot(int, result = str) def getTimeCompleted(self, time_remaining: int) -> str: current_time = time() datetime_completed = datetime.fromtimestamp(current_time + time_remaining) return "{hour:02d}:{minute:02d}".format(hour=datetime_completed.hour, minute=datetime_completed.minute) - @pyqtSlot(int, result=str) + @pyqtSlot(int, result = str) def getDateCompleted(self, time_remaining: int) -> str: current_time = time() datetime_completed = datetime.fromtimestamp(current_time + time_remaining) @@ -379,8 +372,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): def _update(self) -> None: super()._update() - self.get("printers/", on_finished=self._onGetPrintersDataFinished) - self.get("print_jobs/", on_finished=self._onGetPrintJobsFinished) + self.get("printers/", on_finished = self._onGetPrintersDataFinished) + self.get("print_jobs/", on_finished = self._onGetPrintJobsFinished) def _onGetPrintJobsFinished(self, reply: QNetworkReply) -> None: if not checkValidGetReply(reply): @@ -419,7 +412,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): removed_jobs = [print_job for print_job in self._print_jobs if print_job not in print_jobs_seen] for removed_job in removed_jobs: - job_list_changed |= self._removeJob(removed_job) + job_list_changed = job_list_changed or self._removeJob(removed_job) if job_list_changed: self.printJobsChanged.emit() # Do a single emit for all print job changes. @@ -453,27 +446,27 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): if removed_printers or printer_list_changed: self.printersChanged.emit() - def _createPrinterModel(self, data: Dict) -> PrinterOutputModel: - printer = PrinterOutputModel(output_controller=ClusterUM3PrinterOutputController(self), - number_of_extruders=self._number_of_extruders) + def _createPrinterModel(self, data: Dict[str, Any]) -> PrinterOutputModel: + printer = PrinterOutputModel(output_controller = ClusterUM3PrinterOutputController(self), + number_of_extruders = self._number_of_extruders) printer.setCamera(NetworkCamera("http://" + data["ip_address"] + ":8080/?action=stream")) self._printers.append(printer) return printer - def _createPrintJobModel(self, data: Dict) -> PrintJobOutputModel: + def _createPrintJobModel(self, data: Dict[str, Any]) -> PrintJobOutputModel: print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self), key=data["uuid"], name= data["name"]) print_job.stateChanged.connect(self._printJobStateChanged) self._print_jobs.append(print_job) return print_job - def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict) -> None: + def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict[str, Any]) -> None: print_job.updateTimeTotal(data["time_total"]) print_job.updateTimeElapsed(data["time_elapsed"]) print_job.updateState(data["status"]) print_job.updateOwner(data["owner"]) - def _updatePrinter(self, printer: PrinterOutputModel, data: Dict) -> None: + def _updatePrinter(self, printer: PrinterOutputModel, data: Dict[str, Any]) -> None: # For some unknown reason the cluster wants UUID for everything, except for sending a job directly to a printer. # Then we suddenly need the unique name. So in order to not have to mess up all the other code, we save a mapping. self._printer_uuid_to_unique_name_mapping[data["uuid"]] = data["unique_name"] @@ -489,7 +482,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): printer.updateKey(data["uuid"]) printer.updateType(data["machine_variant"]) - # Do not store the buildplate information that comes from connect if the current printer has not buildplate information + # Do not store the build plate information that comes from connect if the current printer has not build plate information if "build_plate" in data and machine_definition.getMetaDataEntry("has_variant_buildplates", False): printer.updateBuildplateName(data["build_plate"]["type"]) if not data["enabled"]: @@ -528,7 +521,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): brand=brand, color=color, name=name) extruder.updateActiveMaterial(material) - def _removeJob(self, job: PrintJobOutputModel): + def _removeJob(self, job: PrintJobOutputModel) -> bool: if job not in self._print_jobs: return False @@ -539,23 +532,23 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): return True - def _removePrinter(self, printer: PrinterOutputModel): + def _removePrinter(self, printer: PrinterOutputModel) -> None: self._printers.remove(printer) if self._active_printer == printer: self._active_printer = None self.activePrinterChanged.emit() -def loadJsonFromReply(reply): +def loadJsonFromReply(reply: QNetworkReply) -> Optional[List[Dict[str, Any]]]: try: result = json.loads(bytes(reply.readAll()).decode("utf-8")) except json.decoder.JSONDecodeError: Logger.logException("w", "Unable to decode JSON from reply.") - return + return None return result -def checkValidGetReply(reply): +def checkValidGetReply(reply: QNetworkReply) -> bool: status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) if status_code != 200: @@ -564,7 +557,8 @@ def checkValidGetReply(reply): return True -def findByKey(list, key): +def findByKey(list: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: str) -> Optional[PrintJobOutputModel]: for item in list: if item.key == key: - return item \ No newline at end of file + return item + return None \ No newline at end of file diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.py b/plugins/UM3NetworkPrinting/DiscoverUM3Action.py index 9b25de7f42..c51092ed98 100644 --- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.py +++ b/plugins/UM3NetworkPrinting/DiscoverUM3Action.py @@ -1,43 +1,47 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + import os.path import time +from typing import Optional from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QObject -from UM.Application import Application from UM.PluginRegistry import PluginRegistry from UM.Logger import Logger from UM.i18n import i18nCatalog +from cura.CuraApplication import CuraApplication from cura.MachineAction import MachineAction +from .UM3OutputDevicePlugin import UM3OutputDevicePlugin + catalog = i18nCatalog("cura") class DiscoverUM3Action(MachineAction): discoveredDevicesChanged = pyqtSignal() - def __init__(self): + def __init__(self) -> None: super().__init__("DiscoverUM3Action", catalog.i18nc("@action","Connect via Network")) self._qml_url = "DiscoverUM3Action.qml" - self._network_plugin = None + self._network_plugin = None #type: Optional[UM3OutputDevicePlugin] - self.__additional_components_context = None - self.__additional_component = None - self.__additional_components_view = None + self.__additional_components_view = None #type: Optional[QObject] - Application.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView) + CuraApplication.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView) - self._last_zero_conf_event_time = time.time() + self._last_zero_conf_event_time = time.time() #type: float # Time to wait after a zero-conf service change before allowing a zeroconf reset - self._zero_conf_change_grace_period = 0.25 + self._zero_conf_change_grace_period = 0.25 #type: float @pyqtSlot() def startDiscovery(self): if not self._network_plugin: Logger.log("d", "Starting device discovery.") - self._network_plugin = Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting") + self._network_plugin = CuraApplication.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting") self._network_plugin.discoveredDevicesChanged.connect(self._onDeviceDiscoveryChanged) self.discoveredDevicesChanged.emit() @@ -93,16 +97,16 @@ class DiscoverUM3Action(MachineAction): return [] @pyqtSlot(str) - def setGroupName(self, group_name): + def setGroupName(self, group_name: str) -> None: Logger.log("d", "Attempting to set the group name of the active machine to %s", group_name) - global_container_stack = Application.getInstance().getGlobalContainerStack() + global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() if global_container_stack: meta_data = global_container_stack.getMetaData() if "connect_group_name" in meta_data: previous_connect_group_name = meta_data["connect_group_name"] global_container_stack.setMetaDataEntry("connect_group_name", group_name) # Find all the places where there is the same group name and change it accordingly - Application.getInstance().getMachineManager().replaceContainersMetadata(key = "connect_group_name", value = previous_connect_group_name, new_value = group_name) + CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "connect_group_name", value = previous_connect_group_name, new_value = group_name) else: global_container_stack.addMetaDataEntry("connect_group_name", group_name) global_container_stack.addMetaDataEntry("hidden", False) @@ -112,9 +116,9 @@ class DiscoverUM3Action(MachineAction): self._network_plugin.reCheckConnections() @pyqtSlot(str) - def setKey(self, key): + def setKey(self, key: str) -> None: Logger.log("d", "Attempting to set the network key of the active machine to %s", key) - global_container_stack = Application.getInstance().getGlobalContainerStack() + global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() if global_container_stack: meta_data = global_container_stack.getMetaData() if "um_network_key" in meta_data: @@ -124,7 +128,7 @@ class DiscoverUM3Action(MachineAction): Logger.log("d", "Removing old authentication id %s for device %s", global_container_stack.getMetaDataEntry("network_authentication_id", None), key) global_container_stack.removeMetaDataEntry("network_authentication_id") global_container_stack.removeMetaDataEntry("network_authentication_key") - Application.getInstance().getMachineManager().replaceContainersMetadata(key = "um_network_key", value = previous_network_key, new_value = key) + CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "um_network_key", value = previous_network_key, new_value = key) else: global_container_stack.addMetaDataEntry("um_network_key", key) @@ -134,7 +138,7 @@ class DiscoverUM3Action(MachineAction): @pyqtSlot(result = str) def getStoredKey(self) -> str: - global_container_stack = Application.getInstance().getGlobalContainerStack() + global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() if global_container_stack: meta_data = global_container_stack.getMetaData() if "um_network_key" in meta_data: @@ -149,12 +153,12 @@ class DiscoverUM3Action(MachineAction): return "" @pyqtSlot(str, result = bool) - def existsKey(self, key) -> bool: - return Application.getInstance().getMachineManager().existNetworkInstances(network_key = key) + def existsKey(self, key: str) -> bool: + return CuraApplication.getInstance().getMachineManager().existNetworkInstances(network_key = key) @pyqtSlot() - def loadConfigurationFromPrinter(self): - machine_manager = Application.getInstance().getMachineManager() + def loadConfigurationFromPrinter(self) -> None: + machine_manager = CuraApplication.getInstance().getMachineManager() hotend_ids = machine_manager.printerOutputDevices[0].hotendIds for index in range(len(hotend_ids)): machine_manager.printerOutputDevices[0].hotendIdChanged.emit(index, hotend_ids[index]) @@ -162,16 +166,16 @@ class DiscoverUM3Action(MachineAction): for index in range(len(material_ids)): machine_manager.printerOutputDevices[0].materialIdChanged.emit(index, material_ids[index]) - def _createAdditionalComponentsView(self): + def _createAdditionalComponentsView(self) -> None: Logger.log("d", "Creating additional ui components for UM3.") # Create networking dialog path = os.path.join(PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"), "UM3InfoComponents.qml") - self.__additional_components_view = Application.getInstance().createQmlComponent(path, {"manager": self}) + self.__additional_components_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) if not self.__additional_components_view: Logger.log("w", "Could not create ui components for UM3.") return # Create extra components - Application.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton")) - Application.getInstance().addAdditionalComponent("machinesDetailPane", self.__additional_components_view.findChild(QObject, "networkPrinterConnectionInfo")) + CuraApplication.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton")) + CuraApplication.getInstance().addAdditionalComponent("machinesDetailPane", self.__additional_components_view.findChild(QObject, "networkPrinterConnectionInfo")) diff --git a/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py b/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py index 0111ad5e4f..9b90f8542d 100644 --- a/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py @@ -2,6 +2,7 @@ from typing import List, Optional from UM.FileHandler.FileHandler import FileHandler from UM.Scene.SceneNode import SceneNode +from cura.CuraApplication import CuraApplication from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel @@ -13,12 +14,11 @@ from cura.Settings.ExtruderManager import ExtruderManager from UM.Logger import Logger from UM.Settings.ContainerRegistry import ContainerRegistry -from UM.Application import Application from UM.i18n import i18nCatalog from UM.Message import Message from PyQt5.QtNetwork import QNetworkRequest -from PyQt5.QtCore import QTimer, QCoreApplication +from PyQt5.QtCore import QTimer from PyQt5.QtWidgets import QMessageBox from .LegacyUM3PrinterOutputController import LegacyUM3PrinterOutputController @@ -129,7 +129,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): def connect(self): super().connect() self._setupMessages() - global_container = Application.getInstance().getGlobalContainerStack() + global_container = CuraApplication.getInstance().getGlobalContainerStack() if global_container: self._authentication_id = global_container.getMetaDataEntry("network_authentication_id", None) self._authentication_key = global_container.getMetaDataEntry("network_authentication_key", None) @@ -187,8 +187,8 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): self.writeStarted.emit(self) - gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", []) - active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate + gcode_dict = getattr(CuraApplication.getInstance().getController().getScene(), "gcode_dict", []) + active_build_plate_id = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate gcode_list = gcode_dict[active_build_plate_id] if not gcode_list: @@ -207,7 +207,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): for error in errors: detailed_text += error + "\n" - Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"), + CuraApplication.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"), text, informative_text, detailed_text, @@ -229,7 +229,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): for warning in warnings: detailed_text += warning + "\n" - Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"), + CuraApplication.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"), text, informative_text, detailed_text, @@ -243,7 +243,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): self._startPrint() # Notify the UI that a switch to the print monitor should happen - Application.getInstance().getController().setActiveStage("MonitorStage") + CuraApplication.getInstance().getController().setActiveStage("MonitorStage") def _startPrint(self): Logger.log("i", "Sending print job to printer.") @@ -268,7 +268,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): # Abort was called. return - file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName + file_name = "%s.gcode.gz" % CuraApplication.getInstance().getPrintInformation().jobName self.postForm("print_job", "form-data; name=\"file\";filename=\"%s\"" % file_name, compressed_gcode, onFinished=self._onPostPrintJobFinished) @@ -280,7 +280,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): self._progress_message.hide() self._compressing_gcode = False self._sending_gcode = False - Application.getInstance().getController().setActiveStage("PrepareStage") + CuraApplication.getInstance().getController().setActiveStage("PrepareStage") def _onPostPrintJobFinished(self, reply): self._progress_message.hide() @@ -305,7 +305,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): if button == QMessageBox.Yes: self._startPrint() else: - Application.getInstance().getController().setActiveStage("PrepareStage") + CuraApplication.getInstance().getController().setActiveStage("PrepareStage") # For some unknown reason Cura on OSX will hang if we do the call back code # immediately without first returning and leaving QML's event system. @@ -313,7 +313,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): def _checkForErrors(self): errors = [] - print_information = Application.getInstance().getPrintInformation() + print_information = CuraApplication.getInstance().getPrintInformation() if not print_information.materialLengths: Logger.log("w", "There is no material length information. Unable to check for errors.") return errors @@ -333,7 +333,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): def _checkForWarnings(self): warnings = [] - print_information = Application.getInstance().getPrintInformation() + print_information = CuraApplication.getInstance().getPrintInformation() if not print_information.materialLengths: Logger.log("w", "There is no material length information. Unable to check for warnings.") @@ -456,7 +456,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): self._authentication_failed_message.show() def _saveAuthentication(self): - global_container_stack = Application.getInstance().getGlobalContainerStack() + global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() if global_container_stack: if "network_authentication_key" in global_container_stack.getMetaData(): global_container_stack.setMetaDataEntry("network_authentication_key", self._authentication_key) @@ -469,7 +469,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): global_container_stack.addMetaDataEntry("network_authentication_id", self._authentication_id) # Force save so we are sure the data is not lost. - Application.getInstance().saveStack(global_container_stack) + CuraApplication.getInstance().saveStack(global_container_stack) Logger.log("i", "Authentication succeeded for id %s and key %s", self._authentication_id, self._getSafeAuthKey()) else: @@ -500,7 +500,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): self._authentication_id = None self.post("auth/request", - json.dumps({"application": "Cura-" + Application.getInstance().getVersion(), + json.dumps({"application": "Cura-" + CuraApplication.getInstance().getVersion(), "user": self._getUserName()}).encode(), onFinished=self._onRequestAuthenticationFinished) @@ -546,7 +546,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): "Got status code {status_code} while trying to get printer data".format(status_code=status_code)) def materialHotendChangedMessage(self, callback): - Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Sync with your printer"), + CuraApplication.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Sync with your printer"), i18n_catalog.i18nc("@label", "Would you like to use your current printer configuration in Cura?"), i18n_catalog.i18nc("@label", From 79c6b2dad88cd10b6c237a9ac93b315e52815d65 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 16:55:30 +0200 Subject: [PATCH 82/83] Use CuraApplication instead of Application Because UM.Application doesn't have any getPrintJobInformation function or anything like that. Contributes to issue CURA-5330. --- plugins/USBPrinting/USBPrinterOutputDevice.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index d61cf03337..69a0ecb40c 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -3,10 +3,10 @@ from UM.Logger import Logger from UM.i18n import i18nCatalog -from UM.Application import Application from UM.Qt.Duration import DurationFormat from UM.PluginRegistry import PluginRegistry +from cura.CuraApplication import CuraApplication from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel @@ -107,11 +107,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice): # cancel any ongoing preheat timer before starting a print self._printers[0].getController().stopPreheatTimers() - Application.getInstance().getController().setActiveStage("MonitorStage") + CuraApplication.getInstance().getController().setActiveStage("MonitorStage") # find the G-code for the active build plate to print - active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate - gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict") + active_build_plate_id = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate + gcode_dict = getattr(CuraApplication.getInstance().getController().getScene(), "gcode_dict") gcode_list = gcode_dict[active_build_plate_id] self._printGCode(gcode_list) @@ -121,7 +121,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): def showFirmwareInterface(self): if self._firmware_view is None: path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml") - self._firmware_view = Application.getInstance().createQmlComponent(path, {"manager": self}) + self._firmware_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) self._firmware_view.show() @@ -180,7 +180,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): 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. - Application.getInstance().callLater(self.connect) + CuraApplication.getInstance().callLater(self.connect) @pyqtProperty(float, notify = firmwareProgressChanged) def firmwareProgress(self): @@ -214,7 +214,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._gcode_position = 0 self._print_start_time = time() - self._print_estimated_time = int(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds)) + self._print_estimated_time = int(CuraApplication.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds)) for i in range(0, 4): # Push first 4 entries before accepting other inputs self._sendNextGcodeLine() @@ -250,7 +250,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): except SerialException: Logger.log("w", "An exception occured while trying to create serial connection") return - container_stack = Application.getInstance().getGlobalContainerStack() + 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)] From a401c1d64f90088fd95a98c2bc4bedcbd82b978f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Jun 2018 17:03:04 +0200 Subject: [PATCH 83/83] Disambiguate between list of metadatas and metadata Because MyPy doesn't allow a variable to change its type later. Contributes to issue CURA-5330. --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index e1e17b20ea..eafb504deb 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -870,11 +870,11 @@ class XmlMaterialProfile(InstanceContainer): machine_id_list = cls.getPossibleDefinitionIDsFromName(identifier.get("product")) for machine_id in machine_id_list: - definition_metadata = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id) - if not definition_metadata: + definition_metadatas = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id) + if not definition_metadatas: continue - definition_metadata = definition_metadata[0] + definition_metadata = definition_metadatas[0] machine_manufacturer = identifier.get("manufacturer", definition_metadata.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.