diff --git a/cura/OAuth2/AuthorizationRequestServer.py b/cura/OAuth2/AuthorizationRequestServer.py index 74b0b5f012..4ed3975638 100644 --- a/cura/OAuth2/AuthorizationRequestServer.py +++ b/cura/OAuth2/AuthorizationRequestServer.py @@ -2,6 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from http.server import HTTPServer +from socketserver import ThreadingMixIn from typing import Callable, Any, TYPE_CHECKING if TYPE_CHECKING: @@ -9,7 +10,7 @@ if TYPE_CHECKING: from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers -class AuthorizationRequestServer(HTTPServer): +class AuthorizationRequestServer(ThreadingMixIn, HTTPServer): """The authorization request callback handler server. This subclass is needed to be able to pass some data to the request handler. This cannot be done on the request diff --git a/cura/OAuth2/LocalAuthorizationServer.py b/cura/OAuth2/LocalAuthorizationServer.py index a41de2d406..0e017c5318 100644 --- a/cura/OAuth2/LocalAuthorizationServer.py +++ b/cura/OAuth2/LocalAuthorizationServer.py @@ -81,6 +81,7 @@ class LocalAuthorizationServer: if self._web_server: try: + self._web_server.shutdown() self._web_server.server_close() except OSError: # OS error can happen if the socket was already closed. We really don't care about that case. diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 2dc01425fc..2cc9ec4631 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -302,6 +302,7 @@ class ExtruderManager(QObject): for extruder in self.getMachineExtruders(machine_id): ContainerRegistry.getInstance().removeContainer(extruder.userChanges.getId()) + ContainerRegistry.getInstance().removeContainer(extruder.definitionChanges.getId()) ContainerRegistry.getInstance().removeContainer(extruder.getId()) if machine_id in self._extruder_trains: del self._extruder_trains[machine_id] diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 516579ede2..423df167cd 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -496,7 +496,11 @@ class MachineManager(QObject): @pyqtProperty(bool, notify = printerConnectedStatusChanged) def activeMachineIsGroup(self) -> bool: - return bool(self._printer_output_devices) and len(self._printer_output_devices[0].printers) > 1 + if self.activeMachine is None: + return False + + group_size = int(self.activeMachine.getMetaDataEntry("group_size", "-1")) + return group_size > 1 @pyqtProperty(bool, notify = printerConnectedStatusChanged) def activeMachineIsLinkedToCurrentAccount(self) -> bool: @@ -734,6 +738,8 @@ class MachineManager(QObject): containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id) for container in containers: CuraContainerRegistry.getInstance().removeContainer(container["id"]) + machine_stack = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", name = machine_id)[0] + CuraContainerRegistry.getInstance().removeContainer(machine_stack.definitionChanges.getId()) CuraContainerRegistry.getInstance().removeContainer(machine_id) # If the printer that is being removed is a network printer, the hidden printers have to be also removed diff --git a/plugins/PostProcessingPlugin/scripts/Stretch.py b/plugins/PostProcessingPlugin/scripts/Stretch.py index 480ba60606..e56a9f48b1 100644 --- a/plugins/PostProcessingPlugin/scripts/Stretch.py +++ b/plugins/PostProcessingPlugin/scripts/Stretch.py @@ -289,6 +289,13 @@ class Stretcher: self.layergcode = self.layergcode + sout + "\n" ipos = ipos + 1 else: + # The command is intended to be passed through unmodified via + # the comment field. In the case of an extruder only move, though, + # the extruder and potentially the feed rate are modified. + # We need to update self.outpos accordingly so that subsequent calls + # to stepToGcode() knows about the extruder and feed rate change. + self.outpos.step_e = layer_steps[i].step_e + self.outpos.step_f = layer_steps[i].step_f self.layergcode = self.layergcode + layer_steps[i].comment + "\n" def workOnSequence(self, orig_seq, modif_seq): diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py index b8c5a30524..4abab245e8 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py @@ -74,7 +74,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): b"name": cluster.friendly_name.encode() if cluster.friendly_name else b"", b"firmware_version": cluster.host_version.encode() if cluster.host_version else b"", b"printer_type": cluster.printer_type.encode() if cluster.printer_type else b"", - b"cluster_size": b"1" # cloud devices are always clusters of at least one + b"cluster_size": str(cluster.printer_count).encode() if cluster.printer_count else b"1" } super().__init__( diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index c6aa5ef3f7..d2968acb2c 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -192,7 +192,11 @@ class CloudOutputDeviceManager: self._connectToActiveMachine() return - new_devices.sort(key = lambda x: x.name.lower()) + # Sort new_devices on online status first, alphabetical second. + # Since the first device might be activated in case there is no active printer yet, + # it would be nice to prioritize online devices + online_cluster_names = {c.friendly_name.lower() for c in clusters if c.is_online and not c.friendly_name is None} + new_devices.sort(key = lambda x: ("a{}" if x.name.lower() in online_cluster_names else "b{}").format(x.name.lower())) image_path = os.path.join( CuraApplication.getInstance().getPluginRegistry().getPluginPath("UM3NetworkPrinting") or "", @@ -376,6 +380,7 @@ class CloudOutputDeviceManager: machine.setName(device.name) machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key) machine.setMetaDataEntry("group_name", device.name) + machine.setMetaDataEntry("group_size", device.clusterSize) machine.setMetaDataEntry("removal_warning", self.I18N_CATALOG.i18nc( "@label ({} is printer name)", "{} will be removed until the next account sync.
To remove {} permanently, " diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py index 94f20b65c6..a9107db3c8 100644 --- a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py +++ b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py @@ -11,7 +11,7 @@ class CloudClusterResponse(BaseModel): def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str, host_internal_ip: Optional[str] = None, host_version: Optional[str] = None, - friendly_name: Optional[str] = None, printer_type: str = "ultimaker3", **kwargs) -> None: + friendly_name: Optional[str] = None, printer_type: str = "ultimaker3", printer_count: int = 1, **kwargs) -> None: """Creates a new cluster response object. :param cluster_id: The secret unique ID, e.g. 'kBEeZWEifXbrXviO8mRYLx45P8k5lHVGs43XKvRniPg='. @@ -23,6 +23,7 @@ class CloudClusterResponse(BaseModel): :param host_internal_ip: The internal IP address of the host printer. :param friendly_name: The human readable name of the host printer. :param printer_type: The machine type of the host printer. + :param printer_count: The amount of printers in the print cluster. 1 for a single printer """ self.cluster_id = cluster_id @@ -34,6 +35,7 @@ class CloudClusterResponse(BaseModel): self.host_internal_ip = host_internal_ip self.friendly_name = friendly_name self.printer_type = printer_type + self.printer_count = printer_count super().__init__(**kwargs) # Validates the model, raising an exception if the model is invalid.