Merge branch 'main' into PP-108_Improved_support_settings

This commit is contained in:
p.kuiper 2022-06-08 08:42:34 +02:00
commit 8a9c202aef
17 changed files with 729 additions and 346 deletions

View File

@ -5,13 +5,13 @@ import os
from typing import Any, cast, Dict, List, Set, Tuple, TYPE_CHECKING, Optional from typing import Any, cast, Dict, List, Set, Tuple, TYPE_CHECKING, Optional
from UM.Logger import Logger from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry
from cura.CuraApplication import CuraApplication # To find some resource types. from cura.CuraApplication import CuraApplication # To find some resource types.
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
from UM.PackageManager import PackageManager # The class we're extending. 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.Resources import Resources # To find storage paths for some resource types.
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from plugins.XmlMaterialProfile.XmlMaterialProfile import XmlMaterialProfile
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
@ -68,7 +68,8 @@ class CuraPackageManager(PackageManager):
with open(root + "/" + file_name, encoding="utf-8") as f: with open(root + "/" + file_name, encoding="utf-8") as f:
# Make sure the file we found has the same guid as our material # 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. # 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: if guid == parsed_guid:
return package_id return package_id

View File

@ -23,6 +23,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from UM.Job import Job from UM.Job import Job
from UM.Preferences import Preferences from UM.Preferences import Preferences
from cura.CuraPackageManager import CuraPackageManager
from cura.Machines.ContainerTree import ContainerTree from cura.Machines.ContainerTree import ContainerTree
from cura.Settings.CuraStackBuilder import CuraStackBuilder from cura.Settings.CuraStackBuilder import CuraStackBuilder
@ -579,6 +580,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
is_printer_group = True is_printer_group = True
machine_name = group_name machine_name = group_name
# Getting missing required package ids
package_metadata = self._parse_packages_metadata(archive)
missing_package_metadata = self._filter_missing_package_metadata(package_metadata)
# Show the dialog, informing the user what is about to happen. # Show the dialog, informing the user what is about to happen.
self._dialog.setMachineConflict(machine_conflict) self._dialog.setMachineConflict(machine_conflict)
self._dialog.setIsPrinterGroup(is_printer_group) self._dialog.setIsPrinterGroup(is_printer_group)
@ -599,6 +604,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._dialog.setExtruders(extruders) self._dialog.setExtruders(extruders)
self._dialog.setVariantType(variant_type_name) self._dialog.setVariantType(variant_type_name)
self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity) self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity)
self._dialog.setMissingPackagesMetadata(missing_package_metadata)
self._dialog.show() self._dialog.show()
# Block until the dialog is closed. # Block until the dialog is closed.
@ -1243,3 +1249,26 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
metadata = data.iterfind("./um:metadata/um:name/um:label", {"um": "http://www.ultimaker.com/material"}) metadata = data.iterfind("./um:metadata/um:name/um:label", {"um": "http://www.ultimaker.com/material"})
for entry in metadata: for entry in metadata:
return entry.text 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 _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)
return missing_packages

View File

