Browse Source

Merge branch 'master' into CURA-7689_beta_release_notifications

Konstantinos Karmas 3 years ago
parent
commit
b006d9111f

+ 11 - 0
CITATION.cff

@@ -0,0 +1,11 @@
+# YAML 1.2
+---
+authors: 
+cff-version: "1.1.0"
+date-released: 2021-06-28
+license: "LGPL-3.0"
+message: "If you use this software, please cite it using these metadata."
+repository-code: "https://github.com/ultimaker/cura/"
+title: "Ultimaker Cura"
+version: "4.10.0"
+...

+ 4 - 2
cura/API/Account.py

@@ -178,7 +178,9 @@ class Account(QObject):
             if self._error_message:
                 self._error_message.hide()
             Logger.log("w", "Failed to login: %s", error_message)
-            self._error_message = Message(error_message, title = i18n_catalog.i18nc("@info:title", "Login failed"))
+            self._error_message = Message(error_message,
+                                          title = i18n_catalog.i18nc("@info:title", "Login failed"),
+                                          message_type = Message.MessageType.ERROR)
             self._error_message.show()
             self._logged_in = False
             self.loginStateChanged.emit(False)
@@ -209,7 +211,7 @@ class Account(QObject):
         if self._update_timer.isActive():
             self._update_timer.stop()
         elif self._sync_state == SyncState.SYNCING:
-            Logger.warning("Starting a new sync while previous sync was not completed\n{}", str(self._sync_services))
+            Logger.debug("Starting a new sync while previous sync was not completed")
 
         self.syncRequested.emit()
 

+ 4 - 2
cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py

@@ -147,6 +147,8 @@ class ArrangeObjectsAllBuildPlatesJob(Job):
         status_message.hide()
 
         if not found_solution_for_all:
-            no_full_solution_message = Message(i18n_catalog.i18nc("@info:status", "Unable to find a location within the build volume for all objects"),
-                                               title = i18n_catalog.i18nc("@info:title", "Can't Find Location"))
+            no_full_solution_message = Message(i18n_catalog.i18nc("@info:status",
+                                                                  "Unable to find a location within the build volume for all objects"),
+                                               title = i18n_catalog.i18nc("@info:title", "Can't Find Location"),
+                                               message_type = Message.MessageType.WARNING)
             no_full_solution_message.show()

+ 2 - 1
cura/Arranging/ArrangeObjectsJob.py

@@ -39,6 +39,7 @@ class ArrangeObjectsJob(Job):
             no_full_solution_message = Message(
                     i18n_catalog.i18nc("@info:status",
                                        "Unable to find a location within the build volume for all objects"),
-                    title = i18n_catalog.i18nc("@info:title", "Can't Find Location"))
+                    title = i18n_catalog.i18nc("@info:title", "Can't Find Location"),
+                    message_type = Message.MessageType.ERROR)
             no_full_solution_message.show()
         self.finished.emit(self)

+ 16 - 12
cura/Backups/Backup.py

@@ -111,15 +111,15 @@ class Backup:
             return archive
         except (IOError, OSError, BadZipfile) as error:
             Logger.log("e", "Could not create archive from user data directory: %s", error)
-            self._showMessage(
-                self.catalog.i18nc("@info:backup_failed",
-                                   "Could not create archive from user data directory: {}".format(error)))
+            self._showMessage(self.catalog.i18nc("@info:backup_failed",
+                                                 "Could not create archive from user data directory: {}".format(error)),
+                              message_type = Message.MessageType.ERROR)
             return None
 
-    def _showMessage(self, message: str) -> None:
+    def _showMessage(self, message: str, message_type: Message.MessageType = Message.MessageType.NEUTRAL) -> None:
         """Show a UI message."""
 
