Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Ultimaker/Cura.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaime van Kessel <nallath@gmail.com>2021-10-27 15:51:16 +0300
committerGitHub <noreply@github.com>2021-10-27 15:51:16 +0300
commitf47738f5587b5bd9add0d9f2165f6c95962fb050 (patch)
treeffb4e0a588bd2de80ef559c2fed6305298de7ded /cura/Machines
parentb9c9b820524c5523a8ea52cb08af08ff89a53e32 (diff)
parent79117d5898b77c2c9645fbb8ffa47c6b18d5770e (diff)
Merge pull request #10607 from Ultimaker/CURA-8609_sync_materials_to_printer
Sync materials to printers via cloud
Diffstat (limited to 'cura/Machines')
-rw-r--r--cura/Machines/Models/GlobalStacksModel.py48
-rw-r--r--cura/Machines/Models/MaterialManagementModel.py68
2 files changed, 63 insertions, 53 deletions
diff --git a/cura/Machines/Models/GlobalStacksModel.py b/cura/Machines/Models/GlobalStacksModel.py
index 712597c2e7..586bd11819 100644
--- a/cura/Machines/Models/GlobalStacksModel.py
+++ b/cura/Machines/Models/GlobalStacksModel.py
@@ -1,7 +1,8 @@
-# Copyright (c) 2018 Ultimaker B.V.
+# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from PyQt5.QtCore import Qt, QTimer
+from PyQt5.QtCore import Qt, QTimer, pyqtProperty, pyqtSignal
+from typing import Optional
from UM.Qt.ListModel import ListModel
from UM.i18n import i18nCatalog
@@ -20,6 +21,7 @@ class GlobalStacksModel(ListModel):
MetaDataRole = Qt.UserRole + 5
DiscoverySourceRole = Qt.UserRole + 6 # For separating local and remote printers in the machine management page
RemovalWarningRole = Qt.UserRole + 7
+ IsOnlineRole = Qt.UserRole + 8
def __init__(self, parent = None) -> None:
super().__init__(parent)
@@ -31,18 +33,49 @@ class GlobalStacksModel(ListModel):
self.addRoleName(self.HasRemoteConnectionRole, "hasRemoteConnection")
self.addRoleName(self.MetaDataRole, "metadata")
self.addRoleName(self.DiscoverySourceRole, "discoverySource")
+ self.addRoleName(self.IsOnlineRole, "isOnline")
self._change_timer = QTimer()
self._change_timer.setInterval(200)
self._change_timer.setSingleShot(True)
self._change_timer.timeout.connect(self._update)
+ self._filter_connection_type = None # type: Optional[ConnectionType]
+ self._filter_online_only = False
+
# Listen to changes
CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
CuraContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerChanged)
CuraContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
self._updateDelayed()
+ filterConnectionTypeChanged = pyqtSignal()
+ def setFilterConnectionType(self, new_filter: Optional[ConnectionType]) -> None:
+ self._filter_connection_type = new_filter
+
+ @pyqtProperty(int, fset = setFilterConnectionType, notify = filterConnectionTypeChanged)
+ def filterConnectionType(self) -> int:
+ """
+ The connection type to filter the list of printers by.
+
+ Only printers that match this connection type will be listed in the
+ model.
+ """
+ if self._filter_connection_type is None:
+ return -1
+ return self._filter_connection_type.value
+
+ filterOnlineOnlyChanged = pyqtSignal()
+ def setFilterOnlineOnly(self, new_filter: bool) -> None:
+ self._filter_online_only = new_filter
+
+ @pyqtProperty(bool, fset = setFilterOnlineOnly, notify = filterOnlineOnlyChanged)
+ def filterOnlineOnly(self) -> bool:
+ """
+ Whether to filter the global stacks to show only printers that are online.
+ """
+ return self._filter_online_only
+
def _onContainerChanged(self, container) -> None:
"""Handler for container added/removed events from registry"""
@@ -58,6 +91,10 @@ class GlobalStacksModel(ListModel):
container_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine")
for container_stack in container_stacks:
+ if self._filter_connection_type is not None: # We want to filter on connection types.
+ if not any((connection_type == self._filter_connection_type for connection_type in container_stack.configuredConnectionTypes)):
+ continue # No connection type on this printer matches the filter.
+
has_remote_connection = False
for connection_type in container_stack.configuredConnectionTypes:
@@ -67,6 +104,10 @@ class GlobalStacksModel(ListModel):
if parseBool(container_stack.getMetaDataEntry("hidden", False)):
continue
+ is_online = container_stack.getMetaDataEntry("is_online", False)
+ if self._filter_online_only and not is_online:
+ continue
+
device_name = container_stack.getMetaDataEntry("group_name", container_stack.getName())
section_name = "Connected printers" if has_remote_connection else "Preset printers"
section_name = self._catalog.i18nc("@info:title", section_name)
@@ -82,6 +123,7 @@ class GlobalStacksModel(ListModel):
"hasRemoteConnection": has_remote_connection,
"metadata": container_stack.getMetaData().copy(),
"discoverySource": section_name,
- "removalWarning": removal_warning})
+ "removalWarning": removal_warning,
+ "isOnline": is_online})
items.sort(key=lambda i: (not i["hasRemoteConnection"], i["name"]))
self.setItems(items)
diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py
index 53c7721a8e..de91703ecf 100644
--- a/cura/Machines/Models/MaterialManagementModel.py
+++ b/cura/Machines/Models/MaterialManagementModel.py
@@ -2,21 +2,21 @@
# Cura is released under the terms of the LGPLv3 or higher.
import copy # To duplicate materials.
-from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl
+from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
+from PyQt5.QtGui import QDesktopServices
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 PyQt5.QtGui import QDesktopServices
+from UM.Message import Message
from UM.i18n import i18nCatalog
from UM.Logger import Logger
-from UM.Message import Message
+from UM.Resources import Resources # To find QML files.
from UM.Signal import postponeSignals, CompressTechnique
import cura.CuraApplication # Imported like this to prevent circular imports.
from cura.Machines.ContainerTree import ContainerTree
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To find the sets of materials belonging to each other, and currently loaded extruder stacks.
+from cura.UltimakerCloud.CloudMaterialSync import CloudMaterialSync
if TYPE_CHECKING:
from cura.Machines.MaterialNode import MaterialNode
@@ -33,6 +33,7 @@ class MaterialManagementModel(QObject):
def __init__(self, parent: Optional[QObject] = None) -> None:
super().__init__(parent = parent)
+ self._material_sync = CloudMaterialSync(parent=self)
self._checkIfNewMaterialsWereInstalled()
def _checkIfNewMaterialsWereInstalled(self) -> None:
@@ -89,6 +90,7 @@ class MaterialManagementModel(QObject):
elif sync_message_action == "learn_more":
QDesktopServices.openUrl(QUrl("https://support.ultimaker.com/hc/en-us/articles/360013137919?utm_source=cura&utm_medium=software&utm_campaign=sync-material-printer-message"))
+
@pyqtSlot("QVariant", result = bool)
def canMaterialBeRemoved(self, material_node: "MaterialNode") -> bool:
"""Can a certain material be deleted, or is it still in use in one of the container stacks anywhere?
@@ -323,52 +325,18 @@ 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))
- @pyqtSlot(result = QUrl)
- def getPreferredExportAllPath(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.
+ @pyqtSlot()
+ def openSyncAllWindow(self) -> None:
"""
- 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:
+ Opens the window to sync all materials.
"""
- Export all materials to a certain file path.
- :param file_path: The path to export the materials to.
- """
- registry = CuraContainerRegistry.getInstance()
+ self._material_sync.reset()
- try:
- archive = zipfile.ZipFile(file_path.toLocalFile(), "w", compression = zipfile.ZIP_DEFLATED)
- except OSError as e:
- Logger.log("e", f"Can't write to destination {file_path.toLocalFile()}: {type(e)} - {str(e)}")
- error_message = Message(
- text = catalog.i18nc("@error:text Followed by an error message of why it could not save", "Could not save material archive to {filename}:").format(filename = file_path.toLocalFile()) + " " + str(e),
- title = catalog.i18nc("@error:title", "Failed to save material archive"),
- message_type = Message.MessageType.ERROR
- )
- error_message.show()
+ if self._material_sync.sync_all_dialog is None:
+ qml_path = Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.QmlFiles, "Preferences", "Materials", "MaterialsSyncDialog.qml")
+ self._material_sync.sync_all_dialog = cura.CuraApplication.CuraApplication.getInstance().createQmlComponent(qml_path, {})
+ if self._material_sync.sync_all_dialog is None: # Failed to load QML file.
return
- 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
- try:
- archive.writestr(filename, material.serialize())
- except OSError as e:
- Logger.log("e", f"An error has occurred while writing the material \'{metadata['id']}\' in the file \'{filename}\': {e}.")
+ self._material_sync.sync_all_dialog.setProperty("syncModel", self._material_sync)
+ self._material_sync.sync_all_dialog.setProperty("pageIndex", 0) # Return to first page.
+ self._material_sync.sync_all_dialog.show()