diff --git a/cura/Machines/MachineNode.py b/cura/Machines/MachineNode.py index e71801fbb1..29968512ce 100644 --- a/cura/Machines/MachineNode.py +++ b/cura/Machines/MachineNode.py @@ -134,6 +134,9 @@ class MachineNode(ContainerNode): groups_by_name[name] = QualityChangesGroup(name, quality_type = quality_changes["quality_type"], intent_category = quality_changes.get("intent_category", "default"), parent = CuraApplication.getInstance()) + # CURA-6882 + # Custom qualities are always available, even if they are based on the "not supported" profile. + groups_by_name[name].is_available = True elif groups_by_name[name].intent_category == "default": # Intent category should be stored as "default" if everything is default or as the intent if any of the extruder have an actual intent. groups_by_name[name].intent_category = quality_changes.get("intent_category", "default") @@ -142,14 +145,6 @@ class MachineNode(ContainerNode): else: # Global profile. groups_by_name[name].metadata_for_global = quality_changes - quality_groups = self.getQualityGroups(variant_names, material_bases, extruder_enabled) - for quality_changes_group in groups_by_name.values(): - if quality_changes_group.quality_type not in quality_groups: - quality_changes_group.is_available = False - else: - # Quality changes group is available iff the quality group it depends on is available. Irrespective of whether the intent category is available. - quality_changes_group.is_available = quality_groups[quality_changes_group.quality_type].is_available - return list(groups_by_name.values()) ## Gets the preferred global quality node, going by the preferred quality diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py index 6f6f3906b8..0f37d2e3e7 100644 --- a/cura/Machines/Models/MaterialManagementModel.py +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -8,6 +8,7 @@ import uuid # To generate new GUIDs for new materials. from UM.i18n import i18nCatalog from UM.Logger import Logger +from UM.Signal import postponeSignals, CompressTechnique import cura.CuraApplication # Imported like this to prevent circular imports. from cura.Machines.ContainerTree import ContainerTree @@ -73,8 +74,20 @@ class MaterialManagementModel(QObject): def removeMaterial(self, material_node: "MaterialNode") -> None: container_registry = CuraContainerRegistry.getInstance() materials_this_base_file = container_registry.findContainersMetadata(base_file = material_node.base_file) - for material_metadata in materials_this_base_file: - container_registry.removeContainer(material_metadata["id"]) + + # The material containers belonging to the same material file are supposed to work together. This postponeSignals() + # does two things: + # - optimizing the signal emitting. + # - making sure that the signals will only be emitted after all the material containers have been removed. + with postponeSignals(container_registry.containerRemoved, compress = CompressTechnique.CompressPerParameterValue): + # CURA-6886: Some containers may not have been loaded. If remove one material container, its material file + # will be removed. If later we remove a sub-material container which hasn't been loaded previously, it will + # crash because removeContainer() requires to load the container first, but the material file was already + # gone. + for material_metadata in materials_this_base_file: + container_registry.findInstanceContainers(id = material_metadata["id"]) + for material_metadata in materials_this_base_file: + container_registry.removeContainer(material_metadata["id"]) ## Creates a duplicate of a material with the same GUID and base_file # metadata. @@ -142,15 +155,17 @@ class MaterialManagementModel(QObject): # This sort fixes the problem by emitting the most specific containers first. new_containers = sorted(new_containers, key = lambda x: x.getId(), reverse = True) - for container_to_add in new_containers: - container_to_add.setDirty(True) - container_registry.addContainer(container_to_add) + # Optimization. Serving the same purpose as the postponeSignals() in removeMaterial() + with postponeSignals(container_registry.containerAdded, compress=CompressTechnique.CompressPerParameterValue): + for container_to_add in new_containers: + container_to_add.setDirty(True) + container_registry.addContainer(container_to_add) - # If the duplicated material was favorite then the new material should also be added to the favorites. - favorites_set = set(application.getPreferences().getValue("cura/favorite_materials").split(";")) - if base_file in favorites_set: - favorites_set.add(new_base_id) - application.getPreferences().setValue("cura/favorite_materials", ";".join(favorites_set)) + # If the duplicated material was favorite then the new material should also be added to the favorites. + favorites_set = set(application.getPreferences().getValue("cura/favorite_materials").split(";")) + if base_file in favorites_set: + favorites_set.add(new_base_id) + application.getPreferences().setValue("cura/favorite_materials", ";".join(favorites_set)) return new_base_id diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index adaa4309b7..1d30b1753e 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -13,6 +13,7 @@ from cura.Settings.ContainerManager import ContainerManager from cura.Machines.ContainerTree import ContainerTree from cura.Settings.cura_empty_instance_containers import empty_quality_changes_container from cura.Settings.IntentManager import IntentManager +from cura.Machines.Models.MachineModelUtils import fetchLayerHeight from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -295,6 +296,8 @@ class QualityManagementModel(ListModel): if not quality_group.is_available: continue + layer_height = fetchLayerHeight(quality_group) + item = {"name": quality_group.name, "is_read_only": True, "quality_group": quality_group, @@ -302,10 +305,11 @@ class QualityManagementModel(ListModel): "quality_changes_group": None, "intent_category": "default", "section_name": catalog.i18nc("@label", "Default"), + "layer_height": layer_height, # layer_height is only used for sorting } item_list.append(item) - # Sort by quality names - item_list = sorted(item_list, key = lambda x: x["name"].upper()) + # Sort by layer_height for built-in qualities + item_list = sorted(item_list, key = lambda x: x["layer_height"]) # Create intent items (non-default) available_intent_list = IntentManager.getInstance().getCurrentAvailableIntents() diff --git a/cura/Machines/VariantNode.py b/cura/Machines/VariantNode.py index ef8981a3c2..1c5474422e 100644 --- a/cura/Machines/VariantNode.py +++ b/cura/Machines/VariantNode.py @@ -104,6 +104,14 @@ class VariantNode(ContainerNode): def _materialAdded(self, container: ContainerInterface) -> None: if container.getMetaDataEntry("type") != "material": return # Not interested. + if not ContainerRegistry.getInstance().findContainersMetadata(id = container.getId()): + # CURA-6889 + # containerAdded and removed signals may be triggered in the next event cycle. If a container gets added + # and removed in the same event cycle, in the next cycle, the connections should just ignore the signals. + # The check here makes sure that the container in the signal still exists. + Logger.log("d", "Got container added signal for container [%s] but it no longer exists, do nothing.", + container.getId()) + return if not self.machine.has_materials: return # We won't add any materials. material_definition = container.getMetaDataEntry("definition") diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 1fadcf01c5..dec433e8f8 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1514,7 +1514,8 @@ class MachineManager(QObject): if self._global_container_stack is None: return machine_definition_id = self._global_container_stack.definition.id - variant_node = self._variant_manager.getVariantNode(machine_definition_id, variant_name) + machine_node = ContainerTree.getInstance().machines.get(machine_definition_id) + variant_node = machine_node.variants.get(variant_name) self.setVariant(position, variant_node) @pyqtSlot(str, "QVariant") diff --git a/resources/definitions/creality_base.def.json b/resources/definitions/creality_base.def.json index d7e028f31a..7e91fb4989 100644 --- a/resources/definitions/creality_base.def.json +++ b/resources/definitions/creality_base.def.json @@ -125,7 +125,7 @@ "overrides": { "machine_name": { "default_value": "Creawsome Base Printer" }, "machine_start_gcode": { "default_value": "M201 X500.00 Y500.00 Z100.00 E5000.00 ;Setup machine max acceleration\nM203 X500.00 Y500.00 Z10.00 E50.00 ;Setup machine max feedrate\nM204 P500.00 R1000.00 T500.00 ;Setup Print/Retract/Travel acceleration\nM205 X8.00 Y8.00 Z0.40 E5.00 ;Setup Jerk\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\n\nG28 ;Home\n\nG92 E0 ;Reset Extruder\nG1 Z2.0 F3000 ;Move Z Axis up\nG1 X10.1 Y20 Z0.28 F5000.0 ;Move to start position\nG1 X10.1 Y200.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X10.4 Y200.0 Z0.28 F5000.0 ;Move to side a little\nG1 X10.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder\nG1 Z2.0 F3000 ;Move Z Axis up\n" }, - "machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute positionning\n\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\n\nM84 X Y E ;Disable all steppers but Z\n" }, + "machine_end_gcode": { "default_value": "G91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute positionning\n\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\n\nM84 X Y E ;Disable all steppers but Z\n" }, "machine_max_feedrate_x": { "value": 500 }, "machine_max_feedrate_y": { "value": 500 }, diff --git a/resources/definitions/creality_ender5.def.json b/resources/definitions/creality_ender5.def.json index d95f4a1467..c1511884ae 100644 --- a/resources/definitions/creality_ender5.def.json +++ b/resources/definitions/creality_ender5.def.json @@ -4,7 +4,7 @@ "inherits": "creality_base", "overrides": { "machine_name": { "default_value": "Creality Ender-5" }, - "machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute positionning\n\nG1 X0 Y0 ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\n\nM84 X Y E ;Disable all steppers but Z\n" }, + "machine_end_gcode": { "default_value": "G91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute positionning\n\nG1 X0 Y0 ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\n\nM84 X Y E ;Disable all steppers but Z\n" }, "machine_width": { "default_value": 220 }, "machine_depth": { "default_value": 220 }, "machine_height": { "default_value": 300 }, diff --git a/resources/qml/IconWithText.qml b/resources/qml/IconWithText.qml index 24b6dc7fe2..b9fe873b25 100644 --- a/resources/qml/IconWithText.qml +++ b/resources/qml/IconWithText.qml @@ -19,6 +19,7 @@ Item property alias color: label.color property alias text: label.text property alias font: label.font + property alias elide: label.elide property real margin: UM.Theme.getSize("narrow_margin").width // These properties can be used in combination with layouts. diff --git a/resources/qml/Preferences/Materials/MaterialsDetailsPanel.qml b/resources/qml/Preferences/Materials/MaterialsDetailsPanel.qml index b54af103fe..e821dfb955 100644 --- a/resources/qml/Preferences/Materials/MaterialsDetailsPanel.qml +++ b/resources/qml/Preferences/Materials/MaterialsDetailsPanel.qml @@ -64,8 +64,10 @@ Item height: childrenRect.height Label { + width: parent.width text: materialProperties.name font: UM.Theme.getFont("large_bold") + elide: Text.ElideRight } } diff --git a/resources/qml/PrintSetupSelector/Custom/CustomPrintSetup.qml b/resources/qml/PrintSetupSelector/Custom/CustomPrintSetup.qml index 2698089d0c..46297659ff 100644 --- a/resources/qml/PrintSetupSelector/Custom/CustomPrintSetup.qml +++ b/resources/qml/PrintSetupSelector/Custom/CustomPrintSetup.qml @@ -4,6 +4,7 @@ import QtQuick 2.10 import QtQuick.Controls 2.3 import QtQuick.Controls 1.4 as OldControls +import QtQuick.Layouts 1.3 import UM 1.3 as UM import Cura 1.6 as Cura @@ -66,7 +67,6 @@ Item { id: intentSelection onClicked: menu.opened ? menu.close() : menu.open() - text: generateActiveQualityText() anchors.right: parent.right width: UM.Theme.getSize("print_setup_big_item").width @@ -75,18 +75,67 @@ Item baselineOffset: null // If we don't do this, there is a binding loop. WHich is a bit weird, since we override the contentItem anyway... - contentItem: Label + contentItem: RowLayout { - id: textLabel - text: intentSelection.text - font: UM.Theme.getFont("default") - color: UM.Theme.getColor("text") + spacing: 0 anchors.left: parent.left + anchors.right: customisedSettings.left anchors.leftMargin: UM.Theme.getSize("default_margin").width - anchors.verticalCenter: intentSelection.verticalCenter - height: contentHeight - verticalAlignment: Text.AlignVCenter - renderType: Text.NativeRendering + + Label + { + id: textLabel + text: Cura.MachineManager.activeQualityDisplayNameMap["main"] + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + Layout.margins: 0 + Layout.maximumWidth: Math.floor(parent.width * 0.7) // Always leave >= 30% for the rest of the row. + height: contentHeight + verticalAlignment: Text.AlignVCenter + renderType: Text.NativeRendering + elide: Text.ElideRight + } + + Label + { + text: activeQualityDetailText() + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text_detail") + Layout.margins: 0 + Layout.fillWidth: true + + height: contentHeight + verticalAlignment: Text.AlignVCenter + renderType: Text.NativeRendering + elide: Text.ElideRight + + function activeQualityDetailText() + { + var resultMap = Cura.MachineManager.activeQualityDisplayNameMap + var resultSuffix = resultMap["suffix"] + var result = "" + + if (Cura.MachineManager.isActiveQualityExperimental) + { + resultSuffix += " (Experimental)" + } + + if (Cura.MachineManager.isActiveQualitySupported) + { + if (Cura.MachineManager.activeQualityLayerHeight > 0) + { + if (resultSuffix) + { + result += " - " + resultSuffix + } + result += " - " + result += Cura.MachineManager.activeQualityLayerHeight + "mm" + } + } + + return result + } + } } background: Rectangle @@ -98,41 +147,6 @@ Item color: UM.Theme.getColor("main_background") } - function generateActiveQualityText() - { - var resultMap = Cura.MachineManager.activeQualityDisplayNameMap - var resultMain = resultMap["main"] - var resultSuffix = resultMap["suffix"] - var result = "" - - if (Cura.MachineManager.isActiveQualityExperimental) - { - resultSuffix += " (Experimental)" - } - - if (Cura.MachineManager.isActiveQualitySupported) - { - if (Cura.MachineManager.activeQualityLayerHeight > 0) - { - result = resultMain - if (resultSuffix) - { - result += " - " - } - result += "" - if (resultSuffix) - { - result += resultSuffix - } - result += " - " - result += Cura.MachineManager.activeQualityLayerHeight + "mm" - result += "" - } - } - - return result - } - UM.SimpleButton { id: customisedSettings @@ -164,7 +178,6 @@ Item { id: downArrow - source: UM.Theme.getIcon("arrow_bottom") width: UM.Theme.getSize("standard_arrow").width height: UM.Theme.getSize("standard_arrow").height diff --git a/resources/qml/PrintSetupSelector/PrintSetupSelectorHeader.qml b/resources/qml/PrintSetupSelector/PrintSetupSelectorHeader.qml index a23b87fdbe..1a15980693 100644 --- a/resources/qml/PrintSetupSelector/PrintSetupSelectorHeader.qml +++ b/resources/qml/PrintSetupSelector/PrintSetupSelectorHeader.qml @@ -37,6 +37,7 @@ RowLayout return "" } font: UM.Theme.getFont("medium") + elide: Text.ElideMiddle UM.SettingPropertyProvider { diff --git a/tests/Machines/TestVariantNode.py b/tests/Machines/TestVariantNode.py index 71257bd972..4e58069be3 100644 --- a/tests/Machines/TestVariantNode.py +++ b/tests/Machines/TestVariantNode.py @@ -104,10 +104,11 @@ def test_variantNodeInit_excludedMaterial(container_registry, machine_node): def test_materialAdded(container_registry, machine_node, metadata, material_result_list): variant_node = createVariantNode("machine_1", machine_node, container_registry) machine_node.exclude_materials = ["material_3"] - with patch("cura.Machines.VariantNode.MaterialNode"): # We're not testing the material node here, so patch it out. - with patch.dict(metadata_dict, metadata): - mocked_container = createMockedInstanceContainer() - variant_node._materialAdded(mocked_container) + with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value = container_registry)): + with patch("cura.Machines.VariantNode.MaterialNode"): # We're not testing the material node here, so patch it out. + with patch.dict(metadata_dict, metadata): + mocked_container = createMockedInstanceContainer() + variant_node._materialAdded(mocked_container) assert len(material_result_list) == len(variant_node.materials) for name in material_result_list: