From e4930d3c3beae0885211cbc0e54f904654d895bb Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 28 Feb 2020 13:53:43 +0100 Subject: [PATCH 01/13] Add missing typing --- cura/Machines/Models/QualitySettingsModel.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/cura/Machines/Models/QualitySettingsModel.py b/cura/Machines/Models/QualitySettingsModel.py index 6835ffb68f..8a956263e7 100644 --- a/cura/Machines/Models/QualitySettingsModel.py +++ b/cura/Machines/Models/QualitySettingsModel.py @@ -2,6 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt +from typing import Set import cura.CuraApplication from UM.Logger import Logger @@ -23,7 +24,7 @@ class QualitySettingsModel(ListModel): GLOBAL_STACK_POSITION = -1 - def __init__(self, parent = None): + def __init__(self, parent = None) -> None: super().__init__(parent = parent) self.addRoleName(self.KeyRole, "key") @@ -38,7 +39,9 @@ class QualitySettingsModel(ListModel): self._application = cura.CuraApplication.CuraApplication.getInstance() self._application.getMachineManager().activeStackChanged.connect(self._update) - self._selected_position = self.GLOBAL_STACK_POSITION #Must be either GLOBAL_STACK_POSITION or an extruder position (0, 1, etc.) + # Must be either GLOBAL_STACK_POSITION or an extruder position (0, 1, etc.) + self._selected_position = self.GLOBAL_STACK_POSITION + self._selected_quality_item = None # The selected quality in the quality management page self._i18n_catalog = None @@ -47,14 +50,14 @@ class QualitySettingsModel(ListModel): selectedPositionChanged = pyqtSignal() selectedQualityItemChanged = pyqtSignal() - def setSelectedPosition(self, selected_position): + def setSelectedPosition(self, selected_position: int) -> None: if selected_position != self._selected_position: self._selected_position = selected_position self.selectedPositionChanged.emit() self._update() @pyqtProperty(int, fset = setSelectedPosition, notify = selectedPositionChanged) - def selectedPosition(self): + def selectedPosition(self) -> int: return self._selected_position def setSelectedQualityItem(self, selected_quality_item): @@ -67,7 +70,7 @@ class QualitySettingsModel(ListModel): def selectedQualityItem(self): return self._selected_quality_item - def _update(self): + def _update(self) -> None: Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) if not self._selected_quality_item: @@ -83,7 +86,7 @@ class QualitySettingsModel(ListModel): quality_changes_group = self._selected_quality_item["quality_changes_group"] quality_node = None - settings_keys = set() + settings_keys = set() # type: Set[str] if quality_group: if self._selected_position == self.GLOBAL_STACK_POSITION: quality_node = quality_group.node_for_global From 68d70734ee77c196f8a268cb66523a0d7ea018e4 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 28 Feb 2020 13:56:16 +0100 Subject: [PATCH 02/13] Skip nodes in modelchecker without active extruder Fixes CURA-5S --- plugins/ModelChecker/ModelChecker.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index 0afed28f19..00e87139d5 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -73,6 +73,8 @@ class ModelChecker(QObject, Extension): # Check node material shrinkage and bounding box size for node in self.sliceableNodes(): node_extruder_position = node.callDecoration("getActiveExtruderPosition") + if node_extruder_position is None: + continue # This function can be triggered in the middle of a machine change, so do not proceed if the machine change # has not done yet. From 0ccf45731462678224b85908aff99294fcbf7a07 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 28 Feb 2020 14:25:50 +0100 Subject: [PATCH 03/13] Split up an overly long call into shorter readable bits --- cura/Settings/MachineManager.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 25152b3d5b..9bbf95a996 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1507,7 +1507,13 @@ class MachineManager(QObject): if quality_id == empty_quality_container.getId(): extruder.intent = empty_intent_container continue - quality_node = container_tree.machines[definition_id].variants[variant_name].materials[material_base_file].qualities[quality_id] + + # Yes, we can find this in a single line of code. This makes it easier to read and it has the benefit + # that it doesn't lump key errors together for the crashlogs + machine_node = container_tree.machines[definition_id] + variant_node = machine_node.variants[variant_name] + material_node = variant_node.materials[material_base_file] + quality_node = material_node.qualities[quality_id] for intent_node in quality_node.intents.values(): if intent_node.intent_category == intent_category: # Found an intent with the correct category. From 3cc7382c12570aa56e05816cfa42732b4ad14b98 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 28 Feb 2020 14:29:26 +0100 Subject: [PATCH 04/13] Remove some unused code from TestCuraStackBuilder --- tests/Settings/TestCuraStackBuilder.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/Settings/TestCuraStackBuilder.py b/tests/Settings/TestCuraStackBuilder.py index aebde3406f..6bd19a0d30 100644 --- a/tests/Settings/TestCuraStackBuilder.py +++ b/tests/Settings/TestCuraStackBuilder.py @@ -6,6 +6,7 @@ from UM.Settings.InstanceContainer import InstanceContainer from cura.Machines.QualityGroup import QualityGroup from cura.Settings.CuraStackBuilder import CuraStackBuilder + @pytest.fixture def global_variant(): container = InstanceContainer(container_id="global_variant") @@ -13,6 +14,7 @@ def global_variant(): return container + @pytest.fixture def material_instance_container(): container = InstanceContainer(container_id="material container") @@ -20,6 +22,7 @@ def material_instance_container(): return container + @pytest.fixture def quality_container(): container = InstanceContainer(container_id="quality container") @@ -54,16 +57,12 @@ def test_createMachineWithUnknownDefinition(application, container_registry): def test_createMachine(application, container_registry, definition_container, global_variant, material_instance_container, quality_container, intent_container, quality_changes_container): - variant_manager = MagicMock(name = "Variant Manager") - quality_manager = MagicMock(name = "Quality Manager") global_variant_node = MagicMock(name = "global variant node") global_variant_node.container = global_variant - variant_manager.getDefaultVariantNode = MagicMock(return_value = global_variant_node) quality_group = QualityGroup(name = "zomg", quality_type = "normal") quality_group.node_for_global = MagicMock(name = "Node for global") quality_group.node_for_global.container = quality_container - quality_manager.getQualityGroups = MagicMock(return_value = {"normal": quality_group}) application.getContainerRegistry = MagicMock(return_value=container_registry) application.empty_material_container = material_instance_container From f4d1d5d93637dfea43d59cb5588b351b1681e244 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 28 Feb 2020 15:06:11 +0100 Subject: [PATCH 05/13] Add a check that fixes wrong variants upon setting an active machine --- cura/Settings/MachineManager.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 9bbf95a996..b70cdfd1ec 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -320,8 +320,19 @@ class MachineManager(QObject): # This signal might not have been emitted yet (if it didn't change) but we still want the models to update that depend on it because we changed the contents of the containers too. extruder_manager.activeExtruderChanged.emit() + # Validate if the machine has the correct variants + # It can happen that a variant is empty, even though the machine has variants. This will ensure that that + # that situation will be fixed (and not occur again, since it switches it out to the preferred variant instead!) + machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()] + for extruder in self._global_container_stack.extruderList: + variant_name = self._global_container_stack.variant.getName() + variant_node = machine_node.variants.get(variant_name) + if variant_node is None: + Logger.log("w", "An extruder has an unknown variant, switching it to the preferred variant") + self.setVariantByName(extruder.getMetaDataEntry("position"), machine_node.preferred_variant_name) self.__emitChangedSignals() + ## Given a definition id, return the machine with this id. # Optional: add a list of keys and values to filter the list of machines with the given definition id # \param definition_id \type{str} definition id that needs to look for From 27c6cb4c1ebfec4f561b8f95926872e361dd2941 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 28 Feb 2020 16:31:28 +0100 Subject: [PATCH 06/13] Prevent max recursion for convex hull calculation fixes CURA-3W --- cura/Scene/ConvexHullDecorator.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cura/Scene/ConvexHullDecorator.py b/cura/Scene/ConvexHullDecorator.py index 2a160f6069..b5f5fb4540 100644 --- a/cura/Scene/ConvexHullDecorator.py +++ b/cura/Scene/ConvexHullDecorator.py @@ -36,8 +36,10 @@ class ConvexHullDecorator(SceneNodeDecorator): # Make sure the timer is created on the main thread self._recompute_convex_hull_timer = None # type: Optional[QTimer] + self._timer_scheduled_to_be_created = False from cura.CuraApplication import CuraApplication if CuraApplication.getInstance() is not None: + self._timer_scheduled_to_be_created = True CuraApplication.getInstance().callLater(self.createRecomputeConvexHullTimer) self._raft_thickness = 0.0 @@ -171,7 +173,12 @@ class ConvexHullDecorator(SceneNodeDecorator): if self._recompute_convex_hull_timer is not None: self._recompute_convex_hull_timer.start() else: - self.recomputeConvexHull() + from cura.CuraApplication import CuraApplication + if not self._timer_scheduled_to_be_created: + # The timer is not created and we never scheduled it. Time to create it now! + CuraApplication.getInstance().callLater(self.createRecomputeConvexHullTimer) + # Now we know for sure that the timer has been scheduled for creation, so we can try this again. + CuraApplication.getInstance().callLater(self.recomputeConvexHullDelayed) def recomputeConvexHull(self) -> None: controller = Application.getInstance().getController() From 670a10695777f5d6db1369666a67e5232cace6ee Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 28 Feb 2020 17:03:38 +0100 Subject: [PATCH 07/13] Be a bit more gracefull when adding a machine failed This seems to happen for people that manually add machines to cura but mess something up when copying the files. CURA-3X --- cura/Settings/CuraStackBuilder.py | 5 ++++- cura/Settings/MachineManager.py | 8 +++++--- .../qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml | 7 ++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index 61a04e1be6..c8287696ae 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -58,7 +58,10 @@ class CuraStackBuilder: # Create ExtruderStacks extruder_dict = machine_definition.getMetaDataEntry("machine_extruder_trains") for position in extruder_dict: - cls.createExtruderStackWithDefaultSetup(new_global_stack, position) + try: + cls.createExtruderStackWithDefaultSetup(new_global_stack, position) + except IndexError: + return None for new_extruder in new_global_stack.extruders.values(): # Only register the extruders if we're sure that all of them are correct. registry.addContainer(new_extruder) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index b70cdfd1ec..6b1460b17b 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -347,9 +347,9 @@ class MachineManager(QObject): return cast(GlobalStack, machine) return None - @pyqtSlot(str) - @pyqtSlot(str, str) - def addMachine(self, definition_id: str, name: Optional[str] = None) -> None: + @pyqtSlot(str, result=bool) + @pyqtSlot(str, str, result = bool) + def addMachine(self, definition_id: str, name: Optional[str] = None) -> bool: Logger.log("i", "Trying to add a machine with the definition id [%s]", definition_id) if name is None: definitions = CuraContainerRegistry.getInstance().findDefinitionContainers(id = definition_id) @@ -364,6 +364,8 @@ class MachineManager(QObject): self.setActiveMachine(new_stack.getId()) else: Logger.log("w", "Failed creating a new machine!") + return False + return True def _checkStacksHaveErrors(self) -> bool: time_start = time.time() diff --git a/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml b/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml index 81dd345f3f..e3018a6825 100644 --- a/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml +++ b/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml @@ -151,9 +151,10 @@ Item // Create a local printer const localPrinterItem = addLocalPrinterDropDown.contentItem.currentItem const printerName = addLocalPrinterDropDown.contentItem.printerName - Cura.MachineManager.addMachine(localPrinterItem.id, printerName) - - base.showNextPage() + if(Cura.MachineManager.addMachine(localPrinterItem.id, printerName)) + { + base.showNextPage() + } } } } From 7c70ace8f0b08a3d1e896901e53b4ab6a5d565f5 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 28 Feb 2020 17:06:57 +0100 Subject: [PATCH 08/13] Prevent crash when the start_idx could not be found CURA-59 --- cura/Arranging/Arrange.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cura/Arranging/Arrange.py b/cura/Arranging/Arrange.py index c0aca9a893..a99e747c48 100644 --- a/cura/Arranging/Arrange.py +++ b/cura/Arranging/Arrange.py @@ -173,7 +173,10 @@ class Arrange: def bestSpot(self, shape_arr, start_prio = 0, step = 1): start_idx_list = numpy.where(self._priority_unique_values == start_prio) if start_idx_list: - start_idx = start_idx_list[0][0] + try: + start_idx = start_idx_list[0][0] + except IndexError: + start_idx = 0 else: start_idx = 0 for priority in self._priority_unique_values[start_idx::step]: From 0f662da2c39d4675dd739d01d957b2ff6cccd6e2 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 28 Feb 2020 22:29:20 +0100 Subject: [PATCH 09/13] Show the number of active postprocessing scripts --- .../PostProcessingPlugin.qml | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.qml b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml index cd8303d1d3..61b172c6bc 100644 --- a/plugins/PostProcessingPlugin/PostProcessingPlugin.qml +++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml @@ -484,15 +484,36 @@ UM.Dialog onClicked: dialog.accept() } - Cura.SecondaryButton + Item { objectName: "postProcessingSaveAreaButton" visible: activeScriptsList.count > 0 height: UM.Theme.getSize("action_button").height width: height - tooltip: catalog.i18nc("@info:tooltip", "Change active post-processing scripts") - onClicked: dialog.show() - iconSource: "postprocessing.svg" - fixedWidthMode: true + + Cura.SecondaryButton + { + height: UM.Theme.getSize("action_button").height + tooltip: catalog.i18nc("@info:tooltip", "Change active post-processing scripts") + toolTipContentAlignment: Cura.ToolTip.ContentAlignment.AlignLeft + onClicked: dialog.show() + iconSource: "postprocessing.svg" + fixedWidthMode: false + } + + Cura.NotificationIcon + { + id: activeScriptCountIcon + visible: activeScriptsList.count > 0 + anchors + { + top: parent.top + right: parent.right + rightMargin: (-0.5 * width) | 0 + topMargin: (-0.5 * height) | 0 + } + + labelText: activeScriptsList.count + } } } \ No newline at end of file From ddbd843d917d6681c000c4eb71fb82564e00c124 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 28 Feb 2020 22:31:34 +0100 Subject: [PATCH 10/13] Fix showing tooltips for actionbuttons that have a tooltip text set --- resources/qml/ActionButton.qml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/resources/qml/ActionButton.qml b/resources/qml/ActionButton.qml index bb1abcf57e..04b57fc9d9 100644 --- a/resources/qml/ActionButton.qml +++ b/resources/qml/ActionButton.qml @@ -133,7 +133,18 @@ Button Cura.ToolTip { id: tooltip - visible: button.hovered && buttonTextMetrics.elidedText != buttonText.text + visible: + { + if (!button.hovered) + { + return false; + } + if (tooltipText == button.text) + { + return buttonTextMetrics.elidedText != buttonText.text; + } + return true; + } } BusyIndicator From 00f406b5dd8ad9eb02281b4ffec880a6cbd6cc05 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 28 Feb 2020 22:51:37 +0100 Subject: [PATCH 11/13] Add list of active scripts to tooltip --- .../PostProcessingPlugin.qml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.qml b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml index 61b172c6bc..f94c0a1cca 100644 --- a/plugins/PostProcessingPlugin/PostProcessingPlugin.qml +++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml @@ -494,7 +494,24 @@ UM.Dialog Cura.SecondaryButton { height: UM.Theme.getSize("action_button").height - tooltip: catalog.i18nc("@info:tooltip", "Change active post-processing scripts") + tooltip: + { + var tipText = catalog.i18nc("@info:tooltip", "Change active post-processing scripts."); + if (activeScriptsList.count > 0) + { + tipText += "

