Merge pull request #13195 from Ultimaker/CURA-9278_choose_printer_dialog

[CURA-9278] Press print on abstract cloud printer. User should see dialog.
This commit is contained in:
Casper Lamboo 2022-09-13 10:42:36 +02:00 committed by GitHub
commit 6abce2a81a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 390 additions and 28 deletions

View File

@ -115,6 +115,7 @@ from . import CuraActions
from . import PlatformPhysics from . import PlatformPhysics
from . import PrintJobPreviewImageProvider from . import PrintJobPreviewImageProvider
from .AutoSave import AutoSave from .AutoSave import AutoSave
from .Machines.Models.CompatibleMachineModel import CompatibleMachineModel
from .Machines.Models.MachineListModel import MachineListModel from .Machines.Models.MachineListModel import MachineListModel
from .Machines.Models.ActiveIntentQualitiesModel import ActiveIntentQualitiesModel from .Machines.Models.ActiveIntentQualitiesModel import ActiveIntentQualitiesModel
from .Machines.Models.IntentSelectionModel import IntentSelectionModel from .Machines.Models.IntentSelectionModel import IntentSelectionModel
@ -1191,6 +1192,7 @@ class CuraApplication(QtApplication):
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
qmlRegisterType(GlobalStacksModel, "Cura", 1, 0, "GlobalStacksModel") qmlRegisterType(GlobalStacksModel, "Cura", 1, 0, "GlobalStacksModel")
qmlRegisterType(MachineListModel, "Cura", 1, 0, "MachineListModel") qmlRegisterType(MachineListModel, "Cura", 1, 0, "MachineListModel")
qmlRegisterType(CompatibleMachineModel, "Cura", 1, 0, "CompatibleMachineModel")
self.processEvents() self.processEvents()
qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel") qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel")

View File

@ -0,0 +1,76 @@
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from PyQt6.QtCore import Qt, QObject, pyqtSlot, pyqtProperty, pyqtSignal
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from UM.i18n import i18nCatalog
class CompatibleMachineModel(ListModel):
NameRole = Qt.ItemDataRole.UserRole + 1
UniqueIdRole = Qt.ItemDataRole.UserRole + 2
ExtrudersRole = Qt.ItemDataRole.UserRole + 3
def __init__(self, parent: Optional[QObject] = None) -> None:
super().__init__(parent)
self._catalog = i18nCatalog("cura")
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.UniqueIdRole, "unique_id")
self.addRoleName(self.ExtrudersRole, "extruders")
self._update()
from cura.CuraApplication import CuraApplication
machine_manager = CuraApplication.getInstance().getMachineManager()
machine_manager.globalContainerChanged.connect(self._update)
machine_manager.outputDevicesChanged.connect(self._update)
def _update(self) -> None:
self.clear()
from cura.CuraApplication import CuraApplication
machine_manager = CuraApplication.getInstance().getMachineManager()
# Loop over the output-devices, not the stacks; need all applicable configurations, not just the current loaded one.
for output_device in machine_manager.printerOutputDevices:
for printer in output_device.printers:
extruder_configs = dict()
# initialize & add current active material:
for extruder in printer.extruders:
materials = [{
"brand": extruder.activeMaterial.brand,
"name": extruder.activeMaterial.name,
"hexcolor": extruder.activeMaterial.color,
}]
extruder_configs[extruder.getPosition()] = {
"position": extruder.getPosition(),
"core": extruder.hotendID,
"materials": materials
}
# add currently inactive, but possible materials:
for configuration in printer.availableConfigurations:
for extruder in configuration.extruderConfigurations:
if not extruder.position in extruder_configs:
Logger.log("w", f"No active extruder for position {extruder.position}.")
continue
extruder_configs[extruder.position]["materials"].append({
"brand": extruder.material.brand,
"name": extruder.material.name,
"hexcolor": extruder.material.color
})
if any([len(extruder["materials"]) > 0 for extruder in extruder_configs.values()]):
self.appendItem({
"name": printer.name,
"unique_id": printer.name, # <- Can assume the cloud doesn't have duplicate names?
"extruders": list(extruder_configs.values())
})

View File

@ -5,10 +5,13 @@
# online cloud connected printers are represented within this ListModel. Additional information such as the number of # online cloud connected printers are represented within this ListModel. Additional information such as the number of
# connected printers for each printer type is gathered. # connected printers for each printer type is gathered.
from PyQt6.QtCore import Qt, QTimer, pyqtSlot, pyqtProperty, pyqtSignal from typing import Optional
from PyQt6.QtCore import Qt, QTimer, QObject, pyqtSlot, pyqtProperty, pyqtSignal
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.Interfaces import ContainerInterface
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Util import parseBool from UM.Util import parseBool
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
@ -27,7 +30,7 @@ class MachineListModel(ListModel):
IsAbstractMachineRole = Qt.ItemDataRole.UserRole + 7 IsAbstractMachineRole = Qt.ItemDataRole.UserRole + 7
ComponentTypeRole = Qt.ItemDataRole.UserRole + 8 ComponentTypeRole = Qt.ItemDataRole.UserRole + 8
def __init__(self, parent=None) -> None: def __init__(self, parent: Optional[QObject] = None) -> None:
super().__init__(parent) super().__init__(parent)
self._show_cloud_printers = False self._show_cloud_printers = False
@ -66,7 +69,7 @@ class MachineListModel(ListModel):
self._updateDelayed() self._updateDelayed()
self.showCloudPrintersChanged.emit(show_cloud_printers) self.showCloudPrintersChanged.emit(show_cloud_printers)
def _onContainerChanged(self, container) -> None: def _onContainerChanged(self, container: ContainerInterface) -> None:
"""Handler for container added/removed events from registry""" """Handler for container added/removed events from registry"""
# We only need to update when the added / removed container GlobalStack # We only need to update when the added / removed container GlobalStack
@ -79,14 +82,15 @@ class MachineListModel(ListModel):
def _update(self) -> None: def _update(self) -> None:
self.clear() self.clear()
from cura.CuraApplication import CuraApplication
machines_manager = CuraApplication.getInstance().getMachineManager()
other_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type="machine") other_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type="machine")
abstract_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(is_abstract_machine = "True") abstract_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(is_abstract_machine = "True")
abstract_machine_stacks.sort(key = lambda machine: machine.getName(), reverse = True) abstract_machine_stacks.sort(key = lambda machine: machine.getName(), reverse = True)
for abstract_machine in abstract_machine_stacks: for abstract_machine in abstract_machine_stacks:
definition_id = abstract_machine.definition.getId() definition_id = abstract_machine.definition.getId()
from cura.CuraApplication import CuraApplication
machines_manager = CuraApplication.getInstance().getMachineManager()
online_machine_stacks = machines_manager.getMachinesWithDefinition(definition_id, online_only = True) online_machine_stacks = machines_manager.getMachinesWithDefinition(definition_id, online_only = True)
online_machine_stacks = list(filter(lambda machine: machine.hasNetworkedConnection(), online_machine_stacks)) online_machine_stacks = list(filter(lambda machine: machine.hasNetworkedConnection(), online_machine_stacks))
@ -128,11 +132,11 @@ class MachineListModel(ListModel):
return return
self.appendItem({ self.appendItem({
"componentType": "MACHINE", "componentType": "MACHINE",
"name": container_stack.getName(), "name": container_stack.getName(),
"id": container_stack.getId(), "id": container_stack.getId(),
"metadata": container_stack.getMetaData().copy(), "metadata": container_stack.getMetaData().copy(),
"isOnline": is_online, "isOnline": is_online,
"isAbstractMachine": parseBool(container_stack.getMetaDataEntry("is_abstract_machine", False)), "isAbstractMachine": parseBool(container_stack.getMetaDataEntry("is_abstract_machine", False)),
"machineCount": machine_count, "machineCount": machine_count,
}) })

View File

@ -13,9 +13,9 @@ class ExtruderConfigurationModel(QObject):
def __init__(self, position: int = -1) -> None: def __init__(self, position: int = -1) -> None:
super().__init__() super().__init__()
self._position = position # type: int self._position: int = position
self._material = None # type: Optional[MaterialOutputModel] self._material: Optional[MaterialOutputModel] = None
self._hotend_id = None # type: Optional[str] self._hotend_id: Optional[str] = None
def setPosition(self, position: int) -> None: def setPosition(self, position: int) -> None:
self._position = position self._position = position

View File

