Browse Source

Merge remote-tracking branch 'origin/master' into CURA-6959_collapse_categories

# Conflicts:
#	resources/qml/Menus/SettingVisibilityPresetsMenu.qml
#	resources/qml/Settings/SettingView.qml
Nino van Hooff 5 years ago
parent
commit
8dbd67f5bd

+ 4 - 0
cura/Machines/Models/SettingVisibilityPresetsModel.py

@@ -77,6 +77,10 @@ class SettingVisibilityPresetsModel(QObject):
 
             items.append(setting_visibility_preset)
 
+        # Add the "all" visibility:
+        all_setting_visibility_preset = SettingVisibilityPreset(preset_id = "all", name = "All", weight = 9001)
+        all_setting_visibility_preset.setSettings(list(CuraApplication.getInstance().getMachineManager().getAllSettingKeys()))
+        items.append(all_setting_visibility_preset)
         # Sort them on weight (and if that fails, use ID)
         items.sort(key = lambda k: (int(k.weight), k.presetId))
 

+ 7 - 5
cura/Settings/MachineManager.py

@@ -4,12 +4,11 @@
 import time
 import re
 import unicodedata
-from typing import Any, List, Dict, TYPE_CHECKING, Optional, cast
+from typing import Any, List, Dict, TYPE_CHECKING, Optional, cast, Set
 
 from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer
 
 from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
-from UM.Decorators import deprecated
 from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
 from UM.Settings.InstanceContainer import InstanceContainer
 from UM.Settings.Interfaces import ContainerInterface
@@ -212,10 +211,13 @@ class MachineManager(QObject):
 
     @pyqtProperty(int, constant=True)
     def totalNumberOfSettings(self) -> int:
-        general_definition_containers = CuraContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")
+        return len(self.getAllSettingKeys())
+
+    def getAllSettingKeys(self) -> Set[str]:
+        general_definition_containers = CuraContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")
         if not general_definition_containers:
-            return 0
-        return len(general_definition_containers[0].getAllKeys())
+            return set()
+        return general_definition_containers[0].getAllKeys()
 
     ##  Triggered when the global container stack is changed in CuraApplication.
     def _onGlobalContainerChanged(self) -> None:

+ 5 - 2
plugins/SimulationView/SimulationView.py

@@ -292,8 +292,9 @@ class SimulationView(CuraView):
     #
     #   \param layer_view_type integer as in SimulationView.qml and this class
     def setSimulationViewType(self, layer_view_type: int) -> None:
-        self._layer_view_type = layer_view_type
-        self.currentLayerNumChanged.emit()
+        if layer_view_type != self._layer_view_type:
+            self._layer_view_type = layer_view_type
+            self.currentLayerNumChanged.emit()
 
     ##  Return the layer view type, integer as in SimulationView.qml and this class
     def getSimulationViewType(self) -> int:
@@ -571,6 +572,8 @@ class SimulationView(CuraView):
 
     def _onCurrentLayerNumChanged(self) -> None:
         self.calculateMaxPathsOnLayer(self._current_layer_num)
+        scene = Application.getInstance().getController().getScene()
+        scene.sceneChanged.emit(scene.getRoot())
 
     def _startUpdateTopLayers(self) -> None:
         if not self._compatibility_mode:

+ 3 - 0
plugins/SimulationView/SimulationViewProxy.py

@@ -149,6 +149,9 @@ class SimulationViewProxy(QObject):
         self.currentPathChanged.emit()
         self._layerActivityChanged()
 
+        scene = Application.getInstance().getController().getScene()
+        scene.sceneChanged.emit(scene.getRoot())
+
     def _onMaxLayersChanged(self):
         self.maxLayersChanged.emit()
 

+ 142 - 0
plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml

@@ -0,0 +1,142 @@
+// Copyright (c) 2020 Ultimaker B.V.
+// Toolbox is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.10
+import QtQuick.Window 2.2
+import QtQuick.Controls 2.3
+
+import UM 1.1 as UM
+import Cura 1.6 as Cura
+
+
+UM.Dialog{
+    visible: true
+    title: catalog.i18nc("@title", "Changes from your account")
+    width: UM.Theme.getSize("popup_dialog").width
+    height: UM.Theme.getSize("popup_dialog").height
+    minimumWidth: width
+    maximumWidth: minimumWidth
+    minimumHeight: height
+    maximumHeight: minimumHeight
+    margin: 0
+
+    Rectangle
+    {
+        id: root
+        anchors.fill: parent
+        color: UM.Theme.getColor("main_background")
+
+        UM.I18nCatalog
+        {
+            id: catalog
+            name: "cura"
+        }
+
+        ScrollView
+        {
+            width: parent.width
+            height: parent.height - nextButton.height - nextButton.anchors.margins * 2 // We want some leftover space for the button at the bottom
+            clip: true
+
+            Column
+            {
+                anchors.fill: parent
+                anchors.margins: UM.Theme.getSize("default_margin").width
+
+                // Compatible packages
+                Label
+                {
+                    font: UM.Theme.getFont("default")
+                    text: catalog.i18nc("@label", "The following packages will be added:")
+                    color: UM.Theme.getColor("text")
+                    height: contentHeight + UM.Theme.getSize("default_margin").height
+                }
+                Repeater
+                {
+                    model: toolbox.subscribedPackagesModel
+                    Component
+                    {
+                        Item
+                        {
+                            width: parent.width
+                            property var lineHeight: 60
+                            visible: model.is_compatible == "True" ? true : false
+                            height: visible ? (lineHeight + UM.Theme.getSize("default_margin").height) : 0 // We only show the compatible packages here
+                            Image
+                            {
+                                id: packageIcon
+                                source: model.icon_url || "../../images/logobot.svg"
+                                height: lineHeight
+                                width: height
+                                mipmap: true
+                                fillMode: Image.PreserveAspectFit
+                            }
+                            Label
+                            {
+                                text: model.name
+                                font: UM.Theme.getFont("medium_bold")
+                                anchors.left: packageIcon.right
+                                anchors.leftMargin: UM.Theme.getSize("default_margin").width
+                                anchors.verticalCenter: packageIcon.verticalCenter
+                                color: UM.Theme.getColor("text")
+                                elide: Text.ElideRight
+                            }
+                        }
+                    }
+                }
+
+                // Incompatible packages
+                Label
+                {
+                    font: UM.Theme.getFont("default")
+                    text: catalog.i18nc("@label", "The following packages can not be installed because of incompatible Cura version:")
+                    color: UM.Theme.getColor("text")
+                    height: contentHeight + UM.Theme.getSize("default_margin").height
+                }
+                Repeater
+                {
+                    model: toolbox.subscribedPackagesModel
+                    Component
+                    {
+                        Item
+                        {
+                            width: parent.width
+                            property var lineHeight: 60
+                            visible: model.is_compatible == "True" ? false : true
+                            height: visible ? (lineHeight + UM.Theme.getSize("default_margin").height) : 0 // We only show the incompatible packages here
+                            Image
+                            {
+                                id: packageIcon
+                                source: model.icon_url || "../../images/logobot.svg"
+                                height: lineHeight
+                                width: height
+                                mipmap: true
+                                fillMode: Image.PreserveAspectFit
+                            }
+                            Label
+                            {
+                                text: model.name
+                                font: UM.Theme.getFont("medium_bold")
+                                anchors.left: packageIcon.right
+                                anchors.leftMargin: UM.Theme.getSize("default_margin").width
+                                anchors.verticalCenter: packageIcon.verticalCenter
+                                color: UM.Theme.getColor("text")
+                                elide: Text.ElideRight
+                            }
+                        }
+                    }
+                }
+            }
+
+        } // End of ScrollView
+
+        Cura.ActionButton
+        {
+            id: nextButton
+            anchors.bottom: parent.bottom
+            anchors.right: parent.right
+            anchors.margins: UM.Theme.getSize("default_margin").height
+            text: catalog.i18nc("@button", "Next")
+        }
+    }
+}

+ 46 - 0
plugins/Toolbox/src/SubscribedPackagesModel.py

@@ -0,0 +1,46 @@
+# Copyright (c) 2020 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import Qt
+from UM.Qt.ListModel import ListModel
+from cura import ApplicationMetadata
+
+
+class SubscribedPackagesModel(ListModel):
+    def __init__(self, parent = None):
+        super().__init__(parent)
+
+        self._metadata = None
+        self._discrepancies = None
+        self._sdk_version = ApplicationMetadata.CuraSDKVersion
+
+        self.addRoleName(Qt.UserRole + 1, "name")
+        self.addRoleName(Qt.UserRole + 2, "icon_url")
+        self.addRoleName(Qt.UserRole + 3, "is_compatible")
+
+    def setMetadata(self, data):
+        if self._metadata != data:
+            self._metadata = data
+
+    def addValue(self, discrepancy):
+        if self._discrepancies != discrepancy:
+            self._discrepancies = discrepancy
+
+    def update(self):
+        items = []
+
+        for item in self._metadata:
+            if item["package_id"] not in self._discrepancies:
+                continue
+            package = {"name": item["display_name"], "sdk_versions": item["sdk_versions"]}
+            if self._sdk_version not in item["sdk_versions"]:
+                package.update({"is_compatible": "False"})
+            else:
+                package.update({"is_compatible": "True"})
+            try:
+                package.update({"icon_url": item["icon_url"]})
+            except KeyError:  # There is no 'icon_url" in the response payload for this package
+                package.update({"icon_url": ""})
+
+            items.append(package)
+        self.setItems(items)

+ 76 - 25
plugins/Toolbox/src/Toolbox.py

@@ -15,6 +15,7 @@ from UM.PluginRegistry import PluginRegistry
 from UM.Extension import Extension
 from UM.i18n import i18nCatalog
 from UM.Version import Version
+from UM.Message import Message
 
 from cura import ApplicationMetadata
 from cura import UltimakerCloudAuthentication
@@ -23,6 +24,7 @@ from cura.Machines.ContainerTree import ContainerTree
 
 from .AuthorsModel import AuthorsModel
 from .PackagesModel import PackagesModel
+from .SubscribedPackagesModel import SubscribedPackagesModel
 
 if TYPE_CHECKING:
     from cura.Settings.GlobalStack import GlobalStack
@@ -58,17 +60,19 @@ class Toolbox(QObject, Extension):
 
         # The responses as given by the server parsed to a list.
         self._server_response_data = {
-            "authors":             [],
-            "packages":            [],
-            "updates":             [],
+            "authors":              [],
+            "packages":             [],
+            "updates":              [],
+            "subscribed_packages":  [],
         }  # type: Dict[str, List[Any]]
 
         # Models:
         self._models = {
-            "authors":             AuthorsModel(self),
-            "packages":            PackagesModel(self),
-            "updates":             PackagesModel(self),
-        }  # type: Dict[str, Union[AuthorsModel, PackagesModel]]
+            "authors":              AuthorsModel(self),
+            "packages":             PackagesModel(self),
+            "updates":              PackagesModel(self),
+            "subscribed_packages":  SubscribedPackagesModel(self),
+        }  # type: Dict[str, Union[AuthorsModel, PackagesModel, SubscribedPackagesModel]]
 
         self._plugins_showcase_model = PackagesModel(self)
         self._plugins_available_model = PackagesModel(self)
@@ -161,7 +165,7 @@ class Toolbox(QObject, Extension):
 
     @pyqtSlot(str, int)
     def ratePackage(self, package_id: str, rating: int) -> None:
-        url = QUrl("{base_url}/packages/{package_id}/ratings".format(base_url=self._api_url, package_id = package_id))
+        url = QUrl("{base_url}/packages/{package_id}/ratings".format(base_url = self._api_url, package_id = package_id))
 
         self._rate_request = QNetworkRequest(url)
         for header_name, header_value in self._request_headers:
@@ -197,6 +201,11 @@ class Toolbox(QObject, Extension):
             cloud_api_version = self._cloud_api_version,
             sdk_version = self._sdk_version
         )
+        # https://api.ultimaker.com/cura-packages/v1/user/packages
+        self._api_url_user_packages = "{cloud_api_root}/cura-packages/v{cloud_api_version}/user/packages".format(
+            cloud_api_root = self._cloud_api_root,
+            cloud_api_version = self._cloud_api_version,
+        )
 
         # We need to construct a query like installed_packages=ID:VERSION&installed_packages=ID:VERSION, etc.
         installed_package_ids_with_versions = [":".join(items) for items in
@@ -207,15 +216,18 @@ class Toolbox(QObject, Extension):
             "authors": QUrl("{base_url}/authors".format(base_url = self._api_url)),
             "packages": QUrl("{base_url}/packages".format(base_url = self._api_url)),
             "updates": QUrl("{base_url}/packages/package-updates?installed_packages={query}".format(
-                base_url = self._api_url, query = installed_packages_query))
+                base_url = self._api_url, query = installed_packages_query)),
+            "subscribed_packages": QUrl(self._api_url_user_packages)
         }
 
         self._application.getCuraAPI().account.loginStateChanged.connect(self._restart)