" + catalog.i18ncp("@info:tooltip", + "The following script is active:", + "The following scripts are active:", + activeScriptsList.count + ) + "
    "; + for(var i = 0; i < activeScriptsList.count; i++) + { + tipText += "
  • " + manager.getScriptLabelByKey(manager.scriptList[i]) + "
  • "; + } + tipText += "
"; + } + return tipText + } toolTipContentAlignment: Cura.ToolTip.ContentAlignment.AlignLeft onClicked: dialog.show() iconSource: "postprocessing.svg" From 67af1aa19d0cfeb739a7143d486032e2a4190d44 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 28 Feb 2020 23:00:56 +0100 Subject: [PATCH 12/13] Fix the positioning of multi-line tooltip anchor-points --- resources/qml/ActionButton.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/ActionButton.qml b/resources/qml/ActionButton.qml index 04b57fc9d9..6d28d0ed52 100644 --- a/resources/qml/ActionButton.qml +++ b/resources/qml/ActionButton.qml @@ -145,6 +145,7 @@ Button } return true; } + targetPoint: Qt.point(parent.x, Math.round(parent.y + parent.height / 2)) } BusyIndicator From d501f7730a42e45177fa3cdb89a4cdce1f1bd9a4 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 2 Mar 2020 10:48:47 +0100 Subject: [PATCH 13/13] Show Full name for quit message We're a bit insecure about Cura, so we have to put the company name in front of it ;) --- plugins/Toolbox/resources/qml/components/ToolboxFooter.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Toolbox/resources/qml/components/ToolboxFooter.qml b/plugins/Toolbox/resources/qml/components/ToolboxFooter.qml index 6f46e939ff..7a2728d046 100644 --- a/plugins/Toolbox/resources/qml/components/ToolboxFooter.qml +++ b/plugins/Toolbox/resources/qml/components/ToolboxFooter.qml @@ -42,7 +42,7 @@ Item rightMargin: UM.Theme.getSize("wide_margin").width } height: UM.Theme.getSize("toolbox_footer_button").height - text: catalog.i18nc("@info:button", "Quit Cura") + text: catalog.i18nc("@info:button", "Quit Ultimaker Cura") onClicked: toolbox.restart() }