Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Ultimaker/Cura.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRemco Burema <41987080+rburema@users.noreply.github.com>2022-06-07 14:26:31 +0300
committerGitHub <noreply@github.com>2022-06-07 14:26:31 +0300
commitd2381f129febc9cc5ca813e2965d8b1390579bc8 (patch)
treee36277ba488ffe8fa335c1bdefc1dbd53abaeeed /plugins
parent979c436b6cf16cb09badc63a3ae375038a14722b (diff)
parent76180eee011c4c51ec88c83913b399ba700fb268 (diff)
Merge pull request #12434 from Ultimaker/CURA-6990
Use "required packages" metadata to load a project file with a third party material
Diffstat (limited to 'plugins')
-rwxr-xr-xplugins/3MFReader/ThreeMFWorkspaceReader.py29
-rw-r--r--plugins/3MFReader/WorkspaceDialog.py60
-rw-r--r--plugins/3MFReader/WorkspaceDialog.qml622
-rw-r--r--plugins/Marketplace/InstallMissingPackagesDialog.py66
-rw-r--r--plugins/Marketplace/Marketplace.py3
-rw-r--r--plugins/Marketplace/MissingPackageList.py46
-rw-r--r--plugins/Marketplace/PackageModel.py25
-rw-r--r--plugins/Marketplace/RemotePackageList.py3
-rw-r--r--plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml21
-rw-r--r--plugins/Marketplace/resources/qml/Marketplace.qml8
-rw-r--r--plugins/Marketplace/resources/qml/MissingPackages.qml15
-rw-r--r--plugins/Marketplace/resources/qml/PackageCard.qml2
-rw-r--r--plugins/Marketplace/resources/qml/PackageCardHeader.qml38
-rw-r--r--plugins/Marketplace/resources/qml/Packages.qml7
14 files changed, 662 insertions, 283 deletions
diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py
index ddc7922546..7456ce2b95 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_metadata = self._filter_missing_package_metadata(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.setMissingPackagesMetadata(missing_package_metadata)
self._dialog.show()
# 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"})
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 _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
diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py
index b63d8b6288..419f1fe69c 100644
--- a/plugins/3MFReader/WorkspaceDialog.py
+++ b/plugins/3MFReader/WorkspaceDialog.py
@@ -2,14 +2,18 @@
# 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
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
+from UM.Message import Message
import os
import threading
@@ -23,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
@@ -59,6 +63,9 @@ class WorkspaceDialog(QObject):
self._objects_on_plate = False
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()
@@ -79,6 +86,7 @@ class WorkspaceDialog(QObject):
variantTypeChanged = pyqtSignal()
extrudersChanged = pyqtSignal()
isPrinterGroupChanged = pyqtSignal()
+ missingPackagesChanged = pyqtSignal()
@pyqtProperty(bool, notify = isPrinterGroupChanged)
def isPrinterGroup(self) -> bool:
@@ -274,6 +282,19 @@ class WorkspaceDialog(QObject):
self._has_quality_changes_conflict = quality_changes_conflict
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]]:
if "machine" in self._result and self.updatableMachinesModel.count <= 1:
self._result["machine"] = None
@@ -360,6 +381,41 @@ 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.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:
if self._view is None:
self._createViewFromQML()
diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml
index 4c384b306b..d0250abfd8 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()
@@ -31,413 +31,470 @@ 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"
- }
+ id: dialogSummaryItem
+ width: verticalScrollBar.visible ? parent.width - verticalScrollBar.width - UM.Theme.getSize("default_margin").width : parent.width
+ height: childrenRect.height
+ anchors.margins: 10 * screenScaleFactor
- 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:
+ UM.I18nCatalog
{
- 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")});
+ id: catalog
+ name: "cura"
}
- }
- Column
- {
- width: parent.width
- height: childrenRect.height
- spacing: UM.Theme.getSize("default_margin").height
+ 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")
+ width: parent.width
+ height: childrenRect.height
+
+ UM.Label
+ {
+ 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
+ }
}
- Rectangle
+ Item
{
- 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
+ {
+ id: machineResolveComboBox
+ model: manager.updatableMachinesModel
+ visible: machineResolveStrategyTooltip.visible
+ textRole: "displayName"
+ 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)
+ }
+ }
- 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
+ onVisibleChanged:
+ {
+ 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
{
- id: machineResolveComboBox
- model: manager.updatableMachinesModel
- visible: machineResolveStrategyTooltip.visible
- textRole: "displayName"
width: parent.width
- height: UM.Theme.getSize("button").height
- onCurrentIndexChanged:
+ height: childrenRect.height
+
+ UM.Label
{
- if (model.getItem(currentIndex).id == "new"
- && model.getItem(currentIndex).type == "default_option")
+ id: printer_settings_label
+ text: catalog.i18nc("@action:label", "Printer settings")
+ font: UM.Theme.getFont("default_bold")
+ }
+
+ Row
+ {
+ width: parent.width
+ height: childrenRect.height
+
+ UM.Label
{
- manager.setResolveStrategy("machine", "new")
+ text: catalog.i18nc("@action:label", "Type")
+ width: (parent.width / 3) | 0
}
- else
+ UM.Label
{
- manager.setResolveStrategy("machine", "override")
- manager.setMachineToOverride(model.getItem(currentIndex).id)
+ text: manager.machineType
+ width: (parent.width / 3) | 0
}
}
- onVisibleChanged:
+ Row
{
- if (!visible) {return}
+ 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.Label
{
- if (model.getItem(i).name == manager.machineName)
- {
- currentIndex = i
- break
- }
+ text: catalog.i18nc("@action:label", manager.isPrinterGroup ? "Printer Group" : "Printer Name")
+ width: (parent.width / 3) | 0
}
- // 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)
+ UM.Label
{
- currentIndex = 1
+ text: manager.machineName
+ width: (parent.width / 3) | 0
+ wrapMode: Text.WordWrap
}
}
}
}
- Column
+ Item
{
width: parent.width
height: childrenRect.height
- UM.Label
+ UM.TooltipArea
{
- id: printer_settings_label
- text: catalog.i18nc("@action:label", "Printer settings")
- font: UM.Theme.getFont("default_bold")
+ 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)
+ }
+ }
}
- Row
+ Column
{
width: parent.width
height: childrenRect.height
UM.Label
{
- text: catalog.i18nc("@action:label", "Type")
- width: (parent.width / 3) | 0
+ text: catalog.i18nc("@action:label", "Profile settings")
+ font: UM.Theme.getFont("default_bold")
}
- UM.Label
+
+ Row
{
- text: manager.machineType
- width: (parent.width / 3) | 0
- }
- }
+ width: parent.width
+ height: childrenRect.height
- 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
+ }
+ }
- UM.Label
+ Row
{
- text: catalog.i18nc("@action:label", manager.isPrinterGroup ? "Printer Group" : "Printer Name")
- width: (parent.width / 3) | 0
+ 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
+ }
}
- UM.Label
+
+ Row
{
- text: manager.machineName
- width: (parent.width / 3) | 0
- wrapMode: Text.WordWrap
- }
- }
- }
- }
+ width: parent.width
+ height: childrenRect.height
- Item
- {
- 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
+ }
+ }
- 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:
+ Row
{
- manager.setResolveStrategy("quality_changes", resolveStrategiesModel.get(index).key)
+ 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
+ Item
{
width: parent.width
height: childrenRect.height
- UM.Label
+ UM.TooltipArea
{
- 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
+ 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
{
- text: manager.qualityName
- width: (parent.width / 3) | 0
- wrapMode: Text.WordWrap
+ model: resolveStrategiesModel
+ textRole: "label"
+ id: materialResolveComboBox
+ width: parent.width
+ height: UM.Theme.getSize("button").height
+ onActivated:
+ {
+ manager.setResolveStrategy("material", resolveStrategiesModel.get(index).key)
+ }
}
}
- Row
+ Column
{
width: parent.width
height: childrenRect.height
-
- UM.Label
+ Row
{
- text: catalog.i18nc("@action:label", "Intent")
- width: (parent.width / 3) | 0
+ 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
+ }
}
- UM.Label
+
+ Repeater
{
- text: manager.intentName
- width: (parent.width / 3) | 0
- wrapMode: Text.WordWrap
+ 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", "Not in profile")
- visible: manager.numUserSettings != 0
+ text: catalog.i18nc("@action:label", "Mode")
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
+ 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", "Derivative from")
- visible: manager.numSettingsOverridenByQualityChanges != 0
+ text: catalog.i18nc("@action:label", "Visible settings:")
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)
+ text: catalog.i18nc("@action:label", "%1 out of %2" ).arg(manager.numVisibleSettings).arg(manager.totalNumberOfSettings)
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
+ Row
{
width: parent.width
height: childrenRect.height
- Row
+ visible: manager.hasObjectsOnPlate
+ UM.ColorImage
{
- 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
- }
+ width: warningLabel.height
+ height: width
+ source: UM.Theme.getIcon("Information")
+ color: UM.Theme.getColor("text")
}
-
- Repeater
+ UM.Label
{
- 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
- }
- }
+ id: warningLabel
+ text: catalog.i18nc("@action:warning", "Loading a project will clear all models on the build plate.")
}
}
}
+ }
+ }
- Column
+ 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
{
- width: parent.width
+ id: warningRow
height: childrenRect.height
-
- UM.Label
- {
- text: catalog.i18nc("@action:label", "Setting visibility")
- font: UM.Theme.getFont("default_bold")
- }
- Row
+ visible: warning
+ spacing: base.margin
+ UM.ColorImage
{
- 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
- }
+ width: UM.Theme.getSize("extruder_icon").width
+ height: UM.Theme.getSize("extruder_icon").height
+ source: UM.Theme.getIcon("Warning")
}
- Row
+
+ UM.Label
{
- 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
- }
+ id: warningText
+ text: "The material used in this project is currently not installed in Cura.<br/>Install the material profile and reopen the project."
}
}
- Row
+ Loader
{
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.")
- }
+ sourceComponent: buttonRow
}
}
}
@@ -447,13 +504,30 @@ UM.Dialog
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: {
+ manager.showMissingMaterialsWarning();
+ accept();
+ }
+ },
+ Cura.PrimaryButton
+ {
+ visible: warning
+ text: catalog.i18nc("@action:button", "Install missing material")
+ onClicked: manager.installMissingPackages()
}
]
diff --git a/plugins/Marketplace/InstallMissingPackagesDialog.py b/plugins/Marketplace/InstallMissingPackagesDialog.py
new file mode 100644
index 0000000000..282dc54492
--- /dev/null
+++ b/plugins/Marketplace/InstallMissingPackagesDialog.py
@@ -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()
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
diff --git a/plugins/Marketplace/MissingPackageList.py b/plugins/Marketplace/MissingPackageList.py
new file mode 100644
index 0000000000..385e78b95f
--- /dev/null
+++ b/plugins/Marketplace/MissingPackageList.py
@@ -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()
+
+
diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py
index bd1b0681fc..078bfc879e 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) -> "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()
def _processUpdatedPackages(self):
self.setCanUpdate(self._package_manager.checkIfPackageCanUpdate(self._package_id))
@@ -385,3 +399,14 @@ class PackageModel(QObject):
def canUpdate(self) -> bool:
"""Flag indicating if the package can be updated"""
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
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:
diff --git a/plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml b/plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml
new file mode 100644
index 0000000000..edad18f1a8
--- /dev/null
+++ b/plugins/Marketplace/resources/qml/InstallMissingPackagesDialog.qml
@@ -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()
+}
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
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
+}
diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml
index 52254a478f..b74dad0712 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 ? UM.Theme.getSize("default_lining").width : 0
PackageCardHeader
{
diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml
index 00d107f4fc..6c9d533bb5 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://ultimaker.atlassian.net/wiki/spaces/SD/pages/1231916580/Campaign+links+from+Cura+to+the+Ultimaker+domain"
+
width: parent.width
height: UM.Theme.getSize("card").height
@@ -87,6 +89,14 @@ Item
Layout.preferredWidth: parent.width
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
{
text: packageData.displayName
@@ -109,6 +119,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 +166,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 +177,7 @@ Item
// clickable author name
Item
{
+ visible: !packageData.isMissingPackageInformation
Layout.fillWidth: true
implicitHeight: authorBy.height
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
{
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 +231,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 +261,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