diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index a4d7bc303e..19f8174a95 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -207,6 +207,7 @@ class CuraApplication(QtApplication): self._first_start_machine_actions_model = None self._welcome_pages_model = WelcomePagesModel(self, parent = self) self._add_printer_pages_model = AddPrinterPagesModel(self, parent = self) + self._add_printer_pages_model_without_cancel = AddPrinterPagesModel(self, parent = self) self._whats_new_pages_model = WhatsNewPagesModel(self, parent = self) self._text_manager = TextManager(parent = self) @@ -647,7 +648,7 @@ class CuraApplication(QtApplication): return self._global_container_stack @override(Application) - def setGlobalContainerStack(self, stack: "GlobalStack") -> None: + def setGlobalContainerStack(self, stack: Optional["GlobalStack"]) -> None: self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing Active Machine...")) super().setGlobalContainerStack(stack) @@ -812,6 +813,7 @@ class CuraApplication(QtApplication): self._output_device_manager.start() self._welcome_pages_model.initialize() self._add_printer_pages_model.initialize() + self._add_printer_pages_model_without_cancel.initialize(cancellable = False) self._whats_new_pages_model.initialize() # Detect in which mode to run and execute that mode @@ -849,6 +851,7 @@ class CuraApplication(QtApplication): self.callLater(self._openFile, file_name) initializationFinished = pyqtSignal() + showAddPrintersUncancellableDialog = pyqtSignal() # Used to show the add printers dialog with a greyed background def runWithoutGUI(self): """Run Cura without GUI elements and interaction (server mode).""" @@ -939,6 +942,10 @@ class CuraApplication(QtApplication): def getAddPrinterPagesModel(self, *args) -> "AddPrinterPagesModel": return self._add_printer_pages_model + @pyqtSlot(result = QObject) + def getAddPrinterPagesModelWithoutCancel(self, *args) -> "AddPrinterPagesModel": + return self._add_printer_pages_model_without_cancel + @pyqtSlot(result = QObject) def getWhatsNewPagesModel(self, *args) -> "WhatsNewPagesModel": return self._whats_new_pages_model diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py index aa8552bebb..776d540867 100644 --- a/cura/Machines/Models/BaseMaterialsModel.py +++ b/cura/Machines/Models/BaseMaterialsModel.py @@ -154,7 +154,7 @@ class BaseMaterialsModel(ListModel): # Update the available materials (ContainerNode) for the current active machine and extruder setup. global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() - if not global_stack.hasMaterials: + if not global_stack or not global_stack.hasMaterials: return # There are no materials for this machine, so nothing to do. extruder_list = global_stack.extruderList if self._extruder_position > len(extruder_list): diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 523572dae0..423df167cd 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -290,9 +290,15 @@ class MachineManager(QObject): self.activeStackValueChanged.emit() @pyqtSlot(str) - def setActiveMachine(self, stack_id: str) -> None: + def setActiveMachine(self, stack_id: Optional[str]) -> None: self.blurSettings.emit() # Ensure no-one has focus. + if not stack_id: + self._application.setGlobalContainerStack(None) + self.globalContainerChanged.emit() + self._application.showAddPrintersUncancellableDialog.emit() + return + container_registry = CuraContainerRegistry.getInstance() containers = container_registry.findContainerStacks(id = stack_id) if not containers: @@ -721,6 +727,8 @@ class MachineManager(QObject): 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"]) + else: + self.setActiveMachine(None) metadatas = CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id) if not metadatas: diff --git a/cura/UI/AddPrinterPagesModel.py b/cura/UI/AddPrinterPagesModel.py index b06f220374..9b35dbcacc 100644 --- a/cura/UI/AddPrinterPagesModel.py +++ b/cura/UI/AddPrinterPagesModel.py @@ -10,12 +10,11 @@ from .WelcomePagesModel import WelcomePagesModel # class AddPrinterPagesModel(WelcomePagesModel): - def initialize(self) -> None: + def initialize(self, cancellable: bool = True) -> None: self._pages.append({"id": "add_network_or_local_printer", "page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"), "next_page_id": "machine_actions", "next_page_button_text": self._catalog.i18nc("@action:button", "Add"), - "previous_page_button_text": self._catalog.i18nc("@action:button", "Cancel"), }) self._pages.append({"id": "add_printer_by_ip", "page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"), @@ -30,6 +29,9 @@ class AddPrinterPagesModel(WelcomePagesModel): "page_url": self._getBuiltinWelcomePagePath("FirstStartMachineActionsContent.qml"), "should_show_function": self.shouldShowMachineActions, }) + if cancellable: + self._pages[0]["previous_page_button_text"] = self._catalog.i18nc("@action:button", "Cancel") + self.setItems(self._pages) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 9f93d0fe54..33c9caba05 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -4,6 +4,7 @@ import os from typing import Dict, List, Optional, Set from PyQt5.QtNetwork import QNetworkReply +from PyQt5.QtWidgets import QMessageBox from UM import i18nCatalog from UM.Logger import Logger # To log errors talking to the API. @@ -50,6 +51,7 @@ class CloudOutputDeviceManager: self._account = CuraApplication.getInstance().getCuraAPI().account # type: Account self._api = CloudApiClient(CuraApplication.getInstance(), on_error = lambda error: Logger.log("e", str(error))) self._account.loginStateChanged.connect(self._onLoginStateChanged) + self._removed_printers_message = None # type: Optional[Message] # Ensure we don't start twice. self._running = False @@ -120,6 +122,11 @@ class CloudOutputDeviceManager: self._um_cloud_printers[device_id].setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True) self._onDevicesDiscovered(new_clusters) + # Hide the current removed_printers_message, if there is any + if self._removed_printers_message: + self._removed_printers_message.actionTriggered.disconnect(self._onRemovedPrintersMessageActionTriggered) + self._removed_printers_message.hide() + # Remove the CloudOutput device for offline printers offline_device_keys = set(self._remote_clusters.keys()) - set(online_clusters.keys()) for device_id in offline_device_keys: @@ -269,14 +276,13 @@ class CloudOutputDeviceManager: return # Generate message - removed_printers_message = Message( + self._removed_printers_message = Message( title = self.I18N_CATALOG.i18ncp( "info:status", "Cloud connection is not available for a printer", "Cloud connection is not available for some printers", len(self.reported_device_ids) - ), - lifetime = 0 + ) ) device_names = "\n".join(["
  • {} ({})
  • ".format(self._um_cloud_printers[device].name, self._um_cloud_printers[device].definition.name) for device in self.reported_device_ids]) message_text = self.I18N_CATALOG.i18ncp( @@ -291,13 +297,19 @@ class CloudOutputDeviceManager: "Ultimaker Digital Factory.", device_names ) - removed_printers_message.setText(message_text) - removed_printers_message.addAction("keep_printer_configurations_action", - name = self.I18N_CATALOG.i18nc("@action:button", "Keep printer configurations"), - icon = "", - description = "Keep the configuration of the cloud printer(s) synced with Cura which are not linked to your account.", - button_align = Message.ActionButtonAlignment.ALIGN_RIGHT) - removed_printers_message.actionTriggered.connect(self._onRemovedPrintersMessageActionTriggered) + self._removed_printers_message.setText(message_text) + self._removed_printers_message.addAction("keep_printer_configurations_action", + name = self.I18N_CATALOG.i18nc("@action:button", "Keep printer configurations"), + icon = "", + description = "Keep the configuration of the cloud printer(s) synced with Cura which are not linked to your account.", + button_align = Message.ActionButtonAlignment.ALIGN_RIGHT) + self._removed_printers_message.addAction("remove_printers_action", + name = self.I18N_CATALOG.i18nc("@action:button", "Remove printers"), + icon = "", + description = "Remove the cloud printer(s) which are not linked to your account.", + button_style = Message.ActionButtonStyle.SECONDARY, + button_align = Message.ActionButtonAlignment.ALIGN_LEFT) + self._removed_printers_message.actionTriggered.connect(self._onRemovedPrintersMessageActionTriggered) output_device_manager = CuraApplication.getInstance().getOutputDeviceManager() @@ -314,7 +326,7 @@ class CloudOutputDeviceManager: # Update the printer's metadata to mark it as not linked to the account device.setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, False) - removed_printers_message.show() + self._removed_printers_message.show() def _onDiscoveredDeviceRemoved(self, device_id: str) -> None: device = self._remote_clusters.pop(device_id, None) # type: Optional[CloudOutputDevice] @@ -402,7 +414,23 @@ class CloudOutputDeviceManager: if container_cluster_id in self._remote_clusters.keys(): del self._remote_clusters[container_cluster_id] - @staticmethod - def _onRemovedPrintersMessageActionTriggered(removed_printers_message: Message, action: str) -> None: + def _onRemovedPrintersMessageActionTriggered(self, removed_printers_message: Message, action: str) -> None: if action == "keep_printer_configurations_action": removed_printers_message.hide() + elif action == "remove_printers_action": + machine_manager = CuraApplication.getInstance().getMachineManager() + remove_printers_ids = {self._um_cloud_printers[i].getId() for i in self.reported_device_ids} + all_ids = {m.getId() for m in CuraApplication.getInstance().getContainerRegistry().findContainerStacks(type = "machine")} + + question_title = self.I18N_CATALOG.i18nc("@title:window", "Remove printers?") + question_content = self.I18N_CATALOG.i18nc("@label", "You are about to remove {} printer(s) from Cura. This action cannot be undone. \nAre you sure you want to continue?".format(len(remove_printers_ids))) + if remove_printers_ids == all_ids: + question_content = self.I18N_CATALOG.i18nc("@label", "You are about to remove all printers from Cura. This action cannot be undone. \nAre you sure you want to continue?") + result = QMessageBox.question(None, question_title, question_content) + if result == QMessageBox.No: + return + + for machine_cloud_id in self.reported_device_ids: + machine_manager.setActiveMachine(self._um_cloud_printers[machine_cloud_id].getId()) + machine_manager.removeMachine(self._um_cloud_printers[machine_cloud_id].getId()) + removed_printers_message.hide() diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index ed2c6dc5fe..8ba651a5b0 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -84,6 +84,21 @@ UM.MainWindow CuraApplication.purgeWindows() } + Connections + { + // This connection is used when there is no ActiveMachine and the user is logged in + target: CuraApplication + onShowAddPrintersUncancellableDialog: + { + Cura.Actions.parent = backgroundItem + + // Reuse the welcome dialog item to show "Add a printer" only. + welcomeDialogItem.model = CuraApplication.getAddPrinterPagesModelWithoutCancel() + welcomeDialogItem.progressBarVisible = false + welcomeDialogItem.visible = true + } + } + Connections { target: CuraApplication @@ -117,6 +132,15 @@ UM.MainWindow welcomeDialogItem.progressBarVisible = false welcomeDialogItem.visible = true } + + // Reuse the welcome dialog item to show the "Add printers" dialog. Triggered when there is no active + // machine and the user is logged in. + if (!Cura.MachineManager.activeMachine && Cura.API.account.isLoggedIn) + { + welcomeDialogItem.model = CuraApplication.getAddPrinterPagesModelWithoutCancel() + welcomeDialogItem.progressBarVisible = false + welcomeDialogItem.visible = true + } } } diff --git a/resources/qml/Dialogs/WorkspaceSummaryDialog.qml b/resources/qml/Dialogs/WorkspaceSummaryDialog.qml index 6fe9607274..670766204f 100644 --- a/resources/qml/Dialogs/WorkspaceSummaryDialog.qml +++ b/resources/qml/Dialogs/WorkspaceSummaryDialog.qml @@ -143,7 +143,7 @@ UM.Dialog { width: parent.width height: childrenRect.height - model: Cura.MachineManager.activeMachine.extruderList + model: Cura.MachineManager.activeMachine ? Cura.MachineManager.activeMachine.extruderList : null delegate: Column { height: childrenRect.height diff --git a/resources/qml/Menus/ConfigurationMenu/ConfigurationMenu.qml b/resources/qml/Menus/ConfigurationMenu/ConfigurationMenu.qml index 5d8414846b..cb498bcef0 100644 --- a/resources/qml/Menus/ConfigurationMenu/ConfigurationMenu.qml +++ b/resources/qml/Menus/ConfigurationMenu/ConfigurationMenu.qml @@ -33,7 +33,7 @@ Cura.ExpandablePopup } contentPadding: UM.Theme.getSize("default_lining").width - enabled: Cura.MachineManager.activeMachine.hasMaterials || Cura.MachineManager.activeMachine.hasVariants || Cura.MachineManager.activeMachine.hasVariantBuildplates; //Only let it drop down if there is any configuration that you could change. + enabled: Cura.MachineManager.activeMachine ? Cura.MachineManager.activeMachine.hasMaterials || Cura.MachineManager.activeMachine.hasVariants || Cura.MachineManager.activeMachine.hasVariantBuildplates : false; //Only let it drop down if there is any configuration that you could change. headerItem: Item { @@ -84,7 +84,7 @@ Cura.ExpandablePopup { id: variantLabel - visible: Cura.MachineManager.activeMachine.hasVariants + visible: Cura.MachineManager.activeMachine ? Cura.MachineManager.activeMachine.hasVariants : false text: model.variant elide: Text.ElideRight @@ -114,7 +114,7 @@ Cura.ExpandablePopup color: UM.Theme.getColor("text") renderType: Text.NativeRendering - visible: !Cura.MachineManager.activeMachine.hasMaterials && (Cura.MachineManager.activeMachine.hasVariants || Cura.MachineManager.activeMachine.hasVariantBuildplates) + visible: Cura.MachineManager.activeMachine ? !Cura.MachineManager.activeMachine.hasMaterials && (Cura.MachineManager.activeMachine.hasVariants || Cura.MachineManager.activeMachine.hasVariantBuildplates) : false anchors { diff --git a/resources/qml/Menus/ConfigurationMenu/CustomConfiguration.qml b/resources/qml/Menus/ConfigurationMenu/CustomConfiguration.qml index 65f5bcce8c..010e2e77b0 100644 --- a/resources/qml/Menus/ConfigurationMenu/CustomConfiguration.qml +++ b/resources/qml/Menus/ConfigurationMenu/CustomConfiguration.qml @@ -244,7 +244,7 @@ Item Row { height: visible ? UM.Theme.getSize("print_setup_big_item").height : 0 - visible: Cura.MachineManager.activeMachine.hasMaterials + visible: Cura.MachineManager.activeMachine ? Cura.MachineManager.activeMachine.hasMaterials : false Label { @@ -305,7 +305,7 @@ Item Row { height: visible ? UM.Theme.getSize("print_setup_big_item").height : 0 - visible: Cura.MachineManager.activeMachine.hasVariants + visible: Cura.MachineManager.activeMachine ? Cura.MachineManager.activeMachine.hasVariants : false Label { diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedSupportSelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedSupportSelector.qml index f227dddaf9..92f0024b23 100644 --- a/resources/qml/PrintSetupSelector/Recommended/RecommendedSupportSelector.qml +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedSupportSelector.qml @@ -130,7 +130,11 @@ Item target: extruderModel onModelChanged: { - supportExtruderCombobox.color = supportExtruderCombobox.model.getItem(supportExtruderCombobox.currentIndex).color + var maybeColor = supportExtruderCombobox.model.getItem(supportExtruderCombobox.currentIndex).color + if (maybeColor) + { + supportExtruderCombobox.color = maybeColor + } } } onCurrentIndexChanged: diff --git a/resources/qml/PrinterSelector/MachineSelectorList.qml b/resources/qml/PrinterSelector/MachineSelectorList.qml index a7c041630f..18b1a68b20 100644 --- a/resources/qml/PrinterSelector/MachineSelectorList.qml +++ b/resources/qml/PrinterSelector/MachineSelectorList.qml @@ -28,11 +28,11 @@ ListView delegate: MachineSelectorButton { - text: model.name + text: model.name ? model.name : "" width: listView.width outputDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null - checked: Cura.MachineManager.activeMachine.id == model.id + checked: Cura.MachineManager.activeMachine ? Cura.MachineManager.activeMachine.id == model.id : false onClicked: {