@ -99,7 +99,7 @@ class MachineManager(QObject):
self._application.getPreferences().addPreference("cura/active_machine", "") self._application.getPreferences().addPreference("cura/active_machine", "")
self._printer_output_devices = [] # type: List[PrinterOutputDevice] self._printer_output_devices: List[PrinterOutputDevice] = []
self._application.getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged) self._application.getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
# There might already be some output devices by the time the signal is connected # There might already be some output devices by the time the signal is connected
self._onOutputDevicesChanged() self._onOutputDevicesChanged()
@ -112,7 +112,7 @@ class MachineManager(QObject):
self._application.callLater(self.setInitialActiveMachine) self._application.callLater(self.setInitialActiveMachine)
containers = CuraContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) # type: List[InstanceContainer] containers: List[InstanceContainer] = CuraContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId)
if containers: if containers:
containers[0].nameChanged.connect(self._onMaterialNameChanged) containers[0].nameChanged.connect(self._onMaterialNameChanged)

View File

@ -1,15 +1,19 @@
from time import time from time import time
from typing import List from typing import Callable, List, Optional
from PyQt6.QtCore import QObject from PyQt6.QtCore import QObject, pyqtSlot
from PyQt6.QtNetwork import QNetworkReply from PyQt6.QtNetwork import QNetworkReply
from UM import i18nCatalog from UM import i18nCatalog
from UM.Logger import Logger from UM.Logger import Logger
from UM.FileHandler.FileHandler import FileHandler
from UM.Resources import Resources
from UM.Scene.SceneNode import SceneNode
from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
from .CloudApiClient import CloudApiClient from .CloudApiClient import CloudApiClient
from ..Models.Http.CloudClusterResponse import CloudClusterResponse
from ..Models.Http.CloudClusterWithConfigResponse import CloudClusterWithConfigResponse from ..Models.Http.CloudClusterWithConfigResponse import CloudClusterWithConfigResponse
from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOutputDevice from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOutputDevice
@ -19,7 +23,7 @@ I18N_CATALOG = i18nCatalog("cura")
class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
API_CHECK_INTERVAL = 10.0 # seconds API_CHECK_INTERVAL = 10.0 # seconds
def __init__(self, api_client: CloudApiClient, printer_type: str, parent: QObject = None) -> None: def __init__(self, api_client: CloudApiClient, printer_type: str, request_write_callback: Callable, refresh_callback: Callable, parent: QObject = None) -> None:
self._api = api_client self._api = api_client
properties = {b"printer_type": printer_type.encode()} properties = {b"printer_type": printer_type.encode()}
@ -31,6 +35,11 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
parent=parent parent=parent
) )
self._on_print_dialog: Optional[QObject] = None
self._nodes: List[SceneNode] = None
self._request_write_callback = request_write_callback
self._refresh_callback = refresh_callback
self._setInterfaceElements() self._setInterfaceElements()
def connect(self) -> None: def connect(self) -> None:
@ -41,7 +50,6 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
Logger.log("i", "Attempting to connect AbstractCloudOutputDevice %s", self.key) Logger.log("i", "Attempting to connect AbstractCloudOutputDevice %s", self.key)
super().connect() super().connect()
#CuraApplication.getInstance().getBackend().backendStateChange.connect(self._onBackendStateChange)
self._update() self._update()
def disconnect(self) -> None: def disconnect(self) -> None:
@ -84,4 +92,31 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
self._updatePrinters(all_configurations) self._updatePrinters(all_configurations)
def _onError(self, reply: QNetworkReply, error: QNetworkReply.NetworkError) -> None: def _onError(self, reply: QNetworkReply, error: QNetworkReply.NetworkError) -> None:
pass Logger.log("w", f"Failed to get clusters by machine type: {str(error)}.")
@pyqtSlot(str)
def printerSelected(self, unique_id: str):
self._request_write_callback(unique_id, self._nodes)
if self._on_print_dialog:
self._on_print_dialog.close()
@pyqtSlot()
def refresh(self):
self._refresh_callback()
self._update()
def _openChoosePrinterDialog(self) -> None:
if self._on_print_dialog is None:
qml_path = Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Dialogs", "ChoosePrinterDialog.qml")
self._on_print_dialog = CuraApplication.getInstance().createQmlComponent(qml_path, {})
if self._on_print_dialog is None: # Failed to load QML file.
return
self._on_print_dialog.setProperty("manager", self)
self._on_print_dialog.show()
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs) -> None:
if not nodes or len(nodes) < 1:
Logger.log("w", "Nothing to print.")
return
self._nodes = nodes
self._openChoosePrinterDialog()

View File

@ -172,6 +172,13 @@ class CloudOutputDeviceManager:
self._syncing = False self._syncing = False
self._account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR) self._account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR)
def _requestWrite(self, unique_id: str, nodes: List["SceneNode"]):
for remote in self._remote_clusters.values():
if unique_id == remote.name: # No other id-type would match. Assume cloud doesn't have duplicate names.
remote.requestWrite(nodes)
return
Logger.log("e", f"Failed writing to specific cloud printer: {unique_id} not in remote clusters.")
def _createMachineStacksForDiscoveredClusters(self, discovered_clusters: List[CloudClusterResponse]) -> None: def _createMachineStacksForDiscoveredClusters(self, discovered_clusters: List[CloudClusterResponse]) -> None:
"""**Synchronously** create machines for discovered devices """**Synchronously** create machines for discovered devices
@ -193,7 +200,7 @@ class CloudOutputDeviceManager:
output_device = CloudOutputDevice(self._api, cluster_data) output_device = CloudOutputDevice(self._api, cluster_data)
if cluster_data.printer_type not in self._abstract_clusters: if cluster_data.printer_type not in self._abstract_clusters:
self._abstract_clusters[cluster_data.printer_type] = AbstractCloudOutputDevice(self._api, cluster_data.printer_type) self._abstract_clusters[cluster_data.printer_type] = AbstractCloudOutputDevice(self._api, cluster_data.printer_type, self._requestWrite, self.refreshConnections)
# Ensure that the abstract machine is added (either because it was never added, or it somehow got # Ensure that the abstract machine is added (either because it was never added, or it somehow got
# removed) # removed)
_abstract_machine = CuraStackBuilder.createAbstractMachine(cluster_data.printer_type) _abstract_machine = CuraStackBuilder.createAbstractMachine(cluster_data.printer_type)

View File

@ -0,0 +1,94 @@
// Copyright (c) 2022 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 2.9
import QtQuick.Layouts 2.10
import UM 1.5 as UM
import Cura 1.0 as Cura
UM.Dialog
{
property var manager
id: base
title: catalog.i18nc("@title:window", "Select Printer")
backgroundColor: UM.Theme.getColor("background_2")
width: minimumWidth
minimumWidth: 550 * screenScaleFactor
height: minimumHeight
minimumHeight: 550 * screenScaleFactor
modality: Qt.ApplicationModal
ScrollView
{
// Workaround for Windowing bugs in Qt:
width: 550 * screenScaleFactor - 3 * UM.Theme.getSize("default_margin").width
height: 550 * screenScaleFactor - 3 * UM.Theme.getSize("default_margin").height
UM.I18nCatalog
{
id: catalog
name: "cura"
}
anchors.fill: parent
Column
{
anchors.fill: parent
spacing: UM.Theme.getSize("default_margin").height
Item
{
width: parent.width
height: childrenRect.height
UM.Label
{
anchors.left: parent.left
text: catalog.i18nc("@title:label", "Compatible Printers")
font: UM.Theme.getFont("large")
}
UM.SimpleButton
{
anchors.right: parent.right
width: UM.Theme.getSize("small_button").width
height: UM.Theme.getSize("small_button").height
iconSource: UM.Theme.getIcon("ArrowDoubleCircleRight")
color: UM.Theme.getColor("text_link")
hoverColor: UM.Theme.getColor("text_scene_hover")
onClicked: manager.refresh()
}
}
Repeater
{
id: contents
model: Cura.CompatibleMachineModel {}
delegate: Cura.PrintSelectorCard
{
name: model.name
unique_id: model.unique_id
extruders: model.extruders
manager: base.manager
}
}
UM.Label
{
visible: contents.count < 1
text: catalog.i18nc("@description", "No compatible printers, that are currently online, where found.")
}
}
}
}

View File

@ -8,16 +8,16 @@ Item
{ {
id: extruderIconItem id: extruderIconItem
implicitWidth: UM.Theme.getSize("extruder_icon").width
implicitHeight: UM.Theme.getSize("extruder_icon").height
property bool checked: true property bool checked: true
property color materialColor property color materialColor
property alias textColor: extruderNumberText.color property alias textColor: extruderNumberText.color
property bool extruderEnabled: true property bool extruderEnabled: true
property var iconSize property var iconSize: UM.Theme.getSize("extruder_icon").width
property string iconVariant: "medium" property string iconVariant: "medium"
implicitWidth: iconSize
implicitHeight: iconSize
Item Item
{ {
opacity: extruderEnabled ? 1 : UM.Theme.getColor("extruder_disabled").a opacity: extruderEnabled ? 1 : UM.Theme.getColor("extruder_disabled").a
@ -27,8 +27,8 @@ Item
UM.ColorImage UM.ColorImage
{ {
anchors.fill: parent anchors.fill: parent
width: mainIcon.width width: iconSize
height: mainIcon.height height: iconSize
source: UM.Theme.getIcon("ExtruderColor", iconVariant) source: UM.Theme.getIcon("ExtruderColor", iconVariant)
color: materialColor color: materialColor
@ -37,8 +37,8 @@ Item
{ {
id: mainIcon id: mainIcon
anchors.fill: parent anchors.fill: parent
width: UM.Theme.getSize("extruder_icon").width width: iconSize
height: UM.Theme.getSize("extruder_icon").height height: iconSize
source: UM.Theme.getIcon("Extruder", iconVariant) source: UM.Theme.getIcon("Extruder", iconVariant)
color: extruderNumberText.color color: extruderNumberText.color

View File

@ -0,0 +1,143 @@
// Copyright (c) 2022 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 2.9
import QtQuick.Layouts 2.10
import UM 1.5 as UM
import Cura 1.0 as Cura
Rectangle
{
property alias name: printerTitle.text
property string unique_id
property var extruders
property var manager
width: parent.width
height: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height
color: UM.Theme.getColor("background_1")
border.color: UM.Theme.getColor("border_main")
border.width: UM.Theme.getSize("default_lining").width
RowLayout
{
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: UM.Theme.getSize("default_margin").width
Cura.IconWithText
{
id: printerTitle
Layout.preferredWidth: parent.width / 3
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
Layout.fillHeight: false
source: UM.Theme.getIcon("Printer")
spacing: UM.Theme.getSize("thin_margin").width
iconSize: UM.Theme.getSize("medium_button_icon").width
font: UM.Theme.getFont("medium_bold")
}
ColumnLayout
{
id: extruderInformation
Layout.fillWidth: true
Layout.preferredWidth: parent.width / 2
Layout.alignment: Qt.AlignTop
spacing: UM.Theme.getSize("default_margin").width
Repeater
{
model: extruders
Item
{
height: childrenRect.height
Cura.ExtruderIcon
{
id: extruderIcon
anchors.top: parent.top
anchors.left: parent.left
materialColor: modelData.materials.length == 1 ? modelData.materials[0].hexcolor : "white"
iconSize: UM.Theme.getSize("medium_button_icon").width
}
UM.Label
{
id: extruderCore
anchors.verticalCenter: extruderIcon.verticalCenter
anchors.left: extruderIcon.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
text: modelData.core
font: UM.Theme.getFont("default_bold")
}
UM.Label
{
id: singleMaterialText
anchors.left: extruderCore.right
anchors.verticalCenter: extruderCore.verticalCenter
anchors.leftMargin: UM.Theme.getSize("default_margin").width
text: modelData.materials.length == 1 ? `${modelData.materials[0].brand} ${modelData.materials[0].name}` : ""
visible: modelData.materials.length == 1
}
ColumnLayout
{
id: multiMaterialText
anchors.top: extruderCore.bottom
anchors.left: extruderCore.left
anchors.topMargin: UM.Theme.getSize("narrow_margin").height
visible: modelData.materials.length > 1
Repeater
{
model: modelData.materials
UM.Label
{
text: `${modelData.brand} ${modelData.name}`
}
}
}
}
}
}
Button
{
id: printButton
implicitWidth: UM.Theme.getSize("medium_button").width
implicitHeight: implicitWidth
Layout.alignment: Qt.AlignTop
padding: 0
background: Rectangle
{
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("border_accent_1")
color: printButton.hovered ? UM.Theme.getColor("toolbar_button_hover"): UM.Theme.getColor("background_1")
}
contentItem: Item
{
UM.ColorImage
{
anchors.centerIn: parent
source: UM.Theme.getIcon("Printer")
color: UM.Theme.getColor("border_accent_1")
width: UM.Theme.getSize("small_button_icon").width
height: width
}
}
onClicked: manager.printerSelected(unique_id)
}
}
}

View File

@ -17,6 +17,7 @@ PrinterTypeLabel 1.0 PrinterTypeLabel.qml
ViewsSelector 1.0 ViewsSelector.qml ViewsSelector 1.0 ViewsSelector.qml
SettingView 1.0 SettingView.qml SettingView 1.0 SettingView.qml
ProfileMenu 1.0 ProfileMenu.qml ProfileMenu 1.0 ProfileMenu.qml
PrintSelectorCard 1.0 PrintSelectorCard.qml
# Cura/WelcomePages # Cura/WelcomePages