@ -2,14 +2,18 @@
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import List, Optional, Dict, cast 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.FlameProfiler import pyqtSlot
from UM.PluginRegistry import PluginRegistry from UM.PluginRegistry import PluginRegistry
from UM.Application import Application from UM.Application import Application
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
from plugins.Marketplace.InstallMissingPackagesDialog import InstallMissingPackageDialog
from .UpdatableMachinesModel import UpdatableMachinesModel from .UpdatableMachinesModel import UpdatableMachinesModel
from UM.Message import Message
import os import os
import threading import threading
@ -23,7 +27,7 @@ i18n_catalog = i18nCatalog("cura")
class WorkspaceDialog(QObject): class WorkspaceDialog(QObject):
showDialogSignal = pyqtSignal() showDialogSignal = pyqtSignal()
def __init__(self, parent = None): def __init__(self, parent = None) -> None:
super().__init__(parent) super().__init__(parent)
self._component = None self._component = None
self._context = None self._context = None
@ -59,6 +63,9 @@ class WorkspaceDialog(QObject):
self._objects_on_plate = False self._objects_on_plate = False
self._is_printer_group = False self._is_printer_group = False
self._updatable_machines_model = UpdatableMachinesModel(self) 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() machineConflictChanged = pyqtSignal()
qualityChangesConflictChanged = pyqtSignal() qualityChangesConflictChanged = pyqtSignal()
@ -79,6 +86,7 @@ class WorkspaceDialog(QObject):
variantTypeChanged = pyqtSignal() variantTypeChanged = pyqtSignal()
extrudersChanged = pyqtSignal() extrudersChanged = pyqtSignal()
isPrinterGroupChanged = pyqtSignal() isPrinterGroupChanged = pyqtSignal()
missingPackagesChanged = pyqtSignal()
@pyqtProperty(bool, notify = isPrinterGroupChanged) @pyqtProperty(bool, notify = isPrinterGroupChanged)
def isPrinterGroup(self) -> bool: def isPrinterGroup(self) -> bool:
@ -274,6 +282,19 @@ class WorkspaceDialog(QObject):
self._has_quality_changes_conflict = quality_changes_conflict self._has_quality_changes_conflict = quality_changes_conflict
self.qualityChangesConflictChanged.emit() self.qualityChangesConflictChanged.emit()
def setMissingPackagesMetadata(self, missing_package_metadata: List[Dict[str, str]]) -> None:
self._missing_package_metadata = missing_package_metadata
self.missingPackagesChanged.emit()
@pyqtProperty("QVariantList", notify=missingPackagesChanged)
def missingPackages(self) -> List[Dict[str, str]]:
return self._missing_package_metadata
@pyqtSlot()
def installMissingPackages(self) -> None:
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]]: def getResult(self) -> Dict[str, Optional[str]]:
if "machine" in self._result and self.updatableMachinesModel.count <= 1: if "machine" in self._result and self.updatableMachinesModel.count <= 1:
self._result["machine"] = None self._result["machine"] = None
@ -360,6 +381,41 @@ class WorkspaceDialog(QObject):
time.sleep(1 / 50) time.sleep(1 / 50)
QCoreApplication.processEvents() # Ensure that the GUI does not freeze. 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.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) -> None:
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: def __show(self) -> None:
if self._view is None: if self._view is None:
self._createViewFromQML() self._createViewFromQML()

View File

@ -17,7 +17,7 @@ UM.Dialog
minimumWidth: UM.Theme.getSize("popup_dialog").width minimumWidth: UM.Theme.getSize("popup_dialog").width
minimumHeight: UM.Theme.getSize("popup_dialog").height minimumHeight: UM.Theme.getSize("popup_dialog").height
width: minimumWidth width: minimumWidth
margin: UM.Theme.getSize("default_margin").width
property int comboboxHeight: UM.Theme.getSize("default_margin").height property int comboboxHeight: UM.Theme.getSize("default_margin").height
onClosing: manager.notifyClosed() onClosing: manager.notifyClosed()
@ -31,10 +31,18 @@ UM.Dialog
} }
} }
Flickable
{
clip: true
width: parent.width
height: parent.height
contentHeight: dialogSummaryItem.height
ScrollBar.vertical: UM.ScrollBar { id: verticalScrollBar }
Item Item
{ {
id: dialogSummaryItem id: dialogSummaryItem
width: parent.width width: verticalScrollBar.visible ? parent.width - verticalScrollBar.width - UM.Theme.getSize("default_margin").width : parent.width
height: childrenRect.height height: childrenRect.height
anchors.margins: 10 * screenScaleFactor anchors.margins: 10 * screenScaleFactor
@ -441,19 +449,85 @@ UM.Dialog
} }
} }
} }
}
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.<br/>Install the material profile and reopen the project."
}
}
Loader
{
width: parent.width
height: childrenRect.height
sourceComponent: buttonRow
}
}
}
buttonSpacing: UM.Theme.getSize("default_margin").width buttonSpacing: UM.Theme.getSize("default_margin").width
rightButtons: [ rightButtons: [
Cura.TertiaryButton Cura.TertiaryButton
{ {
visible: !warning
text: catalog.i18nc("@action:button", "Cancel") text: catalog.i18nc("@action:button", "Cancel")
onClicked: reject() onClicked: reject()
}, },
Cura.PrimaryButton Cura.PrimaryButton
{ {
visible: !warning
text: catalog.i18nc("@action:button", "Open") text: catalog.i18nc("@action:button", "Open")
onClicked: accept() onClicked: accept()
},
Cura.TertiaryButton
{
visible: warning
text: catalog.i18nc("@action:button", "Open project anyway")
onClicked: {
manager.showMissingMaterialsWarning();
accept();
}
},
Cura.PrimaryButton
{
visible: warning
text: catalog.i18nc("@action:button", "Install missing material")
onClicked: manager.installMissingPackages()
} }
] ]

