From 888fd0382a5b782fcadee0a76c0fb07f0a83d3eb Mon Sep 17 00:00:00 2001 From: Andrew Donaldson Date: Mon, 4 Dec 2017 21:16:30 +1100 Subject: [PATCH 01/48] Add Fedora to test for nvidia driver work around. --- cura_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura_app.py b/cura_app.py index d725bc1200..313b161a7d 100755 --- a/cura_app.py +++ b/cura_app.py @@ -12,7 +12,7 @@ from UM.Platform import Platform #WORKAROUND: GITHUB-88 GITHUB-385 GITHUB-612 if Platform.isLinux(): # Needed for platform.linux_distribution, which is not available on Windows and OSX # For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826 - if platform.linux_distribution()[0] in ("debian", "Ubuntu", "LinuxMint"): # TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix. + if platform.linux_distribution()[0] in ("debian", "Ubuntu", "LinuxMint", "Fedora"): # TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix. import ctypes from ctypes.util import find_library libGL = find_library("GL") From 1bb5b8ff3e77b20938d6eae30938b992fe6f638e Mon Sep 17 00:00:00 2001 From: Guillem Date: Tue, 13 Mar 2018 12:31:03 +0100 Subject: [PATCH 02/48] add Copy all values to all extruders context menu option --- cura/Settings/MachineManager.py | 12 ++++++++++++ resources/qml/Settings/SettingView.qml | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index eb720000bf..557fd97b43 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -550,6 +550,18 @@ class MachineManager(QObject): if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved + ## Copy the value of all settings of the current extruder to all other extruders as well as the global container. + @pyqtSlot() + def copyAllValuesToExtruders(self): + for key in self._active_container_stack.getAllKeys(): + new_value = self._active_container_stack.getProperty(key, "value") + extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())] + + # check in which stack the value has to be replaced + for extruder_stack in extruder_stacks: + if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: + extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved + @pyqtProperty(str, notify = activeVariantChanged) def activeVariantName(self) -> str: if self._active_container_stack: diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 615e66277b..5865ed5182 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -485,6 +485,15 @@ Item onTriggered: Cura.MachineManager.copyValueToExtruders(contextMenu.key) } + MenuItem + { + //: Settings context menu action + text: catalog.i18nc("@action:menu", "Copy all values to all extruders") + visible: machineExtruderCount.properties.value > 1 + enabled: contextMenu.provider != undefined + onTriggered: Cura.MachineManager.copyAllValuesToExtruders() + } + MenuSeparator { visible: machineExtruderCount.properties.value > 1 From d0609e97e441573e32367306c0ba3ba0f7f235eb Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Thu, 15 Mar 2018 11:19:14 +0100 Subject: [PATCH 03/48] CURA-4557 added material_shrinkage_ratio_percentage setting --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 3 ++- resources/definitions/fdmprinter.def.json | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 8b17721794..76227f193a 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -988,7 +988,8 @@ class XmlMaterialProfile(InstanceContainer): "retraction amount": "retraction_amount", "retraction speed": "retraction_speed", "adhesion tendency": "material_adhesion_tendency", - "surface energy": "material_surface_energy" + "surface energy": "material_surface_energy", + "shrinkage ratio percentage": "material_shrinkage_ratio_percentage", } __unmapped_settings = [ "hardware compatible", diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 21ee543333..24699fa95c 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -2097,6 +2097,19 @@ "settable_per_mesh": false, "settable_per_extruder": true }, + "material_shrinkage_ratio_percentage": + { + "label": "Shrinkage Ratio", + "description": "Shrinkage ratio in percentage.", + "unit": "%", + "type": "float", + "default_value": 0, + "minimum_value": "0", + "maximum_value": "100", + "enabled": false, + "settable_per_mesh": false, + "settable_per_extruder": true + }, "material_flow": { "label": "Flow", From 50f9548da015f3bb24bfdf37b502294fbb01eed0 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Thu, 15 Mar 2018 14:33:31 +0100 Subject: [PATCH 04/48] CURA-4557 setting up plugin --- plugins/ModelChecker/ModelChecker.py | 38 ++++++++++++++++++++++++++++ plugins/ModelChecker/__init__.py | 23 +++++++++++++++++ plugins/ModelChecker/plugin.json | 8 ++++++ 3 files changed, 69 insertions(+) create mode 100644 plugins/ModelChecker/ModelChecker.py create mode 100644 plugins/ModelChecker/__init__.py create mode 100644 plugins/ModelChecker/plugin.json diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py new file mode 100644 index 0000000000..dedfc86938 --- /dev/null +++ b/plugins/ModelChecker/ModelChecker.py @@ -0,0 +1,38 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from PyQt5.QtCore import QTimer +from cura.Scene.CuraSceneNode import CuraSceneNode + +from UM.Application import Application +from UM.Extension import Extension +from UM.Logger import Logger + + +class ModelChecker(Extension): + def __init__(self): + super().__init__() + + self._update_timer = QTimer() + self._update_timer.setInterval(2000) + self._update_timer.setSingleShot(True) + self._update_timer.timeout.connect(self.checkObjects) + + self._nodes_to_check = set() + + ## Reacting to an event. ## + Application.getInstance().mainWindowChanged.connect(self.logMessage) #When the main window is created, log a message. + Application.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged) + + ## Adds a message to the log, as an example of how to listen to events. + def logMessage(self): + Logger.log("i", "This is an example log message. yeaaa") + + def checkObjects(self): + Logger.log("d", "############# checking....") + + def _onSceneChanged(self, source): + if isinstance(source, CuraSceneNode) and source.callDecoration("isSliceable"): + Logger.log("d", "triggurrrr") + self._nodes_to_check.add(source) + self._update_timer.start() diff --git a/plugins/ModelChecker/__init__.py b/plugins/ModelChecker/__init__.py new file mode 100644 index 0000000000..ae4aa7d0c0 --- /dev/null +++ b/plugins/ModelChecker/__init__.py @@ -0,0 +1,23 @@ +# Copyright (c) 2017 Ultimaker B.V. +# This example is released under the terms of the AGPLv3 or higher. + +from . import ModelChecker + +## Defines additional metadata for the plug-in. +# +# Some types of plug-ins require additional metadata, such as which file types +# they are able to read or the name of the tool they define. In the case of +# the "Extension" type plug-in, there is no additional metadata though. +def getMetaData(): + return {} + +## Lets Uranium know that this plug-in exists. +# +# This is called when starting the application to find out which plug-ins +# exist and what their types are. We need to return a dictionary mapping from +# strings representing plug-in types (in this case "extension") to objects +# that inherit from PluginObject. +# +# \param app The application that the plug-in needs to register with. +def register(app): + return {"extension": ModelChecker.ModelChecker()} diff --git a/plugins/ModelChecker/plugin.json b/plugins/ModelChecker/plugin.json new file mode 100644 index 0000000000..db8819a325 --- /dev/null +++ b/plugins/ModelChecker/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "Model Checker", + "author": "Ultimaker", + "version": "0.1", + "api": 4, + "description": "Checks models for possible printing issues and give suggestions.", + "catalog": "cura" +} From 18fba5b52938809632c71a610d97e8d85260f556 Mon Sep 17 00:00:00 2001 From: Guillem Date: Mon, 19 Mar 2018 14:21:45 +0100 Subject: [PATCH 05/48] Avoid replacing machine_settings and not settable_per_extruder settings --- cura/Settings/MachineManager.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 557fd97b43..2550d1da40 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -553,14 +553,15 @@ class MachineManager(QObject): ## Copy the value of all settings of the current extruder to all other extruders as well as the global container. @pyqtSlot() def copyAllValuesToExtruders(self): - for key in self._active_container_stack.getAllKeys(): - new_value = self._active_container_stack.getProperty(key, "value") - extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())] + for key in self._active_container_stack.getAllKeys() - set([machine_setting.key for machine_setting in self._active_container_stack.getProperty("machine_settings", "children")]): + if self._active_container_stack.getProperty(key, "settable_per_extruder"): + new_value = self._active_container_stack.getProperty(key, "value") + extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())] - # check in which stack the value has to be replaced - for extruder_stack in extruder_stacks: - if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: - extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved + # check in which stack the value has to be replaced + for extruder_stack in extruder_stacks: + if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: + extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved @pyqtProperty(str, notify = activeVariantChanged) def activeVariantName(self) -> str: From faf8ed3ba654fb72a8ec614d29228890d02fa3d1 Mon Sep 17 00:00:00 2001 From: Guillem Date: Mon, 19 Mar 2018 14:57:31 +0100 Subject: [PATCH 06/48] Replace only user changed values removed machine_settings and settable_per_extruder_check. Not needed. --- cura/Settings/MachineManager.py | 15 +++++++-------- resources/qml/Settings/SettingView.qml | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 2550d1da40..549eeadeb4 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -553,15 +553,14 @@ class MachineManager(QObject): ## Copy the value of all settings of the current extruder to all other extruders as well as the global container. @pyqtSlot() def copyAllValuesToExtruders(self): - for key in self._active_container_stack.getAllKeys() - set([machine_setting.key for machine_setting in self._active_container_stack.getProperty("machine_settings", "children")]): - if self._active_container_stack.getProperty(key, "settable_per_extruder"): - new_value = self._active_container_stack.getProperty(key, "value") - extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())] + for key in self._active_container_stack.userChanges.getAllKeys(): + new_value = self._active_container_stack.getProperty(key, "value") + extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())] - # check in which stack the value has to be replaced - for extruder_stack in extruder_stacks: - if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: - extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved + # check in which stack the value has to be replaced + for extruder_stack in extruder_stacks: + if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: + extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved @pyqtProperty(str, notify = activeVariantChanged) def activeVariantName(self) -> str: diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 5865ed5182..67dd7622a3 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -488,7 +488,7 @@ Item MenuItem { //: Settings context menu action - text: catalog.i18nc("@action:menu", "Copy all values to all extruders") + text: catalog.i18nc("@action:menu", "Copy all changed values to all extruders") visible: machineExtruderCount.properties.value > 1 enabled: contextMenu.provider != undefined onTriggered: Cura.MachineManager.copyAllValuesToExtruders() From ef7139d3b2c8ba019fff43665e39c86161a8598a Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Mon, 19 Mar 2018 16:17:24 +0100 Subject: [PATCH 07/48] CURA-4557 First version of model checker --- plugins/ModelChecker/ModelChecker.py | 66 ++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index dedfc86938..764aaafcb8 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -6,7 +6,17 @@ from cura.Scene.CuraSceneNode import CuraSceneNode from UM.Application import Application from UM.Extension import Extension -from UM.Logger import Logger +from UM.Message import Message +from UM.i18n import i18nCatalog + +catalog = i18nCatalog("cura") + + +SHRINKAGE_THRESHOLD = 0.5 +WARNING_SIZE_XY = 150 +WARNING_SIZE_Z = 100 + +MESSAGE_LIFETIME = 10 class ModelChecker(Extension): @@ -14,25 +24,63 @@ class ModelChecker(Extension): super().__init__() self._update_timer = QTimer() - self._update_timer.setInterval(2000) + self._update_timer.setInterval(5000) self._update_timer.setSingleShot(True) self._update_timer.timeout.connect(self.checkObjects) self._nodes_to_check = set() + self._warning_model_names = set() # Collect the names of models so we show the next warning with timeout + ## Reacting to an event. ## - Application.getInstance().mainWindowChanged.connect(self.logMessage) #When the main window is created, log a message. Application.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged) - ## Adds a message to the log, as an example of how to listen to events. - def logMessage(self): - Logger.log("i", "This is an example log message. yeaaa") - def checkObjects(self): - Logger.log("d", "############# checking....") + warning_nodes = [] + global_container_stack = Application.getInstance().getGlobalContainerStack() + material_shrinkage = {} + need_check = False + + # Get all shrinkage values of materials used + for extruder_position, extruder in global_container_stack.extruders.items(): + shrinkage = extruder.material.getProperty("material_shrinkage_ratio_percentage", "value") + if shrinkage is None: + shrinkage = 0 + if shrinkage > SHRINKAGE_THRESHOLD: + need_check = True + material_shrinkage[extruder_position] = shrinkage + + # Check if we can bail out fast + if not need_check: + return + + # Check node material shrinkage and bounding box size + for node in self._nodes_to_check: + node_extruder_position = node.callDecoration("getActiveExtruderPosition") + if material_shrinkage[node_extruder_position] > SHRINKAGE_THRESHOLD: + bbox = node.getBoundingBox() + if bbox.width >= WARNING_SIZE_XY or bbox.depth >= WARNING_SIZE_XY or bbox.height >= WARNING_SIZE_Z: + warning_nodes.append(node) + + # Display warning message + if warning_nodes: + message_lifetime = MESSAGE_LIFETIME + for node in warning_nodes: + if node.getName() not in self._warning_model_names: + message_lifetime = 0 # infinite + self._warning_model_names.add(node.getName()) + caution_message = Message(catalog.i18nc( + "@info:warning", + "Some models may not be printed optimal due to object size and material chosen [%s].\n" + "Tips that may be useful to improve the print quality:\n" + "1) Use rounded corners\n" + "2) Turn the fan off (only if the are no tiny details on the model)\n" + "3) Use a different material") % ", ".join([n.getName() for n in warning_nodes]), + lifetime = message_lifetime, + title = catalog.i18nc("@info:title", "Model Warning")) + caution_message.show() def _onSceneChanged(self, source): if isinstance(source, CuraSceneNode) and source.callDecoration("isSliceable"): - Logger.log("d", "triggurrrr") self._nodes_to_check.add(source) self._update_timer.start() From e9585169136378edc771a61b851fa68708e97a0f Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Mon, 19 Mar 2018 16:50:35 +0100 Subject: [PATCH 08/48] CURA-4557 add checking on material change as well --- plugins/ModelChecker/ModelChecker.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index 764aaafcb8..b4c79387a8 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -8,6 +8,7 @@ from UM.Application import Application from UM.Extension import Extension from UM.Message import Message from UM.i18n import i18nCatalog +from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator catalog = i18nCatalog("cura") @@ -24,7 +25,7 @@ class ModelChecker(Extension): super().__init__() self._update_timer = QTimer() - self._update_timer.setInterval(5000) + self._update_timer.setInterval(2000) self._update_timer.setSingleShot(True) self._update_timer.timeout.connect(self.checkObjects) @@ -32,8 +33,11 @@ class ModelChecker(Extension): self._warning_model_names = set() # Collect the names of models so we show the next warning with timeout - ## Reacting to an event. ## + Application.getInstance().initializationFinished.connect(self.bindSignals) + + def bindSignals(self): Application.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged) + Application.getInstance().getMachineManager().rootMaterialChanged.connect(self._checkAllSliceableNodes) def checkObjects(self): warning_nodes = [] @@ -61,6 +65,7 @@ class ModelChecker(Extension): bbox = node.getBoundingBox() if bbox.width >= WARNING_SIZE_XY or bbox.depth >= WARNING_SIZE_XY or bbox.height >= WARNING_SIZE_Z: warning_nodes.append(node) + self._nodes_to_check = set() # Display warning message if warning_nodes: @@ -84,3 +89,12 @@ class ModelChecker(Extension): if isinstance(source, CuraSceneNode) and source.callDecoration("isSliceable"): self._nodes_to_check.add(source) self._update_timer.start() + + def _checkAllSliceableNodes(self, *args): + # Add all scene nodes + scene = Application.getInstance().getController().getScene() + for node in DepthFirstIterator(scene.getRoot()): + if node.callDecoration("isSliceable"): + self._nodes_to_check.add(node) + if self._nodes_to_check: + self._update_timer.start() From 1aba1cfe6ab319c31d17f215c77afa278bee258a Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Tue, 20 Mar 2018 16:30:11 +0100 Subject: [PATCH 09/48] CURA-4557 model checker is now a tool, click to activate --- plugins/ModelChecker/ModelChecker.py | 98 +++++++++++++++------------- plugins/ModelChecker/__init__.py | 30 ++++----- plugins/ModelChecker/plugin.json | 6 +- 3 files changed, 68 insertions(+), 66 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index b4c79387a8..0ae9cc5d01 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -5,7 +5,7 @@ from PyQt5.QtCore import QTimer from cura.Scene.CuraSceneNode import CuraSceneNode from UM.Application import Application -from UM.Extension import Extension +from UM.Tool import Tool from UM.Message import Message from UM.i18n import i18nCatalog from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator @@ -17,31 +17,20 @@ SHRINKAGE_THRESHOLD = 0.5 WARNING_SIZE_XY = 150 WARNING_SIZE_Z = 100 -MESSAGE_LIFETIME = 10 - -class ModelChecker(Extension): +class ModelChecker(Tool): def __init__(self): super().__init__() - self._update_timer = QTimer() - self._update_timer.setInterval(2000) - self._update_timer.setSingleShot(True) - self._update_timer.timeout.connect(self.checkObjects) + self._last_known_tool_id = None - self._nodes_to_check = set() + self._controller.activeToolChanged.connect(self._onActiveToolChanged) - self._warning_model_names = set() # Collect the names of models so we show the next warning with timeout - - Application.getInstance().initializationFinished.connect(self.bindSignals) - - def bindSignals(self): - Application.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged) - Application.getInstance().getMachineManager().rootMaterialChanged.connect(self._checkAllSliceableNodes) - - def checkObjects(self): + def checkObjects(self, nodes_to_check): warning_nodes = [] global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack is None: + return [] material_shrinkage = {} need_check = False @@ -59,42 +48,57 @@ class ModelChecker(Extension): return # Check node material shrinkage and bounding box size - for node in self._nodes_to_check: + for node in nodes_to_check: node_extruder_position = node.callDecoration("getActiveExtruderPosition") if material_shrinkage[node_extruder_position] > SHRINKAGE_THRESHOLD: bbox = node.getBoundingBox() if bbox.width >= WARNING_SIZE_XY or bbox.depth >= WARNING_SIZE_XY or bbox.height >= WARNING_SIZE_Z: warning_nodes.append(node) - self._nodes_to_check = set() - # Display warning message - if warning_nodes: - message_lifetime = MESSAGE_LIFETIME - for node in warning_nodes: - if node.getName() not in self._warning_model_names: - message_lifetime = 0 # infinite - self._warning_model_names.add(node.getName()) - caution_message = Message(catalog.i18nc( - "@info:warning", - "Some models may not be printed optimal due to object size and material chosen [%s].\n" - "Tips that may be useful to improve the print quality:\n" - "1) Use rounded corners\n" - "2) Turn the fan off (only if the are no tiny details on the model)\n" - "3) Use a different material") % ", ".join([n.getName() for n in warning_nodes]), - lifetime = message_lifetime, - title = catalog.i18nc("@info:title", "Model Warning")) - caution_message.show() + return warning_nodes - def _onSceneChanged(self, source): - if isinstance(source, CuraSceneNode) and source.callDecoration("isSliceable"): - self._nodes_to_check.add(source) - self._update_timer.start() - - def _checkAllSliceableNodes(self, *args): - # Add all scene nodes + def checkAllSliceableNodes(self): + # Add all sliceable scene nodes to check scene = Application.getInstance().getController().getScene() + nodes_to_check = [] for node in DepthFirstIterator(scene.getRoot()): if node.callDecoration("isSliceable"): - self._nodes_to_check.add(node) - if self._nodes_to_check: - self._update_timer.start() + nodes_to_check.append(node) + return self.checkObjects(nodes_to_check) + + ## Display warning message + def showWarningMessage(self, warning_nodes): + caution_message = Message(catalog.i18nc( + "@info:status", + "Some models may not be printed optimal due to object size and material chosen [%s].\n" + "Tips that may be useful to improve the print quality:\n" + "1) Use rounded corners\n" + "2) Turn the fan off (only if the are no tiny details on the model)\n" + "3) Use a different material") % ", ".join([n.getName() for n in warning_nodes]), + lifetime = 0, + title = catalog.i18nc("@info:title", "Model Checker Warning")) + caution_message.show() + + def showHappyMessage(self): + happy_message = Message(catalog.i18nc( + "@info:status", + "The Model Checker did not detect any problems with your model / print setup combination."), + lifetime = 5, + title = catalog.i18nc("@info:title", "Model Checker")) + happy_message.show() + + def _onActiveToolChanged(self): + active_tool = self.getController().getActiveTool() + if active_tool is None: + return + active_tool_id = active_tool.getPluginId() + if active_tool_id != self.getPluginId(): + self._last_known_tool_id = active_tool_id + if active_tool_id == self.getPluginId(): + warning_nodes = self.checkAllSliceableNodes() + if warning_nodes: + self.showWarningMessage(warning_nodes) + else: + self.showHappyMessage() + if self._last_known_tool_id is not None: + self.getController().setActiveTool(self._last_known_tool_id) diff --git a/plugins/ModelChecker/__init__.py b/plugins/ModelChecker/__init__.py index ae4aa7d0c0..b2b8587a7a 100644 --- a/plugins/ModelChecker/__init__.py +++ b/plugins/ModelChecker/__init__.py @@ -3,21 +3,19 @@ from . import ModelChecker -## Defines additional metadata for the plug-in. -# -# Some types of plug-ins require additional metadata, such as which file types -# they are able to read or the name of the tool they define. In the case of -# the "Extension" type plug-in, there is no additional metadata though. -def getMetaData(): - return {} +from UM.i18n import i18nCatalog +i18n_catalog = i18nCatalog("uranium") + + +def getMetaData(): + return { + "tool": { + "name": i18n_catalog.i18nc("@label", "Model Checker"), + "description": i18n_catalog.i18nc("@info:tooltip", "Checks models and print configuration for possible printing issues and give suggestions."), + "icon": "model_checker.svg", + "weight": 10 + } + } -## Lets Uranium know that this plug-in exists. -# -# This is called when starting the application to find out which plug-ins -# exist and what their types are. We need to return a dictionary mapping from -# strings representing plug-in types (in this case "extension") to objects -# that inherit from PluginObject. -# -# \param app The application that the plug-in needs to register with. def register(app): - return {"extension": ModelChecker.ModelChecker()} + return { "tool": ModelChecker.ModelChecker() } diff --git a/plugins/ModelChecker/plugin.json b/plugins/ModelChecker/plugin.json index db8819a325..a9190adcaa 100644 --- a/plugins/ModelChecker/plugin.json +++ b/plugins/ModelChecker/plugin.json @@ -1,8 +1,8 @@ { "name": "Model Checker", - "author": "Ultimaker", + "author": "Ultimaker B.V.", "version": "0.1", "api": 4, - "description": "Checks models for possible printing issues and give suggestions.", - "catalog": "cura" + "description": "Checks models and print configuration for possible printing issues and give suggestions.", + "i18n-catalog": "cura" } From 9ea59c1976c7d440cb7b9f4fd23be069c73b254f Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Tue, 20 Mar 2018 17:07:03 +0100 Subject: [PATCH 10/48] CURA-4557 rename ratio percentage to percentage --- plugins/ModelChecker/ModelChecker.py | 2 +- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 2 +- resources/definitions/fdmprinter.def.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index 0ae9cc5d01..afe8a7e47d 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -36,7 +36,7 @@ class ModelChecker(Tool): # Get all shrinkage values of materials used for extruder_position, extruder in global_container_stack.extruders.items(): - shrinkage = extruder.material.getProperty("material_shrinkage_ratio_percentage", "value") + shrinkage = extruder.material.getProperty("material_shrinkage_percentage", "value") if shrinkage is None: shrinkage = 0 if shrinkage > SHRINKAGE_THRESHOLD: diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 229292fe01..341f2bd3bb 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -984,7 +984,7 @@ class XmlMaterialProfile(InstanceContainer): "retraction speed": "retraction_speed", "adhesion tendency": "material_adhesion_tendency", "surface energy": "material_surface_energy", - "shrinkage ratio percentage": "material_shrinkage_ratio_percentage", + "shrinkage percentage": "material_shrinkage_percentage", } __unmapped_settings = [ "hardware compatible", diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 24699fa95c..d7d9698439 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -2097,7 +2097,7 @@ "settable_per_mesh": false, "settable_per_extruder": true }, - "material_shrinkage_ratio_percentage": + "material_shrinkage_percentage": { "label": "Shrinkage Ratio", "description": "Shrinkage ratio in percentage.", From 25bf7a6ababeaee3428924a25bc2f748897db817 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Wed, 21 Mar 2018 13:11:42 +0100 Subject: [PATCH 11/48] CURA-4557 added icon --- plugins/ModelChecker/model_checker.svg | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 plugins/ModelChecker/model_checker.svg diff --git a/plugins/ModelChecker/model_checker.svg b/plugins/ModelChecker/model_checker.svg new file mode 100644 index 0000000000..ca5228155a --- /dev/null +++ b/plugins/ModelChecker/model_checker.svg @@ -0,0 +1,10 @@ + + + ModelChecker + + + + + + + From 457b3543d78fba3ef11f6a9b4dd957592710eedd Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Wed, 21 Mar 2018 13:57:51 +0100 Subject: [PATCH 12/48] CURA-5128 WIP compressed g-code reader --- plugins/GCodeGzReader/GCodeGzReader.py | 33 ++++++++++++++++++++++++++ plugins/GCodeGzReader/__init__.py | 21 ++++++++++++++++ plugins/GCodeGzReader/plugin.json | 8 +++++++ 3 files changed, 62 insertions(+) create mode 100644 plugins/GCodeGzReader/GCodeGzReader.py create mode 100644 plugins/GCodeGzReader/__init__.py create mode 100644 plugins/GCodeGzReader/plugin.json diff --git a/plugins/GCodeGzReader/GCodeGzReader.py b/plugins/GCodeGzReader/GCodeGzReader.py new file mode 100644 index 0000000000..df1b8439cd --- /dev/null +++ b/plugins/GCodeGzReader/GCodeGzReader.py @@ -0,0 +1,33 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import gzip +import tempfile + +from io import StringIO, BufferedIOBase #To write the g-code to a temporary buffer, and for typing. +from typing import List + +from UM.Logger import Logger +from UM.Mesh.MeshReader import MeshReader #The class we're extending/implementing. +from UM.PluginRegistry import PluginRegistry +from UM.Scene.SceneNode import SceneNode #For typing. + +## A file writer that writes gzipped g-code. +# +# If you're zipping g-code, you might as well use gzip! +class GCodeGzReader(MeshReader): + + def __init__(self): + super(GCodeGzReader, self).__init__() + self._supported_extensions = [".gcode.gz", ".gz"] + + def read(self, file_name): + with open(file_name, "rb") as file: + file_data = file.read() + uncompressed_gcode = gzip.decompress(file_data) + with tempfile.NamedTemporaryFile() as temp_file: + temp_file.write(uncompressed_gcode) + PluginRegistry.getInstance().getPluginObject("GCodeReader").preRead(temp_file.name) + result = PluginRegistry.getInstance().getPluginObject("GCodeReader").read(temp_file.name) + + return result diff --git a/plugins/GCodeGzReader/__init__.py b/plugins/GCodeGzReader/__init__.py new file mode 100644 index 0000000000..67424a7d45 --- /dev/null +++ b/plugins/GCodeGzReader/__init__.py @@ -0,0 +1,21 @@ +# Copyright (c) 2016 Aleph Objects, Inc. +# Cura is released under the terms of the LGPLv3 or higher. + +from . import GCodeGzReader + +from UM.i18n import i18nCatalog +i18n_catalog = i18nCatalog("cura") + +def getMetaData(): + return { + "mesh_reader": [ + { + "extension": "gcode.gz", + "description": i18n_catalog.i18nc("@item:inlistbox", "Compressed G-code File") + } + ] + } + +def register(app): + app.addNonSliceableExtension(".gcode.gz") + return { "mesh_reader": GCodeGzReader.GCodeGzReader() } diff --git a/plugins/GCodeGzReader/plugin.json b/plugins/GCodeGzReader/plugin.json new file mode 100644 index 0000000000..e9f14724e0 --- /dev/null +++ b/plugins/GCodeGzReader/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "Compressed G-code Reader", + "author": "Ultimaker B.V.", + "version": "1.0.0", + "description": "Reads g-code from a compressed archive.", + "api": 4, + "i18n-catalog": "cura" +} From d8d226c0d654b3dbfc05e800785a013db8cc1642 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Wed, 21 Mar 2018 16:55:58 +0100 Subject: [PATCH 13/48] CURA-4557 it's now a small button next to the job spec when one of the warping materials is selected --- plugins/ModelChecker/ModelChecker.py | 140 +++++++++++++++++-------- plugins/ModelChecker/ModelChecker.qml | 47 +++++++++ plugins/ModelChecker/__init__.py | 15 +-- plugins/ModelChecker/model_checker.svg | 6 +- resources/qml/JobSpecs.qml | 37 ++++++- resources/themes/cura-light/theme.json | 2 + 6 files changed, 190 insertions(+), 57 deletions(-) create mode 100644 plugins/ModelChecker/ModelChecker.qml diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index afe8a7e47d..285002deda 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -1,13 +1,16 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from PyQt5.QtCore import QTimer -from cura.Scene.CuraSceneNode import CuraSceneNode +import os + +from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, pyqtProperty from UM.Application import Application -from UM.Tool import Tool +from UM.Extension import Extension +from UM.Logger import Logger from UM.Message import Message from UM.i18n import i18nCatalog +from UM.PluginRegistry import PluginRegistry from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator catalog = i18nCatalog("cura") @@ -17,35 +20,34 @@ SHRINKAGE_THRESHOLD = 0.5 WARNING_SIZE_XY = 150 WARNING_SIZE_Z = 100 +# Use this when actual shrinkage data is not in fdm_materials yet +MATERIALS_LOOKUP = { + "generic_abs": 1, + "generic_pc": 1, + "generic_pp": 1, + "generic_cpe_plus": 1 +} + + +class ModelChecker(QObject, Extension): + + needCheckChanged = pyqtSignal() -class ModelChecker(Tool): def __init__(self): super().__init__() - self._last_known_tool_id = None + self._button_view = None + self._need_checks = False - self._controller.activeToolChanged.connect(self._onActiveToolChanged) + Application.getInstance().initializationFinished.connect(self.bindSignals) + + def bindSignals(self): + Application.getInstance().getMachineManager().rootMaterialChanged.connect(self._onChanged) + + def checkObjectsForShrinkage(self, nodes_to_check): + material_shrinkage = self.getMaterialShrinkage() - def checkObjects(self, nodes_to_check): warning_nodes = [] - global_container_stack = Application.getInstance().getGlobalContainerStack() - if global_container_stack is None: - return [] - material_shrinkage = {} - need_check = False - - # Get all shrinkage values of materials used - for extruder_position, extruder in global_container_stack.extruders.items(): - shrinkage = extruder.material.getProperty("material_shrinkage_percentage", "value") - if shrinkage is None: - shrinkage = 0 - if shrinkage > SHRINKAGE_THRESHOLD: - need_check = True - material_shrinkage[extruder_position] = shrinkage - - # Check if we can bail out fast - if not need_check: - return # Check node material shrinkage and bounding box size for node in nodes_to_check: @@ -64,7 +66,7 @@ class ModelChecker(Tool): for node in DepthFirstIterator(scene.getRoot()): if node.callDecoration("isSliceable"): nodes_to_check.append(node) - return self.checkObjects(nodes_to_check) + return self.checkObjectsForShrinkage(nodes_to_check) ## Display warning message def showWarningMessage(self, warning_nodes): @@ -82,23 +84,77 @@ class ModelChecker(Tool): def showHappyMessage(self): happy_message = Message(catalog.i18nc( "@info:status", - "The Model Checker did not detect any problems with your model / print setup combination."), + "The Model Checker did not detect any problems with your model / chosen materials combination."), lifetime = 5, title = catalog.i18nc("@info:title", "Model Checker")) happy_message.show() - def _onActiveToolChanged(self): - active_tool = self.getController().getActiveTool() - if active_tool is None: - return - active_tool_id = active_tool.getPluginId() - if active_tool_id != self.getPluginId(): - self._last_known_tool_id = active_tool_id - if active_tool_id == self.getPluginId(): - warning_nodes = self.checkAllSliceableNodes() - if warning_nodes: - self.showWarningMessage(warning_nodes) - else: - self.showHappyMessage() - if self._last_known_tool_id is not None: - self.getController().setActiveTool(self._last_known_tool_id) + ## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection. + def _createView(self): + Logger.log("d", "Creating model checker view.") + + # Create the plugin dialog component + path = os.path.join(PluginRegistry.getInstance().getPluginPath("ModelChecker"), "ModelChecker.qml") + self._button_view = Application.getInstance().createQmlComponent(path, {"manager": self}) + + # The qml is only the button + Application.getInstance().addAdditionalComponent("jobSpecsButton", self._button_view) + + Logger.log("d", "Model checker view created.") + + def _onChanged(self, *args): + if self._button_view is None: + self._createView() + old_need_checks = self._need_checks + self._need_checks = self.calculateNeedCheck() + if old_need_checks != self._need_checks: + self.needCheckChanged.emit() + + @pyqtSlot() + def runChecks(self): + warning_nodes = self.checkAllSliceableNodes() + if warning_nodes: + self.showWarningMessage(warning_nodes) + else: + self.showHappyMessage() + + # TODO: use this if branch feature_model_check is merged in fdm_materials to master + # def getMaterialShrinkage(self): + # global_container_stack = Application.getInstance().getGlobalContainerStack() + # if global_container_stack is None: + # return {} + # + # material_shrinkage = {} + # # Get all shrinkage values of materials used + # for extruder_position, extruder in global_container_stack.extruders.items(): + # shrinkage = extruder.material.getProperty("material_shrinkage_percentage", "value") + # if shrinkage is None: + # shrinkage = 0 + # material_shrinkage[extruder_position] = shrinkage + # return material_shrinkage + + def getMaterialShrinkage(self): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack is None: + return {} + + material_shrinkage = {} + # Get all shrinkage values of materials used + for extruder_position, extruder in global_container_stack.extruders.items(): + base_file = extruder.material.getMetaDataEntry("base_file") + shrinkage = MATERIALS_LOOKUP.get(base_file, 0) + material_shrinkage[extruder_position] = shrinkage + return material_shrinkage + + @pyqtProperty(bool, notify = needCheckChanged) + def needCheck(self): + return self._need_checks + + def calculateNeedCheck(self): + need_check = False + + for shrinkage in self.getMaterialShrinkage().values(): + if shrinkage > SHRINKAGE_THRESHOLD: + need_check = True + + return need_check diff --git a/plugins/ModelChecker/ModelChecker.qml b/plugins/ModelChecker/ModelChecker.qml new file mode 100644 index 0000000000..77a483c2c6 --- /dev/null +++ b/plugins/ModelChecker/ModelChecker.qml @@ -0,0 +1,47 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Window 2.2 + +import UM 1.2 as UM +import Cura 1.0 as Cura + + +Button +{ + id: modelCheckerButton + + UM.I18nCatalog{id: catalog; name:"cura"} + + visible: manager.needCheck + tooltip: catalog.i18nc("@info:tooltip", "Check current setup for known problems.") + onClicked: manager.runChecks() + + //anchors.leftMargin: UM.Theme.getSize("default_margin").width + + //anchors.right: parent.right + //anchors.verticalCenter: parent.verticalCenter + width: UM.Theme.getSize("save_button_specs_icons").width + height: UM.Theme.getSize("save_button_specs_icons").height + + style: ButtonStyle + { + background: Item + { + UM.RecolorImage + { + width: UM.Theme.getSize("save_button_specs_icons").width; + height: UM.Theme.getSize("save_button_specs_icons").height; + sourceSize.width: width; + sourceSize.height: width; + color: control.hovered ? UM.Theme.getColor("text_scene_hover") : UM.Theme.getColor("text_scene"); + source: "model_checker.svg" + } + } + } +} diff --git a/plugins/ModelChecker/__init__.py b/plugins/ModelChecker/__init__.py index b2b8587a7a..5f4d443729 100644 --- a/plugins/ModelChecker/__init__.py +++ b/plugins/ModelChecker/__init__.py @@ -1,21 +1,14 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # This example is released under the terms of the AGPLv3 or higher. from . import ModelChecker from UM.i18n import i18nCatalog -i18n_catalog = i18nCatalog("uranium") +i18n_catalog = i18nCatalog("cura") def getMetaData(): - return { - "tool": { - "name": i18n_catalog.i18nc("@label", "Model Checker"), - "description": i18n_catalog.i18nc("@info:tooltip", "Checks models and print configuration for possible printing issues and give suggestions."), - "icon": "model_checker.svg", - "weight": 10 - } - } + return {} def register(app): - return { "tool": ModelChecker.ModelChecker() } + return { "extension": ModelChecker.ModelChecker() } diff --git a/plugins/ModelChecker/model_checker.svg b/plugins/ModelChecker/model_checker.svg index ca5228155a..5b9dd4d197 100644 --- a/plugins/ModelChecker/model_checker.svg +++ b/plugins/ModelChecker/model_checker.svg @@ -1,10 +1,10 @@ ModelChecker - - + + - + diff --git a/resources/qml/JobSpecs.qml b/resources/qml/JobSpecs.qml index 04e8ec397f..5cf6987f75 100644 --- a/resources/qml/JobSpecs.qml +++ b/resources/qml/JobSpecs.qml @@ -124,15 +124,50 @@ Item { } } + Row { + id: additionalComponentsRow + anchors.top: jobNameRow.bottom + anchors.right: parent.right + } + Label { id: boundingSpec anchors.top: jobNameRow.bottom - anchors.right: parent.right + anchors.right: additionalComponentsRow.left + anchors.rightMargin: + { + if (additionalComponentsRow.width > 0) + { + return UM.Theme.getSize("default_margin").width + } + else + { + return 0; + } + } height: UM.Theme.getSize("jobspecs_line").height verticalAlignment: Text.AlignVCenter font: UM.Theme.getFont("small") color: UM.Theme.getColor("text_scene") text: CuraApplication.getSceneBoundingBoxString } + + Component.onCompleted: { + base.addAdditionalComponents("jobSpecsButton") + } + + Connections { + target: CuraApplication + onAdditionalComponentsChanged: base.addAdditionalComponents("jobSpecsButton") + } + + function addAdditionalComponents (areaId) { + if(areaId == "jobSpecsButton") { + for (var component in CuraApplication.additionalComponents["jobSpecsButton"]) { + CuraApplication.additionalComponents["jobSpecsButton"][component].parent = additionalComponentsRow + } + } + } + } diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 4f4b2306a8..0fde7f3bc9 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -411,6 +411,8 @@ "save_button_save_to_button": [0.3, 2.7], "save_button_specs_icons": [1.4, 1.4], + "job_specs_button": [2.7, 2.7], + "monitor_preheat_temperature_control": [4.5, 2.0], "modal_window_minimum": [60.0, 45], From 6646d898c23a612410c0fa74e52f4a741a24b782 Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 00:14:58 +0100 Subject: [PATCH 14/48] Remove debug code This code was put in to allow debugging before the branches are merged. We should just assume that the two branches get merged at the same time. Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.py | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index 285002deda..9625b18b9b 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -20,14 +20,6 @@ SHRINKAGE_THRESHOLD = 0.5 WARNING_SIZE_XY = 150 WARNING_SIZE_Z = 100 -# Use this when actual shrinkage data is not in fdm_materials yet -MATERIALS_LOOKUP = { - "generic_abs": 1, - "generic_pc": 1, - "generic_pp": 1, - "generic_cpe_plus": 1 -} - class ModelChecker(QObject, Extension): @@ -118,21 +110,6 @@ class ModelChecker(QObject, Extension): else: self.showHappyMessage() - # TODO: use this if branch feature_model_check is merged in fdm_materials to master - # def getMaterialShrinkage(self): - # global_container_stack = Application.getInstance().getGlobalContainerStack() - # if global_container_stack is None: - # return {} - # - # material_shrinkage = {} - # # Get all shrinkage values of materials used - # for extruder_position, extruder in global_container_stack.extruders.items(): - # shrinkage = extruder.material.getProperty("material_shrinkage_percentage", "value") - # if shrinkage is None: - # shrinkage = 0 - # material_shrinkage[extruder_position] = shrinkage - # return material_shrinkage - def getMaterialShrinkage(self): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack is None: @@ -141,8 +118,9 @@ class ModelChecker(QObject, Extension): material_shrinkage = {} # Get all shrinkage values of materials used for extruder_position, extruder in global_container_stack.extruders.items(): - base_file = extruder.material.getMetaDataEntry("base_file") - shrinkage = MATERIALS_LOOKUP.get(base_file, 0) + shrinkage = extruder.material.getProperty("material_shrinkage_percentage", "value") + if shrinkage is None: + shrinkage = 0 material_shrinkage[extruder_position] = shrinkage return material_shrinkage From c7ba9f9e3711985500f3738edcd14126d583fe22 Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 00:18:12 +0100 Subject: [PATCH 15/48] Improve wording of warning for too large models A bit simpler English here. Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index 9625b18b9b..229eff1c7f 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -64,7 +64,7 @@ class ModelChecker(QObject, Extension): def showWarningMessage(self, warning_nodes): caution_message = Message(catalog.i18nc( "@info:status", - "Some models may not be printed optimal due to object size and material chosen [%s].\n" + "Some models may not be printed optimal due to object size and chosen material [%s].\n" "Tips that may be useful to improve the print quality:\n" "1) Use rounded corners\n" "2) Turn the fan off (only if the are no tiny details on the model)\n" From ab7f10ea52bb40e1c037f78717b5aa8b97a3b805 Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 00:20:10 +0100 Subject: [PATCH 16/48] Make model checker warning easier to translate It's often more clear to have a variable name in there so that the translator knows what'll be filled in for the variable. Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index 229eff1c7f..8557fb8380 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -64,11 +64,11 @@ class ModelChecker(QObject, Extension): def showWarningMessage(self, warning_nodes): caution_message = Message(catalog.i18nc( "@info:status", - "Some models may not be printed optimal due to object size and chosen material [%s].\n" + "Some models may not be printed optimal due to object size and chosen material for models: {model_names}.\n" "Tips that may be useful to improve the print quality:\n" "1) Use rounded corners\n" "2) Turn the fan off (only if the are no tiny details on the model)\n" - "3) Use a different material") % ", ".join([n.getName() for n in warning_nodes]), + "3) Use a different material").format(model_names = ", ".join([n.getName() for n in warning_nodes])), lifetime = 0, title = catalog.i18nc("@info:title", "Model Checker Warning")) caution_message.show() From 37edce597681ee206bc1b45f86f7f616f784ecd3 Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 00:28:00 +0100 Subject: [PATCH 17/48] Reuse message instances Slightly more performant. And now if you click on the button again you won't get a spammy new message each time. Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index 8557fb8380..f5b5549575 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -31,6 +31,15 @@ class ModelChecker(QObject, Extension): self._button_view = None self._need_checks = False + self._happy_message = Message(catalog.i18nc( + "@info:status", + "The Model Checker did not detect any problems with your model / chosen materials combination."), + lifetime = 5, + title = catalog.i18nc("@info:title", "Model Checker")) + self._caution_message = Message("", #Message text gets set when the message gets shown, to display the models in question. + lifetime = 0, + title = catalog.i18nc("@info:title", "Model Checker Warning")) + Application.getInstance().initializationFinished.connect(self.bindSignals) def bindSignals(self): @@ -62,24 +71,17 @@ class ModelChecker(QObject, Extension): ## Display warning message def showWarningMessage(self, warning_nodes): - caution_message = Message(catalog.i18nc( + self._caution_message.setText(catalog.i18nc( "@info:status", "Some models may not be printed optimal due to object size and chosen material for models: {model_names}.\n" "Tips that may be useful to improve the print quality:\n" "1) Use rounded corners\n" "2) Turn the fan off (only if the are no tiny details on the model)\n" - "3) Use a different material").format(model_names = ", ".join([n.getName() for n in warning_nodes])), - lifetime = 0, - title = catalog.i18nc("@info:title", "Model Checker Warning")) - caution_message.show() + "3) Use a different material").format(model_names = ", ".join([n.getName() for n in warning_nodes]))) + self._caution_message.show() def showHappyMessage(self): - happy_message = Message(catalog.i18nc( - "@info:status", - "The Model Checker did not detect any problems with your model / chosen materials combination."), - lifetime = 5, - title = catalog.i18nc("@info:title", "Model Checker")) - happy_message.show() + self._happy_message.show() ## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection. def _createView(self): From 55b247abbdf13e5e412237c5243f502c18b66424 Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 00:32:46 +0100 Subject: [PATCH 18/48] Hide outdated message if checking again And if the result is different. Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index f5b5549575..58c7e380bf 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -71,6 +71,7 @@ class ModelChecker(QObject, Extension): ## Display warning message def showWarningMessage(self, warning_nodes): + self._happy_message.hide() self._caution_message.setText(catalog.i18nc( "@info:status", "Some models may not be printed optimal due to object size and chosen material for models: {model_names}.\n" @@ -81,6 +82,7 @@ class ModelChecker(QObject, Extension): self._caution_message.show() def showHappyMessage(self): + self._caution_message.hide() self._happy_message.show() ## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection. From f91abf042f49ad4122e6f47c3ae085c99bde10c5 Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 00:35:55 +0100 Subject: [PATCH 19/48] Add documentation for globals And maybe they should not be globals. Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index 58c7e380bf..b1d61416b9 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -16,9 +16,9 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator catalog = i18nCatalog("cura") -SHRINKAGE_THRESHOLD = 0.5 -WARNING_SIZE_XY = 150 -WARNING_SIZE_Z = 100 +SHRINKAGE_THRESHOLD = 0.5 #From what shrinkage percentage a warning will be issued about the model size. +WARNING_SIZE_XY = 150 #The horizontal size of a model that would be too large when dealing with shrinking materials. +WARNING_SIZE_Z = 100 #The vertical size of a model that would be too large when dealing with shrinking materials. class ModelChecker(QObject, Extension): From 2db208cc758690269b792a4096e0590987567de0 Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 00:38:39 +0100 Subject: [PATCH 20/48] Remove unused code The IDs, group, and xlink and such are not used at all. Contributes to issue CURA-4557. --- plugins/ModelChecker/model_checker.svg | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/plugins/ModelChecker/model_checker.svg b/plugins/ModelChecker/model_checker.svg index 5b9dd4d197..ce9594302e 100644 --- a/plugins/ModelChecker/model_checker.svg +++ b/plugins/ModelChecker/model_checker.svg @@ -1,10 +1,7 @@ - - ModelChecker - - - - - - + + + + + From 7ec7136a3ad7ca2e48845c4198182af255e58066 Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 00:39:07 +0100 Subject: [PATCH 21/48] Remove debug code Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.qml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.qml b/plugins/ModelChecker/ModelChecker.qml index 77a483c2c6..9ed023c6ac 100644 --- a/plugins/ModelChecker/ModelChecker.qml +++ b/plugins/ModelChecker/ModelChecker.qml @@ -22,10 +22,6 @@ Button tooltip: catalog.i18nc("@info:tooltip", "Check current setup for known problems.") onClicked: manager.runChecks() - //anchors.leftMargin: UM.Theme.getSize("default_margin").width - - //anchors.right: parent.right - //anchors.verticalCenter: parent.verticalCenter width: UM.Theme.getSize("save_button_specs_icons").width height: UM.Theme.getSize("save_button_specs_icons").height From f5c1e59166d2b44b1f311ffd79735320acba6920 Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 00:46:29 +0100 Subject: [PATCH 22/48] Call checkObjectsForShrinkage directly from runChecks I'd like to transition to a state where you can read in runChecks all the checks that are being run. The former function checkAllSliceableNodes is now a helper function to list sliceable nodes. Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index b1d61416b9..8c26c24619 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -45,13 +45,13 @@ class ModelChecker(QObject, Extension): def bindSignals(self): Application.getInstance().getMachineManager().rootMaterialChanged.connect(self._onChanged) - def checkObjectsForShrinkage(self, nodes_to_check): + def checkObjectsForShrinkage(self): material_shrinkage = self.getMaterialShrinkage() warning_nodes = [] # Check node material shrinkage and bounding box size - for node in nodes_to_check: + for node in self.sliceableNodes(): node_extruder_position = node.callDecoration("getActiveExtruderPosition") if material_shrinkage[node_extruder_position] > SHRINKAGE_THRESHOLD: bbox = node.getBoundingBox() @@ -60,14 +60,12 @@ class ModelChecker(QObject, Extension): return warning_nodes - def checkAllSliceableNodes(self): + def sliceableNodes(self): # Add all sliceable scene nodes to check scene = Application.getInstance().getController().getScene() - nodes_to_check = [] for node in DepthFirstIterator(scene.getRoot()): if node.callDecoration("isSliceable"): - nodes_to_check.append(node) - return self.checkObjectsForShrinkage(nodes_to_check) + yield node ## Display warning message def showWarningMessage(self, warning_nodes): @@ -108,7 +106,7 @@ class ModelChecker(QObject, Extension): @pyqtSlot() def runChecks(self): - warning_nodes = self.checkAllSliceableNodes() + warning_nodes = self.checkObjectsForShrinkage() if warning_nodes: self.showWarningMessage(warning_nodes) else: From 987b475e3b88090050b40b706ead9a97b3ba0456 Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 00:48:23 +0100 Subject: [PATCH 23/48] Remove unused parameter Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index 8c26c24619..d1bdeeb328 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -96,7 +96,7 @@ class ModelChecker(QObject, Extension): Logger.log("d", "Model checker view created.") - def _onChanged(self, *args): + def _onChanged(self): if self._button_view is None: self._createView() old_need_checks = self._need_checks From 5bf93a8398974502c7ddaa5644cf3133a3d19a3a Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 01:31:35 +0100 Subject: [PATCH 24/48] Only display plug-in checker when there's really a warning As discussed with LukeChen-Ultimaker, we only want to display a button when there is really something up with the current set-up, not when there might be something up. Most users will be confused to see a button that always says that everything is fine. This also simplifies the signal handling a lot. Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.py | 73 +++++++++++++-------------- plugins/ModelChecker/ModelChecker.qml | 4 +- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index d1bdeeb328..ab8154cfec 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -22,14 +22,14 @@ WARNING_SIZE_Z = 100 #The vertical size of a model that would be too large when class ModelChecker(QObject, Extension): - - needCheckChanged = pyqtSignal() + ## Signal that gets emitted when anything changed that we need to check. + onChanged = pyqtSignal() def __init__(self): super().__init__() self._button_view = None - self._need_checks = False + self._has_warnings = False self._happy_message = Message(catalog.i18nc( "@info:status", @@ -40,10 +40,20 @@ class ModelChecker(QObject, Extension): lifetime = 0, title = catalog.i18nc("@info:title", "Model Checker Warning")) - Application.getInstance().initializationFinished.connect(self.bindSignals) + Application.getInstance().initializationFinished.connect(self._pluginsInitialized) + Application.getInstance().getController().getScene().sceneChanged.connect(self._onChanged) - def bindSignals(self): - Application.getInstance().getMachineManager().rootMaterialChanged.connect(self._onChanged) + ## Pass-through to allow UM.Signal to connect with a pyqtSignal. + def _onChanged(self, _): + self.onChanged.emit() + + ## Called when plug-ins are initialized. + # + # This makes sure that we listen to changes of the material and that the + # button is created that indicates warnings with the current set-up. + def _pluginsInitialized(self): + Application.getInstance().getMachineManager().rootMaterialChanged.connect(self.onChanged) + self._createView() def checkObjectsForShrinkage(self): material_shrinkage = self.getMaterialShrinkage() @@ -68,15 +78,8 @@ class ModelChecker(QObject, Extension): yield node ## Display warning message - def showWarningMessage(self, warning_nodes): + def showWarningMessage(self, ): self._happy_message.hide() - self._caution_message.setText(catalog.i18nc( - "@info:status", - "Some models may not be printed optimal due to object size and chosen material for models: {model_names}.\n" - "Tips that may be useful to improve the print quality:\n" - "1) Use rounded corners\n" - "2) Turn the fan off (only if the are no tiny details on the model)\n" - "3) Use a different material").format(model_names = ", ".join([n.getName() for n in warning_nodes]))) self._caution_message.show() def showHappyMessage(self): @@ -96,19 +99,28 @@ class ModelChecker(QObject, Extension): Logger.log("d", "Model checker view created.") - def _onChanged(self): - if self._button_view is None: - self._createView() - old_need_checks = self._need_checks - self._need_checks = self.calculateNeedCheck() - if old_need_checks != self._need_checks: - self.needCheckChanged.emit() - - @pyqtSlot() + @pyqtProperty(bool, notify = onChanged) def runChecks(self): warning_nodes = self.checkObjectsForShrinkage() if warning_nodes: - self.showWarningMessage(warning_nodes) + self._caution_message.setText(catalog.i18nc( + "@info:status", + "Some models may not be printed optimal due to object size and chosen material for models: {model_names}.\n" + "Tips that may be useful to improve the print quality:\n" + "1) Use rounded corners\n" + "2) Turn the fan off (only if the are no tiny details on the model)\n" + "3) Use a different material").format(model_names = ", ".join([n.getName() for n in warning_nodes]))) + return True + else: + return False + + @pyqtSlot() + def showWarnings(self): + if not self._button_view: + self._createView() + + if self._has_warnings: + self.showWarningMessage() else: self.showHappyMessage() @@ -125,16 +137,3 @@ class ModelChecker(QObject, Extension): shrinkage = 0 material_shrinkage[extruder_position] = shrinkage return material_shrinkage - - @pyqtProperty(bool, notify = needCheckChanged) - def needCheck(self): - return self._need_checks - - def calculateNeedCheck(self): - need_check = False - - for shrinkage in self.getMaterialShrinkage().values(): - if shrinkage > SHRINKAGE_THRESHOLD: - need_check = True - - return need_check diff --git a/plugins/ModelChecker/ModelChecker.qml b/plugins/ModelChecker/ModelChecker.qml index 9ed023c6ac..8934b52eea 100644 --- a/plugins/ModelChecker/ModelChecker.qml +++ b/plugins/ModelChecker/ModelChecker.qml @@ -18,9 +18,9 @@ Button UM.I18nCatalog{id: catalog; name:"cura"} - visible: manager.needCheck + visible: manager.runChecks tooltip: catalog.i18nc("@info:tooltip", "Check current setup for known problems.") - onClicked: manager.runChecks() + onClicked: manager.showWarnings() width: UM.Theme.getSize("save_button_specs_icons").width height: UM.Theme.getSize("save_button_specs_icons").height From 15ad528aa54aa35d45f8bba2d397f64e7b84c5e4 Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 01:34:04 +0100 Subject: [PATCH 25/48] Remove happy message Since the button can only get shown when there is something wrong, the happy message can never be shown any more. Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index ab8154cfec..60804be28b 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -31,11 +31,6 @@ class ModelChecker(QObject, Extension): self._button_view = None self._has_warnings = False - self._happy_message = Message(catalog.i18nc( - "@info:status", - "The Model Checker did not detect any problems with your model / chosen materials combination."), - lifetime = 5, - title = catalog.i18nc("@info:title", "Model Checker")) self._caution_message = Message("", #Message text gets set when the message gets shown, to display the models in question. lifetime = 0, title = catalog.i18nc("@info:title", "Model Checker Warning")) @@ -77,15 +72,6 @@ class ModelChecker(QObject, Extension): if node.callDecoration("isSliceable"): yield node - ## Display warning message - def showWarningMessage(self, ): - self._happy_message.hide() - self._caution_message.show() - - def showHappyMessage(self): - self._caution_message.hide() - self._happy_message.show() - ## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection. def _createView(self): Logger.log("d", "Creating model checker view.") @@ -120,9 +106,7 @@ class ModelChecker(QObject, Extension): self._createView() if self._has_warnings: - self.showWarningMessage() - else: - self.showHappyMessage() + self.self._caution_message.show() def getMaterialShrinkage(self): global_container_stack = Application.getInstance().getGlobalContainerStack() From 25e7cb457d400aac0fc2308692401d131bfa8957 Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 01:35:35 +0100 Subject: [PATCH 26/48] No need to create view again upon showing warnings It is now already done when initialization is completed. Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index 60804be28b..8e27171d68 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -102,9 +102,6 @@ class ModelChecker(QObject, Extension): @pyqtSlot() def showWarnings(self): - if not self._button_view: - self._createView() - if self._has_warnings: self.self._caution_message.show() From 329a0b121d8831c47f730c8db72d5654b90d4a74 Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 01:38:34 +0100 Subject: [PATCH 27/48] Move shrinkage parameters into shrinkage function So that they are closer to where they are relevant if we're going to have more checks in this class. Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index 8e27171d68..5f3f7a8430 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -16,11 +16,6 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator catalog = i18nCatalog("cura") -SHRINKAGE_THRESHOLD = 0.5 #From what shrinkage percentage a warning will be issued about the model size. -WARNING_SIZE_XY = 150 #The horizontal size of a model that would be too large when dealing with shrinking materials. -WARNING_SIZE_Z = 100 #The vertical size of a model that would be too large when dealing with shrinking materials. - - class ModelChecker(QObject, Extension): ## Signal that gets emitted when anything changed that we need to check. onChanged = pyqtSignal() @@ -51,6 +46,10 @@ class ModelChecker(QObject, Extension): self._createView() def checkObjectsForShrinkage(self): + shrinkage_threshold = 0.5 #From what shrinkage percentage a warning will be issued about the model size. + warning_size_xy = 150 #The horizontal size of a model that would be too large when dealing with shrinking materials. + warning_size_z = 100 #The vertical size of a model that would be too large when dealing with shrinking materials. + material_shrinkage = self.getMaterialShrinkage() warning_nodes = [] @@ -58,9 +57,9 @@ class ModelChecker(QObject, Extension): # Check node material shrinkage and bounding box size for node in self.sliceableNodes(): node_extruder_position = node.callDecoration("getActiveExtruderPosition") - if material_shrinkage[node_extruder_position] > SHRINKAGE_THRESHOLD: + if material_shrinkage[node_extruder_position] > shrinkage_threshold: bbox = node.getBoundingBox() - if bbox.width >= WARNING_SIZE_XY or bbox.depth >= WARNING_SIZE_XY or bbox.height >= WARNING_SIZE_Z: + if bbox.width >= warning_size_xy or bbox.depth >= warning_size_xy or bbox.height >= warning_size_z: warning_nodes.append(node) return warning_nodes From 8e6ee411241cf54430f8bcd35a8afad1386f5b55 Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 01:43:06 +0100 Subject: [PATCH 28/48] Let shrinkage check set its own message Another attempt to keep the shrinkage check contained to functions related to shrinkage more. Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index 5f3f7a8430..e5a09f7ea1 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -62,7 +62,15 @@ class ModelChecker(QObject, Extension): if bbox.width >= warning_size_xy or bbox.depth >= warning_size_xy or bbox.height >= warning_size_z: warning_nodes.append(node) - return warning_nodes + self._caution_message.setText(catalog.i18nc( + "@info:status", + "Some models may not be printed optimal due to object size and chosen material for models: {model_names}.\n" + "Tips that may be useful to improve the print quality:\n" + "1) Use rounded corners\n" + "2) Turn the fan off (only if the are no tiny details on the model)\n" + "3) Use a different material").format(model_names = ", ".join([n.getName() for n in warning_nodes]))) + + return len(warning_nodes) > 0 def sliceableNodes(self): # Add all sliceable scene nodes to check @@ -86,18 +94,9 @@ class ModelChecker(QObject, Extension): @pyqtProperty(bool, notify = onChanged) def runChecks(self): - warning_nodes = self.checkObjectsForShrinkage() - if warning_nodes: - self._caution_message.setText(catalog.i18nc( - "@info:status", - "Some models may not be printed optimal due to object size and chosen material for models: {model_names}.\n" - "Tips that may be useful to improve the print quality:\n" - "1) Use rounded corners\n" - "2) Turn the fan off (only if the are no tiny details on the model)\n" - "3) Use a different material").format(model_names = ", ".join([n.getName() for n in warning_nodes]))) - return True - else: - return False + danger_shrinkage = self.checkObjectsForShrinkage() + + return any((danger_shrinkage, )) #If any of the checks fail, show the warning button. @pyqtSlot() def showWarnings(self): From 5d51b75f4d604667e0418820985df673abf2ebaf Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 01:56:12 +0100 Subject: [PATCH 29/48] Remove unnecessary _has_warnings We just always show the message because the button can only be pressed while there are warnings. Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index e5a09f7ea1..1ceaa775d3 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -24,7 +24,6 @@ class ModelChecker(QObject, Extension): super().__init__() self._button_view = None - self._has_warnings = False self._caution_message = Message("", #Message text gets set when the message gets shown, to display the models in question. lifetime = 0, @@ -100,8 +99,7 @@ class ModelChecker(QObject, Extension): @pyqtSlot() def showWarnings(self): - if self._has_warnings: - self.self._caution_message.show() + self._caution_message.show() def getMaterialShrinkage(self): global_container_stack = Application.getInstance().getGlobalContainerStack() From 58205fb9653a4c6ffdd81685b2a62cac6313d852 Mon Sep 17 00:00:00 2001 From: Ruben D Date: Thu, 22 Mar 2018 01:59:49 +0100 Subject: [PATCH 30/48] Make _getMaterialShrinkage protected No need to expose this. Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index 1ceaa775d3..8a501ceb27 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -49,7 +49,7 @@ class ModelChecker(QObject, Extension): warning_size_xy = 150 #The horizontal size of a model that would be too large when dealing with shrinking materials. warning_size_z = 100 #The vertical size of a model that would be too large when dealing with shrinking materials. - material_shrinkage = self.getMaterialShrinkage() + material_shrinkage = self._getMaterialShrinkage() warning_nodes = [] @@ -101,7 +101,7 @@ class ModelChecker(QObject, Extension): def showWarnings(self): self._caution_message.show() - def getMaterialShrinkage(self): + def _getMaterialShrinkage(self): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack is None: return {} From 2fad098f0b2923855cb9755f82c137b157afc37f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 22 Mar 2018 09:11:47 +0100 Subject: [PATCH 31/48] Adjust description of button You now no longer click to check if there's something wrong. Contributes to issue CURA-4557. --- plugins/ModelChecker/ModelChecker.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ModelChecker/ModelChecker.qml b/plugins/ModelChecker/ModelChecker.qml index 8934b52eea..3db54d4387 100644 --- a/plugins/ModelChecker/ModelChecker.qml +++ b/plugins/ModelChecker/ModelChecker.qml @@ -19,7 +19,7 @@ Button UM.I18nCatalog{id: catalog; name:"cura"} visible: manager.runChecks - tooltip: catalog.i18nc("@info:tooltip", "Check current setup for known problems.") + tooltip: catalog.i18nc("@info:tooltip", "Some things could be problematic in this print. Click to see tips for adjustment.") onClicked: manager.showWarnings() width: UM.Theme.getSize("save_button_specs_icons").width From ce0e3f89b707cf7cafd769b09839e8a9815736ad Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 22 Mar 2018 09:42:11 +0100 Subject: [PATCH 32/48] Fix code style and typo CURA-4846 --- cura/CuraApplication.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 6d5bd34ee4..56f1528b9b 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1288,7 +1288,7 @@ class CuraApplication(QtApplication): has_merged_nodes = False for node in DepthFirstIterator(self.getController().getScene().getRoot()): if not isinstance(node, CuraSceneNode) or not node.getMeshData() : - if node.getName() == 'MergedMesh': + if node.getName() == "MergedMesh": has_merged_nodes = True continue @@ -1380,7 +1380,7 @@ class CuraApplication(QtApplication): # Use the previously found center of the group bounding box as the new location of the group group_node.setPosition(group_node.getBoundingBox().center) - group_node.setName("MergedMesh") # add a specific name to destinguis this node + group_node.setName("MergedMesh") # add a specific name to distinguish this node ## Updates origin position of all merged meshes From e2478b636ae59a59d180f2f9912aac6c978c8964 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 22 Mar 2018 09:49:22 +0100 Subject: [PATCH 33/48] Typo --- cura/CrashHandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index f51174aec0..7700ee2e71 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -85,7 +85,7 @@ class CrashHandler: dialog = QDialog() dialog.setMinimumWidth(500) dialog.setMinimumHeight(170) - dialog.setWindowTitle(catalog.i18nc("@title:window", "Cura can't startup")) + dialog.setWindowTitle(catalog.i18nc("@title:window", "Cura can't start")) dialog.finished.connect(self._closeEarlyCrashDialog) layout = QVBoxLayout(dialog) From 01ec20f5ced559283db2637d69a40adceca18ef8 Mon Sep 17 00:00:00 2001 From: Guillem Date: Thu, 22 Mar 2018 11:39:42 +0100 Subject: [PATCH 34/48] Fixed docs, removed todo, changed way to get extruder stacks, slightly faster --- cura/Settings/MachineManager.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 549eeadeb4..ef3a7189a5 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -550,17 +550,18 @@ class MachineManager(QObject): if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved - ## Copy the value of all settings of the current extruder to all other extruders as well as the global container. + ## Copy the value of all manually changed settings of the current extruder to all other extruders. @pyqtSlot() def copyAllValuesToExtruders(self): - for key in self._active_container_stack.userChanges.getAllKeys(): - new_value = self._active_container_stack.getProperty(key, "value") - extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())] + extruder_stacks = list(self._global_container_stack.extruders.values()) + for extruder_stack in extruder_stacks: + if extruder_stack != self._active_container_stack: + for key in self._active_container_stack.userChanges.getAllKeys(): + new_value = self._active_container_stack.getProperty(key, "value") - # check in which stack the value has to be replaced - for extruder_stack in extruder_stacks: - if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: - extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved + # check if the value has to be replaced + if extruder_stack.getProperty(key, "value") != new_value: + extruder_stack.userChanges.setProperty(key, "value", new_value) @pyqtProperty(str, notify = activeVariantChanged) def activeVariantName(self) -> str: From b75e0c75cec6520cd8e8a2d4f59334e14912d4bd Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Thu, 22 Mar 2018 14:23:16 +0100 Subject: [PATCH 35/48] CURA-5128 add .gz to non sliceable extensions --- plugins/GCodeGzReader/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/GCodeGzReader/__init__.py b/plugins/GCodeGzReader/__init__.py index 67424a7d45..d1f3cf34b1 100644 --- a/plugins/GCodeGzReader/__init__.py +++ b/plugins/GCodeGzReader/__init__.py @@ -18,4 +18,5 @@ def getMetaData(): def register(app): app.addNonSliceableExtension(".gcode.gz") + app.addNonSliceableExtension(".gz") # in some parts only the last extension is taken. Let's make it a non sliceable extension for now return { "mesh_reader": GCodeGzReader.GCodeGzReader() } From c2888529cbd1036ca28b0c6fc22b42d3892702ab Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Thu, 22 Mar 2018 14:35:21 +0100 Subject: [PATCH 36/48] CURA-5128 cleanup .gz and only leave .gcode.gz as Cura and Uranium now accept extensions with multiple periods --- cura/CuraApplication.py | 9 +++++++-- plugins/GCodeGzReader/GCodeGzReader.py | 2 +- plugins/GCodeGzReader/__init__.py | 1 - 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 56f1528b9b..292d0bce94 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1625,8 +1625,13 @@ class CuraApplication(QtApplication): node.setName(os.path.basename(filename)) self.getBuildVolume().checkBoundsAndUpdate(node) - extension = os.path.splitext(filename)[1] - if extension.lower() in self._non_sliceable_extensions: + is_non_sliceable = False + filename_lower = filename.lower() + for extension in self._non_sliceable_extensions: + if filename_lower.endswith(extension): + is_non_sliceable = True + break + if is_non_sliceable: self.callLater(lambda: self.getController().setActiveView("SimulationView")) block_slicing_decorator = BlockSlicingDecorator() diff --git a/plugins/GCodeGzReader/GCodeGzReader.py b/plugins/GCodeGzReader/GCodeGzReader.py index df1b8439cd..52df7f074f 100644 --- a/plugins/GCodeGzReader/GCodeGzReader.py +++ b/plugins/GCodeGzReader/GCodeGzReader.py @@ -19,7 +19,7 @@ class GCodeGzReader(MeshReader): def __init__(self): super(GCodeGzReader, self).__init__() - self._supported_extensions = [".gcode.gz", ".gz"] + self._supported_extensions = [".gcode.gz"] def read(self, file_name): with open(file_name, "rb") as file: diff --git a/plugins/GCodeGzReader/__init__.py b/plugins/GCodeGzReader/__init__.py index d1f3cf34b1..67424a7d45 100644 --- a/plugins/GCodeGzReader/__init__.py +++ b/plugins/GCodeGzReader/__init__.py @@ -18,5 +18,4 @@ def getMetaData(): def register(app): app.addNonSliceableExtension(".gcode.gz") - app.addNonSliceableExtension(".gz") # in some parts only the last extension is taken. Let's make it a non sliceable extension for now return { "mesh_reader": GCodeGzReader.GCodeGzReader() } From 0787f0b2ba4a311c6b39be8e0d8b8f886ce6528c Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 22 Mar 2018 14:46:14 +0100 Subject: [PATCH 37/48] Fix autoslice check so it doesn't slice if there is no need CURA-5130 --- plugins/CuraEngineBackend/CuraEngineBackend.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index e55abe59a2..401ed71347 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -119,6 +119,7 @@ class CuraEngineBackend(QObject, Backend): self._postponed_scene_change_sources = [] # scene change is postponed (by a tool) self._slice_start_time = None + self._is_disabled = False Preferences.getInstance().addPreference("general/auto_slice", True) @@ -405,6 +406,7 @@ class CuraEngineBackend(QObject, Backend): # - decorator isBlockSlicing is found (used in g-code reader) def determineAutoSlicing(self): enable_timer = True + self._is_disabled = False if not Preferences.getInstance().getValue("general/auto_slice"): enable_timer = False @@ -412,6 +414,7 @@ class CuraEngineBackend(QObject, Backend): if node.callDecoration("isBlockSlicing"): enable_timer = False self.backendStateChange.emit(BackendState.Disabled) + self._is_disabled = True gcode_list = node.callDecoration("getGCodeList") if gcode_list is not None: self._scene.gcode_dict[node.callDecoration("getBuildPlateNumber")] = gcode_list @@ -524,6 +527,9 @@ class CuraEngineBackend(QObject, Backend): ## Convenient function: mark everything to slice, emit state and clear layer data def needsSlicing(self): + self.determineAutoSlicing() + if self._is_disabled: + return self.stopSlicing() self.markSliceAll() self.processingProgress.emit(0.0) From f332b833644c45212c182477b6dbecf3aaf563b8 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 22 Mar 2018 14:50:12 +0100 Subject: [PATCH 38/48] Fix typo and remove unused imports CURA-5135 --- plugins/PluginBrowser/PluginBrowser.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/PluginBrowser/PluginBrowser.py b/plugins/PluginBrowser/PluginBrowser.py index c8a5e1e545..bb4d5fb395 100644 --- a/plugins/PluginBrowser/PluginBrowser.py +++ b/plugins/PluginBrowser/PluginBrowser.py @@ -1,11 +1,10 @@ # Copyright (c) 2017 Ultimaker B.V. # PluginBrowser is released under the terms of the LGPLv3 or higher. -from PyQt5.QtCore import QUrl, QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot +from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply from UM.Application import Application -from UM.Qt.ListModel import ListModel from UM.Logger import Logger from UM.PluginRegistry import PluginRegistry from UM.Qt.Bindings.PluginsModel import PluginsModel @@ -20,7 +19,6 @@ import os import tempfile import platform import zipfile -import shutil from cura.CuraApplication import CuraApplication @@ -44,7 +42,7 @@ class PluginBrowser(QObject, Extension): self._plugins_metadata = [] self._plugins_model = None - # Can be 'installed' or 'availble' + # Can be 'installed' or 'available' self._view = "available" self._restart_required = False From fecbf82551fc512838110ed8e8a1bc118275af09 Mon Sep 17 00:00:00 2001 From: Guillem Date: Thu, 22 Mar 2018 15:49:30 +0100 Subject: [PATCH 39/48] Replace all user changed values Removed check to change the values only if they were different --- cura/Settings/MachineManager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index ef3a7189a5..12e70841f7 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -560,8 +560,7 @@ class MachineManager(QObject): new_value = self._active_container_stack.getProperty(key, "value") # check if the value has to be replaced - if extruder_stack.getProperty(key, "value") != new_value: - extruder_stack.userChanges.setProperty(key, "value", new_value) + extruder_stack.userChanges.setProperty(key, "value", new_value) @pyqtProperty(str, notify = activeVariantChanged) def activeVariantName(self) -> str: From 283cbed1ad7a81abf571776649d9b30cb6d68353 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Thu, 22 Mar 2018 16:02:09 +0100 Subject: [PATCH 40/48] CURA-5130 move the part that determines if it should slice --- plugins/CuraEngineBackend/CuraEngineBackend.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 401ed71347..9873d91c05 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -527,9 +527,6 @@ class CuraEngineBackend(QObject, Backend): ## Convenient function: mark everything to slice, emit state and clear layer data def needsSlicing(self): - self.determineAutoSlicing() - if self._is_disabled: - return self.stopSlicing() self.markSliceAll() self.processingProgress.emit(0.0) @@ -551,6 +548,10 @@ class CuraEngineBackend(QObject, Backend): self._change_timer.stop() def _onStackErrorCheckFinished(self): + self.determineAutoSlicing() + if self._is_disabled: + return + if not self._slicing and self._build_plates_to_be_sliced: self.needsSlicing() self._onChanged() From 378cde202c0a484fb79f5b2e804fdd1d30aeaa4c Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 22 Mar 2018 16:33:30 +0100 Subject: [PATCH 41/48] Correct documentation Contributes to issue CURA-5128. --- plugins/GCodeGzReader/GCodeGzReader.py | 2 +- plugins/GCodeGzReader/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/GCodeGzReader/GCodeGzReader.py b/plugins/GCodeGzReader/GCodeGzReader.py index 52df7f074f..ec473fb299 100644 --- a/plugins/GCodeGzReader/GCodeGzReader.py +++ b/plugins/GCodeGzReader/GCodeGzReader.py @@ -12,7 +12,7 @@ from UM.Mesh.MeshReader import MeshReader #The class we're extending/implementin from UM.PluginRegistry import PluginRegistry from UM.Scene.SceneNode import SceneNode #For typing. -## A file writer that writes gzipped g-code. +## A file reader that reads gzipped g-code. # # If you're zipping g-code, you might as well use gzip! class GCodeGzReader(MeshReader): diff --git a/plugins/GCodeGzReader/__init__.py b/plugins/GCodeGzReader/__init__.py index 67424a7d45..98965c00aa 100644 --- a/plugins/GCodeGzReader/__init__.py +++ b/plugins/GCodeGzReader/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Aleph Objects, Inc. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from . import GCodeGzReader From 4430a15a2fc7141b13256bb73289c578dbd8533d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 22 Mar 2018 16:34:11 +0100 Subject: [PATCH 42/48] Remove needlessly specific super call There is only one parent. Contributes to issue CURA-5128. --- plugins/GCodeGzReader/GCodeGzReader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/GCodeGzReader/GCodeGzReader.py b/plugins/GCodeGzReader/GCodeGzReader.py index ec473fb299..9d671a70bf 100644 --- a/plugins/GCodeGzReader/GCodeGzReader.py +++ b/plugins/GCodeGzReader/GCodeGzReader.py @@ -18,7 +18,7 @@ from UM.Scene.SceneNode import SceneNode #For typing. class GCodeGzReader(MeshReader): def __init__(self): - super(GCodeGzReader, self).__init__() + super().__init__() self._supported_extensions = [".gcode.gz"] def read(self, file_name): From 30b75c79886b63c6e3ad5a8f9ec1cc39ab32bd95 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Thu, 22 Mar 2018 17:05:07 +0100 Subject: [PATCH 43/48] CURA-5140 Show confirmation rather than switching to monitor tab --- .../ClusterUM3OutputDevice.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py index 72f5260249..f3667fc2f3 100644 --- a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py @@ -127,10 +127,6 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._sending_job.send("") #No specifically selected printer. is_job_sent = self._sending_job.send(None) - # Notify the UI that a switch to the print monitor should happen - if is_job_sent: - Application.getInstance().getController().setActiveStage("MonitorStage") - def _spawnPrinterSelectionDialog(self): if self._printer_selection_dialog is None: path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PrintWindow.qml") @@ -242,6 +238,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): if new_progress > self._progress_message.getProgress(): self._progress_message.show() # Ensure that the message is visible. self._progress_message.setProgress(bytes_sent / bytes_total * 100) + + # If successfully sent: + if bytes_sent == bytes_total: + # Show a confirmation to the user so they know the job was sucessful and provide the option to switch to the + # monitor tab. + self._success_message = Message( + i18n_catalog.i18nc("@info:status", "Print job was successfully sent to the printer."), + lifetime=5, dismissable=True, + title=i18n_catalog.i18nc("@info:title", "Data Sent")) + self._success_message.addAction("View", i18n_catalog.i18nc("@action:button", "View in Montior"), icon=None, + description="") + self._success_message.actionTriggered.connect(self._successMessageActionTriggered) + self._success_message.show() else: self._progress_message.setProgress(0) self._progress_message.hide() @@ -260,6 +269,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._latest_reply_handler.disconnect() self._latest_reply_handler = None + def _successMessageActionTriggered(self, message_id: Optional[str]=None, action_id: Optional[str]=None) -> None: + if action_id == "View": + Application.getInstance().getController().setActiveStage("MonitorStage") @pyqtSlot() def openPrintJobControlPanel(self) -> None: From 3eb50cf37e8fbcbb7b50fd2c744d04c801eddc4b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 22 Mar 2018 17:30:15 +0100 Subject: [PATCH 44/48] Only list configurations if we're still connected Otherwise there are no available configurations, so no syncing. --- .../qml/Menus/ConfigurationMenu/ConfigurationListView.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/resources/qml/Menus/ConfigurationMenu/ConfigurationListView.qml b/resources/qml/Menus/ConfigurationMenu/ConfigurationListView.qml index 331d78ead9..7aaf87b4df 100644 --- a/resources/qml/Menus/ConfigurationMenu/ConfigurationListView.qml +++ b/resources/qml/Menus/ConfigurationMenu/ConfigurationListView.qml @@ -21,7 +21,10 @@ Column { // FIXME For now the model should be removed and then created again, otherwise changes in the printer don't automatically update the UI configurationList.model = [] - configurationList.model = outputDevice.uniqueConfigurations + if(outputDevice) + { + configurationList.model = outputDevice.uniqueConfigurations + } } Label From 749846e09c19b207ce66e6aa332cd6c219a4432a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 23 Mar 2018 09:49:47 +0100 Subject: [PATCH 45/48] Shorten 'Save to Removable Drive' translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wechseldatenträger was a bit too long to fit on that button. Fixes #2643. --- resources/i18n/de_DE/cura.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/i18n/de_DE/cura.po b/resources/i18n/de_DE/cura.po index 250e3e5e1b..f398d1ec4e 100644 --- a/resources/i18n/de_DE/cura.po +++ b/resources/i18n/de_DE/cura.po @@ -194,7 +194,7 @@ msgstr "Vorbereiten" #: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:23 msgctxt "@action:button Preceded by 'Ready to'." msgid "Save to Removable Drive" -msgstr "Speichern auf Wechseldatenträger" +msgstr "Speichern auf Datenträger" #: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:24 #, python-brace-format From e8491b4a8364680294b6c2e341f2fc2a234d3eec Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 23 Mar 2018 11:10:21 +0100 Subject: [PATCH 46/48] CURA-5135 Easy version This is the easy fix. When a plugin is downloading, the other plugins' download buttons are not possible to be clicked. This avoids having to write any new logic. It does detract a bit from the user experience though. The complicated version requires re-writing a big part of the plugin browser code to enable the queueing of downloads and stuff. That's sort of how it "should" be but is a lot more work. --- plugins/PluginBrowser/PluginEntry.qml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/plugins/PluginBrowser/PluginEntry.qml b/plugins/PluginBrowser/PluginEntry.qml index eff9eb8943..fab8f43e07 100644 --- a/plugins/PluginBrowser/PluginEntry.qml +++ b/plugins/PluginBrowser/PluginEntry.qml @@ -129,6 +129,28 @@ Component { return catalog.i18nc("@action:button", "Install"); } } + enabled: + { + if ( manager.isDownloading ) + { + return pluginList.activePlugin == model ? true : false + } + else + { + return true + } + } + opacity: + { + if ( pluginList.activePlugin == model ) + { + return 1.0 + } + else + { + manager.isDownloading ? 0.5 : 1.0 + } + } visible: model.external && ((model.status !== "installed") || model.can_upgrade) style: ButtonStyle { background: Rectangle { From d859f71d6eb8248ed9297e3fef2f749389189b23 Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Fri, 23 Mar 2018 11:41:34 +0100 Subject: [PATCH 47/48] Fix: The Print simulation view was broken because of constant refresheing scene CURA-5142 --- plugins/SimulationView/SimulationView.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index 3697e38661..85849efb2f 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -74,7 +74,7 @@ class SimulationView(View): self._global_container_stack = None self._proxy = SimulationViewProxy() - self._controller.getScene().sceneChanged.connect(self._onSceneChanged) + self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged) self._resetSettings() self._legend_items = None @@ -160,10 +160,10 @@ class SimulationView(View): def _onSceneChanged(self, node): if node.getMeshData() is None: self.resetLayerData() - else: - self.setActivity(False) - self.calculateMaxLayers() - self.calculateMaxPathsOnLayer(self._current_layer_num) + + self.setActivity(False) + self.calculateMaxLayers() + self.calculateMaxPathsOnLayer(self._current_layer_num) def isBusy(self): return self._busy From 8a36f1e0744c6fbd85efd76ace60315bcc850056 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 23 Mar 2018 14:23:10 +0100 Subject: [PATCH 48/48] Make opacity depend on enabled Simplifies the code a bit. Contributes to issue CURA-5128. --- plugins/PluginBrowser/PluginEntry.qml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/plugins/PluginBrowser/PluginEntry.qml b/plugins/PluginBrowser/PluginEntry.qml index fab8f43e07..9dbcb96e79 100644 --- a/plugins/PluginBrowser/PluginEntry.qml +++ b/plugins/PluginBrowser/PluginEntry.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Ultimaker B.V. +// Copyright (c) 2018 Ultimaker B.V. // PluginBrowser is released under the terms of the LGPLv3 or higher. import QtQuick 2.2 @@ -140,17 +140,7 @@ Component { return true } } - opacity: - { - if ( pluginList.activePlugin == model ) - { - return 1.0 - } - else - { - manager.isDownloading ? 0.5 : 1.0 - } - } + opacity: enabled ? 1.0 : 0.5 visible: model.external && ((model.status !== "installed") || model.can_upgrade) style: ButtonStyle { background: Rectangle {