Browse Source

Merge branch 'master' of https://github.com/Ultimaker/Cura

Matt Jani 5 years ago
parent
commit
155408d77d

+ 6 - 5
cura/API/__init__.py

@@ -28,11 +28,12 @@ class CuraAPI(QObject):
     #   The main reason for this is that we want to prevent consumers of API to have a dependency on CuraApplication.
     #   Since the API is intended to be used by plugins, the cura application should have already created this.
     def __new__(cls, application: Optional["CuraApplication"] = None):
-        if cls.__instance is None:
-            if application is None:
-                raise Exception("Upon first time creation, the application must be set.")
-            cls.__instance = super(CuraAPI, cls).__new__(cls)
-            cls._application = application
+        if cls.__instance is not None:
+            raise RuntimeError("Tried to create singleton '{class_name}' more than once.".format(class_name = CuraAPI.__name__))
+        if application is None:
+            raise RuntimeError("Upon first time creation, the application must be set.")
+        cls.__instance = super(CuraAPI, cls).__new__(cls)
+        cls._application = application
         return cls.__instance
 
     def __init__(self, application: Optional["CuraApplication"] = None) -> None:

+ 5 - 0
cura/Settings/MachineManager.py

@@ -747,6 +747,11 @@ class MachineManager(QObject):
         result = []  # type: List[str]
         for setting_instance in container.findInstances():
             setting_key = setting_instance.definition.key
+            if setting_key == "print_sequence":
+                old_value = container.getProperty(setting_key, "value")
+                Logger.log("d", "Reset setting [%s] in [%s] because its old value [%s] is no longer valid", setting_key, container, old_value)
+                result.append(setting_key)
+                continue
             if not self._global_container_stack.getProperty(setting_key, "type") in ("extruder", "optional_extruder"):
                 continue
 

+ 6 - 2
cura_app.py

@@ -29,9 +29,13 @@ parser.add_argument("--debug",
 known_args = vars(parser.parse_known_args()[0])
 
 if with_sentry_sdk:
-    sentry_env = "production"
-    if ApplicationMetadata.CuraVersion == "master":
+    sentry_env = "unknown"  # Start off with a "IDK"
+    if hasattr(sys, "frozen"):
+        sentry_env = "production"  # A frozen build is a "real" distribution.
+    elif ApplicationMetadata.CuraVersion == "master":
         sentry_env = "development"
+    elif "beta" in ApplicationMetadata.CuraVersion or "BETA" in ApplicationMetadata.CuraVersion:
+        sentry_env = "beta"
     try:
         if ApplicationMetadata.CuraVersion.split(".")[2] == "99":
             sentry_env = "nightly"

+ 21 - 2
plugins/3MFReader/ThreeMFWorkspaceReader.py

@@ -4,7 +4,8 @@
 from configparser import ConfigParser
 import zipfile
 import os
-from typing import cast, Dict, List, Optional, Tuple
+import json
+from typing import cast, Dict, List, Optional, Tuple, Any
 
 import xml.etree.ElementTree as ET
 
@@ -732,7 +733,25 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
 
         base_file_name = os.path.basename(file_name)
         self.setWorkspaceName(base_file_name)
-        return nodes
+
+        return nodes, self._loadMetadata(file_name)
+
+    @staticmethod
+    def _loadMetadata(file_name: str) -> Dict[str, Dict[str, Any]]:
+        archive = zipfile.ZipFile(file_name, "r")
+
+        metadata_files = [name for name in archive.namelist() if name.endswith("plugin_metadata.json")]
+
+        result = dict()
+
+        for metadata_file in metadata_files:
+            try:
+                plugin_id = metadata_file.split("/")[0]
+                result[plugin_id] = json.loads(archive.open("%s/plugin_metadata.json" % plugin_id).read().decode("utf-8"))
+            except Exception:
+                Logger.logException("w", "Unable to retrieve metadata for %s", metadata_file)
+
+        return result
 
     def _processQualityChanges(self, global_stack):
         if self._machine_info.quality_changes_info is None:

+ 14 - 0
plugins/3MFWriter/ThreeMFWorkspaceWriter.py

@@ -73,11 +73,25 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
         version_config_parser.write(version_file_string)
         archive.writestr(version_file, version_file_string.getvalue())
 
+        self._writePluginMetadataToArchive(archive)
+
         # Close the archive & reset states.
         archive.close()
         mesh_writer.setStoreArchive(False)
         return True
 
+    @staticmethod
+    def _writePluginMetadataToArchive(archive: zipfile.ZipFile) -> None:
+        file_name_template = "%s/plugin_metadata.json"
+
+        for plugin_id, metadata in Application.getInstance().getWorkspaceMetadataStorage().getAllData().items():
+            file_name = file_name_template % plugin_id
+            file_in_archive = zipfile.ZipInfo(file_name)
+            # We have to set the compress type of each file as well (it doesn't keep the type of the entire archive)
+            file_in_archive.compress_type = zipfile.ZIP_DEFLATED
+            import json
+            archive.writestr(file_in_archive, json.dumps(metadata, separators = (", ", ": "), indent = 4, skipkeys = True))
+
     ##  Helper function that writes ContainerStacks, InstanceContainers and DefinitionContainers to the archive.
     #   \param container That follows the \type{ContainerInterface} to archive.
     #   \param archive The archive to write to.

+ 2 - 1
plugins/3MFWriter/ThreeMFWriter.py

@@ -1,5 +1,6 @@
 # Copyright (c) 2015 Ultimaker B.V.
 # Uranium is released under the terms of the LGPLv3 or higher.
+from typing import Optional
 
 from UM.Mesh.MeshWriter import MeshWriter
 from UM.Math.Vector import Vector
@@ -40,7 +41,7 @@ class ThreeMFWriter(MeshWriter):
         }
 
         self._unit_matrix_string = self._convertMatrixToString(Matrix())
