From f82384d93c445cee18a9b5f20161cb80519a2d50 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 24 Jun 2021 14:20:38 +0200 Subject: [PATCH] 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