-        Message(message, title=self.catalog.i18nc("@info:title", "Backup"), lifetime=30).show()
+        Message(message, title=self.catalog.i18nc("@info:title", "Backup"), message_type = message_type).show()
 
     def restore(self) -> bool:
         """Restore this back-up.
@@ -130,9 +130,9 @@ class Backup:
         if not self.zip_file or not self.meta_data or not self.meta_data.get("cura_release", None):
             # We can restore without the minimum required information.
             Logger.log("w", "Tried to restore a Cura backup without having proper data or meta data.")
-            self._showMessage(
-                self.catalog.i18nc("@info:backup_failed",
-                                   "Tried to restore a Cura backup without having proper data or meta data."))
+            self._showMessage(self.catalog.i18nc("@info:backup_failed",
+                                                 "Tried to restore a Cura backup without having proper data or meta data."),
+                              message_type = Message.MessageType.ERROR)
             return False
 
         current_version = Version(self._application.getVersion())
@@ -141,9 +141,9 @@ class Backup:
         if current_version < version_to_restore:
             # Cannot restore version newer than current because settings might have changed.
             Logger.log("d", "Tried to restore a Cura backup of version {version_to_restore} with cura version {current_version}".format(version_to_restore = version_to_restore, current_version = current_version))
-            self._showMessage(
-                self.catalog.i18nc("@info:backup_failed",
-                                   "Tried to restore a Cura backup that is higher than the current version."))
+            self._showMessage(self.catalog.i18nc("@info:backup_failed",
+                                                 "Tried to restore a Cura backup that is higher than the current version."),
+                              message_type = Message.MessageType.ERROR)
             return False
 
         # Get the current secrets and store since the back-up doesn't contain those
@@ -154,7 +154,11 @@ class Backup:
             archive = ZipFile(io.BytesIO(self.zip_file), "r")
         except LookupError as e:
             Logger.log("d", f"The following error occurred while trying to restore a Cura backup: {str(e)}")
-            self._showMessage(self.catalog.i18nc("@info:backup_failed", "The following error occurred while trying to restore a Cura backup:") + str(e))
+            Message(self.catalog.i18nc("@info:backup_failed",
+                                       "The following error occurred while trying to restore a Cura backup:") + str(e),
+                    title = self.catalog.i18nc("@info:title", "Backup"),
+                    message_type = Message.MessageType.ERROR).show()
+
             return False
         extracted = self._extractArchive(archive, version_data_dir)
 

+ 5 - 3
cura/BuildVolume.py

@@ -95,9 +95,11 @@ class BuildVolume(SceneNode):
         self._edge_disallowed_size = None
 
         self._build_volume_message = Message(catalog.i18nc("@info:status",
-            "The build volume height has been reduced due to the value of the"
-            " \"Print Sequence\" setting to prevent the gantry from colliding"
-            " with printed models."), title = catalog.i18nc("@info:title", "Build Volume"))
+                "The build volume height has been reduced due to the value of the"
+                " \"Print Sequence\" setting to prevent the gantry from colliding"
+                " with printed models."),
+            title = catalog.i18nc("@info:title", "Build Volume"),
+            message_type = Message.MessageType.WARNING)
 
         self._global_container_stack = None  # type: Optional[GlobalStack]
 

+ 8 - 4
cura/CuraApplication.py

@@ -1798,8 +1798,10 @@ class CuraApplication(QtApplication):
             if extension in self._non_sliceable_extensions:
                 message = Message(
                     self._i18n_catalog.i18nc("@info:status",
-                                       "Only one G-code file can be loaded at a time. Skipped importing {0}",
-                                       filename), title = self._i18n_catalog.i18nc("@info:title", "Warning"))
+                                             "Only one G-code file can be loaded at a time. Skipped importing {0}",
+                                             filename),
+                    title = self._i18n_catalog.i18nc("@info:title", "Warning"),
+                    message_type = Message.MessageType.WARNING)
                 message.show()
                 return
             # If file being loaded is non-slicable file, then prevent loading of any other files
@@ -1808,8 +1810,10 @@ class CuraApplication(QtApplication):
             if extension in self._non_sliceable_extensions:
                 message = Message(
                     self._i18n_catalog.i18nc("@info:status",
-                                       "Can't open any other file if G-code is loading. Skipped importing {0}",
-                                       filename), title = self._i18n_catalog.i18nc("@info:title", "Error"))
+                                             "Can't open any other file if G-code is loading. Skipped importing {0}",
+                                             filename),
+                    title = self._i18n_catalog.i18nc("@info:title", "Error"),
+                    message_type = Message.MessageType.ERROR)
                 message.show()
                 return
 

+ 39 - 6
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
@@ -20,11 +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.
-    """
-
     favoritesChanged = pyqtSignal(str)
     """Triggered when a favorite is added or removed.
 
@@ -264,3 +260,40 @@ 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))
+
+    @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.
+        """
+        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:
+        """
+        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())

+ 2 - 1
cura/MultiplyObjectsJob.py

@@ -74,5 +74,6 @@ class MultiplyObjectsJob(Job):
         if not found_solution_for_all:
             no_full_solution_message = Message(
                 i18n_catalog.i18nc("@info:status", "Unable to find a location within the build volume for all objects"),
-                title = i18n_catalog.i18nc("@info:title", "Placing Object"))
+                title = i18n_catalog.i18nc("@info:title", "Placing Object"),
+                message_type = Message.MessageType.WARNING)
             no_full_solution_message.show()

+ 8 - 3
cura/OAuth2/AuthorizationService.py

@@ -186,8 +186,10 @@ class AuthorizationService:
             self._server.start(verification_code, state)
         except OSError:
             Logger.logException("w", "Unable to create authorization request server")
-            Message(i18n_catalog.i18nc("@info", "Unable to start a new sign in process. Check if another sign in attempt is still active."),
-                    title=i18n_catalog.i18nc("@info:title", "Warning")).show()
+            Message(i18n_catalog.i18nc("@info",
+                                       "Unable to start a new sign in process. Check if another sign in attempt is still active."),
+                    title=i18n_catalog.i18nc("@info:title", "Warning"),
+                    message_type = Message.MessageType.WARNING).show()
             return
 
         auth_url = self._generate_auth_url(query_parameters_dict, force_browser_logout)
@@ -241,7 +243,10 @@ class AuthorizationService:
                     if self._unable_to_get_data_message is not None:
                         self._unable_to_get_data_message.hide()
 
-                    self._unable_to_get_data_message = Message(i18n_catalog.i18nc("@info", "Unable to reach the Ultimaker account server."), title = i18n_catalog.i18nc("@info:title", "Warning"))
+                    self._unable_to_get_data_message = Message(i18n_catalog.i18nc("@info",
+                                                                                  "Unable to reach the Ultimaker account server."),
+                                                               title = i18n_catalog.i18nc("@info:title", "Warning"),
+                                                               message_type = Message.MessageType.ERROR)
                     self._unable_to_get_data_message.show()
         except (ValueError, TypeError):
             Logger.logException("w", "Could not load auth data from preferences")

Some files were not shown because too many files changed in this diff