diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index e9be1f96c9..3b15069598 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -61,6 +61,7 @@ from cura.Settings.UserProfilesModel import UserProfilesModel from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager from cura.Machines.VariantManager import VariantManager +from cura.Machines.Models.QualityManagementModel import QualityManagementModel from . import PlatformPhysics from . import BuildVolume @@ -939,6 +940,9 @@ class CuraApplication(QtApplication): qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel") qmlRegisterType(NewMaterialsModel, "Cura", 1, 0, "NewMaterialsModel") + # TODO: make this singleton? + qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel") + qmlRegisterSingletonType(NewQualityProfilesModel, "Cura", 1, 0, "NewQualityProfilesModel", self.getNewQualityProfileModel) qmlRegisterSingletonType(NewCustomQualityProfilesModel, "Cura", 1, 0, "NewCustomQualityProfilesModel", self.getNewCustomQualityProfilesModel) qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel") diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 550308f1dc..84f08a3b27 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -59,7 +59,7 @@ class MaterialManager(QObject): self._update_timer = QTimer(self) self._update_timer.setInterval(300) self._update_timer.setSingleShot(True) - self._update_timer.timeout.connect(self._updateTables) + self._update_timer.timeout.connect(self._updateMaps) self._container_registry.containerMetaDataChanged.connect(self._onContainerMetadataChanged) self._container_registry.containerAdded.connect(self._onContainerMetadataChanged) @@ -192,7 +192,7 @@ class MaterialManager(QObject): self.materialsUpdated.emit() - def _updateTables(self): + def _updateMaps(self): self.initialize() def _onContainerMetadataChanged(self, container): @@ -203,7 +203,7 @@ class MaterialManager(QObject): if container_type != "material": return - # TODO: update the cache table + # update the maps self._update_timer.start() def getMaterialGroup(self, root_material_id: str) -> Optional[MaterialGroup]: diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py new file mode 100644 index 0000000000..fab6a36410 --- /dev/null +++ b/cura/Machines/Models/QualityManagementModel.py @@ -0,0 +1,75 @@ + + +from PyQt5.QtCore import Qt + +from UM.Qt.ListModel import ListModel + + +class QualityManagementModel(ListModel): + NameRole = Qt.UserRole + 1 + IsReadOnlyRole = Qt.UserRole + 2 + QualityGroupRole = Qt.UserRole + 3 + QualityChangesGroupRole = Qt.UserRole + 4 + + def __init__(self, parent = None): + super().__init__(parent) + + self.addRoleName(self.NameRole, "name") + self.addRoleName(self.IsReadOnlyRole, "is_read_only") + self.addRoleName(self.QualityGroupRole, "quality_group") + self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group") + + from cura.CuraApplication import CuraApplication + self._container_registry = CuraApplication.getInstance().getContainerRegistry() + self._machine_manager = CuraApplication.getInstance().getMachineManager() + self._extruder_manager = CuraApplication.getInstance().getExtruderManager() + self._quality_manager = CuraApplication.getInstance()._quality_manager + + self._machine_manager.globalContainerChanged.connect(self._update) + #self._quality_manager.materialsUpdated.connect(self._update) # TODO + + self._update() + + def _update(self): + global_stack = self._machine_manager._global_container_stack + + quality_group_dict = self._quality_manager.getQualityGroups(global_stack) + quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(global_stack) + + available_quality_types = set(qt for qt, qg in quality_group_dict.items() if qg.is_available) + if not available_quality_types and not quality_changes_group_dict: + # Nothing to show + self.setItems([]) + return + + item_list = [] + # Create quality group items + for quality_group in quality_group_dict.values(): + if not quality_group.is_available: + continue + + item = {"name": quality_group.name, + "is_read_only": True, + "quality_group": quality_group, + "quality_changes_group": None} + item_list.append(item) + # Sort by quality names + item_list = sorted(item_list, key = lambda x: x["name"]) + + # Create quality_changes group items + quality_changes_item_list = [] + for quality_changes_group in quality_changes_group_dict.values(): + if quality_changes_group.quality_type not in available_quality_types: + continue + quality_group = quality_group_dict[quality_changes_group.quality_type] + item = {"name": quality_changes_group.name, + "is_read_only": False, + "quality_group": quality_group, + "quality_changes_group": quality_changes_group} + quality_changes_item_list.append(item) + + # Sort quality_changes items by names and append to the item list + quality_changes_item_list = sorted(quality_changes_item_list, key = lambda x: x["name"]) + item_list += quality_changes_item_list + + self.setItems(item_list) diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index f0fd0168a6..53b85a1dca 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -1,10 +1,9 @@ from typing import Optional -from PyQt5.Qt import QObject +from PyQt5.QtCore import QObject, QTimer from UM.Application import Application from UM.Logger import Logger -from UM.Util import parseBool from cura.Machines.ContainerGroup import ContainerGroup from cura.Machines.ContainerNode import ContainerNode @@ -126,6 +125,15 @@ class QualityManager(QObject): self._default_machine_definition_id = "fdmprinter" + self._container_registry.containerMetaDataChanged.connect(self._onContainerMetadataChanged) + self._container_registry.containerAdded.connect(self._onContainerMetadataChanged) + self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged) + + self._update_timer = QTimer(self) + self._update_timer.setInterval(300) + self._update_timer.setSingleShot(True) + self._update_timer.timeout.connect(self._updateMaps) + def initialize(self): # Initialize the lookup tree for quality profiles with following structure: # -> -> @@ -186,10 +194,6 @@ class QualityManager(QObject): material_node.addQualityMetadata(quality_type, metadata) - # Initialize quality - self._initializeQualityChangesTables() - - def _initializeQualityChangesTables(self): # Initialize the lookup tree for quality_changes profiles with following structure: # -> -> quality_changes_metadata_list = self._container_registry.findContainersMetadata(type = "quality_changes") @@ -206,6 +210,20 @@ class QualityManager(QObject): machine_node.addQualityChangesMetadata(quality_type, metadata) + def _updateMaps(self): + self.initialize() + + def _onContainerMetadataChanged(self, container): + self._onContainerChanged(container) + + def _onContainerChanged(self, container): + container_type = container.getMetaDataEntry("type") + if container_type not in ("quality", "quality_changes"): + return + + # update the cache table + self._update_timer.start() + # Updates the given quality groups' availabilities according to which extruders are being used/ enabled. def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list): used_extruders = set() @@ -234,8 +252,8 @@ class QualityManager(QObject): machine_node = self._machine_quality_type_to_quality_changes_dict.get(machine_definition_id) if not machine_node: - Logger.log("e", "Cannot find node for machine def [%s] in QualityChanges lookup table", machine_definition_id) - return {} + Logger.log("i", "Cannot find node for machine def [%s] in QualityChanges lookup table", machine_definition_id) + return dict() # Update availability for each QualityChangesGroup: # A custom profile is always available as long as the quality_type it's based on is available diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 89106f9bda..8f66f92f1c 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -12,7 +12,6 @@ from UM.Signal import Signal from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer import UM.FlameProfiler from UM.FlameProfiler import pyqtSlot -from PyQt5.QtWidgets import QMessageBox from UM import Util from UM.Application import Application @@ -1371,27 +1370,6 @@ class MachineManager(QObject): self._current_root_material_id[position] = self._global_container_stack.extruders[position].material.getMetaDataEntry("base_file") return self._current_root_material_id - def _setQualityChangesGroup(self, quality_changes_group): - self._current_quality_changes_group = quality_changes_group - - # TODO: quality_changes groups depend on a quality_type. Here it's fetching the quality_types every time. - # Can we do this better, like caching the quality group a quality_changes group depends on? - quality_manager = Application.getInstance()._quality_manager - quality_group_dict = quality_manager.getQualityGroups(self._global_container_stack) - - quality_type = quality_changes_group.quality_type - - container = self._empty_quality_changes_container - if quality_changes_group.node_for_global is not None: - container = quality_changes_group.node_for_global.getContainer() - self._global_container_stack.qualityChanges = container - self._global_container_stack.quality = quality_group_dict[quality_type] - - for position, extruder in self._global_container_stack.extruders.items(): - container = quality_changes_group.nodes_for_extruders.get(position, - self._empty_quality_changes_container) - extruder.qualityChanges = container - def _setEmptyQuality(self): self._current_quality_group = None self._global_container_stack.quality = self._empty_quality_container @@ -1405,8 +1383,6 @@ class MachineManager(QObject): def _setQualityGroup(self, quality_group, empty_quality_changes = True): self._current_quality_group = quality_group - #TODO: check quality_changes - # Set quality and quality_changes for the GlobalStack self._global_container_stack.quality = quality_group.node_for_global.getContainer() if empty_quality_changes: @@ -1419,6 +1395,44 @@ class MachineManager(QObject): self._global_container_stack.extruders[position].qualityChanges = self._empty_quality_changes_container self.activeQualityGroupChanged.emit() + self.activeQualityChangesGroupChanged.emit() + + def _setQualityChangesGroup(self, quality_changes_group): + # TODO: quality_changes groups depend on a quality_type. Here it's fetching the quality_types every time. + # Can we do this better, like caching the quality group a quality_changes group depends on? + quality_type = quality_changes_group.quality_type + quality_manager = Application.getInstance()._quality_manager + quality_group_dict = quality_manager.getQualityGroups(self._global_container_stack) + quality_group = quality_group_dict[quality_type] + + quality_changes_container = self._empty_quality_changes_container + quality_container = self._empty_quality_changes_container + if quality_changes_group.node_for_global: + quality_changes_container = quality_changes_group.node_for_global.getContainer() + if quality_group.node_for_global: + quality_container = quality_group.node_for_global.getContainer() + + self._global_container_stack.quality = quality_container + self._global_container_stack.qualityChanges = quality_changes_container + + for position, extruder in self._global_container_stack.extruders.items(): + quality_changes_node = quality_changes_group.nodes_for_extruders.get(position) + quality_node = quality_group.nodes_for_extruders.get(position) + + quality_changes_container = self._empty_quality_changes_container + quality_container = self._empty_quality_changes_container + if quality_changes_node: + quality_changes_container = quality_changes_node.getContainer() + if quality_node: + quality_container = quality_node.getContainer() + + extruder.quality = quality_container + extruder.qualityChanges = quality_changes_container + + self._current_quality_group = quality_group + self._current_quality_changes_group = quality_changes_group + self.activeQualityGroupChanged.emit() + self.activeQualityChangesGroupChanged.emit() def _setVariantGroup(self, position, container_node): self._global_container_stack.extruders[position].variant = container_node.getContainer() @@ -1515,3 +1529,23 @@ class MachineManager(QObject): def activeQualityGroup(self): return self._current_quality_group + @pyqtSlot("QVariant") + def setQualityChangesGroup(self, quality_changes_group): + Logger.log("d", "---------------- qcg = [%s]", quality_changes_group.name) + self.blurSettings.emit() + with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): + self._setQualityChangesGroup(quality_changes_group) + Logger.log("d", "Quality changes set!") + + @pyqtProperty("QVariant", fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged) + def activeQualityChangesGroup(self): + return self._current_quality_changes_group + + @pyqtProperty(str, notify = activeQualityGroupChanged) + def activeQualityOrQualityChangesName(self): + name = "" + if self._current_quality_changes_group: + name = self._current_quality_changes_group.name + elif self._current_quality_group: + name = self._current_quality_group.name + return name diff --git a/cura/Settings/MaterialsModel.py b/cura/Settings/MaterialsModel.py index 51f9e09be6..72b39a90aa 100644 --- a/cura/Settings/MaterialsModel.py +++ b/cura/Settings/MaterialsModel.py @@ -2,7 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from typing import Any, List, Optional -from PyQt5.QtCore import Qt, QObject +from PyQt5.QtCore import Qt from UM.Logger import Logger from UM.Qt.ListModel import ListModel diff --git a/cura/Settings/ProfilesModel.py b/cura/Settings/ProfilesModel.py index b05bc46dd5..eaef48e168 100644 --- a/cura/Settings/ProfilesModel.py +++ b/cura/Settings/ProfilesModel.py @@ -27,6 +27,7 @@ class NewQualityProfilesModel(ListModel): LayerHeightRole = Qt.UserRole + 4 AvailableRole = Qt.UserRole + 5 QualityGroupRole = Qt.UserRole + 6 + QualityChangesGroupRole = Qt.UserRole + 7 def __init__(self, parent = None): super().__init__(parent) @@ -37,6 +38,7 @@ class NewQualityProfilesModel(ListModel): self.addRoleName(self.LayerHeightRole, "layer_height") self.addRoleName(self.AvailableRole, "available") self.addRoleName(self.QualityGroupRole, "quality_group") + self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group") # connect signals Application.getInstance().globalContainerStackChanged.connect(self._update) diff --git a/resources/qml/Preferences/ProfileTab.qml b/resources/qml/Preferences/ProfileTab.qml new file mode 100644 index 0000000000..47446c82c2 --- /dev/null +++ b/resources/qml/Preferences/ProfileTab.qml @@ -0,0 +1,98 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.8 +import QtQuick.Controls 1.4 + +import UM 1.2 as UM +import Cura 1.0 as Cura + +Tab +{ + id: base + + property string extruderId: ""; + property string extruderDefinition: ""; + property string quality: ""; + property string material: ""; + + TableView + { + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_margin").width + + Component + { + id: itemDelegate + + UM.TooltipArea + { + property var setting: qualitySettings.getItem(styleData.row) + height: childrenRect.height + width: (parent != null) ? parent.width : 0 + text: (styleData.value.substr(0,1) == "=") ? styleData.value : "" + + Label + { + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + anchors.right: parent.right + text: (styleData.value.substr(0,1) == "=") ? catalog.i18nc("@info:status", "Calculated") : styleData.value + font.strikeout: styleData.column == 1 && quality == Cura.MachineManager.globalQualityId && setting.user_value != "" + font.italic: setting.profile_value_source == "quality_changes" || (quality == Cura.MachineManager.globalQualityId && setting.user_value != "") + opacity: font.strikeout ? 0.5 : 1 + color: styleData.textColor + elide: Text.ElideRight + } + } + } + + TableViewColumn + { + role: "label" + title: catalog.i18nc("@title:column", "Setting") + width: (parent.width * 0.4) | 0 + delegate: itemDelegate + } + TableViewColumn + { + role: "profile_value" + title: catalog.i18nc("@title:column", "Profile") + width: (parent.width * 0.18) | 0 + delegate: itemDelegate + } + TableViewColumn + { + role: "user_value" + title: catalog.i18nc("@title:column", "Current"); + visible: quality == Cura.MachineManager.globalQualityId + width: (parent.width * 0.18) | 0 + delegate: itemDelegate + } + TableViewColumn + { + role: "unit" + title: catalog.i18nc("@title:column", "Unit") + width: (parent.width * 0.14) | 0 + delegate: itemDelegate + } + + section.property: "category" + section.delegate: Label + { + text: section + font.bold: true + } + + model: Cura.QualitySettingsModel + { + id: qualitySettings + extruderId: base.extruderId + extruderDefinition: base.extruderDefinition + quality: base.quality != null ? base.quality : "" + material: base.material != null ? base.material : "" + } + + SystemPalette { id: palette } + } +} diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml new file mode 100644 index 0000000000..633e965bd9 --- /dev/null +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -0,0 +1,294 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Uranium is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.8 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.3 + +import UM 1.2 as UM +import Cura 1.0 as Cura + + +Item +{ + id: base + property var resetEnabled: false // Keep PreferencesDialog happy + + UM.I18nCatalog { id: catalog; name: "cura"; } + + Cura.QualityManagementModel { + id: qualitiesModel + } + + Label { + id: titleLabel + + anchors { + top: parent.top + left: parent.left + right: parent.right + margins: 5 * screenScaleFactor + } + + font.pointSize: 18 + text: catalog.i18nc("@title:tab", "Profiles") + } + + Row // Button Row + { + id: buttonRow + anchors { + left: parent.left + right: parent.right + top: titleLabel.bottom + } + height: childrenRect.height + + // Activate button + Button + { + text: catalog.i18nc("@action:button", "Activate") + iconName: "list-activate" + //enabled: base.currentItem != null ? base.currentItem.id != Cura.MachineManager.activeQualityId : false; + enabled: true // TODO + onClicked: { + // TODO + } + } + + // Create button + Button + { + text: catalog.i18nc("@label", "Create") + iconName: "list-add" + //enabled: base.canCreateProfile() && !Cura.MachineManager.stacksHaveErrors + enabled: true // TODO + //visible: base.canCreateProfile() + visible: true // TODO + + onClicked: { + // TODO + } + } + + // Duplicate button + Button + { + text: catalog.i18nc("@label", "Duplicate") + iconName: "list-add" + //enabled: ! base.canCreateProfile() + enabled: true // TODO + //visible: ! base.canCreateProfile() + visible: true // TODO + + onClicked: { + // TODO + } + } + + // Remove button + Button + { + text: catalog.i18nc("@action:button", "Remove") + iconName: "list-remove" + //enabled: base.currentItem != null ? !base.currentItem.readOnly && !Cura.ContainerManager.isContainerUsed(base.currentItem.id) : false; + enabled: true // TODO + onClicked: { + // TODO + } + } + + // Rename button + Button + { + text: catalog.i18nc("@action:button", "Rename") + iconName: "edit-rename" + //enabled: base.currentItem != null ? !base.currentItem.readOnly : false; + enabled: true // TODO + onClicked: { + // TODO + } + } + + // Import button + Button + { + text: catalog.i18nc("@action:button", "Import") + iconName: "document-import" + onClicked: { + // TODO + } + } + + // Export button + Button + { + text: catalog.i18nc("@action:button", "Export") + iconName: "document-export" + //enabled: currentItem != null && !base.currentItem.readOnly + enabled: true // TODO + onClicked: { + // TODO + } + } + } + + + + Item { + id: contentsItem + + anchors { + top: titleLabel.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + margins: 5 * screenScaleFactor + bottomMargin: 0 + } + + clip: true + } + + Item + { + anchors { + top: buttonRow.bottom + topMargin: UM.Theme.getSize("default_margin").height + left: parent.left + right: parent.right + bottom: parent.bottom + } + + SystemPalette { id: palette } + + Label + { + id: captionLabel + anchors { + top: parent.top + left: parent.left + } + visible: text != "" + text: { + // OLD STUFF + return catalog.i18nc("@label %1 is printer name", "Printer: %1").arg(Cura.MachineManager.activeMachineName); + } + width: profileScrollView.width + elide: Text.ElideRight + } + + ScrollView + { + id: profileScrollView + anchors { + top: captionLabel.visible ? captionLabel.bottom : parent.top + topMargin: captionLabel.visible ? UM.Theme.getSize("default_margin").height : 0 + bottom: parent.bottom + left: parent.left + } + + Rectangle { + parent: viewport + anchors.fill: parent + color: palette.light + } + + width: true ? (parent.width * 0.4) | 0 : parent.width + + ListView + { + id: qualityListView + + model: qualitiesModel + + section.property: "is_read_only" + section.delegate: Rectangle + { + height: childrenRect.height + + Label + { + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_lining").width + text: section == "true" ? catalog.i18nc("@label", "Protected profiles") : catalog.i18nc("@label", "Custom profiles") + font.bold: true + } + } + + delegate: Rectangle + { + width: profileScrollView.width + height: childrenRect.height + color: ListView.isCurrentItem ? palette.highlight : (model.index % 2) ? palette.base : palette.alternateBase + + Row + { + spacing: (UM.Theme.getSize("default_margin").width / 2) | 0 + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + anchors.right: parent.right + Label + { + width: Math.floor((parent.width * 0.3)) + text: model.name + elide: Text.ElideRight + font.italic: { // TODO: make it easier + return model.name == Cura.MachineManager.activeQualityOrQualityChangesName; + } + color: parent.ListView.isCurrentItem ? palette.highlightedText : palette.text; + } + } + + MouseArea + { + anchors.fill: parent + onClicked: { + parent.ListView.view.currentIndex = model.index; + } + } + } + + onCurrentIndexChanged: + { + var model = qualitiesModel.getItem(currentIndex); + // TODO + } + } + } + + + Item + { + id: detailsPanel + + anchors { + left: profileScrollView.right + leftMargin: UM.Theme.getSize("default_margin").width + top: parent.top + bottom: parent.bottom + right: parent.right + } + + Item + { + anchors.fill: parent + + Item // Profile title Label + { + id: profileName + + width: parent.width + height: childrenRect.height + + Label { + text: "TODO" // TODO + font: UM.Theme.getFont("large") + } + } + + } + } + } +}