+        self._application.getCuraAPI().account.loginStateChanged.connect(self._fetchUserSubscribedPackages)
 
         # On boot we check which packages have updates.
         if CuraApplication.getInstance().getPreferences().getValue("info/automatic_update_check") and len(installed_package_ids_with_versions) > 0:
             # Request the latest and greatest!
             self._fetchPackageUpdates()
+        self._fetchUserSubscribedPackages()
 
     def _prepareNetworkManager(self):
         if self._network_manager is not None:
@@ -237,6 +249,11 @@ class Toolbox(QObject, Extension):
         # Gather installed packages:
         self._updateInstalledModels()
 
+    def _fetchUserSubscribedPackages(self):
+        if self._application.getCuraAPI().account.isLoggedIn:
+            self._prepareNetworkManager()
+            self._makeRequestByType("subscribed_packages")
+
     # Displays the toolbox
     @pyqtSlot()
     def launch(self) -> None:
@@ -540,9 +557,7 @@ class Toolbox(QObject, Extension):
 
     @pyqtSlot(str, result = bool)
     def isEnabled(self, package_id: str) -> bool:
-        if package_id in self._plugin_registry.getActivePlugins():
-            return True
-        return False
+        return package_id in self._plugin_registry.getActivePlugins()
 
     # Check for plugins that were installed with the old plugin browser
     def isOldPlugin(self, plugin_id: str) -> bool:
@@ -561,10 +576,11 @@ class Toolbox(QObject, Extension):
     # Make API Calls
     # --------------------------------------------------------------------------
     def _makeRequestByType(self, request_type: str) -> None:
-        Logger.log("d", "Requesting %s metadata from server.", request_type)
+        Logger.log("d", "Requesting '%s' metadata from server.", request_type)
         request = QNetworkRequest(self._request_urls[request_type])
         for header_name, header_value in self._request_headers:
             request.setRawHeader(header_name, header_value)
+        self._updateRequestHeader()
         if self._network_manager:
             self._network_manager.get(request)
 
@@ -661,6 +677,8 @@ class Toolbox(QObject, Extension):
                                 # Tell the package manager that there's a new set of updates available.
                                 packages = set([pkg["package_id"] for pkg in self._server_response_data[response_type]])
                                 self._package_manager.setPackagesWithUpdate(packages)
+                            elif response_type == "subscribed_packages":
+                                self._checkCompatibilities(json_data["data"])
 
                             self.metadataChanged.emit()
 
@@ -674,9 +692,38 @@ class Toolbox(QObject, Extension):
                         Logger.log("w", "Unable to connect with the server, we got a response code %s while trying to connect to %s", reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url())
                         self.setViewPage("errored")
                         self.resetDownload()