-        self._archive = None
+        self._archive = None  # type: Optional[zipfile.ZipFile]
         self._store_archive = False
 
     def _convertMatrixToString(self, matrix):

+ 9 - 2
plugins/PostProcessingPlugin/scripts/DisplayFilenameAndLayerOnLCD.py

@@ -72,18 +72,25 @@ class DisplayFilenameAndLayerOnLCD(Script):
             lcd_text = "M117 Printing " + name + " - Layer "
         i = self.getSettingValueByKey("startNum")
         for layer in data:
-            display_text = lcd_text + str(i) + " " + name
+            display_text = lcd_text + str(i)
             layer_index = data.index(layer)
             lines = layer.split("\n")
             for line in lines:
                 if line.startswith(";LAYER_COUNT:"):
                     max_layer = line
                     max_layer = max_layer.split(":")[1]
+                    if self.getSettingValueByKey("startNum") == 0:
+                        max_layer = str(int(max_layer) - 1)
                 if line.startswith(";LAYER:"):
                     if self.getSettingValueByKey("maxlayer"):
                         display_text = display_text + " of " + max_layer
+                        if not self.getSettingValueByKey("scroll"):
+                            display_text = display_text + " " + name
                     else:
-                        display_text = display_text + "!"
+                        if not self.getSettingValueByKey("scroll"):
+                            display_text = display_text + " " + name + "!"
+                        else:
+                            display_text = display_text + "!"
                     line_index = lines.index(line)
                     lines.insert(line_index + 1, display_text)
                     i += 1

+ 3 - 21
plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml

@@ -20,6 +20,8 @@ UM.Dialog{
     maximumHeight: minimumHeight
     margin: 0
 
+    property string actionButtonText: subscribedPackagesModel.hasIncompatiblePackages && !subscribedPackagesModel.hasCompatiblePackages ? catalog.i18nc("@button", "Dismiss") : catalog.i18nc("@button", "Next")
+
     Rectangle
     {
         id: root
@@ -125,26 +127,6 @@ UM.Dialog{
                                 color: UM.Theme.getColor("text")
                                 elide: Text.ElideRight
                             }
-                            UM.TooltipArea
-                            {
-                                width: childrenRect.width;
-                                height: childrenRect.height;
-                                text: catalog.i18nc("@info:tooltip", "Dismisses the package and won't be shown in this dialog anymore")
-                                anchors.right: parent.right
-                                anchors.verticalCenter: packageIcon.verticalCenter
-                                Label
-                                {
-                                    text: "(Dismiss)"
-                                    font: UM.Theme.getFont("small")
-                                    color: UM.Theme.getColor("text")
-                                    MouseArea
-                                    {
-                                        cursorShape: Qt.PointingHandCursor
-                                        anchors.fill: parent
-                                        onClicked: handler.dismissIncompatiblePackage(subscribedPackagesModel, model.package_id)
-                                    }
-                                }
-                            }
                         }
                     }
                 }