View File

@ -0,0 +1,66 @@
import os
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]], show_missing_materials_warning: Callable[[], None]) -> None:
"""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 = MissingPackageList(packages_metadata)
self._show_missing_materials_warning = show_missing_materials_warning
def show(self) -> None:
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
self._restart_needed = self._package_manager.hasPackagesToRemoveOrInstall
self.showRestartChanged.emit()
showRestartChanged = pyqtSignal()
@pyqtProperty(bool, notify=showRestartChanged)
def showRestartNotification(self) -> bool:
return self._restart_needed
@pyqtProperty(QObject)
def model(self) -> MissingPackageList:
return self._package_model
@pyqtSlot()
def showMissingMaterialsWarning(self) -> None:
self._show_missing_materials_warning()

View File

@ -103,6 +103,9 @@ class Marketplace(Extension, QObject):
self.setTabShown(1) self.setTabShown(1)
def checkIfRestartNeeded(self) -> None: def checkIfRestartNeeded(self) -> None:
if self._window is None:
return
if self._package_manager.hasPackagesToRemoveOrInstall or \ if self._package_manager.hasPackagesToRemoveOrInstall or \
cast(PluginRegistry, self._plugin_registry).getCurrentSessionActivationChangedPlugins(): cast(PluginRegistry, self._plugin_registry).getCurrentSessionActivationChangedPlugins():
self._restart_needed = True self._restart_needed = True

View File

@ -0,0 +1,46 @@
# 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, packages_metadata: List[Dict[str, str]], parent: Optional["QObject"] = None) -> None:
super().__init__(parent)
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_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) -> 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]
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()

View File

@ -84,6 +84,20 @@ class PackageModel(QObject):
self._is_busy = False self._is_busy = False
self._is_missing_package_information = False
@classmethod
def fromIncompletePackageInformation(cls, display_name: str, package_version: str, package_type: str) -> "PackageModel":
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() @pyqtSlot()
def _processUpdatedPackages(self): def _processUpdatedPackages(self):
self.setCanUpdate(self._package_manager.checkIfPackageCanUpdate(self._package_id)) self.setCanUpdate(self._package_manager.checkIfPackageCanUpdate(self._package_id))
@ -385,3 +399,14 @@ class PackageModel(QObject):
def canUpdate(self) -> bool: def canUpdate(self) -> bool:
"""Flag indicating if the package can be updated""" """Flag indicating if the package can be updated"""
return self._can_update return self._can_update
isMissingPackageInformationChanged = pyqtSignal()
def setIsMissingPackageInformation(self, isMissingPackageInformation: bool) -> None:
self._is_missing_package_information = isMissingPackageInformation
self.isMissingPackageInformationChanged.emit()
@pyqtProperty(bool, notify=isMissingPackageInformationChanged)
def isMissingPackageInformation(self) -> bool:
"""Flag indicating if the package can be updated"""
return self._is_missing_package_information

View File

@ -28,6 +28,7 @@ class RemotePackageList(PackageList):
self._package_type_filter = "" self._package_type_filter = ""
self._requested_search_string = "" self._requested_search_string = ""
self._current_search_string = "" self._current_search_string = ""
self._search_type = "search"
self._request_url = self._initialRequestUrl() self._request_url = self._initialRequestUrl()
self._ongoing_requests["get_packages"] = None self._ongoing_requests["get_packages"] = None
self.isLoadingChanged.connect(self._onLoadingChanged) self.isLoadingChanged.connect(self._onLoadingChanged)
@ -100,7 +101,7 @@ class RemotePackageList(PackageList):
if self._package_type_filter != "": if self._package_type_filter != "":
request_url += f"&package_type={self._package_type_filter}" request_url += f"&package_type={self._package_type_filter}"
if self._current_search_string != "": 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 return request_url
def _parseResponse(self, reply: "QNetworkReply") -> None: def _parseResponse(self, reply: "QNetworkReply") -> None:

View File

@ -0,0 +1,21 @@
// 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
Marketplace
{
modality: Qt.ApplicationModal
title: catalog.i18nc("@title", "Install missing Materials")
pageContentsSource: "MissingPackages.qml"
showSearchHeader: false
showOnboadBanner: false
onClosing: manager.showMissingMaterialsWarning()
}

View File

@ -16,6 +16,10 @@ Window
signal searchStringChanged(string new_search) 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 minimumWidth: UM.Theme.getSize("modal_window_minimum").width
minimumHeight: UM.Theme.getSize("modal_window_minimum").height minimumHeight: UM.Theme.getSize("modal_window_minimum").height
width: minimumWidth width: minimumWidth
@ -86,6 +90,7 @@ Window
OnboardBanner OnboardBanner
{ {
id: onBoardBanner
visible: content.item && content.item.bannerVisible visible: content.item && content.item.bannerVisible
text: content.item && content.item.bannerText text: content.item && content.item.bannerText
icon: content.item && content.item.bannerIcon icon: content.item && content.item.bannerIcon
@ -100,6 +105,7 @@ Window
// Search & Top-Level Tabs // Search & Top-Level Tabs
Item Item
{ {
id: searchHeader
implicitHeight: childrenRect.height implicitHeight: childrenRect.height
implicitWidth: parent.width - 2 * UM.Theme.getSize("default_margin").width implicitWidth: parent.width - 2 * UM.Theme.getSize("default_margin").width
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -186,7 +192,7 @@ Window
{ {
text: catalog.i18nc("@info", "Search in the browser") text: catalog.i18nc("@info", "Search in the browser")
iconSource: UM.Theme.getIcon("LinkExternal") iconSource: UM.Theme.getIcon("LinkExternal")
visible: pageSelectionTabBar.currentItem.hasSearch visible: pageSelectionTabBar.currentItem.hasSearch && searchHeader.visible
isIconOnRightSide: true isIconOnRightSide: true
height: fontMetrics.height height: fontMetrics.height
textFont: fontMetrics.font textFont: fontMetrics.font

View File

@ -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
}

View File

@ -18,6 +18,8 @@ Rectangle
height: childrenRect.height height: childrenRect.height
color: UM.Theme.getColor("main_background") color: UM.Theme.getColor("main_background")
radius: UM.Theme.getSize("default_radius").width radius: UM.Theme.getSize("default_radius").width
border.color: packageData.isMissingPackageInformation ? UM.Theme.getColor("warning") : "transparent"
border.width: packageData.isMissingPackageInformation ? UM.Theme.getSize("default_lining").width : 0
PackageCardHeader PackageCardHeader
{ {

View File

@ -19,6 +19,8 @@ Item
property bool showInstallButton: false property bool showInstallButton: false
property bool showUpdateButton: false property bool showUpdateButton: false
property string missingPackageReadMoreUrl: "https://ultimaker.atlassian.net/wiki/spaces/SD/pages/1231916580/Campaign+links+from+Cura+to+the+Ultimaker+domain"
width: parent.width width: parent.width
height: UM.Theme.getSize("card").height height: UM.Theme.getSize("card").height
@ -87,6 +89,14 @@ Item
Layout.preferredWidth: parent.width Layout.preferredWidth: parent.width
Layout.preferredHeight: childrenRect.height Layout.preferredHeight: childrenRect.height
UM.StatusIcon
{
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
}
UM.Label UM.Label
{ {
text: packageData.displayName text: packageData.displayName
@ -109,6 +119,7 @@ Item
Button Button
{ {
id: externalLinkButton 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? // 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 leftPadding: UM.Theme.getSize("narrow_margin").width
@ -155,6 +166,7 @@ Item
UM.Label UM.Label
{ {
id: authorBy id: authorBy
visible: !packageData.isMissingPackageInformation
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
text: catalog.i18nc("@label Is followed by the name of an author", "By") text: catalog.i18nc("@label Is followed by the name of an author", "By")
@ -165,6 +177,7 @@ Item
// clickable author name // clickable author name
Item Item
{ {
visible: !packageData.isMissingPackageInformation
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: authorBy.height implicitHeight: authorBy.height
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
@ -182,10 +195,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 ManageButton
{ {
id: enableManageButton 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 enabled: !packageData.busy
button_style: !packageData.isActive button_style: !packageData.isActive
@ -199,7 +231,7 @@ Item
ManageButton ManageButton
{ {
id: installManageButton id: installManageButton
visible: showInstallButton && (packageData.canDowngrade || !packageData.isBundled) visible: showInstallButton && (packageData.canDowngrade || !packageData.isBundled) && !packageData.isMissingPackageInformation
enabled: !packageData.busy enabled: !packageData.busy
busy: packageData.busy busy: packageData.busy
button_style: !(packageData.isInstalled || packageData.isToBeInstalled) button_style: !(packageData.isInstalled || packageData.isToBeInstalled)
@ -229,7 +261,7 @@ Item
ManageButton ManageButton
{ {
id: updateManageButton id: updateManageButton
visible: showUpdateButton && packageData.canUpdate visible: showUpdateButton && packageData.canUpdate && !packageData.isMissingPackageInformation
enabled: !packageData.busy enabled: !packageData.busy
busy: packageData.busy busy: packageData.busy
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop

View File

@ -61,10 +61,13 @@ ListView
hoverEnabled: true hoverEnabled: true
onClicked: onClicked:
{
if (!model.package.isMissingPackageInformation)
{ {
packages.selectedPackage = model.package; packages.selectedPackage = model.package;
contextStack.push(packageDetailsComponent); contextStack.push(packageDetailsComponent);
} }
}
PackageCard PackageCard
{ {

View File

@ -169,7 +169,10 @@ class ClusterApiClient:
""" """
def parse() -> None: def parse() -> None:
try:
self._anti_gc_callbacks.remove(parse) 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 # Don't try to parse the reply if we didn't get one
if reply.attribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute) is None: if reply.attribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute) is None:

View File

@ -29,7 +29,7 @@ UM.Dialog
// the size of the dialog ourselves. // the size of the dialog ourselves.
// Ugly workaround for windows having overlapping elements due to incorrect dialog width // Ugly workaround for windows having overlapping elements due to incorrect dialog width
minimumWidth: content.width + (Qt.platform.os == "windows" ? 4 * margin : 2 * margin) 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 alias color: colorInput.text
property var swatchColors: [ property var swatchColors: [