-        elif reply.operation() == QNetworkAccessManager.PutOperation:
-            # Ignore any operation that is not a get operation
-            pass
+
+    def _checkCompatibilities(self, json_data) -> None:
+        user_subscribed_packages = [plugin["package_id"] for plugin in json_data]
+        user_installed_packages = self._package_manager.getUserInstalledPackages()
+
+        # We check if there are packages installed in Cloud Marketplace but not in Cura marketplace (discrepancy)
+        package_discrepancy = list(set(user_subscribed_packages).difference(user_installed_packages))
+        if package_discrepancy:
+            self._models["subscribed_packages"].addValue(package_discrepancy)
+            self._models["subscribed_packages"].update()
+            Logger.log("d", "Discrepancy found between Cloud subscribed packages and Cura installed packages")
+            sync_message = Message(i18n_catalog.i18nc(
+                "@info:generic",
+                "\nDo you want to sync material and software packages with your account?"),
+                lifetime=0,
+                title=i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", ))
+            sync_message.addAction("sync",
+                                   name=i18n_catalog.i18nc("@action:button", "Sync"),
+                                   icon="",
+                                   description="Sync your Cloud subscribed packages to your local environment.",
+                                   button_align=Message.ActionButtonAlignment.ALIGN_RIGHT)
+
+            sync_message.actionTriggered.connect(self._onSyncButtonClicked)
+            sync_message.show()
+
+    def _onSyncButtonClicked(self, sync_message: Message, sync_message_action: str) -> None:
+        sync_message.hide()
+        compatibility_dialog_path = "resources/qml/dialogs/CompatibilityDialog.qml"
+        plugin_path_prefix = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
+        if plugin_path_prefix:
+            path = os.path.join(plugin_path_prefix, compatibility_dialog_path)
+            self.compatibility_dialog_view = self._application.getInstance().createQmlComponent(path, {"toolbox": self})
 
     # This function goes through all known remote versions of a package and notifies the package manager of this change
     def _notifyPackageManager(self):
@@ -772,39 +819,43 @@ class Toolbox(QObject, Extension):
 
     # Exposed Models:
     # --------------------------------------------------------------------------
-    @pyqtProperty(QObject, constant=True)
+    @pyqtProperty(QObject, constant = True)
     def authorsModel(self) -> AuthorsModel:
         return cast(AuthorsModel, self._models["authors"])
 
-    @pyqtProperty(QObject, constant=True)
+    @pyqtProperty(QObject, constant = True)
+    def subscribedPackagesModel(self) -> SubscribedPackagesModel:
+        return cast(SubscribedPackagesModel, self._models["subscribed_packages"])
+
+    @pyqtProperty(QObject, constant = True)
     def packagesModel(self) -> PackagesModel:
         return cast(PackagesModel, self._models["packages"])
 
-    @pyqtProperty(QObject, constant=True)
+    @pyqtProperty(QObject, constant = True)
     def pluginsShowcaseModel(self) -> PackagesModel:
         return self._plugins_showcase_model
 
-    @pyqtProperty(QObject, constant=True)
+    @pyqtProperty(QObject, constant = True)
     def pluginsAvailableModel(self) -> PackagesModel:
         return self._plugins_available_model
 
-    @pyqtProperty(QObject, constant=True)
+    @pyqtProperty(QObject, constant = True)
     def pluginsInstalledModel(self) -> PackagesModel:
         return self._plugins_installed_model
 
-    @pyqtProperty(QObject, constant=True)
+    @pyqtProperty(QObject, constant = True)
     def materialsShowcaseModel(self) -> AuthorsModel:
         return self._materials_showcase_model
 
-    @pyqtProperty(QObject, constant=True)
+    @pyqtProperty(QObject, constant = True)
     def materialsAvailableModel(self) -> AuthorsModel:
         return self._materials_available_model
 
-    @pyqtProperty(QObject, constant=True)
+    @pyqtProperty(QObject, constant = True)
     def materialsInstalledModel(self) -> PackagesModel:
         return self._materials_installed_model
 
-    @pyqtProperty(QObject, constant=True)
+    @pyqtProperty(QObject, constant = True)
     def materialsGenericModel(self) -> PackagesModel:
         return self._materials_generic_model
 

+ 2 - 2
resources/definitions/fdmprinter.def.json

@@ -7650,7 +7650,7 @@
                     "default_value": 50,
                     "minimum_value": "1",
                     "minimum_value_warning": "25",
-                    "maximum_value": "100",
+                    "maximum_value_warning": "100",
                     "settable_per_mesh": true
                 },
                 "small_feature_speed_factor_0":
@@ -7663,7 +7663,7 @@
                     "value": "small_feature_speed_factor",
                     "minimum_value": "1",
                     "minimum_value_warning": "25",
-                    "maximum_value": "100",
+                    "maximum_value_warning": "100",
                     "settable_per_mesh": true
                 }
             }

+ 0 - 12
resources/qml/Menus/SettingVisibilityPresetsMenu.qml

@@ -14,7 +14,6 @@ Menu
 
     property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel()
 
-    signal showAllSettings()
     signal collapseAllCategories()
 
     Instantiator
@@ -37,17 +36,6 @@ Menu
         onObjectRemoved: menu.removeItem(object)
     }
 
-    MenuSeparator {}
-    MenuItem
-    {
-        text: catalog.i18nc("@action:inmenu", "Show All Settings")
-        checkable: false
-        exclusiveGroup: group
-        onTriggered:
-        {
-            showAllSettings();
-        }
-    }
     MenuSeparator {}
     MenuItem
     {

+ 1 - 1
resources/qml/Settings/SettingCategory.qml

@@ -19,7 +19,7 @@ Button
     background: Rectangle
     {
         id: backgroundRectangle
-        implicitHeight: UM.Theme.getSize("section").height
+        height: UM.Theme.getSize("section").height
         color:
         {
             if (base.color)

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