@@ -158,7 +140,7 @@ UM.Dialog{
             anchors.bottom: parent.bottom
             anchors.right: parent.right
             anchors.margins: UM.Theme.getSize("default_margin").height
-            text: catalog.i18nc("@button", "Next")
+            text: actionButtonText
             onClicked: accept()
             leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width
             rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width

+ 45 - 12
plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml

@@ -5,6 +5,7 @@ import QtQuick 2.10
 import QtQuick.Dialogs 1.1
 import QtQuick.Window 2.2
 import QtQuick.Controls 1.4
+import QtQuick.Layouts 1.3
 import QtQuick.Controls.Styles 1.4
 
 // TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles
@@ -20,31 +21,63 @@ UM.Dialog
     minimumHeight: UM.Theme.getSize("license_window_minimum").height
     width: minimumWidth
     height: minimumHeight
+    backgroundColor: UM.Theme.getColor("main_background")
+    margin: screenScaleFactor * 10
 
-    Item
+    ColumnLayout
     {
         anchors.fill: parent
+        spacing: UM.Theme.getSize("thick_margin").height
 
         UM.I18nCatalog{id: catalog; name: "cura"}
 
-
         Label
         {
             id: licenseHeader
-            anchors.top: parent.top
-            anchors.left: parent.left
-            anchors.right: parent.right
-            text: licenseModel.headerText
+            Layout.fillWidth: true
+            text: catalog.i18nc("@label", "You need to accept the license to install the package")
             wrapMode: Text.Wrap
             renderType: Text.NativeRendering
         }
+
+        Row {
+            id: packageRow
+
+            anchors.left: parent.left
+            anchors.right: parent.right
+            height: childrenRect.height
+            spacing: UM.Theme.getSize("default_margin").width
+            leftPadding: UM.Theme.getSize("narrow_margin").width
+
+            Image
+            {
+                id: icon
+                width: 30 * screenScaleFactor
+                height: width
+                fillMode: Image.PreserveAspectFit
+                source: licenseModel.iconUrl || "../../images/logobot.svg"
+                mipmap: true
+            }
+
+            Label
+            {
+                id: packageName
+                text: licenseModel.packageName
+                font.bold: true
+                anchors.verticalCenter: icon.verticalCenter
+                height: contentHeight
+                wrapMode: Text.Wrap
+                renderType: Text.NativeRendering
+            }
+
+
+        }
+
         TextArea
         {
             id: licenseText
-            anchors.top: licenseHeader.bottom
-            anchors.bottom: parent.bottom
-            anchors.left: parent.left
-            anchors.right: parent.right
+            Layout.fillWidth: true
+            Layout.fillHeight: true
             anchors.topMargin: UM.Theme.getSize("default_margin").height
             readOnly: true
             text: licenseModel.licenseText
@@ -57,7 +90,7 @@ UM.Dialog
             leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width
             rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width
 
-            text: catalog.i18nc("@button", "Agree")
+            text: licenseModel.acceptButtonText
             onClicked: { handler.onLicenseAccepted() }
         }
     ]
@@ -67,7 +100,7 @@ UM.Dialog
         Cura.SecondaryButton
         {
             id: declineButton
-            text: catalog.i18nc("@button", "Decline and remove from account")
+            text: licenseModel.declineButtonText
             onClicked: { handler.onLicenseDeclined() }
         }
     ]

+ 42 - 46
plugins/Toolbox/src/CloudSync/CloudPackageChecker.py

@@ -8,11 +8,12 @@ from UM import i18nCatalog
 from UM.Logger import Logger
 from UM.Message import Message
 from UM.Signal import Signal
-from cura.CuraApplication import CuraApplication
+from cura.CuraApplication import CuraApplication, ApplicationMetadata
 from ..CloudApiModel import CloudApiModel
 from .SubscribedPackagesModel import SubscribedPackagesModel
 from ..UltimakerCloudScope import UltimakerCloudScope
 
+from typing import List, Dict, Any
 
 class CloudPackageChecker(QObject):
     def __init__(self, application: CuraApplication) -> None:
@@ -25,12 +26,12 @@ class CloudPackageChecker(QObject):
 
         self._application.initializationFinished.connect(self._onAppInitialized)
         self._i18n_catalog = i18nCatalog("cura")
+        self._sdk_version = ApplicationMetadata.CuraSDKVersion
 
     # This is a plugin, so most of the components required are not ready when
     # this is initialized. Therefore, we wait until the application is ready.
     def _onAppInitialized(self) -> None:
         self._package_manager = self._application.getPackageManager()
-
         # initial check
         self._fetchUserSubscribedPackages()
         # check again whenever the login state changes
@@ -38,25 +39,51 @@ class CloudPackageChecker(QObject):
 
     def _fetchUserSubscribedPackages(self) -> None:
         if self._application.getCuraAPI().account.isLoggedIn:
-            self._getUserPackages()
+            self._getUserSubscribedPackages()
+
+    def _getUserSubscribedPackages(self) -> None:
+        Logger.debug("Requesting subscribed packages metadata from server.")
+        url = CloudApiModel.api_url_user_packages
+        self._application.getHttpRequestManager().get(url,
+                                                      callback = self._onUserPackagesRequestFinished,
+                                                      error_callback = self._onUserPackagesRequestFinished,
+                                                      scope = self._scope)
 
-    def _handleCompatibilityData(self, json_data) -> None:
-        user_subscribed_packages = [plugin["package_id"] for plugin in json_data]
+    def _onUserPackagesRequestFinished(self, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"] = None) -> None:
+        if error is not None or reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
+            Logger.log("w",
+                       "Requesting user packages failed, response code %s while trying to connect to %s",
+                       reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url())
+            return
+
+        try:
+            json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
+            # Check for errors:
+            if "errors" in json_data:
+                for error in json_data["errors"]:
+                    Logger.log("e", "%s", error["title"])
+                return
+            self._handleCompatibilityData(json_data["data"])
+        except json.decoder.JSONDecodeError:
+            Logger.log("w", "Received invalid JSON for user subscribed packages from the Web Marketplace")
+
+    def _handleCompatibilityData(self, subscribed_packages_payload: List[Dict[str, Any]]) -> None:
+        user_subscribed_packages = [plugin["package_id"] for plugin in subscribed_packages_payload]
         user_installed_packages = self._package_manager.getUserInstalledPackages()
+
+        # We need to re-evaluate the dismissed packages
+        # (i.e. some package might got updated to the correct SDK version in the meantime,
+        # hence remove them from the Dismissed Incompatible list)
+        self._package_manager.reEvaluateDismissedPackages(subscribed_packages_payload, self._sdk_version)
         user_dismissed_packages = self._package_manager.getDismissedPackages()
         if user_dismissed_packages:
             user_installed_packages += user_dismissed_packages
-        # We check if there are packages installed in Cloud Marketplace but not in Cura marketplace
-        package_discrepancy = list(set(user_subscribed_packages).difference(user_installed_packages))
-
-        self._model.setMetadata(json_data)
-        self._model.addDiscrepancies(package_discrepancy)
-        self._model.initialize()
-
-        if not self._model.hasCompatiblePackages:
-            return None
 
+        # We check if there are packages installed in Web Marketplace but not in Cura marketplace
+        package_discrepancy = list(set(user_subscribed_packages).difference(user_installed_packages))
         if package_discrepancy:
+            self._model.addDiscrepancies(package_discrepancy)
+            self._model.initialize(subscribed_packages_payload)
             self._handlePackageDiscrepancies()
 
     def _handlePackageDiscrepancies(self) -> None:
@@ -76,35 +103,4 @@ class CloudPackageChecker(QObject):
 
     def _onSyncButtonClicked(self, sync_message: Message, sync_message_action: str) -> None:
         sync_message.hide()
-        self.discrepancies.emit(self._model)
-
-    def _getUserPackages(self) -> None:
-        Logger.log("d", "Requesting subscribed packages metadata from server.")
-        url = CloudApiModel.api_url_user_packages
-
-        self._application.getHttpRequestManager().get(url,
-                                                      callback = self._onUserPackagesRequestFinished,
-                                                      error_callback = self._onUserPackagesRequestFinished,
-                                                      scope = self._scope)
-
-    def _onUserPackagesRequestFinished(self,
-                                      reply: "QNetworkReply",
-                                      error: Optional["QNetworkReply.NetworkError"] = None) -> None:
-        if error is not None or reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
-            Logger.log("w",
-                       "Requesting user packages failed, response code %s while trying to connect to %s",
-                       reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url())
-            return
-
-        try:
-            json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
-
-            # Check for errors:
-            if "errors" in json_data:
-                for error in json_data["errors"]:
-                    Logger.log("e", "%s", error["title"])
-                return
-
-            self._handleCompatibilityData(json_data["data"])
-        except json.decoder.JSONDecodeError:
-            Logger.log("w", "Received invalid JSON for user packages")
+        self.discrepancies.emit(self._model)

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