diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 1192455312..4729bd6a56 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -60,6 +60,7 @@ from cura.Scene.CuraSceneNode import CuraSceneNode from cura.Scene.CuraSceneController import CuraSceneController from cura.UI.WelcomePagesModel import WelcomePagesModel +from cura.UI.MachineSettingsManager import MachineSettingsManager from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType from UM.Settings.ContainerRegistry import ContainerRegistry @@ -212,8 +213,9 @@ class CuraApplication(QtApplication): self._cura_scene_controller = None self._machine_error_checker = None - self._discovered_printer_model = DiscoveredPrintersModel(self) + self._machine_settings_manager = MachineSettingsManager(self) + self._discovered_printer_model = DiscoveredPrintersModel(self) self._welcome_pages_model = WelcomePagesModel(self) self._quality_profile_drop_down_menu_model = None @@ -862,6 +864,10 @@ class CuraApplication(QtApplication): def getWelcomePagesModel(self, *args) -> "WelcomePagesModel": return self._welcome_pages_model + @pyqtSlot(result = QObject) + def getMachineSettingsManager(self, *args) -> "MachineSettingsManager": + return self._machine_settings_manager + def getCuraFormulaFunctions(self, *args) -> "CuraFormulaFunctions": if self._cura_formula_functions is None: self._cura_formula_functions = CuraFormulaFunctions(self) diff --git a/cura/UI/MachineSettingsManager.py b/cura/UI/MachineSettingsManager.py new file mode 100644 index 0000000000..3232323bcb --- /dev/null +++ b/cura/UI/MachineSettingsManager.py @@ -0,0 +1,66 @@ + +from typing import Any, Dict, Optional, TYPE_CHECKING + +from PyQt5.QtCore import Qt, QObject, pyqtSlot + +from UM.i18n import i18nCatalog + + +class MachineSettingsManager(QObject): + + def __init__(self, parent: Optional["QObject"] = None) -> None: + super().__init__(parent) + self._i18n_catalog = i18nCatalog("cura") + + from cura.CuraApplication import CuraApplication + self._application = CuraApplication.getInstance() + + # Force rebuilding the build volume by reloading the global container stack. This is a bit of a hack, but it seems + # quite enough. + @pyqtSlot() + def forceUpdate(self) -> None: + self._application.getMachineManager().globalContainerChanged.emit() + + # Function for the Machine Settings panel (QML) to update the compatible material diameter after a user has changed + # an extruder's compatible material diameter. This ensures that after the modification, changes can be notified + # and updated right away. + @pyqtSlot(int) + def updateMaterialForDiameter(self, extruder_position: int): + # Updates the material container to a material that matches the material diameter set for the printer + self._application.getMachineManager().updateMaterialWithVariant(str(extruder_position)) + + # FIXME(Lipu): Better document what this function does, especially the fuzzy gcode flavor and has_materials logic + # regarding UM2 and UM2+ + # Function for the Machine Settings panel (QML) to update after the usre changes "Number of Extruders". + @pyqtSlot() + def updateHasMaterialsMetadata(self): + machine_manager = self._application.getMachineManager() + material_manager = self._application.getMaterialManager() + + global_stack = material_manager.activeMachine + + definition = global_stack.definition + if definition.getProperty("machine_gcode_flavor", "value") != "UltiGCode" or definition.getMetaDataEntry( + "has_materials", False): + # In other words: only continue for the UM2 (extended), but not for the UM2+ + return + + extruder_positions = list(global_stack.extruders.keys()) + has_materials = global_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode" + + material_node = None + if has_materials: + global_stack.setMetaDataEntry("has_materials", True) + else: + # The metadata entry is stored in an ini, and ini files are parsed as strings only. + # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False. + if "has_materials" in global_stack.getMetaData(): + global_stack.removeMetaDataEntry("has_materials") + + # set materials + for position in extruder_positions: + if has_materials: + material_node = material_manager.getDefaultMaterial(global_stack, position, None) + machine_manager.setMaterial(position, material_node) + + self.forceUpdate() diff --git a/resources/qml/MachineSettings/ComboBoxWithOptions.qml b/resources/qml/MachineSettings/ComboBoxWithOptions.qml index 1d7f9307b6..6abc2bde22 100644 --- a/resources/qml/MachineSettings/ComboBoxWithOptions.qml +++ b/resources/qml/MachineSettings/ComboBoxWithOptions.qml @@ -39,8 +39,8 @@ UM.TooltipArea property string tooltipText: propertyProvider.properties.description // callback functions - property var afterOnActivateFunction: dummy_func property var forceUpdateOnChangeFunction: dummy_func + property var afterOnEditingFinishedFunction: dummy_func // a dummy function for default property values function dummy_func() {} @@ -64,8 +64,10 @@ UM.TooltipArea ListModel { id: defaultOptionsModel - Component.onCompleted: + + function updateModel() { + clear() // Options come in as a string-representation of an OrderedDict var options = propertyProvider.properties.options.match(/^OrderedDict\(\[\((.*)\)\]\)$/) if (options) @@ -74,10 +76,19 @@ UM.TooltipArea for (var i = 0; i < options.length; i++) { var option = options[i].substring(1, options[i].length - 1).split("', '") - defaultOptionsModel.append({text: option[1], value: option[0]}) + append({text: option[1], value: option[0]}) } } } + + Component.onCompleted: updateModel() + } + + // Remake the model when the model is bound to a different container stack + Connections + { + target: propertyProvider + onContainerStackChanged: defaultOptionsModel.updateModel() } CuraComboBox @@ -107,11 +118,11 @@ UM.TooltipArea onActivated: { - if(propertyProvider.properties.value != model.get(index).value) + if (propertyProvider.properties.value != model.get(index).value) { propertyProvider.setPropertyValue("value", model.get(index).value) forceUpdateOnChangeFunction() - afterOnActivateFunction() + afterOnEditingFinishedFunction() } } } diff --git a/resources/qml/MachineSettings/NumericTextFieldWithUnit.qml b/resources/qml/MachineSettings/NumericTextFieldWithUnit.qml index a39fbba0c5..c6872617bc 100644 --- a/resources/qml/MachineSettings/NumericTextFieldWithUnit.qml +++ b/resources/qml/MachineSettings/NumericTextFieldWithUnit.qml @@ -181,7 +181,7 @@ UM.TooltipArea propertyProvider.setPropertyValue("value", text) } forceUpdateOnChangeFunction() - afterOnEditingFinished() + afterOnEditingFinishedFunction() } } diff --git a/resources/qml/WelcomePages/MachineSettingsExtruderTab.qml b/resources/qml/WelcomePages/MachineSettingsExtruderTab.qml index 440c4b2bab..3ed82a6dde 100644 --- a/resources/qml/WelcomePages/MachineSettingsExtruderTab.qml +++ b/resources/qml/WelcomePages/MachineSettingsExtruderTab.qml @@ -23,8 +23,6 @@ Item anchors.right: parent.right anchors.top: parent.top - property string extruderStackId: "" - property int labelWidth: 180 property int controlWidth: UM.Theme.getSize("setting_control").width * 3 / 4 property var labelFont: UM.Theme.getFont("medium") @@ -33,6 +31,15 @@ Item property int columnSpacing: 3 property int propertyStoreIndex: 5 // definition_changes + property string extruderStackId: "" + property int extruderPosition: 0 + property var forceUpdateFunction: CuraApplication.getMachineSettingsManager().forceUpdate + + function updateMaterialDiameter() + { + CuraApplication.getMachineSettingsManager().updateMaterialForDiameter(extruderPosition) + } + Item { id: upperBlock @@ -73,7 +80,7 @@ Item labelWidth: base.labelWidth controlWidth: base.controlWidth unitText: catalog.i18nc("@label", "mm") - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction } NumericTextFieldWithUnit // "Compatible material diameter" @@ -87,7 +94,9 @@ Item labelWidth: base.labelWidth controlWidth: base.controlWidth unitText: catalog.i18nc("@label", "mm") - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction + // Other modules won't automatically respond after the user changes the value, so we need to force it. + afterOnEditingFinishedFunction: updateMaterialDiameter } NumericTextFieldWithUnit // "Nozzle offset X" @@ -101,7 +110,7 @@ Item labelWidth: base.labelWidth controlWidth: base.controlWidth unitText: catalog.i18nc("@label", "mm") - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction } NumericTextFieldWithUnit // "Nozzle offset Y" @@ -115,7 +124,7 @@ Item labelWidth: base.labelWidth controlWidth: base.controlWidth unitText: catalog.i18nc("@label", "mm") - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction } NumericTextFieldWithUnit // "Cooling Fan Number" @@ -129,7 +138,7 @@ Item labelWidth: base.labelWidth controlWidth: base.controlWidth unitText: "" - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction } } } diff --git a/resources/qml/WelcomePages/MachineSettingsPrinterTab.qml b/resources/qml/WelcomePages/MachineSettingsPrinterTab.qml index b22f4a411b..6971ab74db 100644 --- a/resources/qml/WelcomePages/MachineSettingsPrinterTab.qml +++ b/resources/qml/WelcomePages/MachineSettingsPrinterTab.qml @@ -30,6 +30,10 @@ Item property int columnSpacing: 3 property int propertyStoreIndex: 5 // definition_changes + property string machineStackId: Cura.MachineManager.activeMachineId + + property var forceUpdateFunction: CuraApplication.getMachineSettingsManager().forceUpdate + Item { id: upperBlock @@ -61,7 +65,7 @@ Item NumericTextFieldWithUnit // "X (Width)" { id: machineXWidthField - containerStackId: Cura.MachineManager.activeMachineId + containerStackId: machineStackId settingKey: "machine_width" settingStoreIndex: propertyStoreIndex labelText: catalog.i18nc("@label", "X (Width)") @@ -69,13 +73,13 @@ Item labelWidth: base.labelWidth controlWidth: base.controlWidth unitText: catalog.i18nc("@label", "mm") - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction } NumericTextFieldWithUnit // "Y (Depth)" { id: machineYDepthField - containerStackId: Cura.MachineManager.activeMachineId + containerStackId: machineStackId settingKey: "machine_depth" settingStoreIndex: propertyStoreIndex labelText: catalog.i18nc("@label", "Y (Depth)") @@ -83,13 +87,13 @@ Item labelWidth: base.labelWidth controlWidth: base.controlWidth unitText: catalog.i18nc("@label", "mm") - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction } NumericTextFieldWithUnit // "Z (Height)" { id: machineZHeightField - containerStackId: Cura.MachineManager.activeMachineId + containerStackId: machineStackId settingKey: "machine_height" settingStoreIndex: propertyStoreIndex labelText: catalog.i18nc("@label", "Z (Height)") @@ -97,58 +101,61 @@ Item labelWidth: base.labelWidth controlWidth: base.controlWidth unitText: catalog.i18nc("@label", "mm") - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction } ComboBoxWithOptions // "Build plate shape" { id: buildPlateShapeComboBox - containerStackId: Cura.MachineManager.activeMachineId + containerStackId: machineStackId settingKey: "machine_shape" settingStoreIndex: propertyStoreIndex labelText: catalog.i18nc("@label", "Build plate shape") labelFont: base.labelFont labelWidth: base.labelWidth controlWidth: base.controlWidth - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction } SimpleCheckBox // "Origin at center" { id: originAtCenterCheckBox - containerStackId: Cura.MachineManager.activeMachineId + containerStackId: machineStackId settingKey: "machine_center_is_zero" settingStoreIndex: propertyStoreIndex labelText: catalog.i18nc("@label", "Origin at center") labelFont: base.labelFont labelWidth: base.labelWidth - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction } SimpleCheckBox // "Heated bed" { id: heatedBedCheckBox - containerStackId: Cura.MachineManager.activeMachineId + containerStackId: machineStackId settingKey: "machine_heated_bed" settingStoreIndex: propertyStoreIndex labelText: catalog.i18nc("@label", "Heated bed") labelFont: base.labelFont labelWidth: base.labelWidth - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction } ComboBoxWithOptions // "G-code flavor" { id: gcodeFlavorComboBox - containerStackId: Cura.MachineManager.activeMachineId + containerStackId: machineStackId settingKey: "machine_gcode_flavor" settingStoreIndex: propertyStoreIndex labelText: catalog.i18nc("@label", "G-code flavor") labelFont: base.labelFont labelWidth: base.labelWidth controlWidth: base.controlWidth - // TODO: add forceUpdateOnChangeFunction: - // TODO: add afterOnActivate: manager.updateHasMaterialsMetadata + forceUpdateOnChangeFunction: forceUpdateFunction + // FIXME(Lipu): better document this. + // This has something to do with UM2 and UM2+ regarding "has_material" and the gcode flavor settings. + // I don't remember exactly what. + afterOnEditingFinishedFunction: CuraApplication.getMachineSettingsManager().updateHasMaterialsMetadata } } @@ -185,7 +192,7 @@ Item axisName: "x" axisMinOrMax: "min" - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction } PrintHeadMinMaxTextField // "Y min" @@ -203,7 +210,7 @@ Item axisName: "y" axisMinOrMax: "min" - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction } PrintHeadMinMaxTextField // "X max" @@ -221,14 +228,14 @@ Item axisName: "x" axisMinOrMax: "max" - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction } PrintHeadMinMaxTextField // "Y max" { id: machineYMaxField - containerStackId: Cura.MachineManager.activeMachineId + containerStackId: machineStackId settingKey: "machine_head_with_fans_polygon" settingStoreIndex: propertyStoreIndex @@ -241,13 +248,13 @@ Item axisName: "y" axisMinOrMax: "max" - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction } NumericTextFieldWithUnit // "Gantry Height" { id: machineGantryHeightField - containerStackId: Cura.MachineManager.activeMachineId + containerStackId: machineStackId settingKey: "gantry_height" settingStoreIndex: propertyStoreIndex labelText: catalog.i18nc("@label", "Gantry Height") @@ -255,21 +262,24 @@ Item labelWidth: base.labelWidth controlWidth: base.controlWidth unitText: catalog.i18nc("@label", "mm") - // TODO: add forceUpdateOnChangeFunction: + forceUpdateOnChangeFunction: forceUpdateFunction } ComboBoxWithOptions // "Number of Extruders" { id: numberOfExtrudersComboBox - containerStackId: Cura.MachineManager.activeMachineId + containerStackId: machineStackId settingKey: "machine_extruder_count" settingStoreIndex: propertyStoreIndex labelText: catalog.i18nc("@label", "Number of Extruders") labelFont: base.labelFont labelWidth: base.labelWidth controlWidth: base.controlWidth - // TODO: add forceUpdateOnChangeFunction: - // TODO: add afterOnActivate: manager.updateHasMaterialsMetadata + forceUpdateOnChangeFunction: forceUpdateFunction + // FIXME(Lipu): better document this. + // This has something to do with UM2 and UM2+ regarding "has_material" and the gcode flavor settings. + // I don't remember exactly what. + afterOnEditingFinishedFunction: CuraApplication.getMachineSettingsManager().updateHasMaterialsMetadata optionModel: ListModel { @@ -305,7 +315,7 @@ Item width: base.columnWidth - UM.Theme.getSize("default_margin").width labelText: catalog.i18nc("@title:label", "Start G-code") - containerStackId: Cura.MachineManager.activeMachineId + containerStackId: machineStackId settingKey: "machine_start_gcode" settingStoreIndex: propertyStoreIndex } @@ -319,7 +329,7 @@ Item width: base.columnWidth - UM.Theme.getSize("default_margin").width labelText: catalog.i18nc("@title:label", "End G-code") - containerStackId: Cura.MachineManager.activeMachineId + containerStackId: machineStackId settingKey: "machine_end_gcode" settingStoreIndex: propertyStoreIndex } diff --git a/resources/qml/WelcomePages/TestContent.qml b/resources/qml/WelcomePages/TestContent.qml index f9488665c0..6071f965b2 100644 --- a/resources/qml/WelcomePages/TestContent.qml +++ b/resources/qml/WelcomePages/TestContent.qml @@ -26,11 +26,31 @@ Item property var extrudersModel: Cura.ExtrudersModel {} - onVisibleChanged: + // If we create a CuraTabButton for "Printer" and use Repeater for extruders, for some reason, once the component + // finishes it will automatically change "currentIndex = 1", and it is VERY difficult to change "currentIndex = 0" + // after that. Using a model and a Repeater to create both "Printer" and extruder CuraTabButtons seem to solve this + // problem. + Connections { - if (visible) + target: extrudersModel + onItemsChanged: tabNameModel.update() + } + + ListModel + { + id: tabNameModel + + Component.onCompleted: update() + + function update() { - tabBar.currentIndex = 0 + clear() + append({ name: catalog.i18nc("@title:tab", "Printer") }) + for (var i = 0; i < extrudersModel.count; i++) + { + const m = extrudersModel.getItem(i) + append({ name: m.name }) + } } } @@ -46,14 +66,9 @@ Item id: tabBar width: parent.width - CuraTabButton - { - text: catalog.i18nc("@title:tab", "Printer") - } - Repeater { - model: extrudersModel + model: tabNameModel delegate: CuraTabButton { text: model.name @@ -83,6 +98,7 @@ Item delegate: MachineSettingsExtruderTab { id: discoverTab + extruderPosition: model.index extruderStackId: model.id } } diff --git a/resources/qml/Widgets/CuraTabButton.qml b/resources/qml/Widgets/CuraTabButton.qml index 40411ce0b8..86d1180abf 100644 --- a/resources/qml/Widgets/CuraTabButton.qml +++ b/resources/qml/Widgets/CuraTabButton.qml @@ -3,7 +3,6 @@ import QtQuick 2.10 import QtQuick.Controls 2.3 -import QtQuick.Layouts 1.3 import UM 1.3 as UM import Cura 1.1 as Cura