From f82384d93c445cee18a9b5f20161cb80519a2d50 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 24 Jun 2021 14:20:38 +0200 Subject: [PATCH 1/2] Revert "Revert CURA-8055 in the 4.10 branch. This should not have been merged in there." This reverts commit 4d29de45796dc42f76c774a216a0601bb57cf790. Effectively this re-applies the changes for CURA-8055 / #9957. --- .../Models/MaterialManagementModel.py | 57 ++++++++++++++++++- cura/Settings/GlobalStack.py | 8 +++ .../ultimaker2_plus_connect.def.json | 3 +- resources/definitions/ultimaker_s3.def.json | 1 + resources/definitions/ultimaker_s5.def.json | 1 + .../Preferences/Materials/MaterialsPage.qml | 32 ++++++++++- 6 files changed, 98 insertions(+), 4 deletions(-) diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py index cd35a78353..85f208d8b0 100644 --- a/cura/Machines/Models/MaterialManagementModel.py +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -2,9 +2,10 @@ # Cura is released under the terms of the LGPLv3 or higher. import copy # To duplicate materials. -from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot # To allow the preference page proxy to be used from the actual preferences page. +from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl from typing import Any, Dict, Optional, TYPE_CHECKING import uuid # To generate new GUIDs for new materials. +import zipfile # To export all materials in a .zip archive. from UM.i18n import i18nCatalog from UM.Logger import Logger @@ -24,6 +25,11 @@ class MaterialManagementModel(QObject): This class handles the actions in that page, such as creating new materials, renaming them, etc. """ + def __init__(self, parent: QObject) -> None: + super().__init__(parent) + cura_application = cura.CuraApplication.CuraApplication.getInstance() + self._preferred_export_all_path = None # type: Optional[QUrl] # Path to export all materials to. None if not yet initialised. + cura_application.getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged) favoritesChanged = pyqtSignal(str) """Triggered when a favorite is added or removed. @@ -264,3 +270,52 @@ class MaterialManagementModel(QObject): self.favoritesChanged.emit(material_base_file) except ValueError: # Material was not in the favorites list. Logger.log("w", "Material {material_base_file} was already not a favorite material.".format(material_base_file = material_base_file)) + + def _onOutputDevicesChanged(self) -> None: + """ + When the list of output devices changes, we may want to update the + preferred export path. + """ + cura_application = cura.CuraApplication.CuraApplication.getInstance() + device_manager = cura_application.getOutputDeviceManager() + devices = device_manager.getOutputDevices() + for device in devices: + if device.__class__.__name__ == "RemovableDriveOutputDevice": + self._preferred_export_all_path = QUrl.fromLocalFile(device.getId()) + break + else: # No removable drives? Use local path. + self._preferred_export_all_path = cura_application.getDefaultPath("dialog_material_path") + self.outputDevicesChanged.emit() + + outputDevicesChanged = pyqtSignal() # Triggered when adding or removing removable drives. + @pyqtProperty(QUrl, notify = outputDevicesChanged) + def preferredExportAllPath(self) -> QUrl: + """ + Get the preferred path to export materials to. + + If there is a removable drive, that should be the preferred path. Otherwise it should be the most recent local + file path. + :return: The preferred path to export all materials to. + """ + if self._preferred_export_all_path is None: # Not initialised yet. Can happen when output devices changed before class got created. + self._onOutputDevicesChanged() + return self._preferred_export_all_path + + @pyqtSlot(QUrl) + def exportAll(self, file_path: QUrl) -> None: + """ + Export all materials to a certain file path. + :param file_path: The path to export the materials to. + """ + registry = CuraContainerRegistry.getInstance() + + archive = zipfile.ZipFile(file_path.toLocalFile(), "w", compression = zipfile.ZIP_DEFLATED) + for metadata in registry.findInstanceContainersMetadata(type = "material"): + if metadata["base_file"] != metadata["id"]: # Only process base files. + continue + if metadata["id"] == "empty_material": # Don't export the empty material. + continue + material = registry.findContainers(id = metadata["id"])[0] + suffix = registry.getMimeTypeForContainer(type(material)).preferredSuffix + filename = metadata["id"] + "." + suffix + archive.writestr(filename, material.serialize()) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 2c7cbf5e25..282034c0ee 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -86,6 +86,14 @@ class GlobalStack(CuraContainerStack): def supportsNetworkConnection(self): return self.getMetaDataEntry("supports_network_connection", False) + @pyqtProperty(bool, constant = True) + def supportsMaterialExport(self): + """ + Whether the printer supports Cura's export format of material profiles. + :return: ``True`` if it supports it, or ``False`` if not. + """ + return self.getMetaDataEntry("supports_material_export", False) + @classmethod def getLoadingPriority(cls) -> int: return 2 diff --git a/resources/definitions/ultimaker2_plus_connect.def.json b/resources/definitions/ultimaker2_plus_connect.def.json index c0ddcf813f..46c615a262 100644 --- a/resources/definitions/ultimaker2_plus_connect.def.json +++ b/resources/definitions/ultimaker2_plus_connect.def.json @@ -22,7 +22,8 @@ "0": "ultimaker2_plus_connect_extruder_0" }, "supports_usb_connection": false, - "supports_network_connection": true + "supports_network_connection": true, + "supports_material_export": true }, "overrides": { diff --git a/resources/definitions/ultimaker_s3.def.json b/resources/definitions/ultimaker_s3.def.json index 962bff3fa0..43f32a96dc 100644 --- a/resources/definitions/ultimaker_s3.def.json +++ b/resources/definitions/ultimaker_s3.def.json @@ -27,6 +27,7 @@ "first_start_actions": [ "DiscoverUM3Action" ], "supported_actions": [ "DiscoverUM3Action" ], "supports_usb_connection": false, + "supports_material_export": true, "weight": -1, "firmware_update_info": { "id": 213482, diff --git a/resources/definitions/ultimaker_s5.def.json b/resources/definitions/ultimaker_s5.def.json index 8a9880c31a..71de826953 100644 --- a/resources/definitions/ultimaker_s5.def.json +++ b/resources/definitions/ultimaker_s5.def.json @@ -28,6 +28,7 @@ "supported_actions": [ "DiscoverUM3Action" ], "supports_usb_connection": false, "supports_network_connection": true, + "supports_material_export": true, "weight": -2, "firmware_update_info": { "id": 9051, diff --git a/resources/qml/Preferences/Materials/MaterialsPage.qml b/resources/qml/Preferences/Materials/MaterialsPage.qml index 791d6685de..8d6dfdfb3a 100644 --- a/resources/qml/Preferences/Materials/MaterialsPage.qml +++ b/resources/qml/Preferences/Materials/MaterialsPage.qml @@ -1,5 +1,5 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Uranium is released under the terms of the LGPLv3 or higher. +// Copyright (c) 2021 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.7 import QtQuick.Controls 1.4 @@ -191,6 +191,20 @@ Item } enabled: base.hasCurrentItem } + + //Sync button. + Button + { + id: syncMaterialsButton + text: catalog.i18nc("@action:button Sending materials to printers", "Sync with Printers") + iconName: "sync-synchronizing" + onClicked: + { + forceActiveFocus(); + exportAllMaterialsDialog.open(); + } + visible: Cura.MachineManager.activeMachine.supportsMaterialExport + } } Item { @@ -368,6 +382,20 @@ Item } } + FileDialog + { + id: exportAllMaterialsDialog + title: catalog.i18nc("@title:window", "Export All Materials") + selectExisting: false + nameFilters: ["Material archives (*.umm)", "All files (*)"] + folder: base.materialManagementModel.preferredExportAllPath + onAccepted: + { + base.materialManagementModel.exportAll(fileUrl); + CuraApplication.setDefaultPath("dialog_material_path", folder); + } + } + MessageDialog { id: messageDialog From a4f6e94ae0a038119701f45380cc232b71b2abe7 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 23 Jul 2021 15:16:38 +0200 Subject: [PATCH 2/2] Update the sync-storage path every time you sync Instead of updating the storage path every time you add or remove a removable drive, we now update the storage path every time you press the button to sync. That way this detail has no impact on performance of other parts of Cura if they don't use this button. It also makes the code a bit simpler. The only downside is that this FileDialog then contains state, instead of automatically syncing with the MaterialManagement property for its folder property. I see that as a lesser of two evils. Contributes to issue CURA-8055. --- .../Models/MaterialManagementModel.py | 42 +++++-------------- .../Preferences/Materials/MaterialsPage.qml | 2 +- 2 files changed, 11 insertions(+), 33 deletions(-) diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py index 85f208d8b0..6663dbdae1 100644 --- a/cura/Machines/Models/MaterialManagementModel.py +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -21,16 +21,6 @@ if TYPE_CHECKING: catalog = i18nCatalog("cura") class MaterialManagementModel(QObject): - """Proxy class to the materials page in the preferences. - - This class handles the actions in that page, such as creating new materials, renaming them, etc. - """ - def __init__(self, parent: QObject) -> None: - super().__init__(parent) - cura_application = cura.CuraApplication.CuraApplication.getInstance() - self._preferred_export_all_path = None # type: Optional[QUrl] # Path to export all materials to. None if not yet initialised. - cura_application.getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged) - favoritesChanged = pyqtSignal(str) """Triggered when a favorite is added or removed. @@ -271,25 +261,8 @@ class MaterialManagementModel(QObject): except ValueError: # Material was not in the favorites list. Logger.log("w", "Material {material_base_file} was already not a favorite material.".format(material_base_file = material_base_file)) - def _onOutputDevicesChanged(self) -> None: - """ - When the list of output devices changes, we may want to update the - preferred export path. - """ - cura_application = cura.CuraApplication.CuraApplication.getInstance() - device_manager = cura_application.getOutputDeviceManager() - devices = device_manager.getOutputDevices() - for device in devices: - if device.__class__.__name__ == "RemovableDriveOutputDevice": - self._preferred_export_all_path = QUrl.fromLocalFile(device.getId()) - break - else: # No removable drives? Use local path. - self._preferred_export_all_path = cura_application.getDefaultPath("dialog_material_path") - self.outputDevicesChanged.emit() - - outputDevicesChanged = pyqtSignal() # Triggered when adding or removing removable drives. - @pyqtProperty(QUrl, notify = outputDevicesChanged) - def preferredExportAllPath(self) -> QUrl: + @pyqtSlot(result = QUrl) + def getPreferredExportAllPath(self) -> QUrl: """ Get the preferred path to export materials to. @@ -297,9 +270,14 @@ class MaterialManagementModel(QObject): file path. :return: The preferred path to export all materials to. """ - if self._preferred_export_all_path is None: # Not initialised yet. Can happen when output devices changed before class got created. - self._onOutputDevicesChanged() - return self._preferred_export_all_path + cura_application = cura.CuraApplication.CuraApplication.getInstance() + device_manager = cura_application.getOutputDeviceManager() + devices = device_manager.getOutputDevices() + for device in devices: + if device.__class__.__name__ == "RemovableDriveOutputDevice": + return QUrl.fromLocalFile(device.getId()) + else: # No removable drives? Use local path. + return cura_application.getDefaultPath("dialog_material_path") @pyqtSlot(QUrl) def exportAll(self, file_path: QUrl) -> None: diff --git a/resources/qml/Preferences/Materials/MaterialsPage.qml b/resources/qml/Preferences/Materials/MaterialsPage.qml index 8d6dfdfb3a..4de3ad918b 100644 --- a/resources/qml/Preferences/Materials/MaterialsPage.qml +++ b/resources/qml/Preferences/Materials/MaterialsPage.qml @@ -201,6 +201,7 @@ Item onClicked: { forceActiveFocus(); + exportAllMaterialsDialog.folder = base.materialManagementModel.getPreferredExportAllPath(); exportAllMaterialsDialog.open(); } visible: Cura.MachineManager.activeMachine.supportsMaterialExport @@ -388,7 +389,6 @@ Item title: catalog.i18nc("@title:window", "Export All Materials") selectExisting: false nameFilters: ["Material archives (*.umm)", "All files (*)"] - folder: base.materialManagementModel.preferredExportAllPath onAccepted: { base.materialManagementModel.exportAll(fileUrl);