From 4572d53e11f6bc5391bd4594d5b407a185467fd5 Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Tue, 31 May 2022 15:12:52 +0200 Subject: [PATCH 01/29] Read package.json required package information. Put package_ids for packages we don't have installed inside the WorkspaceDialog CURA-6990 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 28 +++++++++++++++++++++ plugins/3MFReader/WorkspaceDialog.py | 4 +++ 2 files changed, 32 insertions(+) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index ddc7922546..66ebf6e0b2 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -23,6 +23,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType from UM.Job import Job from UM.Preferences import Preferences +from cura.CuraPackageManager import CuraPackageManager from cura.Machines.ContainerTree import ContainerTree from cura.Settings.CuraStackBuilder import CuraStackBuilder @@ -579,6 +580,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader): is_printer_group = True machine_name = group_name + # Getting missing required package ids + package_metadata = self._parse_packages_metadata(archive) + missing_package_ids = self._get_missing_package_ids(package_metadata) + # Show the dialog, informing the user what is about to happen. self._dialog.setMachineConflict(machine_conflict) self._dialog.setIsPrinterGroup(is_printer_group) @@ -599,6 +604,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._dialog.setExtruders(extruders) self._dialog.setVariantType(variant_type_name) self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity) + self._dialog.setRequiredPackages(missing_package_ids) self._dialog.show() # Block until the dialog is closed. @@ -1243,3 +1249,25 @@ class ThreeMFWorkspaceReader(WorkspaceReader): metadata = data.iterfind("./um:metadata/um:name/um:label", {"um": "http://www.ultimaker.com/material"}) for entry in metadata: return entry.text + + @staticmethod + def _parse_packages_metadata(archive: zipfile.ZipFile) -> List[Dict[str, str]]: + try: + package_metadata = json.loads(archive.open("Metadata/packages.json").read().decode("utf-8")) + return package_metadata["packages"] + except Exception: + Logger.error("Failed to load packes metadata from .3mf file") + return [] + + + @staticmethod + def _get_missing_package_ids(package_metadata: List[Dict[str, str]]) -> List[str]: + missing_packages = [] + package_manager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager()) + + for package in package_metadata: + package_id = package["id"] + if not package_manager.isPackageInstalled(package_id): + missing_packages.append(package_id) + + return missing_packages diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index b63d8b6288..7467134183 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -59,6 +59,7 @@ class WorkspaceDialog(QObject): self._objects_on_plate = False self._is_printer_group = False self._updatable_machines_model = UpdatableMachinesModel(self) + self._missing_package_ids = [] machineConflictChanged = pyqtSignal() qualityChangesConflictChanged = pyqtSignal() @@ -274,6 +275,9 @@ class WorkspaceDialog(QObject): self._has_quality_changes_conflict = quality_changes_conflict self.qualityChangesConflictChanged.emit() + def setRequiredPackages(self, missing_package_ids): + self._missing_package_ids = missing_package_ids + def getResult(self) -> Dict[str, Optional[str]]: if "machine" in self._result and self.updatableMachinesModel.count <= 1: self._result["machine"] = None From ce0623664a49f3c261ae1ec7d889ec074b60844f Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Tue, 31 May 2022 15:21:11 +0200 Subject: [PATCH 02/29] Include all missing package metadata instead of just ids so we have something to display before searching the package on the marketplace. This is also important so we can show something if we can't find the package on the marketplace. CURA-6990 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 9 +++++---- plugins/3MFReader/WorkspaceDialog.py | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 66ebf6e0b2..7456ce2b95 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -582,7 +582,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Getting missing required package ids package_metadata = self._parse_packages_metadata(archive) - missing_package_ids = self._get_missing_package_ids(package_metadata) + missing_package_metadata = self._filter_missing_package_metadata(package_metadata) # Show the dialog, informing the user what is about to happen. self._dialog.setMachineConflict(machine_conflict) @@ -604,7 +604,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._dialog.setExtruders(extruders) self._dialog.setVariantType(variant_type_name) self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity) - self._dialog.setRequiredPackages(missing_package_ids) + self._dialog.setMissingPackagesMetadata(missing_package_metadata) self._dialog.show() # Block until the dialog is closed. @@ -1261,13 +1261,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader): @staticmethod - def _get_missing_package_ids(package_metadata: List[Dict[str, str]]) -> List[str]: + def _filter_missing_package_metadata(package_metadata: List[Dict[str, str]]) -> List[Dict[str, str]]: + """Filters out installed packages from package_metadata""" missing_packages = [] package_manager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager()) for package in package_metadata: package_id = package["id"] if not package_manager.isPackageInstalled(package_id): - missing_packages.append(package_id) + missing_packages.append(package) return missing_packages diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 7467134183..950c9df2b9 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -59,7 +59,7 @@ class WorkspaceDialog(QObject): self._objects_on_plate = False self._is_printer_group = False self._updatable_machines_model = UpdatableMachinesModel(self) - self._missing_package_ids = [] + self._missing_package_metadata = [] machineConflictChanged = pyqtSignal() qualityChangesConflictChanged = pyqtSignal() @@ -275,8 +275,8 @@ class WorkspaceDialog(QObject): self._has_quality_changes_conflict = quality_changes_conflict self.qualityChangesConflictChanged.emit() - def setRequiredPackages(self, missing_package_ids): - self._missing_package_ids = missing_package_ids + def setMissingPackagesMetadata(self, missing_package_metadata): + self._missing_package_metadata = missing_package_metadata def getResult(self) -> Dict[str, Optional[str]]: if "machine" in self._result and self.updatableMachinesModel.count <= 1: From 5c1b7470a79718ef41ed2c5c936b3a55dd456a76 Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Tue, 31 May 2022 15:26:56 +0200 Subject: [PATCH 03/29] Add pyqt access to missing package metadata. CURA-6990 --- plugins/3MFReader/WorkspaceDialog.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 950c9df2b9..0b3e79ed9c 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -278,6 +278,11 @@ class WorkspaceDialog(QObject): def setMissingPackagesMetadata(self, missing_package_metadata): self._missing_package_metadata = missing_package_metadata + @pyqtProperty("QVariantList") + def missingPackages(self): + return self._missing_package_metadata + + def getResult(self) -> Dict[str, Optional[str]]: if "machine" in self._result and self.updatableMachinesModel.count <= 1: self._result["machine"] = None From 84f19458c58ac4ca841672796833aa2d0c22fa49 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Tue, 31 May 2022 16:49:47 +0200 Subject: [PATCH 04/29] Display error when opening 3mf files CURA-6990 --- plugins/3MFReader/WorkspaceDialog.qml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index 4c384b306b..a890714877 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -17,7 +17,7 @@ UM.Dialog minimumWidth: UM.Theme.getSize("popup_dialog").width minimumHeight: UM.Theme.getSize("popup_dialog").height width: minimumWidth - + margin: UM.Theme.getSize("default_margin").width property int comboboxHeight: UM.Theme.getSize("default_margin").height onClosing: manager.notifyClosed() @@ -442,18 +442,36 @@ UM.Dialog } } + property bool warning: true + + buttonWarningText: "The material used in this project is currently not installed in Cura.
Install the material profile and reopen the project." + buttonWarning: warning buttonSpacing: UM.Theme.getSize("default_margin").width rightButtons: [ Cura.TertiaryButton { + visible: !warning text: catalog.i18nc("@action:button", "Cancel") onClicked: reject() }, Cura.PrimaryButton { + visible: !warning text: catalog.i18nc("@action:button", "Open") onClicked: accept() + }, + Cura.TertiaryButton + { + visible: warning + text: catalog.i18nc("@action:button", "Open project anyway") + onClicked: reject() + }, + Cura.PrimaryButton + { + visible: warning + text: catalog.i18nc("@action:button", "Install missing Material") + onClicked: accept() } ] From cd18bfb89316c2847a2cbec2b62f4a12f2187765 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Wed, 1 Jun 2022 10:09:23 +0200 Subject: [PATCH 05/29] Set warning only if there are missing materials CURA-6990 --- plugins/3MFReader/WorkspaceDialog.qml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index a890714877..19f682b631 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -442,34 +442,32 @@ UM.Dialog } } - property bool warning: true - buttonWarningText: "The material used in this project is currently not installed in Cura.
Install the material profile and reopen the project." - buttonWarning: warning + buttonWarning: manager.missingPackages.length > 0 buttonSpacing: UM.Theme.getSize("default_margin").width rightButtons: [ Cura.TertiaryButton { - visible: !warning + visible: !buttonWarning text: catalog.i18nc("@action:button", "Cancel") onClicked: reject() }, Cura.PrimaryButton { - visible: !warning + visible: !buttonWarning text: catalog.i18nc("@action:button", "Open") onClicked: accept() }, Cura.TertiaryButton { - visible: warning + visible: buttonWarning text: catalog.i18nc("@action:button", "Open project anyway") onClicked: reject() }, Cura.PrimaryButton { - visible: warning + visible: buttonWarning text: catalog.i18nc("@action:button", "Install missing Material") onClicked: accept() } From cd9fde946f95e38effdade8e62ccacc039cfb6be Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Wed, 1 Jun 2022 10:09:57 +0200 Subject: [PATCH 06/29] Add typing CURA-6990 --- plugins/3MFReader/WorkspaceDialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 0b3e79ed9c..6ce5d62531 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -59,7 +59,7 @@ class WorkspaceDialog(QObject): self._objects_on_plate = False self._is_printer_group = False self._updatable_machines_model = UpdatableMachinesModel(self) - self._missing_package_metadata = [] + self._missing_package_metadata: List[Dict[str, str]] = [] machineConflictChanged = pyqtSignal() qualityChangesConflictChanged = pyqtSignal() @@ -275,7 +275,7 @@ class WorkspaceDialog(QObject): self._has_quality_changes_conflict = quality_changes_conflict self.qualityChangesConflictChanged.emit() - def setMissingPackagesMetadata(self, missing_package_metadata): + def setMissingPackagesMetadata(self, missing_package_metadata: List[Dict[str, str]]) -> None: self._missing_package_metadata = missing_package_metadata @pyqtProperty("QVariantList") From 5a3836b841c2b4bf4e2272c7e3f943dcba2dc34f Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Wed, 1 Jun 2022 10:39:03 +0200 Subject: [PATCH 07/29] Fix missingPackages not updating CURA-8610 --- plugins/3MFReader/WorkspaceDialog.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 6ce5d62531..04c9494ee7 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -80,6 +80,7 @@ class WorkspaceDialog(QObject): variantTypeChanged = pyqtSignal() extrudersChanged = pyqtSignal() isPrinterGroupChanged = pyqtSignal() + missingPackagesChanged = pyqtSignal() @pyqtProperty(bool, notify = isPrinterGroupChanged) def isPrinterGroup(self) -> bool: @@ -277,8 +278,9 @@ class WorkspaceDialog(QObject): def setMissingPackagesMetadata(self, missing_package_metadata: List[Dict[str, str]]) -> None: self._missing_package_metadata = missing_package_metadata + self.missingPackagesChanged.emit() - @pyqtProperty("QVariantList") + @pyqtProperty("QVariantList", notify=missingPackagesChanged) def missingPackages(self): return self._missing_package_metadata From 0df21e6073cc9fc525e47fe7d300ed32b9407331 Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Wed, 1 Jun 2022 16:50:53 +0200 Subject: [PATCH 08/29] Refactor missing packages dialog into model (MissingPackageList) and controller (InstallMissingPackagesDialog.qml) Rename classes and files to have unified naming. CURA-6990 --- plugins/3MFReader/WorkspaceDialog.py | 7 + plugins/3MFReader/WorkspaceDialog.qml | 2 +- .../InstallMissingPackagesDialog.py | 60 +++++++ plugins/Marketplace/MissingPackageList.py | 32 ++++ .../qml/InstallMissingPackagesDialog.qml | 158 ++++++++++++++++++ .../resources/qml/MissingPackages.qml | 15 ++ 6 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 plugins/Marketplace/InstallMissingPackagesDialog.py create mode 100644 plugins/Marketplace/MissingPackageList.py create mode 100644 plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml create mode 100644 plugins/Marketplace/resources/qml/MissingPackages.qml diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 04c9494ee7..56bc9e2fcc 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -9,6 +9,7 @@ from UM.Application import Application from UM.i18n import i18nCatalog from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack +from plugins.Marketplace.InstallMissingPackagesDialog import InstallMissingPackageDialog from .UpdatableMachinesModel import UpdatableMachinesModel import os @@ -60,6 +61,8 @@ class WorkspaceDialog(QObject): self._is_printer_group = False self._updatable_machines_model = UpdatableMachinesModel(self) self._missing_package_metadata: List[Dict[str, str]] = [] + self._plugin_registry: PluginRegistry = CuraApplication.getInstance().getPluginRegistry() + self._install_missing_package_dialog: Optional[QObject] = None machineConflictChanged = pyqtSignal() qualityChangesConflictChanged = pyqtSignal() @@ -284,6 +287,10 @@ class WorkspaceDialog(QObject): def missingPackages(self): return self._missing_package_metadata + @pyqtSlot() + def installMissingPackages(self): + self._install_missing_package_dialog = InstallMissingPackageDialog(self._missing_package_metadata) + self._install_missing_package_dialog.show() def getResult(self) -> Dict[str, Optional[str]]: if "machine" in self._result and self.updatableMachinesModel.count <= 1: diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index 19f682b631..f6048f8324 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -469,7 +469,7 @@ UM.Dialog { visible: buttonWarning text: catalog.i18nc("@action:button", "Install missing Material") - onClicked: accept() + onClicked: manager.installMissingPackages() } ] diff --git a/plugins/Marketplace/InstallMissingPackagesDialog.py b/plugins/Marketplace/InstallMissingPackagesDialog.py new file mode 100644 index 0000000000..eb6c03f81c --- /dev/null +++ b/plugins/Marketplace/InstallMissingPackagesDialog.py @@ -0,0 +1,60 @@ +import os + +from PyQt6.QtCore import QObject, pyqtSignal, pyqtProperty +from typing import Optional, List, Dict, cast +from cura.CuraApplication import CuraApplication +from UM.PluginRegistry import PluginRegistry +from cura.CuraPackageManager import CuraPackageManager + +from plugins.Marketplace.MissingPackageList import MissingPackageList + + +class InstallMissingPackageDialog(QObject): + """Dialog used to display packages that need to be installed to load 3mf file materials""" + def __init__(self, packages_metadata: List[Dict[str, str]]): + """Initialize + + :param packages_metadata: List of dictionaries containing information about missing packages. + """ + super().__init__() + + self._plugin_registry: PluginRegistry = CuraApplication.getInstance().getPluginRegistry() + self._package_manager: CuraPackageManager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager()) + self._package_manager.installedPackagesChanged.connect(self.checkIfRestartNeeded) + + self._dialog: Optional[QObject] = None + self._restart_needed = False + self._package_metadata: List[Dict[str, str]] = packages_metadata + + self._package_model = MissingPackageList() + self._package_model.setPackageIds(packages_metadata) + + def show(self): + plugin_path = self._plugin_registry.getPluginPath("Marketplace") + if plugin_path is None: + plugin_path = os.path.dirname(__file__) + + # create a QML component for the license dialog + license_dialog_component_path = os.path.join(plugin_path, "resources", "qml", "InstallMissingPackagesDialog.qml") + self._dialog = CuraApplication.getInstance().createQmlComponent(license_dialog_component_path, {"manager": self}) + self._dialog.show() + + def checkIfRestartNeeded(self) -> None: + if self._dialog is None: + return + + if self._package_manager.hasPackagesToRemoveOrInstall: + self._restart_needed = True + else: + self._restart_needed = False + self.showRestartChanged.emit() + + showRestartChanged = pyqtSignal() + + @pyqtProperty(bool, notify=showRestartChanged) + def showRestartNotification(self) -> bool: + return self._restart_needed + + @pyqtProperty(QObject) + def model(self): + return self._package_model diff --git a/plugins/Marketplace/MissingPackageList.py b/plugins/Marketplace/MissingPackageList.py new file mode 100644 index 0000000000..b036e8b966 --- /dev/null +++ b/plugins/Marketplace/MissingPackageList.py @@ -0,0 +1,32 @@ +# Copyright (c) 2022 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from typing import Optional, TYPE_CHECKING, Dict, List + +from .Constants import PACKAGES_URL +from .PackageModel import PackageModel +from .RemotePackageList import RemotePackageList +from PyQt6.QtCore import pyqtSignal, QObject, pyqtProperty, QCoreApplication + +from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To request the package list from the API. +from UM.i18n import i18nCatalog + +if TYPE_CHECKING: + from PyQt6.QtCore import QObject, pyqtProperty, pyqtSignal + +catalog = i18nCatalog("cura") + +class MissingPackageList(RemotePackageList): + def __init__(self, parent: Optional["QObject"] = None) -> None: + super().__init__(parent) + self._package_metadata: List[Dict[str, str]] = [] + # self.packageTypeFilter = None # This will be our new filter + self._package_type_filter = "material" + + def setPackageIds(self, packages: List[Dict[str, str]]) -> None: + self._package_metadata = packages + search_string = ", ".join(map(lambda package: package["id"], packages)) + # self.setSearchString(search_string) + self.setSearchString("ABS") + + diff --git a/plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml b/plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml new file mode 100644 index 0000000000..a5fbe2c965 --- /dev/null +++ b/plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml @@ -0,0 +1,158 @@ +// Copyright (c) 2021 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.2 + +import UM 1.5 as UM +import Cura 1.6 as Cura + +Window +{ + id: marketplaceDialog + + property variant catalog: UM.I18nCatalog { name: "cura" } + + signal searchStringChanged(string new_search) + + minimumWidth: UM.Theme.getSize("modal_window_minimum").width + minimumHeight: UM.Theme.getSize("modal_window_minimum").height + width: minimumWidth + height: minimumHeight + + onVisibleChanged: + { + while(contextStack.depth > 1) + { + contextStack.pop(); //Do NOT use the StackView.Immediate transition here, since it causes the window to stay empty. Seemingly a Qt bug: https://bugreports.qt.io/browse/QTBUG-60670? + } + } + + Connections + { + target: Cura.API.account + function onLoginStateChanged() + { + close(); + } + } + + title: catalog.i18nc("@title", "Install missing Materials") + modality: Qt.ApplicationModal + + // Background color + Rectangle + { + anchors.fill: parent + color: UM.Theme.getColor("main_background") + } + //The Marketplace can have a page in front of everything with package details. The stack view controls its visibility. + StackView + { + id: contextStack + anchors.fill: parent + + initialItem: packageBrowse + + ColumnLayout + { + id: packageBrowse + + spacing: UM.Theme.getSize("narrow_margin").height + + // Page title. + Item + { + Layout.preferredWidth: parent.width + Layout.preferredHeight: childrenRect.height + UM.Theme.getSize("default_margin").height + + UM.Label + { + id: pageTitle + anchors + { + left: parent.left + leftMargin: UM.Theme.getSize("default_margin").width + right: parent.right + rightMargin: UM.Theme.getSize("default_margin").width + bottom: parent.bottom + } + + font: UM.Theme.getFont("large") + text: content.item ? content.item.pageTitle: catalog.i18nc("@title", "Loading...") + } + } + + // Page contents. + Rectangle + { + Layout.preferredWidth: parent.width + Layout.fillHeight: true + color: UM.Theme.getColor("detail_background") + + // Page contents. + Loader + { + id: content + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_margin").width + source: "MissingPackages.qml" + } + } + } + } + + Rectangle + { + height: quitButton.height + 2 * UM.Theme.getSize("default_margin").width + color: UM.Theme.getColor("primary") + visible: manager.showRestartNotification + anchors + { + left: parent.left + right: parent.right + bottom: parent.bottom + } + + RowLayout + { + anchors + { + left: parent.left + right: parent.right + verticalCenter: parent.verticalCenter + margins: UM.Theme.getSize("default_margin").width + } + spacing: UM.Theme.getSize("default_margin").width + UM.ColorImage + { + id: bannerIcon + source: UM.Theme.getIcon("Plugin") + + color: UM.Theme.getColor("primary_button_text") + implicitWidth: UM.Theme.getSize("banner_icon_size").width + implicitHeight: UM.Theme.getSize("banner_icon_size").height + } + Text + { + color: UM.Theme.getColor("primary_button_text") + text: catalog.i18nc("@button", "In order to use the package you will need to restart Cura") + font: UM.Theme.getFont("default") + renderType: Text.NativeRendering + Layout.fillWidth: true + } + Cura.SecondaryButton + { + id: quitButton + text: catalog.i18nc("@info:button, %1 is the application name", "Quit %1").arg(CuraApplication.applicationDisplayName) + onClicked: + { + marketplaceDialog.hide(); + CuraApplication.closeApplication(); + } + } + } + } +} diff --git a/plugins/Marketplace/resources/qml/MissingPackages.qml b/plugins/Marketplace/resources/qml/MissingPackages.qml new file mode 100644 index 0000000000..316d048317 --- /dev/null +++ b/plugins/Marketplace/resources/qml/MissingPackages.qml @@ -0,0 +1,15 @@ +// Copyright (c) 2021 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import UM 1.4 as UM + +Packages +{ + pageTitle: catalog.i18nc("@header", "Install Materials") + + bannerVisible: false + showUpdateButton: false + showInstallButton: true + + model: manager.model +} From b015617e13472a38b1a827fcadf6264cb0615158 Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Wed, 1 Jun 2022 16:56:02 +0200 Subject: [PATCH 09/29] Marketplace plugin registry is None on initialization, and is loaded after running show(). This makes a check that the window is open before continuing through checkIfRestartNeeded CURA-6990 --- plugins/Marketplace/Marketplace.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 171e3f915b..e856245c3d 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -103,6 +103,9 @@ class Marketplace(Extension, QObject): self.setTabShown(1) def checkIfRestartNeeded(self) -> None: + if self._window is None: + return + if self._package_manager.hasPackagesToRemoveOrInstall or \ cast(PluginRegistry, self._plugin_registry).getCurrentSessionActivationChangedPlugins(): self._restart_needed = True From 9c02f6500d6d6977f06e73491c37288b30058d58 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 2 Jun 2022 15:19:00 +0200 Subject: [PATCH 10/29] Use API endpoint for requesting package_ids Use new api endpoint for requesting specific package_ids so we don't have to filter ourselves. CURA-6990 --- plugins/Marketplace/InstallMissingPackagesDialog.py | 3 +-- plugins/Marketplace/MissingPackageList.py | 12 +++--------- plugins/Marketplace/RemotePackageList.py | 3 ++- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/plugins/Marketplace/InstallMissingPackagesDialog.py b/plugins/Marketplace/InstallMissingPackagesDialog.py index eb6c03f81c..9e8f1f6476 100644 --- a/plugins/Marketplace/InstallMissingPackagesDialog.py +++ b/plugins/Marketplace/InstallMissingPackagesDialog.py @@ -26,8 +26,7 @@ class InstallMissingPackageDialog(QObject): self._restart_needed = False self._package_metadata: List[Dict[str, str]] = packages_metadata - self._package_model = MissingPackageList() - self._package_model.setPackageIds(packages_metadata) + self._package_model = MissingPackageList(packages_metadata) def show(self): plugin_path = self._plugin_registry.getPluginPath("Marketplace") diff --git a/plugins/Marketplace/MissingPackageList.py b/plugins/Marketplace/MissingPackageList.py index b036e8b966..818b54eb37 100644 --- a/plugins/Marketplace/MissingPackageList.py +++ b/plugins/Marketplace/MissingPackageList.py @@ -17,16 +17,10 @@ if TYPE_CHECKING: catalog = i18nCatalog("cura") class MissingPackageList(RemotePackageList): - def __init__(self, parent: Optional["QObject"] = None) -> None: + def __init__(self, packages: List[Dict[str, str]], parent: Optional["QObject"] = None) -> None: super().__init__(parent) self._package_metadata: List[Dict[str, str]] = [] # self.packageTypeFilter = None # This will be our new filter self._package_type_filter = "material" - - def setPackageIds(self, packages: List[Dict[str, str]]) -> None: - self._package_metadata = packages - search_string = ", ".join(map(lambda package: package["id"], packages)) - # self.setSearchString(search_string) - self.setSearchString("ABS") - - + self._search_type = "package_ids" + self._requested_search_string = ",".join(map(lambda package: package["id"], packages)) diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index c20efabbc2..d06d2c64c5 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -28,6 +28,7 @@ class RemotePackageList(PackageList): self._package_type_filter = "" self._requested_search_string = "" self._current_search_string = "" + self._search_type = "search" self._request_url = self._initialRequestUrl() self._ongoing_requests["get_packages"] = None self.isLoadingChanged.connect(self._onLoadingChanged) @@ -100,7 +101,7 @@ class RemotePackageList(PackageList): if self._package_type_filter != "": request_url += f"&package_type={self._package_type_filter}" if self._current_search_string != "": - request_url += f"&search={self._current_search_string}" + request_url += f"&{self._search_type}={self._current_search_string}" return request_url def _parseResponse(self, reply: "QNetworkReply") -> None: From 812b728636cc6e8437c56b4f4b6cb3da6439b2df Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 2 Jun 2022 15:36:53 +0200 Subject: [PATCH 11/29] Show warning when opening file without installing missing materials CURA-6990 --- plugins/3MFReader/WorkspaceDialog.py | 11 +++++++++++ plugins/3MFReader/WorkspaceDialog.qml | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 56bc9e2fcc..55261480a0 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -11,6 +11,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack from plugins.Marketplace.InstallMissingPackagesDialog import InstallMissingPackageDialog from .UpdatableMachinesModel import UpdatableMachinesModel +from UM.Message import Message import os import threading @@ -378,6 +379,16 @@ class WorkspaceDialog(QObject): time.sleep(1 / 50) QCoreApplication.processEvents() # Ensure that the GUI does not freeze. + @pyqtSlot() + def showMissingMaterialsWarning(self) -> None: + result_message = Message( + i18n_catalog.i18nc("@info:status", "The material used in this project relies on some material definitions not available in Cura, this might produce undesirable print results. We highly recommend installing the full material package from the Marketplace."), + lifetime=0, + title=i18n_catalog.i18nc("@info:title", "Material profiles not installed"), + message_type=Message.MessageType.WARNING + ) + result_message.show() + def __show(self) -> None: if self._view is None: self._createViewFromQML() diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index f6048f8324..de0f5c8acd 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -463,7 +463,10 @@ UM.Dialog { visible: buttonWarning text: catalog.i18nc("@action:button", "Open project anyway") - onClicked: reject() + onClicked: { + manager.showMissingMaterialsWarning(); + accept(); + } }, Cura.PrimaryButton { From 511b10c084ad7d4daa4ee1dc9d56e873a1de702f Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Thu, 2 Jun 2022 16:54:08 +0200 Subject: [PATCH 12/29] Display package cards in the package list for packages that can't be found on the marketplace api. When the final page of results is fetched, the list of all package_ids retrieved from the api will be compared with the ones we were searching for. Any that are missing have cards displayed with only basic information (name and version). CURA-6990 --- plugins/Marketplace/MissingPackageList.py | 28 ++++++++++++++--- plugins/Marketplace/PackageModel.py | 22 ++++++++++++++ .../resources/qml/PackageCardHeader.qml | 30 +++++++++++++++++-- .../Marketplace/resources/qml/Packages.qml | 7 +++-- 4 files changed, 78 insertions(+), 9 deletions(-) diff --git a/plugins/Marketplace/MissingPackageList.py b/plugins/Marketplace/MissingPackageList.py index 818b54eb37..c979f8796c 100644 --- a/plugins/Marketplace/MissingPackageList.py +++ b/plugins/Marketplace/MissingPackageList.py @@ -17,10 +17,30 @@ if TYPE_CHECKING: catalog = i18nCatalog("cura") class MissingPackageList(RemotePackageList): - def __init__(self, packages: List[Dict[str, str]], parent: Optional["QObject"] = None) -> None: + def __init__(self, packages_metadata: List[Dict[str, str]], parent: Optional["QObject"] = None) -> None: super().__init__(parent) - self._package_metadata: List[Dict[str, str]] = [] - # self.packageTypeFilter = None # This will be our new filter + self._packages_metadata: List[Dict[str, str]] = packages_metadata self._package_type_filter = "material" self._search_type = "package_ids" - self._requested_search_string = ",".join(map(lambda package: package["id"], packages)) + self._requested_search_string = ",".join(map(lambda package: package["id"], packages_metadata)) + + def _parseResponse(self, reply: "QNetworkReply") -> None: + super()._parseResponse(reply) + + # At the end of the list we want to show some information about packages the user is missing that can't be found + # This will add cards with some information about the missing packages + if not self.hasMore: + self._addPackagesMissingFromRequest() + + def _addPackagesMissingFromRequest(self): + """Create cards for packages the user needs to install that could not be found""" + returned_packages_ids = [item["package"].packageId for item in self._items] + + for package_metadata in self._packages_metadata: + if package_metadata["id"] not in returned_packages_ids: + package = PackageModel.fromIncompletePackageInformation(package_metadata["display_name"], package_metadata["package_version"], self._package_type_filter) + self.appendItem({"package": package}) + + self.itemsChanged.emit() + + diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index bd1b0681fc..cf641e3270 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -84,6 +84,20 @@ class PackageModel(QObject): self._is_busy = False + self._is_missing_package_information = False + + @classmethod + def fromIncompletePackageInformation(cls, display_name: str, package_version: str, package_type: str): + package_data = { + "display_name": display_name, + "package_version": package_version, + "package_type": package_type, + "description": "The material package associated with the Cura project could not be found on the Ultimaker marketplace. Use the partial material profile definition stored in the Cura project file at your own risk." + } + package_model = cls(package_data) + package_model.setIsMissingPackageInformation(True) + return package_model + @pyqtSlot() def _processUpdatedPackages(self): self.setCanUpdate(self._package_manager.checkIfPackageCanUpdate(self._package_id)) @@ -385,3 +399,11 @@ class PackageModel(QObject): def canUpdate(self) -> bool: """Flag indicating if the package can be updated""" return self._can_update + + def setIsMissingPackageInformation(self, isMissingPackageInformation: bool): + self._is_missing_package_information = isMissingPackageInformation + + @pyqtProperty(bool) + def isMissingPackageInformation(self) -> bool: + """Flag indicating if the package can be updated""" + return self._is_missing_package_information diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 00d107f4fc..3e8e0fc26f 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -19,6 +19,8 @@ Item property bool showInstallButton: false property bool showUpdateButton: false + property string missingPackageReadMoreUrl: "https://support.ultimaker.com" + width: parent.width height: UM.Theme.getSize("card").height @@ -109,6 +111,7 @@ Item Button { id: externalLinkButton + visible: !packageData.isMissingPackageInformation // For some reason if i set padding, they don't match up. If i set all of them explicitly, it does work? leftPadding: UM.Theme.getSize("narrow_margin").width @@ -155,6 +158,7 @@ Item UM.Label { id: authorBy + visible: !packageData.isMissingPackageInformation Layout.alignment: Qt.AlignCenter text: catalog.i18nc("@label Is followed by the name of an author", "By") @@ -165,6 +169,7 @@ Item // clickable author name Item { + visible: !packageData.isMissingPackageInformation Layout.fillWidth: true implicitHeight: authorBy.height Layout.alignment: Qt.AlignTop @@ -182,10 +187,29 @@ Item } } + Item + { + visible: packageData.isMissingPackageInformation + Layout.fillWidth: true + implicitHeight: readMoreButton.height + Layout.alignment: Qt.AlignTop + Cura.TertiaryButton + { + id: readMoreButton + text: catalog.i18nc("@button:label", "Learn More") + leftPadding: 0 + rightPadding: 0 + iconSource: UM.Theme.getIcon("LinkExternal") + isIconOnRightSide: true + + onClicked: Qt.openUrlExternally(missingPackageReadMoreUrl) + } + } + ManageButton { id: enableManageButton - visible: showDisableButton && packageData.isInstalled && !packageData.isToBeInstalled && packageData.packageType != "material" + visible: showDisableButton && packageData.isInstalled && !packageData.isToBeInstalled && packageData.packageType != "material" && !packageData.isMissingPackageInformation enabled: !packageData.busy button_style: !packageData.isActive @@ -199,7 +223,7 @@ Item ManageButton { id: installManageButton - visible: showInstallButton && (packageData.canDowngrade || !packageData.isBundled) + visible: showInstallButton && (packageData.canDowngrade || !packageData.isBundled) && !packageData.isMissingPackageInformation enabled: !packageData.busy busy: packageData.busy button_style: !(packageData.isInstalled || packageData.isToBeInstalled) @@ -229,7 +253,7 @@ Item ManageButton { id: updateManageButton - visible: showUpdateButton && packageData.canUpdate + visible: showUpdateButton && packageData.canUpdate && !packageData.isMissingPackageInformation enabled: !packageData.busy busy: packageData.busy Layout.alignment: Qt.AlignTop diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 70ff7de195..6e83f1e33b 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -62,8 +62,11 @@ ListView hoverEnabled: true onClicked: { - packages.selectedPackage = model.package; - contextStack.push(packageDetailsComponent); + if (!model.package.isMissingPackageInformation) + { + packages.selectedPackage = model.package; + contextStack.push(packageDetailsComponent); + } } PackageCard From fccfcb0e2b06dad82b4081fd56438f87c6b3b90e Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 2 Jun 2022 16:58:41 +0200 Subject: [PATCH 13/29] Add scroll bar to the `WorkSpaceDialog` The warning message took out some space from the dialog. Added a scroll bar to make sure the content can still be read. CURA-6990 --- plugins/3MFReader/WorkspaceDialog.qml | 693 +++++++++++++------------- 1 file changed, 351 insertions(+), 342 deletions(-) diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index de0f5c8acd..60f5a42094 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -31,337 +31,220 @@ UM.Dialog } } - Item + Flickable { - id: dialogSummaryItem + clip: true width: parent.width - height: childrenRect.height - anchors.margins: 10 * screenScaleFactor + height: parent.height + contentHeight: dialogSummaryItem.height + ScrollBar.vertical: UM.ScrollBar { id: verticalScrollBar } - UM.I18nCatalog + Item { - id: catalog - name: "cura" - } - - ListModel - { - id: resolveStrategiesModel - // Instead of directly adding the list elements, we add them afterwards. - // This is because it's impossible to use setting function results to be bound to listElement properties directly. - // See http://stackoverflow.com/questions/7659442/listelement-fields-as-properties - Component.onCompleted: - { - append({"key": "override", "label": catalog.i18nc("@action:ComboBox Update/override existing profile", "Update existing")}); - append({"key": "new", "label": catalog.i18nc("@action:ComboBox Save settings in a new profile", "Create new")}); - } - } - - Column - { - width: parent.width + id: dialogSummaryItem + width: verticalScrollBar.visible ? parent.width - verticalScrollBar.width - UM.Theme.getSize("default_margin").width : parent.width height: childrenRect.height - spacing: UM.Theme.getSize("default_margin").height + anchors.margins: 10 * screenScaleFactor + + UM.I18nCatalog + { + id: catalog + name: "cura" + } + + ListModel + { + id: resolveStrategiesModel + // Instead of directly adding the list elements, we add them afterwards. + // This is because it's impossible to use setting function results to be bound to listElement properties directly. + // See http://stackoverflow.com/questions/7659442/listelement-fields-as-properties + Component.onCompleted: + { + append({"key": "override", "label": catalog.i18nc("@action:ComboBox Update/override existing profile", "Update existing")}); + append({"key": "new", "label": catalog.i18nc("@action:ComboBox Save settings in a new profile", "Create new")}); + } + } Column { width: parent.width height: childrenRect.height + spacing: UM.Theme.getSize("default_margin").height - UM.Label + Column { - id: titleLabel - text: catalog.i18nc("@action:title", "Summary - Cura Project") - font: UM.Theme.getFont("large") - } - - Rectangle - { - id: separator - color: UM.Theme.getColor("text") width: parent.width - height: UM.Theme.getSize("default_lining").height - } - } + height: childrenRect.height - Item - { - width: parent.width - height: childrenRect.height - - UM.TooltipArea - { - id: machineResolveStrategyTooltip - anchors.top: parent.top - anchors.right: parent.right - width: (parent.width / 3) | 0 - height: visible ? comboboxHeight : 0 - visible: base.visible && machineResolveComboBox.model.count > 1 - text: catalog.i18nc("@info:tooltip", "How should the conflict in the machine be resolved?") - Cura.ComboBox + UM.Label { - id: machineResolveComboBox - model: manager.updatableMachinesModel - visible: machineResolveStrategyTooltip.visible - textRole: "displayName" + id: titleLabel + text: catalog.i18nc("@action:title", "Summary - Cura Project") + font: UM.Theme.getFont("large") + } + + Rectangle + { + id: separator + color: UM.Theme.getColor("text") width: parent.width - height: UM.Theme.getSize("button").height - onCurrentIndexChanged: - { - if (model.getItem(currentIndex).id == "new" - && model.getItem(currentIndex).type == "default_option") - { - manager.setResolveStrategy("machine", "new") - } - else - { - manager.setResolveStrategy("machine", "override") - manager.setMachineToOverride(model.getItem(currentIndex).id) - } - } + height: UM.Theme.getSize("default_lining").height + } + } - onVisibleChanged: - { - if (!visible) {return} + Item + { + width: parent.width + height: childrenRect.height - currentIndex = 0 - // If the project printer exists in Cura, set it as the default dropdown menu option. - // No need to check object 0, which is the "Create new" option - for (var i = 1; i < model.count; i++) + UM.TooltipArea + { + id: machineResolveStrategyTooltip + anchors.top: parent.top + anchors.right: parent.right + width: (parent.width / 3) | 0 + height: visible ? comboboxHeight : 0 + visible: base.visible && machineResolveComboBox.model.count > 1 + text: catalog.i18nc("@info:tooltip", "How should the conflict in the machine be resolved?") + Cura.ComboBox + { + id: machineResolveComboBox + model: manager.updatableMachinesModel + visible: machineResolveStrategyTooltip.visible + textRole: "displayName" + width: parent.width + height: UM.Theme.getSize("button").height + onCurrentIndexChanged: { - if (model.getItem(i).name == manager.machineName) + if (model.getItem(currentIndex).id == "new" + && model.getItem(currentIndex).type == "default_option") { - currentIndex = i - break + manager.setResolveStrategy("machine", "new") + } + else + { + manager.setResolveStrategy("machine", "override") + manager.setMachineToOverride(model.getItem(currentIndex).id) } } - // The project printer does not exist in Cura. If there is at least one printer of the same - // type, select the first one, else set the index to "Create new" - if (currentIndex == 0 && model.count > 1) + + onVisibleChanged: { - currentIndex = 1 + if (!visible) {return} + + currentIndex = 0 + // If the project printer exists in Cura, set it as the default dropdown menu option. + // No need to check object 0, which is the "Create new" option + for (var i = 1; i < model.count; i++) + { + if (model.getItem(i).name == manager.machineName) + { + currentIndex = i + break + } + } + // The project printer does not exist in Cura. If there is at least one printer of the same + // type, select the first one, else set the index to "Create new" + if (currentIndex == 0 && model.count > 1) + { + currentIndex = 1 + } } } } - } - Column - { - width: parent.width - height: childrenRect.height - - UM.Label - { - id: printer_settings_label - text: catalog.i18nc("@action:label", "Printer settings") - font: UM.Theme.getFont("default_bold") - } - - Row + Column { width: parent.width height: childrenRect.height UM.Label { - text: catalog.i18nc("@action:label", "Type") - width: (parent.width / 3) | 0 - } - UM.Label - { - text: manager.machineType - width: (parent.width / 3) | 0 - } - } - - Row - { - width: parent.width - height: childrenRect.height - - UM.Label - { - text: catalog.i18nc("@action:label", manager.isPrinterGroup ? "Printer Group" : "Printer Name") - width: (parent.width / 3) | 0 - } - UM.Label - { - text: manager.machineName - width: (parent.width / 3) | 0 - wrapMode: Text.WordWrap - } - } - } - } - - Item - { - width: parent.width - height: childrenRect.height - - UM.TooltipArea - { - anchors.right: parent.right - anchors.top: parent.top - width: (parent.width / 3) | 0 - height: visible ? comboboxHeight : 0 - visible: manager.qualityChangesConflict - text: catalog.i18nc("@info:tooltip", "How should the conflict in the profile be resolved?") - Cura.ComboBox - { - model: resolveStrategiesModel - textRole: "label" - id: qualityChangesResolveComboBox - width: parent.width - height: UM.Theme.getSize("button").height - onActivated: - { - manager.setResolveStrategy("quality_changes", resolveStrategiesModel.get(index).key) - } - } - } - - Column - { - width: parent.width - height: childrenRect.height - - UM.Label - { - text: catalog.i18nc("@action:label", "Profile settings") - font: UM.Theme.getFont("default_bold") - } - - Row - { - width: parent.width - height: childrenRect.height - - UM.Label - { - text: catalog.i18nc("@action:label", "Name") - width: (parent.width / 3) | 0 - } - UM.Label - { - text: manager.qualityName - width: (parent.width / 3) | 0 - wrapMode: Text.WordWrap - } - } - - Row - { - width: parent.width - height: childrenRect.height - - UM.Label - { - text: catalog.i18nc("@action:label", "Intent") - width: (parent.width / 3) | 0 - } - UM.Label - { - text: manager.intentName - width: (parent.width / 3) | 0 - wrapMode: Text.WordWrap - } - } - - Row - { - width: parent.width - height: childrenRect.height - - UM.Label - { - text: catalog.i18nc("@action:label", "Not in profile") - visible: manager.numUserSettings != 0 - width: (parent.width / 3) | 0 - } - UM.Label - { - text: catalog.i18ncp("@action:label", "%1 override", "%1 overrides", manager.numUserSettings).arg(manager.numUserSettings) - visible: manager.numUserSettings != 0 - width: (parent.width / 3) | 0 - } - } - - Row - { - width: parent.width - height: childrenRect.height - - UM.Label - { - text: catalog.i18nc("@action:label", "Derivative from") - visible: manager.numSettingsOverridenByQualityChanges != 0 - width: (parent.width / 3) | 0 - } - UM.Label - { - text: catalog.i18ncp("@action:label", "%1, %2 override", "%1, %2 overrides", manager.numSettingsOverridenByQualityChanges).arg(manager.qualityType).arg(manager.numSettingsOverridenByQualityChanges) - width: (parent.width / 3) | 0 - visible: manager.numSettingsOverridenByQualityChanges != 0 - wrapMode: Text.WordWrap - } - } - } - } - - Item - { - width: parent.width - height: childrenRect.height - - UM.TooltipArea - { - id: materialResolveTooltip - anchors.right: parent.right - anchors.top: parent.top - width: (parent.width / 3) | 0 - height: visible ? comboboxHeight : 0 - visible: manager.materialConflict - text: catalog.i18nc("@info:tooltip", "How should the conflict in the material be resolved?") - Cura.ComboBox - { - model: resolveStrategiesModel - textRole: "label" - id: materialResolveComboBox - width: parent.width - height: UM.Theme.getSize("button").height - onActivated: - { - manager.setResolveStrategy("material", resolveStrategiesModel.get(index).key) - } - } - } - - Column - { - width: parent.width - height: childrenRect.height - Row - { - height: childrenRect.height - width: parent.width - spacing: UM.Theme.getSize("narrow_margin").width - - UM.Label - { - text: catalog.i18nc("@action:label", "Material settings") + id: printer_settings_label + text: catalog.i18nc("@action:label", "Printer settings") font: UM.Theme.getFont("default_bold") - width: (parent.width / 3) | 0 } - } - Repeater - { - model: manager.materialLabels - delegate: Row + Row { width: parent.width height: childrenRect.height + + UM.Label + { + text: catalog.i18nc("@action:label", "Type") + width: (parent.width / 3) | 0 + } + UM.Label + { + text: manager.machineType + width: (parent.width / 3) | 0 + } + } + + Row + { + width: parent.width + height: childrenRect.height + + UM.Label + { + text: catalog.i18nc("@action:label", manager.isPrinterGroup ? "Printer Group" : "Printer Name") + width: (parent.width / 3) | 0 + } + UM.Label + { + text: manager.machineName + width: (parent.width / 3) | 0 + wrapMode: Text.WordWrap + } + } + } + } + + Item + { + width: parent.width + height: childrenRect.height + + UM.TooltipArea + { + anchors.right: parent.right + anchors.top: parent.top + width: (parent.width / 3) | 0 + height: visible ? comboboxHeight : 0 + visible: manager.qualityChangesConflict + text: catalog.i18nc("@info:tooltip", "How should the conflict in the profile be resolved?") + Cura.ComboBox + { + model: resolveStrategiesModel + textRole: "label" + id: qualityChangesResolveComboBox + width: parent.width + height: UM.Theme.getSize("button").height + onActivated: + { + manager.setResolveStrategy("quality_changes", resolveStrategiesModel.get(index).key) + } + } + } + + Column + { + width: parent.width + height: childrenRect.height + + UM.Label + { + text: catalog.i18nc("@action:label", "Profile settings") + font: UM.Theme.getFont("default_bold") + } + + Row + { + width: parent.width + height: childrenRect.height + UM.Label { text: catalog.i18nc("@action:label", "Name") @@ -369,75 +252,201 @@ UM.Dialog } UM.Label { - text: modelData + text: manager.qualityName width: (parent.width / 3) | 0 wrapMode: Text.WordWrap } } + + Row + { + width: parent.width + height: childrenRect.height + + UM.Label + { + text: catalog.i18nc("@action:label", "Intent") + width: (parent.width / 3) | 0 + } + UM.Label + { + text: manager.intentName + width: (parent.width / 3) | 0 + wrapMode: Text.WordWrap + } + } + + Row + { + width: parent.width + height: childrenRect.height + + UM.Label + { + text: catalog.i18nc("@action:label", "Not in profile") + visible: manager.numUserSettings != 0 + width: (parent.width / 3) | 0 + } + UM.Label + { + text: catalog.i18ncp("@action:label", "%1 override", "%1 overrides", manager.numUserSettings).arg(manager.numUserSettings) + visible: manager.numUserSettings != 0 + width: (parent.width / 3) | 0 + } + } + + Row + { + width: parent.width + height: childrenRect.height + + UM.Label + { + text: catalog.i18nc("@action:label", "Derivative from") + visible: manager.numSettingsOverridenByQualityChanges != 0 + width: (parent.width / 3) | 0 + } + UM.Label + { + text: catalog.i18ncp("@action:label", "%1, %2 override", "%1, %2 overrides", manager.numSettingsOverridenByQualityChanges).arg(manager.qualityType).arg(manager.numSettingsOverridenByQualityChanges) + width: (parent.width / 3) | 0 + visible: manager.numSettingsOverridenByQualityChanges != 0 + wrapMode: Text.WordWrap + } + } } } - } - Column - { - width: parent.width - height: childrenRect.height - - UM.Label + Item { - text: catalog.i18nc("@action:label", "Setting visibility") - font: UM.Theme.getFont("default_bold") + width: parent.width + height: childrenRect.height + + UM.TooltipArea + { + id: materialResolveTooltip + anchors.right: parent.right + anchors.top: parent.top + width: (parent.width / 3) | 0 + height: visible ? comboboxHeight : 0 + visible: manager.materialConflict + text: catalog.i18nc("@info:tooltip", "How should the conflict in the material be resolved?") + Cura.ComboBox + { + model: resolveStrategiesModel + textRole: "label" + id: materialResolveComboBox + width: parent.width + height: UM.Theme.getSize("button").height + onActivated: + { + manager.setResolveStrategy("material", resolveStrategiesModel.get(index).key) + } + } + } + + Column + { + width: parent.width + height: childrenRect.height + Row + { + height: childrenRect.height + width: parent.width + spacing: UM.Theme.getSize("narrow_margin").width + + UM.Label + { + text: catalog.i18nc("@action:label", "Material settings") + font: UM.Theme.getFont("default_bold") + width: (parent.width / 3) | 0 + } + } + + Repeater + { + model: manager.materialLabels + delegate: Row + { + width: parent.width + height: childrenRect.height + UM.Label + { + text: catalog.i18nc("@action:label", "Name") + width: (parent.width / 3) | 0 + } + UM.Label + { + text: modelData + width: (parent.width / 3) | 0 + wrapMode: Text.WordWrap + } + } + } + } } + + Column + { + width: parent.width + height: childrenRect.height + + UM.Label + { + text: catalog.i18nc("@action:label", "Setting visibility") + font: UM.Theme.getFont("default_bold") + } + Row + { + width: parent.width + height: childrenRect.height + UM.Label + { + text: catalog.i18nc("@action:label", "Mode") + width: (parent.width / 3) | 0 + } + UM.Label + { + text: manager.activeMode + width: (parent.width / 3) | 0 + } + } + Row + { + width: parent.width + height: childrenRect.height + visible: manager.hasVisibleSettingsField + UM.Label + { + text: catalog.i18nc("@action:label", "Visible settings:") + width: (parent.width / 3) | 0 + } + UM.Label + { + text: catalog.i18nc("@action:label", "%1 out of %2" ).arg(manager.numVisibleSettings).arg(manager.totalNumberOfSettings) + width: (parent.width / 3) | 0 + } + } + } + Row { width: parent.width height: childrenRect.height - UM.Label + visible: manager.hasObjectsOnPlate + UM.ColorImage { - text: catalog.i18nc("@action:label", "Mode") - width: (parent.width / 3) | 0 + width: warningLabel.height + height: width + source: UM.Theme.getIcon("Information") + color: UM.Theme.getColor("text") } UM.Label { - text: manager.activeMode - width: (parent.width / 3) | 0 + id: warningLabel + text: catalog.i18nc("@action:warning", "Loading a project will clear all models on the build plate.") } } - Row - { - width: parent.width - height: childrenRect.height - visible: manager.hasVisibleSettingsField - UM.Label - { - text: catalog.i18nc("@action:label", "Visible settings:") - width: (parent.width / 3) | 0 - } - UM.Label - { - text: catalog.i18nc("@action:label", "%1 out of %2" ).arg(manager.numVisibleSettings).arg(manager.totalNumberOfSettings) - width: (parent.width / 3) | 0 - } - } - } - - Row - { - width: parent.width - height: childrenRect.height - visible: manager.hasObjectsOnPlate - UM.ColorImage - { - width: warningLabel.height - height: width - source: UM.Theme.getIcon("Information") - color: UM.Theme.getColor("text") - } - UM.Label - { - id: warningLabel - text: catalog.i18nc("@action:warning", "Loading a project will clear all models on the build plate.") - } } } } From 8fe53163b3263e58d85dd860b37bce7e746dac29 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 2 Jun 2022 17:33:56 +0200 Subject: [PATCH 14/29] Add Action button and learn more link to warning message CURA-6990 --- plugins/3MFReader/WorkspaceDialog.py | 29 +++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 55261480a0..30a2a39465 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -2,7 +2,9 @@ # Cura is released under the terms of the LGPLv3 or higher. from typing import List, Optional, Dict, cast -from PyQt6.QtCore import pyqtSignal, QObject, pyqtProperty, QCoreApplication +from PyQt6.QtCore import pyqtSignal, QObject, pyqtProperty, QCoreApplication, QUrl +from PyQt6.QtGui import QDesktopServices + from UM.FlameProfiler import pyqtSlot from UM.PluginRegistry import PluginRegistry from UM.Application import Application @@ -387,8 +389,33 @@ class WorkspaceDialog(QObject): title=i18n_catalog.i18nc("@info:title", "Material profiles not installed"), message_type=Message.MessageType.WARNING ) + result_message.addAction( + "learn_more", + name=i18n_catalog.i18nc("@action:button", "Learn more"), + icon="", + description="Learn more about project materials.", + button_align=Message.ActionButtonAlignment.ALIGN_LEFT, + button_style=Message.ActionButtonStyle.LINK + ) + result_message.addAction( + "install_materials", + name=i18n_catalog.i18nc("@action:button", "Install Materials"), + icon="", + description="Install missing materials from project file.", + button_align=Message.ActionButtonAlignment.ALIGN_RIGHT, + button_style=Message.ActionButtonStyle.DEFAULT + ) + result_message.actionTriggered.connect(self._onMessageActionTriggered) result_message.show() + def _onMessageActionTriggered(self, message: Message, sync_message_action: str): + if sync_message_action == "install_materials": + self.installMissingPackages() + message.hide() + elif sync_message_action == "learn_more": + QDesktopServices.openUrl(QUrl("https://support.ultimaker.com/hc/en-us/articles/360011968360-Using-the-Ultimaker-Marketplace")) + + def __show(self) -> None: if self._view is None: self._createViewFromQML() From a0fd5afbe78ac6e6d9bee9e668af0295f17fba96 Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Thu, 2 Jun 2022 17:38:42 +0200 Subject: [PATCH 15/29] Add missing warning badge CURA-6990 --- .../resources/qml/PackageCardHeader.qml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 3e8e0fc26f..c014c00d8d 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -89,6 +89,31 @@ Item Layout.preferredWidth: parent.width Layout.preferredHeight: childrenRect.height + UM.ColorImage + { + id: badge + + width: UM.Theme.getSize("section_icon").width + height: UM.Theme.getSize("section_icon").height + + visible: packageData.isMissingPackageInformation + + source: UM.Theme.getIcon("WarningBadge", "low") + color: UM.Theme.getColor("warning") + + // Make a themable circle in the background so we can change it in other themes + Rectangle + { + id: iconBackground + color: UM.Theme.getColor("warning_badge_background") + anchors.centerIn: parent + width: parent.width - 1.5 //1.5 pixels smaller, (at least sqrt(2), regardless of screen pixel scale) so that the circle doesn't show up behind the icon due to anti-aliasing. + height: parent.height - 1.5 + radius: width / 2 + z: parent.z - 1 + } + } + UM.Label { text: packageData.displayName From 4c2be68c7b1774a8729b0e6f73d8101289d09e60 Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Thu, 2 Jun 2022 17:39:41 +0200 Subject: [PATCH 16/29] Add pyqtSignal no get rid of annoying errors CURA-6990 --- plugins/Marketplace/PackageModel.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index cf641e3270..3376880ec2 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -400,10 +400,13 @@ class PackageModel(QObject): """Flag indicating if the package can be updated""" return self._can_update + isMissingPackageInformationChanged = pyqtSignal() + def setIsMissingPackageInformation(self, isMissingPackageInformation: bool): self._is_missing_package_information = isMissingPackageInformation + self.isMissingPackageInformationChanged.emit() - @pyqtProperty(bool) + @pyqtProperty(bool, notify=isMissingPackageInformationChanged) def isMissingPackageInformation(self) -> bool: """Flag indicating if the package can be updated""" return self._is_missing_package_information From 277a6a38b214ca546a2116c9f9609c585d2c7a30 Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Thu, 2 Jun 2022 17:40:14 +0200 Subject: [PATCH 17/29] Update campaign link CURA-6990 --- plugins/Marketplace/resources/qml/PackageCardHeader.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index c014c00d8d..416550aa25 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -19,7 +19,7 @@ Item property bool showInstallButton: false property bool showUpdateButton: false - property string missingPackageReadMoreUrl: "https://support.ultimaker.com" + property string missingPackageReadMoreUrl: "https://ultimaker.atlassian.net/wiki/spaces/SD/pages/1231916580/Campaign+links+from+Cura+to+the+Ultimaker+domain" width: parent.width From 70c36fc80fc8bd28959df022f5d4ddc8595c0e3e Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Fri, 3 Jun 2022 10:13:29 +0200 Subject: [PATCH 18/29] Use correct status icon CURA-6990 --- .../resources/qml/PackageCardHeader.qml | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 416550aa25..6c9d533bb5 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -89,29 +89,12 @@ Item Layout.preferredWidth: parent.width Layout.preferredHeight: childrenRect.height - UM.ColorImage + UM.StatusIcon { - id: badge - - width: UM.Theme.getSize("section_icon").width + width: UM.Theme.getSize("section_icon").width + UM.Theme.getSize("narrow_margin").width height: UM.Theme.getSize("section_icon").height - + status: UM.StatusIcon.Status.WARNING visible: packageData.isMissingPackageInformation - - source: UM.Theme.getIcon("WarningBadge", "low") - color: UM.Theme.getColor("warning") - - // Make a themable circle in the background so we can change it in other themes - Rectangle - { - id: iconBackground - color: UM.Theme.getColor("warning_badge_background") - anchors.centerIn: parent - width: parent.width - 1.5 //1.5 pixels smaller, (at least sqrt(2), regardless of screen pixel scale) so that the circle doesn't show up behind the icon due to anti-aliasing. - height: parent.height - 1.5 - radius: width / 2 - z: parent.z - 1 - } } UM.Label From 4492e3fba2eeaff686bb7e2a3d6e9c133af4f3d0 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Fri, 3 Jun 2022 10:39:01 +0200 Subject: [PATCH 19/29] Add warning border to `PackageCard` if information is missing CURA-6990 --- plugins/Marketplace/resources/qml/PackageCard.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 52254a478f..c9dda29f7d 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -18,6 +18,8 @@ Rectangle height: childrenRect.height color: UM.Theme.getColor("main_background") radius: UM.Theme.getSize("default_radius").width + border.color: packageData.isMissingPackageInformation ? UM.Theme.getColor("warning") : "transparent" + border.width: packageData.isMissingPackageInformation ? 1 : 0 PackageCardHeader { From 8d3b3ae411948e1adb333899535b6f3366c8ac83 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Fri, 3 Jun 2022 11:13:19 +0200 Subject: [PATCH 20/29] Use themed border width CURA-6990 --- plugins/Marketplace/resources/qml/PackageCard.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index c9dda29f7d..b74dad0712 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -19,7 +19,7 @@ Rectangle color: UM.Theme.getColor("main_background") radius: UM.Theme.getSize("default_radius").width border.color: packageData.isMissingPackageInformation ? UM.Theme.getColor("warning") : "transparent" - border.width: packageData.isMissingPackageInformation ? 1 : 0 + border.width: packageData.isMissingPackageInformation ? UM.Theme.getSize("default_lining").width : 0 PackageCardHeader { From 2230269ab3d458601fd026c7d0dd8e279d7c252e Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Fri, 3 Jun 2022 15:17:32 +0200 Subject: [PATCH 21/29] Simplify variable assignment CURA-6990 --- plugins/Marketplace/InstallMissingPackagesDialog.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/Marketplace/InstallMissingPackagesDialog.py b/plugins/Marketplace/InstallMissingPackagesDialog.py index 9e8f1f6476..8ed84c1d86 100644 --- a/plugins/Marketplace/InstallMissingPackagesDialog.py +++ b/plugins/Marketplace/InstallMissingPackagesDialog.py @@ -42,10 +42,7 @@ class InstallMissingPackageDialog(QObject): if self._dialog is None: return - if self._package_manager.hasPackagesToRemoveOrInstall: - self._restart_needed = True - else: - self._restart_needed = False + self._restart_needed = self._package_manager.hasPackagesToRemoveOrInstall self.showRestartChanged.emit() showRestartChanged = pyqtSignal() From eccef62dc7818bea012069bff117bae32d6628a4 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Fri, 3 Jun 2022 15:22:29 +0200 Subject: [PATCH 22/29] Add typing CURA-6990 --- plugins/3MFReader/WorkspaceDialog.py | 8 ++++---- plugins/Marketplace/InstallMissingPackagesDialog.py | 8 ++++---- plugins/Marketplace/MissingPackageList.py | 2 +- plugins/Marketplace/PackageModel.py | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 30a2a39465..e3cbef607a 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -27,7 +27,7 @@ i18n_catalog = i18nCatalog("cura") class WorkspaceDialog(QObject): showDialogSignal = pyqtSignal() - def __init__(self, parent = None): + def __init__(self, parent = None) -> None: super().__init__(parent) self._component = None self._context = None @@ -287,11 +287,11 @@ class WorkspaceDialog(QObject): self.missingPackagesChanged.emit() @pyqtProperty("QVariantList", notify=missingPackagesChanged) - def missingPackages(self): + def missingPackages(self) -> List[Dict[str, str]]: return self._missing_package_metadata @pyqtSlot() - def installMissingPackages(self): + def installMissingPackages(self) -> None: self._install_missing_package_dialog = InstallMissingPackageDialog(self._missing_package_metadata) self._install_missing_package_dialog.show() @@ -408,7 +408,7 @@ class WorkspaceDialog(QObject): result_message.actionTriggered.connect(self._onMessageActionTriggered) result_message.show() - def _onMessageActionTriggered(self, message: Message, sync_message_action: str): + def _onMessageActionTriggered(self, message: Message, sync_message_action: str) -> None: if sync_message_action == "install_materials": self.installMissingPackages() message.hide() diff --git a/plugins/Marketplace/InstallMissingPackagesDialog.py b/plugins/Marketplace/InstallMissingPackagesDialog.py index 8ed84c1d86..3ed46959dd 100644 --- a/plugins/Marketplace/InstallMissingPackagesDialog.py +++ b/plugins/Marketplace/InstallMissingPackagesDialog.py @@ -11,7 +11,7 @@ from plugins.Marketplace.MissingPackageList import MissingPackageList class InstallMissingPackageDialog(QObject): """Dialog used to display packages that need to be installed to load 3mf file materials""" - def __init__(self, packages_metadata: List[Dict[str, str]]): + def __init__(self, packages_metadata: List[Dict[str, str]]) -> None: """Initialize :param packages_metadata: List of dictionaries containing information about missing packages. @@ -26,9 +26,9 @@ class InstallMissingPackageDialog(QObject): self._restart_needed = False self._package_metadata: List[Dict[str, str]] = packages_metadata - self._package_model = MissingPackageList(packages_metadata) + self._package_model: MissingPackageList = MissingPackageList(packages_metadata) - def show(self): + def show(self) -> None: plugin_path = self._plugin_registry.getPluginPath("Marketplace") if plugin_path is None: plugin_path = os.path.dirname(__file__) @@ -52,5 +52,5 @@ class InstallMissingPackageDialog(QObject): return self._restart_needed @pyqtProperty(QObject) - def model(self): + def model(self) -> MissingPackageList: return self._package_model diff --git a/plugins/Marketplace/MissingPackageList.py b/plugins/Marketplace/MissingPackageList.py index c979f8796c..385e78b95f 100644 --- a/plugins/Marketplace/MissingPackageList.py +++ b/plugins/Marketplace/MissingPackageList.py @@ -32,7 +32,7 @@ class MissingPackageList(RemotePackageList): if not self.hasMore: self._addPackagesMissingFromRequest() - def _addPackagesMissingFromRequest(self): + def _addPackagesMissingFromRequest(self) -> None: """Create cards for packages the user needs to install that could not be found""" returned_packages_ids = [item["package"].packageId for item in self._items] diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 3376880ec2..21db487d8f 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -87,7 +87,7 @@ class PackageModel(QObject): self._is_missing_package_information = False @classmethod - def fromIncompletePackageInformation(cls, display_name: str, package_version: str, package_type: str): + def fromIncompletePackageInformation(cls, display_name: str, package_version: str, package_type: str) -> PackageModel: package_data = { "display_name": display_name, "package_version": package_version, @@ -402,7 +402,7 @@ class PackageModel(QObject): isMissingPackageInformationChanged = pyqtSignal() - def setIsMissingPackageInformation(self, isMissingPackageInformation: bool): + def setIsMissingPackageInformation(self, isMissingPackageInformation: bool) -> None: self._is_missing_package_information = isMissingPackageInformation self.isMissingPackageInformationChanged.emit() From 34004df7afc0619599aca513218cdfa0833bacba Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Fri, 3 Jun 2022 16:25:43 +0200 Subject: [PATCH 23/29] Un-capitalise materials CURA-6990 --- plugins/3MFReader/WorkspaceDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index 60f5a42094..6ac7ac701e 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -480,7 +480,7 @@ UM.Dialog Cura.PrimaryButton { visible: buttonWarning - text: catalog.i18nc("@action:button", "Install missing Material") + text: catalog.i18nc("@action:button", "Install missing material") onClicked: manager.installMissingPackages() } ] From 781723fbbc54cd625b8bc3090705a17b602d5a0e Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 6 Jun 2022 21:27:32 +0200 Subject: [PATCH 24/29] Fix typing CURA-6990 --- plugins/Marketplace/PackageModel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 21db487d8f..078bfc879e 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -87,7 +87,7 @@ class PackageModel(QObject): self._is_missing_package_information = False @classmethod - def fromIncompletePackageInformation(cls, display_name: str, package_version: str, package_type: str) -> PackageModel: + def fromIncompletePackageInformation(cls, display_name: str, package_version: str, package_type: str) -> "PackageModel": package_data = { "display_name": display_name, "package_version": package_version, From 2f22002fd59e2be44934c0ff5c904c93a03fd690 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 6 Jun 2022 21:45:48 +0200 Subject: [PATCH 25/29] Add header and footer components to Dialog This provides control over how the header (the header is newly introduced in this commit) and footer components are rendered. The footer is the area where the `leftButtoons` and `rightButtons` are rendered. Having control over how this is rendered introduces for instance the possibility to render the buttons within a warning area (an example of this can bee seen in the WorkspaceDialog). CURA-6990 --- plugins/3MFReader/WorkspaceDialog.qml | 58 ++++++++++++++++++++++++--- resources/qml/ColorDialog.qml | 2 +- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index 6ac7ac701e..d0250abfd8 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -451,26 +451,72 @@ UM.Dialog } } - buttonWarningText: "The material used in this project is currently not installed in Cura.
Install the material profile and reopen the project." - buttonWarning: manager.missingPackages.length > 0 + property bool warning: manager.missingPackages.length > 0 + + footerComponent: Rectangle + { + color: warning ? UM.Theme.getColor("warning") : "transparent" + anchors.bottom: parent.bottom + width: parent.width + height: childrenRect.height + 2 * base.margin + + Column + { + height: childrenRect.height + spacing: base.margin + + anchors.margins: base.margin + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + + RowLayout + { + id: warningRow + height: childrenRect.height + visible: warning + spacing: base.margin + UM.ColorImage + { + width: UM.Theme.getSize("extruder_icon").width + height: UM.Theme.getSize("extruder_icon").height + source: UM.Theme.getIcon("Warning") + } + + UM.Label + { + id: warningText + text: "The material used in this project is currently not installed in Cura.
Install the material profile and reopen the project." + } + } + + Loader + { + width: parent.width + height: childrenRect.height + sourceComponent: buttonRow + } + } + } + buttonSpacing: UM.Theme.getSize("default_margin").width rightButtons: [ Cura.TertiaryButton { - visible: !buttonWarning + visible: !warning text: catalog.i18nc("@action:button", "Cancel") onClicked: reject() }, Cura.PrimaryButton { - visible: !buttonWarning + visible: !warning text: catalog.i18nc("@action:button", "Open") onClicked: accept() }, Cura.TertiaryButton { - visible: buttonWarning + visible: warning text: catalog.i18nc("@action:button", "Open project anyway") onClicked: { manager.showMissingMaterialsWarning(); @@ -479,7 +525,7 @@ UM.Dialog }, Cura.PrimaryButton { - visible: buttonWarning + visible: warning text: catalog.i18nc("@action:button", "Install missing material") onClicked: manager.installMissingPackages() } diff --git a/resources/qml/ColorDialog.qml b/resources/qml/ColorDialog.qml index 49adcf0cca..e5216a3404 100644 --- a/resources/qml/ColorDialog.qml +++ b/resources/qml/ColorDialog.qml @@ -29,7 +29,7 @@ UM.Dialog // the size of the dialog ourselves. // Ugly workaround for windows having overlapping elements due to incorrect dialog width minimumWidth: content.width + (Qt.platform.os == "windows" ? 4 * margin : 2 * margin) - minimumHeight: content.height + buttonArea.height + (Qt.platform.os == "windows" ? 5 * margin : 3 * margin) + minimumHeight: content.height + footer.height + (Qt.platform.os == "windows" ? 5 * margin : 3 * margin) property alias color: colorInput.text property var swatchColors: [ From bee4da63681cb38c25b593456409fe6cf8a50777 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 6 Jun 2022 22:27:31 +0200 Subject: [PATCH 26/29] Re-use `Marketplace` Component in the `InstallMissingPackagesDialog` CURA-6990 --- .../qml/InstallMissingPackagesDialog.qml | 149 +----------------- .../Marketplace/resources/qml/Marketplace.qml | 8 +- 2 files changed, 12 insertions(+), 145 deletions(-) diff --git a/plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml b/plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml index a5fbe2c965..f08fed4f85 100644 --- a/plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml +++ b/plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml @@ -9,150 +9,11 @@ import QtQuick.Window 2.2 import UM 1.5 as UM import Cura 1.6 as Cura -Window +Marketplace { - id: marketplaceDialog - - property variant catalog: UM.I18nCatalog { name: "cura" } - - signal searchStringChanged(string new_search) - - minimumWidth: UM.Theme.getSize("modal_window_minimum").width - minimumHeight: UM.Theme.getSize("modal_window_minimum").height - width: minimumWidth - height: minimumHeight - - onVisibleChanged: - { - while(contextStack.depth > 1) - { - contextStack.pop(); //Do NOT use the StackView.Immediate transition here, since it causes the window to stay empty. Seemingly a Qt bug: https://bugreports.qt.io/browse/QTBUG-60670? - } - } - - Connections - { - target: Cura.API.account - function onLoginStateChanged() - { - close(); - } - } - - title: catalog.i18nc("@title", "Install missing Materials") modality: Qt.ApplicationModal - - // Background color - Rectangle - { - anchors.fill: parent - color: UM.Theme.getColor("main_background") - } - //The Marketplace can have a page in front of everything with package details. The stack view controls its visibility. - StackView - { - id: contextStack - anchors.fill: parent - - initialItem: packageBrowse - - ColumnLayout - { - id: packageBrowse - - spacing: UM.Theme.getSize("narrow_margin").height - - // Page title. - Item - { - Layout.preferredWidth: parent.width - Layout.preferredHeight: childrenRect.height + UM.Theme.getSize("default_margin").height - - UM.Label - { - id: pageTitle - anchors - { - left: parent.left - leftMargin: UM.Theme.getSize("default_margin").width - right: parent.right - rightMargin: UM.Theme.getSize("default_margin").width - bottom: parent.bottom - } - - font: UM.Theme.getFont("large") - text: content.item ? content.item.pageTitle: catalog.i18nc("@title", "Loading...") - } - } - - // Page contents. - Rectangle - { - Layout.preferredWidth: parent.width - Layout.fillHeight: true - color: UM.Theme.getColor("detail_background") - - // Page contents. - Loader - { - id: content - anchors.fill: parent - anchors.margins: UM.Theme.getSize("default_margin").width - source: "MissingPackages.qml" - } - } - } - } - - Rectangle - { - height: quitButton.height + 2 * UM.Theme.getSize("default_margin").width - color: UM.Theme.getColor("primary") - visible: manager.showRestartNotification - anchors - { - left: parent.left - right: parent.right - bottom: parent.bottom - } - - RowLayout - { - anchors - { - left: parent.left - right: parent.right - verticalCenter: parent.verticalCenter - margins: UM.Theme.getSize("default_margin").width - } - spacing: UM.Theme.getSize("default_margin").width - UM.ColorImage - { - id: bannerIcon - source: UM.Theme.getIcon("Plugin") - - color: UM.Theme.getColor("primary_button_text") - implicitWidth: UM.Theme.getSize("banner_icon_size").width - implicitHeight: UM.Theme.getSize("banner_icon_size").height - } - Text - { - color: UM.Theme.getColor("primary_button_text") - text: catalog.i18nc("@button", "In order to use the package you will need to restart Cura") - font: UM.Theme.getFont("default") - renderType: Text.NativeRendering - Layout.fillWidth: true - } - Cura.SecondaryButton - { - id: quitButton - text: catalog.i18nc("@info:button, %1 is the application name", "Quit %1").arg(CuraApplication.applicationDisplayName) - onClicked: - { - marketplaceDialog.hide(); - CuraApplication.closeApplication(); - } - } - } - } + title: catalog.i18nc("@title", "Install missing Materials") + pageContentsSource: "MissingPackages.qml" + showSearchHeader: false + showOnboadBanner: false } diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index d925f265d9..2a3c5f69ce 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -16,6 +16,10 @@ Window signal searchStringChanged(string new_search) + property alias showOnboadBanner: onBoardBanner.visible + property alias showSearchHeader: searchHeader.visible + property alias pageContentsSource: content.source + minimumWidth: UM.Theme.getSize("modal_window_minimum").width minimumHeight: UM.Theme.getSize("modal_window_minimum").height width: minimumWidth @@ -86,6 +90,7 @@ Window OnboardBanner { + id: onBoardBanner visible: content.item && content.item.bannerVisible text: content.item && content.item.bannerText icon: content.item && content.item.bannerIcon @@ -100,6 +105,7 @@ Window // Search & Top-Level Tabs Item { + id: searchHeader implicitHeight: childrenRect.height implicitWidth: parent.width - 2 * UM.Theme.getSize("default_margin").width Layout.alignment: Qt.AlignHCenter @@ -186,7 +192,7 @@ Window { text: catalog.i18nc("@info", "Search in the browser") iconSource: UM.Theme.getIcon("LinkExternal") - visible: pageSelectionTabBar.currentItem.hasSearch + visible: pageSelectionTabBar.currentItem.hasSearch && searchHeader.visible isIconOnRightSide: true height: fontMetrics.height textFont: fontMetrics.font From 76180eee011c4c51ec88c83913b399ba700fb268 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 6 Jun 2022 22:49:49 +0200 Subject: [PATCH 27/29] Show warning on-close of the install missing packaged dialog CURA-6990 --- plugins/3MFReader/WorkspaceDialog.py | 2 +- .../Marketplace/InstallMissingPackagesDialog.py | 16 +++++++++++++--- .../qml/InstallMissingPackagesDialog.qml | 2 ++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index e3cbef607a..419f1fe69c 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -292,7 +292,7 @@ class WorkspaceDialog(QObject): @pyqtSlot() def installMissingPackages(self) -> None: - self._install_missing_package_dialog = InstallMissingPackageDialog(self._missing_package_metadata) + self._install_missing_package_dialog = InstallMissingPackageDialog(self._missing_package_metadata, self.showMissingMaterialsWarning) self._install_missing_package_dialog.show() def getResult(self) -> Dict[str, Optional[str]]: diff --git a/plugins/Marketplace/InstallMissingPackagesDialog.py b/plugins/Marketplace/InstallMissingPackagesDialog.py index 3ed46959dd..282dc54492 100644 --- a/plugins/Marketplace/InstallMissingPackagesDialog.py +++ b/plugins/Marketplace/InstallMissingPackagesDialog.py @@ -1,17 +1,22 @@ import os -from PyQt6.QtCore import QObject, pyqtSignal, pyqtProperty -from typing import Optional, List, Dict, cast +from PyQt6.QtCore import QObject, pyqtSignal, pyqtProperty, QUrl +from PyQt6.QtGui import QDesktopServices +from typing import Optional, List, Dict, cast, Callable from cura.CuraApplication import CuraApplication from UM.PluginRegistry import PluginRegistry from cura.CuraPackageManager import CuraPackageManager +from UM.Message import Message +from UM.i18n import i18nCatalog +from UM.FlameProfiler import pyqtSlot from plugins.Marketplace.MissingPackageList import MissingPackageList +i18n_catalog = i18nCatalog("cura") class InstallMissingPackageDialog(QObject): """Dialog used to display packages that need to be installed to load 3mf file materials""" - def __init__(self, packages_metadata: List[Dict[str, str]]) -> None: + def __init__(self, packages_metadata: List[Dict[str, str]], show_missing_materials_warning: Callable[[], None]) -> None: """Initialize :param packages_metadata: List of dictionaries containing information about missing packages. @@ -27,6 +32,7 @@ class InstallMissingPackageDialog(QObject): self._package_metadata: List[Dict[str, str]] = packages_metadata self._package_model: MissingPackageList = MissingPackageList(packages_metadata) + self._show_missing_materials_warning = show_missing_materials_warning def show(self) -> None: plugin_path = self._plugin_registry.getPluginPath("Marketplace") @@ -54,3 +60,7 @@ class InstallMissingPackageDialog(QObject): @pyqtProperty(QObject) def model(self) -> MissingPackageList: return self._package_model + + @pyqtSlot() + def showMissingMaterialsWarning(self) -> None: + self._show_missing_materials_warning() diff --git a/plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml b/plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml index f08fed4f85..edad18f1a8 100644 --- a/plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml +++ b/plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml @@ -16,4 +16,6 @@ Marketplace pageContentsSource: "MissingPackages.qml" showSearchHeader: false showOnboadBanner: false + + onClosing: manager.showMissingMaterialsWarning() } From efeb29c734bb0d9a8094f6e8b9820aabda665ec8 Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Tue, 7 Jun 2022 13:41:54 +0200 Subject: [PATCH 28/29] Fix call to plugin that was causing errors when building CURA-8610 --- cura/CuraPackageManager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 720406fbc6..e23ed47ffa 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -5,13 +5,13 @@ import os from typing import Any, cast, Dict, List, Set, Tuple, TYPE_CHECKING, Optional from UM.Logger import Logger +from UM.PluginRegistry import PluginRegistry from cura.CuraApplication import CuraApplication # To find some resource types. from cura.Settings.GlobalStack import GlobalStack from UM.PackageManager import PackageManager # The class we're extending. from UM.Resources import Resources # To find storage paths for some resource types. from UM.i18n import i18nCatalog -from plugins.XmlMaterialProfile.XmlMaterialProfile import XmlMaterialProfile catalog = i18nCatalog("cura") @@ -68,7 +68,8 @@ class CuraPackageManager(PackageManager): with open(root + "/" + file_name, encoding="utf-8") as f: # Make sure the file we found has the same guid as our material # Parsing this xml would be better but the namespace is needed to search it. - parsed_guid = XmlMaterialProfile.getMetadataFromSerialized(f.read(), "GUID") + parsed_guid = PluginRegistry.getInstance().getPluginObject("XmlMaterialProfile").getMetadataFromSerialized( + f.read(), "GUID") if guid == parsed_guid: return package_id From fb3f065fe0642300219d92b05d8b6cc1b305cce4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 7 Jun 2022 13:55:48 +0200 Subject: [PATCH 29/29] Catch ValueErrors from removing callbacks that were already removed This can happen from asynchronous access. Fixes Sentry issue CURA-3YV. --- plugins/UM3NetworkPrinting/src/Network/ClusterApiClient.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Network/ClusterApiClient.py b/plugins/UM3NetworkPrinting/src/Network/ClusterApiClient.py index e0b156dc08..fd8118306b 100644 --- a/plugins/UM3NetworkPrinting/src/Network/ClusterApiClient.py +++ b/plugins/UM3NetworkPrinting/src/Network/ClusterApiClient.py @@ -169,7 +169,10 @@ class ClusterApiClient: """ def parse() -> None: - self._anti_gc_callbacks.remove(parse) + try: + self._anti_gc_callbacks.remove(parse) + except ValueError: # Already removed asynchronously. + return # Then the rest of the function is also already executed. # Don't try to parse the reply if we didn't get one if reply.attribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute) is None: