diff --git a/cura/Settings/CuraFormulaFunctions.py b/cura/Settings/CuraFormulaFunctions.py index 9ef80bd3d4..a8b416eeb5 100644 --- a/cura/Settings/CuraFormulaFunctions.py +++ b/cura/Settings/CuraFormulaFunctions.py @@ -42,7 +42,14 @@ class CuraFormulaFunctions: try: extruder_stack = global_stack.extruders[str(extruder_position)] except KeyError: - Logger.log("w", "Value for %s of extruder %s was requested, but that extruder is not available" % (property_key, extruder_position)) + if extruder_position != 0: + Logger.log("w", "Value for %s of extruder %s was requested, but that extruder is not available. Returning the result form extruder 0 instead" % (property_key, extruder_position)) + # This fixes a very specific fringe case; If a profile was created for a custom printer and one of the + # extruder settings has been set to non zero and the profile is loaded for a machine that has only a single extruder + # it would cause all kinds of issues (and eventually a crash). + # See https://github.com/Ultimaker/Cura/issues/5535 + return self.getValueInExtruder(0, property_key, context) + Logger.log("w", "Value for %s of extruder %s was requested, but that extruder is not available. " % (property_key, extruder_position)) return None value = extruder_stack.getRawProperty(property_key, "value", context = context) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 17a732c4b9..5c05d28739 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -85,7 +85,15 @@ class GlobalStack(CuraContainerStack): # Requesting it from the metadata actually gets them as strings (as that's what you get from serializing). # But we do want them returned as a list of ints (so the rest of the code can directly compare) connection_types = self.getMetaDataEntry("connection_type", "").split(",") - return [int(connection_type) for connection_type in connection_types if connection_type != ""] + result = [] + for connection_type in connection_types: + if connection_type != "": + try: + result.append(int(connection_type)) + except ValueError: + # We got invalid data, probably a None. + pass + return result ## \sa configuredConnectionTypes def addConfiguredConnectionType(self, connection_type: int) -> None: diff --git a/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml b/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml index 795622cf82..72dd6f91a2 100644 --- a/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml +++ b/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml @@ -14,7 +14,7 @@ Rectangle Column { height: childrenRect.height + 2 * padding - spacing: UM.Theme.getSize("toolbox_showcase_spacing").width + spacing: UM.Theme.getSize("default_margin").width width: parent.width padding: UM.Theme.getSize("wide_margin").height Label diff --git a/plugins/Toolbox/resources/qml/ToolboxTabButton.qml b/plugins/Toolbox/resources/qml/ToolboxTabButton.qml index cde87c5bc4..7a7d2be48a 100644 --- a/plugins/Toolbox/resources/qml/ToolboxTabButton.qml +++ b/plugins/Toolbox/resources/qml/ToolboxTabButton.qml @@ -61,7 +61,7 @@ Button { target: label font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("toolbox_header_button_text_active") + color: UM.Theme.getColor("action_button_text") } } ] diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py index d98cdded9c..688538522e 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py @@ -145,9 +145,17 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice): ## Checks whether the given network key is found in the cloud's host name def matchesNetworkKey(self, network_key: str) -> bool: - # A network key looks like "ultimakersystem-aabbccdd0011._ultimaker._tcp.local." + # Typically, a network key looks like "ultimakersystem-aabbccdd0011._ultimaker._tcp.local." # the host name should then be "ultimakersystem-aabbccdd0011" - return network_key.startswith(self.clusterData.host_name) + if network_key.startswith(self.clusterData.host_name): + return True + + # However, for manually added printers, the local IP address is used in lieu of a proper + # network key, so check for that as well + if self.clusterData.host_internal_ip is not None and network_key.find(self.clusterData.host_internal_ip): + return True + + return False ## Set all the interface elements and texts for this output device. def _setInterfaceElements(self) -> None: diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterResponse.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterResponse.py index 9c0853e7c9..48a4d5f031 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterResponse.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterResponse.py @@ -16,13 +16,14 @@ class CloudClusterResponse(BaseCloudModel): # \param status: The status of the cluster authentication (active or inactive). # \param host_version: The firmware version of the cluster host. This is where the Stardust client is running on. def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str, - host_version: Optional[str] = None, **kwargs) -> None: + host_internal_ip: Optional[str] = None, host_version: Optional[str] = None, **kwargs) -> None: self.cluster_id = cluster_id self.host_guid = host_guid self.host_name = host_name self.status = status self.is_online = is_online self.host_version = host_version + self.host_internal_ip = host_internal_ip super().__init__(**kwargs) # Validates the model, raising an exception if the model is invalid. diff --git a/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py index ac0b980a32..5c1948b977 100644 --- a/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py @@ -77,13 +77,15 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): self.setIconName("print") - if PluginRegistry.getInstance() is not None: + self._output_controller = LegacyUM3PrinterOutputController(self) + + def _createMonitorViewFromQML(self) -> None: + if self._monitor_view_qml_path is None and PluginRegistry.getInstance() is not None: self._monitor_view_qml_path = os.path.join( PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"), "resources", "qml", "MonitorStage.qml" ) - - self._output_controller = LegacyUM3PrinterOutputController(self) + super()._createMonitorViewFromQML() def _onAuthenticationStateChanged(self): # We only accept commands if we are authenticated. diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDeviceManager.py index e24ca1694e..869b39440c 100644 --- a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDeviceManager.py @@ -7,6 +7,7 @@ from UM.OutputDevice.OutputDeviceManager import OutputDeviceManager from cura.UltimakerCloudAuthentication import CuraCloudAPIRoot from ...src.Cloud import CloudApiClient from ...src.Cloud import CloudOutputDeviceManager +from ...src.Cloud.Models.CloudClusterResponse import CloudClusterResponse from .Fixtures import parseFixture, readFixture from .NetworkManagerMock import NetworkManagerMock, FakeSignal @@ -55,7 +56,9 @@ class TestCloudOutputDeviceManager(TestCase): devices = self.device_manager.getOutputDevices() # TODO: Check active device - response_clusters = self.clusters_response.get("data", []) + response_clusters = [] + for cluster in self.clusters_response.get("data", []): + response_clusters.append(CloudClusterResponse(**cluster).toDict()) manager_clusters = sorted([device.clusterData.toDict() for device in self.manager._remote_clusters.values()], key=lambda cluster: cluster['cluster_id'], reverse=True) self.assertEqual(response_clusters, manager_clusters) @@ -97,7 +100,7 @@ class TestCloudOutputDeviceManager(TestCase): self.assertTrue(self.device_manager.getOutputDevice(cluster1["cluster_id"]).isConnected()) self.assertIsNone(self.device_manager.getOutputDevice(cluster2["cluster_id"])) - self.assertEquals([], active_machine_mock.setMetaDataEntry.mock_calls) + self.assertEqual([], active_machine_mock.setMetaDataEntry.mock_calls) def test_device_connects_by_network_key(self): active_machine_mock = self.app.getGlobalContainerStack.return_value diff --git a/plugins/UM3NetworkPrinting/tests/TestSendMaterialJob.py b/plugins/UM3NetworkPrinting/tests/TestSendMaterialJob.py index 952d38dcf4..2cab110861 100644 --- a/plugins/UM3NetworkPrinting/tests/TestSendMaterialJob.py +++ b/plugins/UM3NetworkPrinting/tests/TestSendMaterialJob.py @@ -208,7 +208,7 @@ class TestSendMaterialJob(TestCase): self.assertEqual(1, device_mock.createFormPart.call_count) self.assertEqual(1, device_mock.postFormWithParts.call_count) - self.assertEquals( + self.assertEqual( [call.createFormPart("name=\"file\"; filename=\"generic_pla_white.xml.fdm_material\"", ""), call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)], device_mock.method_calls) @@ -238,7 +238,7 @@ class TestSendMaterialJob(TestCase): self.assertEqual(1, device_mock.createFormPart.call_count) self.assertEqual(1, device_mock.postFormWithParts.call_count) - self.assertEquals( + self.assertEqual( [call.createFormPart("name=\"file\"; filename=\"generic_pla_white.xml.fdm_material\"", ""), call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)], device_mock.method_calls) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 45ff92fea8..cb28a439ee 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -4092,7 +4092,7 @@ "description": "Amount of offset applied to all support polygons in each layer. Positive values can smooth out the support areas and result in more sturdy support.", "unit": "mm", "type": "float", - "default_value": 0.2, + "default_value": 0, "limit_to_extruder": "support_infill_extruder_nr", "minimum_value_warning": "-1 * machine_nozzle_size", "maximum_value_warning": "10 * machine_nozzle_size", diff --git a/resources/qml/ActionPanel/OutputProcessWidget.qml b/resources/qml/ActionPanel/OutputProcessWidget.qml index f4505c620c..7e76768cb4 100644 --- a/resources/qml/ActionPanel/OutputProcessWidget.qml +++ b/resources/qml/ActionPanel/OutputProcessWidget.qml @@ -68,6 +68,7 @@ Column property var printMaterialLengths: PrintInformation.materialLengths property var printMaterialWeights: PrintInformation.materialWeights + property var printMaterialCosts: PrintInformation.materialCosts text: { @@ -77,6 +78,7 @@ Column } var totalLengths = 0 var totalWeights = 0 + var totalCosts = 0.0 if (printMaterialLengths) { for(var index = 0; index < printMaterialLengths.length; index++) @@ -85,9 +87,16 @@ Column { totalLengths += printMaterialLengths[index] totalWeights += Math.round(printMaterialWeights[index]) + var cost = printMaterialCosts[index] == undefined ? 0.0 : printMaterialCosts[index] + totalCosts += cost } } } + if(totalCosts > 0) + { + var costString = "%1 %2".arg(UM.Preferences.getValue("cura/currency")).arg(totalCosts.toFixed(2)) + return totalWeights + "g · " + totalLengths.toFixed(2) + "m · " + costString + } return totalWeights + "g · " + totalLengths.toFixed(2) + "m" } source: UM.Theme.getIcon("spool") diff --git a/resources/themes/cura-light/styles.qml b/resources/themes/cura-light/styles.qml index 121f604362..2cf3b0ed58 100755 --- a/resources/themes/cura-light/styles.qml +++ b/resources/themes/cura-light/styles.qml @@ -103,33 +103,29 @@ QtObject // This property will be back-propagated when the width of the label is calculated property var buttonWidth: 0 - background: Item + background: Rectangle { + id: backgroundRectangle implicitHeight: control.height implicitWidth: buttonWidth - Rectangle - { - id: buttonFace - implicitHeight: parent.height - implicitWidth: parent.width - radius: UM.Theme.getSize("action_button_radius").width + radius: UM.Theme.getSize("action_button_radius").width - color: + color: + { + if (control.checked) { - if (control.checked) + return UM.Theme.getColor("main_window_header_button_background_active") + } + else + { + if (control.hovered) { - return UM.Theme.getColor("main_window_header_button_background_active") - } - else - { - if (control.hovered) - { - return UM.Theme.getColor("main_window_header_button_background_hovered") - } - return UM.Theme.getColor("main_window_header_button_background_inactive") + return UM.Theme.getColor("main_window_header_button_background_hovered") } + return UM.Theme.getColor("main_window_header_button_background_inactive") } } + } label: Item @@ -168,6 +164,8 @@ QtObject buttonWidth = width } } + + } } @@ -398,73 +396,6 @@ QtObject } } - // Combobox with items with colored rectangles - property Component combobox_color: Component - { - - ComboBoxStyle - { - - background: Rectangle - { - color: !enabled ? UM.Theme.getColor("setting_control_disabled") : control._hovered ? UM.Theme.getColor("setting_control_highlight") : UM.Theme.getColor("setting_control") - border.width: UM.Theme.getSize("default_lining").width - border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : control._hovered ? UM.Theme.getColor("setting_control_border_highlight") : UM.Theme.getColor("setting_control_border") - radius: UM.Theme.getSize("setting_control_radius").width - } - - label: Item - { - Label - { - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("default_lining").width - anchors.right: swatch.left - anchors.rightMargin: UM.Theme.getSize("default_lining").width - anchors.verticalCenter: parent.verticalCenter - - text: control.currentText - font: UM.Theme.getFont("default") - color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text") - - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - - UM.RecolorImage - { - id: swatch - height: Math.round(control.height / 2) - width: height - anchors.right: downArrow.left - anchors.verticalCenter: parent.verticalCenter - anchors.rightMargin: UM.Theme.getSize("default_margin").width - - sourceSize.width: width - sourceSize.height: height - source: UM.Theme.getIcon("extruder_button") - color: (control.color_override !== "") ? control.color_override : control.color - } - - UM.RecolorImage - { - id: downArrow - anchors.right: parent.right - anchors.rightMargin: UM.Theme.getSize("default_lining").width * 2 - anchors.verticalCenter: parent.verticalCenter - - source: UM.Theme.getIcon("arrow_bottom") - width: UM.Theme.getSize("standard_arrow").width - height: UM.Theme.getSize("standard_arrow").height - sourceSize.width: width + 5 * screenScaleFactor - sourceSize.height: width + 5 * screenScaleFactor - - color: UM.Theme.getColor("setting_control_button") - } - } - } - } - property Component checkbox: Component { CheckBoxStyle diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 1cccb288c0..f287e60310 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -393,7 +393,6 @@ "printer_config_matched": [50, 130, 255, 255], "printer_config_mismatch": [127, 127, 127, 255], - "toolbox_header_button_text_active": [0, 0, 0, 255], "toolbox_header_button_text_inactive": [0, 0, 0, 255], "favorites_header_bar": [245, 245, 245, 255], @@ -592,10 +591,8 @@ "toolbox_thumbnail_large": [12.0, 10.0], "toolbox_footer": [1.0, 4.5], "toolbox_footer_button": [8.0, 2.5], - "toolbox_showcase_spacing": [1.0, 1.0], "toolbox_header_tab": [8.0, 4.0], "toolbox_detail_header": [1.0, 14.0], - "toolbox_detail_tile": [1.0, 8.0], "toolbox_back_column": [6.0, 1.0], "toolbox_back_button": [6.0, 2.0], "toolbox_installed_tile": [1.0, 8.0], @@ -603,7 +600,6 @@ "toolbox_heading_label": [1.0, 3.8], "toolbox_header": [1.0, 4.0], "toolbox_header_highlight": [0.25, 0.25], - "toolbox_progress_bar": [8.0, 0.5], "toolbox_chart_row": [1.0, 2.0], "toolbox_action_button": [8.0, 2.5], "toolbox_loader": [2.0, 2.0],