From a24dc00bc570c796535830da2eb56b44b3df6bf9 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 12 Feb 2019 18:08:17 +0100 Subject: [PATCH 01/37] Arrange child-nodes (like blocker) with parents. [CURA-6120] --- cura/Arranging/ArrangeObjectsJob.py | 11 +++++++++-- cura/Arranging/ShapeArray.py | 17 ++++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/cura/Arranging/ArrangeObjectsJob.py b/cura/Arranging/ArrangeObjectsJob.py index ce11556b5b..3ec548dbb3 100644 --- a/cura/Arranging/ArrangeObjectsJob.py +++ b/cura/Arranging/ArrangeObjectsJob.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from UM.Application import Application @@ -39,10 +39,17 @@ class ArrangeObjectsJob(Job): arranger = Arrange.create(x = machine_width, y = machine_depth, fixed_nodes = self._fixed_nodes, min_offset = self._min_offset) + # Build set to exclude children (those get arranged together with the parents). + included_as_child = set([]) + for node in self._nodes: + included_as_child.update(node.getAllChildren()) + # Collect nodes to be placed nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr) for node in self._nodes: - offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset) + if node in included_as_child: + continue + offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset, include_children = True) if offset_shape_arr is None: Logger.log("w", "Node [%s] could not be converted to an array for arranging...", str(node)) continue diff --git a/cura/Arranging/ShapeArray.py b/cura/Arranging/ShapeArray.py index ab785cc3e1..64b78d6f17 100644 --- a/cura/Arranging/ShapeArray.py +++ b/cura/Arranging/ShapeArray.py @@ -42,7 +42,7 @@ class ShapeArray: # \param min_offset offset for the offset ShapeArray # \param scale scale the coordinates @classmethod - def fromNode(cls, node, min_offset, scale = 0.5): + def fromNode(cls, node, min_offset, scale = 0.5, include_children = False): transform = node._transformation transform_x = transform._data[0][3] transform_y = transform._data[2][3] @@ -52,6 +52,21 @@ class ShapeArray: return None, None # For one_at_a_time printing you need the convex hull head. hull_head_verts = node.callDecoration("getConvexHullHead") or hull_verts + if hull_head_verts is None: + hull_head_verts = Polygon() + + # If the child-nodes are included, adjust convex hulls as well: + if include_children: + children = node.getAllChildren() + if not children is None: + for child in children: + # 'Inefficient' combination of convex hulls through known code rather than mess it up: + child_hull = child.callDecoration("getConvexHull") + if not child_hull is None: + hull_verts = hull_verts.unionConvexHulls(child_hull) + child_hull_head = child.callDecoration("getConvexHullHead") or child_hull + if not child_hull_head is None: + hull_head_verts = hull_head_verts.unionConvexHulls(child_hull_head) offset_verts = hull_head_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset)) offset_points = copy.deepcopy(offset_verts._points) # x, y From 0e329946f80d7f39f0985d6f6e0f11664c3895d2 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 14 Feb 2019 11:05:13 +0100 Subject: [PATCH 02/37] Small refactor (init set plain instead of with empty list). [CURA-6120] --- cura/Arranging/ArrangeObjectsJob.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Arranging/ArrangeObjectsJob.py b/cura/Arranging/ArrangeObjectsJob.py index 3ec548dbb3..aef051c838 100644 --- a/cura/Arranging/ArrangeObjectsJob.py +++ b/cura/Arranging/ArrangeObjectsJob.py @@ -40,7 +40,7 @@ class ArrangeObjectsJob(Job): arranger = Arrange.create(x = machine_width, y = machine_depth, fixed_nodes = self._fixed_nodes, min_offset = self._min_offset) # Build set to exclude children (those get arranged together with the parents). - included_as_child = set([]) + included_as_child = set() for node in self._nodes: included_as_child.update(node.getAllChildren()) From 0275de7f77665644f1d7dc3710cee598b55e04ab Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Thu, 14 Feb 2019 11:46:41 +0100 Subject: [PATCH 03/37] Make sure to set the build plate so it's not empty Contributes to CL-1250 --- .../src/Cloud/Models/CloudClusterPrinterStatus.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py index a8165ff69c..b641b48235 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py @@ -55,6 +55,7 @@ class CloudClusterPrinterStatus(BaseCloudModel): # \param controller - The controller of the model. def createOutputModel(self, controller: PrinterOutputController) -> PrinterOutputModel: model = PrinterOutputModel(controller, len(self.configuration), firmware_version = self.firmware_version) + model.updateBuildplateName(self.build_plate.type) self.updateOutputModel(model) return model From af565aff551e812a11ec40b7eda05790a213b632 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Thu, 14 Feb 2019 11:49:43 +0100 Subject: [PATCH 04/37] Clarify if using key or human-readable name This is refering to "glass", not "Glass". According to @lipufei, "name" should denote a human readable/capitalized name. Contributes to CL-1250 --- cura/PrinterOutput/PrinterOutputModel.py | 12 ++++++------ .../src/Cloud/Models/CloudClusterPrinterStatus.py | 2 +- .../UM3NetworkPrinting/src/ClusterUM3OutputDevice.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index 4189b9fcbd..e5f06c832c 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -44,7 +44,7 @@ class PrinterOutputModel(QObject): self._printer_state = "unknown" self._is_preheating = False self._printer_type = "" - self._buildplate_name = "" + self._buildplate = "" self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in self._extruders] @@ -86,12 +86,12 @@ class PrinterOutputModel(QObject): @pyqtProperty(str, notify = buildplateChanged) def buildplate(self) -> str: - return self._buildplate_name + return self._buildplate - def updateBuildplateName(self, buildplate_name: str) -> None: - if self._buildplate_name != buildplate_name: - self._buildplate_name = buildplate_name - self._printer_configuration.buildplateConfiguration = self._buildplate_name + def updateBuildplate(self, buildplate_name: str) -> None: + if self._buildplate != buildplate_name: + self._buildplate = buildplate_name + self._printer_configuration.buildplateConfiguration = self._buildplate self.buildplateChanged.emit() self.configurationChanged.emit() diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py index b641b48235..261ab4b389 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py @@ -55,7 +55,7 @@ class CloudClusterPrinterStatus(BaseCloudModel): # \param controller - The controller of the model. def createOutputModel(self, controller: PrinterOutputController) -> PrinterOutputModel: model = PrinterOutputModel(controller, len(self.configuration), firmware_version = self.firmware_version) - model.updateBuildplateName(self.build_plate.type) + model.updateBuildplate(self.build_plate.type) self.updateOutputModel(model) return model diff --git a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py index 790d0c430b..4bbb7ddb5f 100644 --- a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py @@ -627,7 +627,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): # Do not store the build plate information that comes from connect if the current printer has not build plate information if "build_plate" in data and machine_definition.getMetaDataEntry("has_variant_buildplates", False): - printer.updateBuildplateName(data["build_plate"]["type"]) + printer.updateBuildplate(data["build_plate"]["type"]) if not data["enabled"]: printer.updateState("disabled") else: From 10f84184f34fda5d83ef3fa123df0c77e05825e9 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Thu, 14 Feb 2019 11:57:47 +0100 Subject: [PATCH 05/37] Continued clarification on "name" Contributes to CL-1250 --- cura/PrinterOutput/PrinterOutputModel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index e5f06c832c..ea8fca5bac 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -88,9 +88,9 @@ class PrinterOutputModel(QObject): def buildplate(self) -> str: return self._buildplate - def updateBuildplate(self, buildplate_name: str) -> None: - if self._buildplate != buildplate_name: - self._buildplate = buildplate_name + def updateBuildplate(self, buildplate: str) -> None: + if self._buildplate != buildplate: + self._buildplate = buildplate self._printer_configuration.buildplateConfiguration = self._buildplate self.buildplateChanged.emit() self.configurationChanged.emit() From 6ff1f49c347b69e9b280441e4d4bef1698108ea1 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 14 Feb 2019 17:21:54 +0100 Subject: [PATCH 06/37] Ensure the layout gets the right width & height for the output devices popup CURA-6222 --- resources/qml/ActionPanel/OutputDevicesActionButton.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/resources/qml/ActionPanel/OutputDevicesActionButton.qml b/resources/qml/ActionPanel/OutputDevicesActionButton.qml index 3bfaab0fc1..866b8cc627 100644 --- a/resources/qml/ActionPanel/OutputDevicesActionButton.qml +++ b/resources/qml/ActionPanel/OutputDevicesActionButton.qml @@ -90,6 +90,10 @@ Item cornerRadius: 0 hoverColor: UM.Theme.getColor("primary") Layout.fillWidth: true + // The total width of the popup should be defined by the largest button. By stating that each + // button should be minimally the size of it's content (aka; implicitWidth) we can ensure that. + Layout.minimumWidth: implicitWidth + Layout.preferredHeight: widget.height onClicked: { UM.OutputDeviceManager.setActiveDevice(model.id) From 72ce40deba79b498cec694cac65087e3f3a139b3 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 15 Feb 2019 09:36:38 +0100 Subject: [PATCH 07/37] Update buildplate with other props Contributes to CL-1250 --- .../src/Cloud/Models/CloudClusterPrinterStatus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py index 261ab4b389..80c5fc72fc 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py @@ -55,7 +55,6 @@ class CloudClusterPrinterStatus(BaseCloudModel): # \param controller - The controller of the model. def createOutputModel(self, controller: PrinterOutputController) -> PrinterOutputModel: model = PrinterOutputModel(controller, len(self.configuration), firmware_version = self.firmware_version) - model.updateBuildplate(self.build_plate.type) self.updateOutputModel(model) return model @@ -66,6 +65,7 @@ class CloudClusterPrinterStatus(BaseCloudModel): model.updateName(self.friendly_name) model.updateType(self.machine_variant) model.updateState(self.status if self.enabled else "disabled") + model.updateBuildplate(self.build_plate.type) for configuration, extruder_output, extruder_config in \ zip(self.configuration, model.extruders, model.printerConfiguration.extruderConfigurations): From 5d294a7ff50f88485ad6f51f106a1f79c4ccfc7f Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 15 Feb 2019 10:04:39 +0100 Subject: [PATCH 08/37] Handle self.build_plate = None Contributes to CL-1250 --- .../src/Cloud/Models/CloudClusterPrinterStatus.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py index 80c5fc72fc..96310b8229 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py @@ -65,7 +65,13 @@ class CloudClusterPrinterStatus(BaseCloudModel): model.updateName(self.friendly_name) model.updateType(self.machine_variant) model.updateState(self.status if self.enabled else "disabled") - model.updateBuildplate(self.build_plate.type) + + # Make sure to set the build plate even though we don't use it. Since it's optional, use + # glass as a default + if self.build_plate: + model.updateBuildplate(self.build_plate.type) + else: + model.updateBuildplate("glass") for configuration, extruder_output, extruder_config in \ zip(self.configuration, model.extruders, model.printerConfiguration.extruderConfigurations): From 0be6c9e6d37dad83b7c447bffd439a6a2b2bb1e4 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 15 Feb 2019 10:10:39 +0100 Subject: [PATCH 09/37] Expect to always have a build plate Contributes to CL-1250 --- .../src/Cloud/Models/CloudClusterPrinterStatus.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py index 96310b8229..78bf0013f1 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py @@ -48,7 +48,7 @@ class CloudClusterPrinterStatus(BaseCloudModel): self.maintenance_required = maintenance_required self.firmware_update_status = firmware_update_status self.latest_available_firmware = latest_available_firmware - self.build_plate = self.parseModel(CloudClusterBuildPlate, build_plate) if build_plate else None + self.build_plate = self.parseModel(CloudClusterBuildPlate, build_plate) super().__init__(**kwargs) ## Creates a new output model. @@ -65,13 +65,7 @@ class CloudClusterPrinterStatus(BaseCloudModel): model.updateName(self.friendly_name) model.updateType(self.machine_variant) model.updateState(self.status if self.enabled else "disabled") - - # Make sure to set the build plate even though we don't use it. Since it's optional, use - # glass as a default - if self.build_plate: - model.updateBuildplate(self.build_plate.type) - else: - model.updateBuildplate("glass") + model.updateBuildplate(self.build_plate.type) for configuration, extruder_output, extruder_config in \ zip(self.configuration, model.extruders, model.printerConfiguration.extruderConfigurations): From 4feb53fdf485b7932e9749297dbf6c866d196c63 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 15 Feb 2019 10:12:19 +0100 Subject: [PATCH 10/37] Revert "Expect to always have a build plate" This reverts commit 0be6c9e6d37dad83b7c447bffd439a6a2b2bb1e4. --- .../src/Cloud/Models/CloudClusterPrinterStatus.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py index 78bf0013f1..96310b8229 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py @@ -48,7 +48,7 @@ class CloudClusterPrinterStatus(BaseCloudModel): self.maintenance_required = maintenance_required self.firmware_update_status = firmware_update_status self.latest_available_firmware = latest_available_firmware - self.build_plate = self.parseModel(CloudClusterBuildPlate, build_plate) + self.build_plate = self.parseModel(CloudClusterBuildPlate, build_plate) if build_plate else None super().__init__(**kwargs) ## Creates a new output model. @@ -65,7 +65,13 @@ class CloudClusterPrinterStatus(BaseCloudModel): model.updateName(self.friendly_name) model.updateType(self.machine_variant) model.updateState(self.status if self.enabled else "disabled") - model.updateBuildplate(self.build_plate.type) + + # Make sure to set the build plate even though we don't use it. Since it's optional, use + # glass as a default + if self.build_plate: + model.updateBuildplate(self.build_plate.type) + else: + model.updateBuildplate("glass") for configuration, extruder_output, extruder_config in \ zip(self.configuration, model.extruders, model.printerConfiguration.extruderConfigurations): From f7d86667ed81ffd252889802516ed26ad0e220f1 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 15 Feb 2019 10:14:14 +0100 Subject: [PATCH 11/37] Simplify code a bit Contributes to CL-1250 --- .../src/Cloud/Models/CloudClusterPrinterStatus.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py index 96310b8229..6193fd0c26 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py @@ -65,13 +65,7 @@ class CloudClusterPrinterStatus(BaseCloudModel): model.updateName(self.friendly_name) model.updateType(self.machine_variant) model.updateState(self.status if self.enabled else "disabled") - - # Make sure to set the build plate even though we don't use it. Since it's optional, use - # glass as a default - if self.build_plate: - model.updateBuildplate(self.build_plate.type) - else: - model.updateBuildplate("glass") + model.updateBuildplate(self.build_plate.type or "glass") for configuration, extruder_output, extruder_config in \ zip(self.configuration, model.extruders, model.printerConfiguration.extruderConfigurations): From 0a945b8ba5fc726a7695826ea58624cab8948f28 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 15 Feb 2019 10:18:47 +0100 Subject: [PATCH 12/37] Code style Contributes to CL-1250 --- .../src/Cloud/Models/CloudClusterPrinterStatus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py index 6193fd0c26..bd3e482bde 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py @@ -65,7 +65,7 @@ class CloudClusterPrinterStatus(BaseCloudModel): model.updateName(self.friendly_name) model.updateType(self.machine_variant) model.updateState(self.status if self.enabled else "disabled") - model.updateBuildplate(self.build_plate.type or "glass") + model.updateBuildplate(self.build_plate.type if self.build_plate else "glass") for configuration, extruder_output, extruder_config in \ zip(self.configuration, model.extruders, model.printerConfiguration.extruderConfigurations): From 21099e30cb666d65b53fbcd749793ce6f5fe6776 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 15 Feb 2019 12:51:40 +0100 Subject: [PATCH 13/37] Check if on main thread for call_on_qt_thread CURA-6225 --- cura/Utils/Threading.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cura/Utils/Threading.py b/cura/Utils/Threading.py index 3cd6200513..c57a452b72 100644 --- a/cura/Utils/Threading.py +++ b/cura/Utils/Threading.py @@ -31,4 +31,10 @@ def call_on_qt_thread(func): CuraApplication.getInstance().callLater(_handle_call, *new_args, **kwargs) inter_call_object.finish_event.wait() return inter_call_object.result + + # If the current thread is the main thread, which is the Qt thread, directly return the function. + current_thread = threading.current_thread() + if isinstance(current_thread, threading._MainThread): + return func + return _call_on_qt_thread_wrapper From 2d1d0c2d374726ccb352512bc1dc67b3bd3f8619 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 15 Feb 2019 13:39:40 +0100 Subject: [PATCH 14/37] Remove unused image --- resources/qml/Account/AvatarImage.qml | 1 - .../cura-light/images/avatar_default.svg | 76 ------------------- 2 files changed, 77 deletions(-) delete mode 100644 resources/themes/cura-light/images/avatar_default.svg diff --git a/resources/qml/Account/AvatarImage.qml b/resources/qml/Account/AvatarImage.qml index bcbc9f0542..a4f922a10d 100644 --- a/resources/qml/Account/AvatarImage.qml +++ b/resources/qml/Account/AvatarImage.qml @@ -22,7 +22,6 @@ Item { id: profileImage anchors.fill: parent - source: UM.Theme.getImage("avatar_default") fillMode: Image.PreserveAspectCrop visible: false mipmap: true diff --git a/resources/themes/cura-light/images/avatar_default.svg b/resources/themes/cura-light/images/avatar_default.svg deleted file mode 100644 index 7ec704bc8c..0000000000 --- a/resources/themes/cura-light/images/avatar_default.svg +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - From f3cab17e2001cc5d372f643d85f627fc3b85cec2 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 15 Feb 2019 13:44:06 +0100 Subject: [PATCH 15/37] Revert "Check if on main thread for call_on_qt_thread" This reverts commit 21099e30cb666d65b53fbcd749793ce6f5fe6776. It was causing a segfault when saving UFP files. --- cura/Utils/Threading.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cura/Utils/Threading.py b/cura/Utils/Threading.py index c57a452b72..3cd6200513 100644 --- a/cura/Utils/Threading.py +++ b/cura/Utils/Threading.py @@ -31,10 +31,4 @@ def call_on_qt_thread(func): CuraApplication.getInstance().callLater(_handle_call, *new_args, **kwargs) inter_call_object.finish_event.wait() return inter_call_object.result - - # If the current thread is the main thread, which is the Qt thread, directly return the function. - current_thread = threading.current_thread() - if isinstance(current_thread, threading._MainThread): - return func - return _call_on_qt_thread_wrapper From ef73453874886b0b6a8820bb757652891e9740e3 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 15 Feb 2019 14:36:58 +0100 Subject: [PATCH 16/37] Fix height in the about dialog to show the logo correctly --- resources/qml/Dialogs/AboutDialog.qml | 54 ++++++++++++++------------ resources/themes/cura-light/theme.json | 2 +- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/resources/qml/Dialogs/AboutDialog.qml b/resources/qml/Dialogs/AboutDialog.qml index 604cbc16ba..584903dd60 100644 --- a/resources/qml/Dialogs/AboutDialog.qml +++ b/resources/qml/Dialogs/AboutDialog.qml @@ -21,41 +21,45 @@ UM.Dialog Rectangle { + id: header width: parent.width + 2 * margin // margin from Dialog.qml - height: version.y + version.height + margin + height: childrenRect.height + topPadding anchors.top: parent.top - anchors.topMargin: - margin + anchors.topMargin: -margin anchors.horizontalCenter: parent.horizontalCenter + property real topPadding: UM.Theme.getSize("wide_margin").height + color: UM.Theme.getColor("main_window_header_background") - } - Image - { - id: logo - width: (base.minimumWidth * 0.85) | 0 - source: UM.Theme.getImage("logo") - sourceSize.width: width - sourceSize.height: height + Image + { + id: logo + width: (base.minimumWidth * 0.85) | 0 + height: (width * (UM.Theme.getSize("logo").height / UM.Theme.getSize("logo").width)) | 0 + source: UM.Theme.getImage("logo") + sourceSize.width: width + sourceSize.height: height - anchors.top: parent.top - anchors.topMargin: ((base.minimumWidth - width) / 2) | 0 - anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: parent.topPadding + anchors.horizontalCenter: parent.horizontalCenter - UM.I18nCatalog{id: catalog; name: "cura"} - } + UM.I18nCatalog{id: catalog; name: "cura"} + } - Label - { - id: version + Label + { + id: version - text: catalog.i18nc("@label","version: %1").arg(UM.Application.version) - font: UM.Theme.getFont("large_bold") - color: UM.Theme.getColor("button_text") - anchors.right : logo.right - anchors.top: logo.bottom - anchors.topMargin: (UM.Theme.getSize("default_margin").height / 2) | 0 + text: catalog.i18nc("@label","version: %1").arg(UM.Application.version) + font: UM.Theme.getFont("large_bold") + color: UM.Theme.getColor("button_text") + anchors.right : logo.right + anchors.top: logo.bottom + anchors.topMargin: (UM.Theme.getSize("default_margin").height / 2) | 0 + } } Label @@ -67,7 +71,7 @@ UM.Dialog text: catalog.i18nc("@label","End-to-end solution for fused filament 3D printing.") font: UM.Theme.getFont("system") wrapMode: Text.WordWrap - anchors.top: version.bottom + anchors.top: header.bottom anchors.topMargin: UM.Theme.getSize("default_margin").height } diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 573fe8bcfa..92308537dd 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -473,7 +473,7 @@ "default_lining": [0.08, 0.08], "default_arrow": [0.8, 0.8], - "logo": [8, 2.4], + "logo": [8, 1.75], "wide_margin": [2.0, 2.0], "thick_margin": [1.71, 1.43], From f521fae1525f5f7cf2479e5e38444995abb9bb8d Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 15 Feb 2019 15:11:09 +0100 Subject: [PATCH 17/37] Fix call_on_qt_thread decorator CURA-6225 Do thread check in the wrapper function, not outside. --- cura/Utils/Threading.py | 9 ++++++++- plugins/UFPWriter/UFPWriter.py | 6 +++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/cura/Utils/Threading.py b/cura/Utils/Threading.py index 3cd6200513..550a5421ff 100644 --- a/cura/Utils/Threading.py +++ b/cura/Utils/Threading.py @@ -1,3 +1,4 @@ +import functools import threading from cura.CuraApplication import CuraApplication @@ -6,7 +7,7 @@ from cura.CuraApplication import CuraApplication # # HACK: # -# In project loading, when override the existing machine is selected, the stacks and containers that are correctly +# In project loading, when override the existing machine is selected, the stacks and containers that are currently # active in the system will be overridden at runtime. Because the project loading is done in a different thread than # the Qt thread, something else can kick in the middle of the process. One of them is the rendering. It will access # the current stacks and container, which have not completely been updated yet, so Cura will crash in this case. @@ -22,7 +23,13 @@ class InterCallObject: def call_on_qt_thread(func): + @functools.wraps(func) def _call_on_qt_thread_wrapper(*args, **kwargs): + # If the current thread is the main thread, which is the Qt thread, directly call the function. + current_thread = threading.current_thread() + if isinstance(current_thread, threading._MainThread): + return func(*args, **kwargs) + def _handle_call(ico, *args, **kwargs): ico.result = func(*args, **kwargs) ico.finish_event.set() diff --git a/plugins/UFPWriter/UFPWriter.py b/plugins/UFPWriter/UFPWriter.py index c0db104c82..a686837b30 100644 --- a/plugins/UFPWriter/UFPWriter.py +++ b/plugins/UFPWriter/UFPWriter.py @@ -44,8 +44,12 @@ class UFPWriter(MeshWriter): # trigger loading other containers. Because those loaded containers are QtObjects, they must be created on the # Qt thread. The File read/write operations right now are executed on separated threads because they are scheduled # by the Job class. - @call_on_qt_thread def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode): + print("------------> self _write = ", self._write) + return self._write(stream, nodes, mode = mode) + + @call_on_qt_thread + def _write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode): archive = VirtualFile() archive.openStream(stream, "application/x-ufp", OpenMode.WriteOnly) From 3e819f56fc96d541c6d7a9b1c33057789541f25d Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 15 Feb 2019 15:14:10 +0100 Subject: [PATCH 18/37] Remove debug code CURA-6225 --- plugins/UFPWriter/UFPWriter.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/UFPWriter/UFPWriter.py b/plugins/UFPWriter/UFPWriter.py index a686837b30..c0db104c82 100644 --- a/plugins/UFPWriter/UFPWriter.py +++ b/plugins/UFPWriter/UFPWriter.py @@ -44,12 +44,8 @@ class UFPWriter(MeshWriter): # trigger loading other containers. Because those loaded containers are QtObjects, they must be created on the # Qt thread. The File read/write operations right now are executed on separated threads because they are scheduled # by the Job class. - def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode): - print("------------> self _write = ", self._write) - return self._write(stream, nodes, mode = mode) - @call_on_qt_thread - def _write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode): + def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode): archive = VirtualFile() archive.openStream(stream, "application/x-ufp", OpenMode.WriteOnly) From e80eccaea32cec6cd1192fc643cadf08d28851ac Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Fri, 15 Feb 2019 16:46:47 +0100 Subject: [PATCH 19/37] Manually create printer properties for cloud output device --- .../src/Cloud/CloudOutputDevice.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py index 33968beb6d..223d33c7b8 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py @@ -61,8 +61,19 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice): # \param cluster: The device response received from the cloud API. # \param parent: The optional parent of this output device. def __init__(self, api_client: CloudApiClient, cluster: CloudClusterResponse, parent: QObject = None) -> None: + + # The following properties are expected on each networked output device. + # Because the cloud connection does not off all of these, we manually construct this version here. + # An example of why this is needed is the selection of the compatible file type when exporting the tool path. + properties = { + b"address": b"", + b"name": cluster.host_name.encode(), + b"firmware_version": cluster.host_version.encode(), + b"printer_type": b"" + } + super().__init__(device_id = cluster.cluster_id, address = "", - connection_type = ConnectionType.CloudConnection, properties = {}, parent = parent) + connection_type = ConnectionType.CloudConnection, properties = properties, parent = parent) self._api = api_client self._cluster = cluster From 119c868650fa68b1c0d9b1555a9b9af46f300c8f Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Fri, 15 Feb 2019 16:52:11 +0100 Subject: [PATCH 20/37] Add a test for the device properties --- .../tests/Cloud/TestCloudOutputDevice.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py index 14b1f4feba..418e3fe5cb 100644 --- a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py @@ -21,6 +21,7 @@ class TestCloudOutputDevice(TestCase): JOB_ID = "ABCDefGHIjKlMNOpQrSTUvYxWZ0-1234567890abcDE=" HOST_NAME = "ultimakersystem-ccbdd30044ec" HOST_GUID = "e90ae0ac-1257-4403-91ee-a44c9b7e8050" + HOST_VERSION = "5.2.0" STATUS_URL = "{}/connect/v1/clusters/{}/status".format(CuraCloudAPIRoot, CLUSTER_ID) PRINT_URL = "{}/connect/v1/clusters/{}/print/{}".format(CuraCloudAPIRoot, CLUSTER_ID, JOB_ID) @@ -36,7 +37,7 @@ class TestCloudOutputDevice(TestCase): patched_method.start() self.cluster = CloudClusterResponse(self.CLUSTER_ID, self.HOST_GUID, self.HOST_NAME, is_online=True, - status="active") + status="active", host_version=self.HOST_VERSION) self.network = NetworkManagerMock() self.account = MagicMock(isLoggedIn=True, accessToken="TestAccessToken") @@ -56,6 +57,11 @@ class TestCloudOutputDevice(TestCase): for patched_method in self.patches: patched_method.stop() + # We test for these in order to make sure the correct file type is selected depending on the firmware version. + def test_properties(self): + self.assertEqual(self.device.firmwareVersion, self.HOST_VERSION) + self.assertEqual(self.device.name, self.HOST_NAME) + def test_status(self): self.device._update() self.network.flushReplies() From 897c932e8002900691da3b2884187b964ae2de51 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Fri, 15 Feb 2019 17:03:38 +0100 Subject: [PATCH 21/37] Fix test for cloud output device ufp uploading --- .../tests/Cloud/TestCloudOutputDevice.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py index 418e3fe5cb..6a0b87c4da 100644 --- a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py @@ -120,7 +120,7 @@ class TestCloudOutputDevice(TestCase): def test_print_to_cloud(self): active_machine_mock = self.app.getGlobalContainerStack.return_value - active_machine_mock.getMetaDataEntry.side_effect = {"file_formats": "application/gzip"}.get + active_machine_mock.getMetaDataEntry.side_effect = {"file_formats": "application/x-ufp"}.get request_upload_response = parseFixture("putJobUploadResponse") request_print_response = parseFixture("postJobPrintResponse") @@ -130,6 +130,10 @@ class TestCloudOutputDevice(TestCase): file_handler = MagicMock() file_handler.getSupportedFileTypesWrite.return_value = [{ + "extension": "ufp", + "mime_type": "application/x-ufp", + "mode": 2 + }, { "extension": "gcode.gz", "mime_type": "application/gzip", "mode": 2, @@ -138,15 +142,11 @@ class TestCloudOutputDevice(TestCase): lambda stream, nodes: stream.write(str(nodes).encode()) scene_nodes = [SceneNode()] - expected_mesh = str(scene_nodes).encode() self.device.requestWrite(scene_nodes, file_handler=file_handler, file_name="FileName") self.network.flushReplies() self.assertEqual( - {"data": {"content_type": "application/gzip", "file_size": len(expected_mesh), "job_name": "FileName"}}, + {"data": {"content_type": "application/x-ufp", "job_name": "FileName"}}, json.loads(self.network.getRequestBody("PUT", self.REQUEST_UPLOAD_URL).decode()) ) - self.assertEqual(expected_mesh, - self.network.getRequestBody("PUT", request_upload_response["data"]["upload_url"])) - self.assertIsNone(self.network.getRequestBody("POST", self.PRINT_URL)) From 3f11cb911dcd5ac5e4a5524df83f9ef7a0b063a1 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Fri, 15 Feb 2019 17:06:32 +0100 Subject: [PATCH 22/37] Add filesize back to expected result --- plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py index 6a0b87c4da..c0b4919314 100644 --- a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py @@ -146,7 +146,7 @@ class TestCloudOutputDevice(TestCase): self.network.flushReplies() self.assertEqual( - {"data": {"content_type": "application/x-ufp", "job_name": "FileName"}}, + {"data": {"content_type": "application/x-ufp", "file_size": 52, "job_name": "FileName"}}, json.loads(self.network.getRequestBody("PUT", self.REQUEST_UPLOAD_URL).decode()) ) self.assertIsNone(self.network.getRequestBody("POST", self.PRINT_URL)) From 11cf409d71f550c88ae457f57d444930196199e5 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Fri, 15 Feb 2019 17:07:45 +0100 Subject: [PATCH 23/37] Fix codestyle --- plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py index 223d33c7b8..7b5add276a 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py @@ -67,8 +67,8 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice): # An example of why this is needed is the selection of the compatible file type when exporting the tool path. properties = { b"address": b"", - b"name": cluster.host_name.encode(), - b"firmware_version": cluster.host_version.encode(), + b"name": cluster.host_name.encode() if cluster.host_name else b"", + b"firmware_version": cluster.host_version.encode() if cluster.host_version else b"", b"printer_type": b"" } From f25fefdbcb5121ed696d4d0b1de0879e13f60dd5 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Fri, 15 Feb 2019 17:24:03 +0100 Subject: [PATCH 24/37] Add expected mesh back to test --- .../UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py index c0b4919314..c4d891302e 100644 --- a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py @@ -142,11 +142,14 @@ class TestCloudOutputDevice(TestCase): lambda stream, nodes: stream.write(str(nodes).encode()) scene_nodes = [SceneNode()] + expected_mesh = str(scene_nodes).encode() self.device.requestWrite(scene_nodes, file_handler=file_handler, file_name="FileName") self.network.flushReplies() self.assertEqual( - {"data": {"content_type": "application/x-ufp", "file_size": 52, "job_name": "FileName"}}, + {"data": {"content_type": "application/x-ufp", "file_size": len(expected_mesh), "job_name": "FileName"}}, json.loads(self.network.getRequestBody("PUT", self.REQUEST_UPLOAD_URL).decode()) ) + self.assertEqual(expected_mesh, + self.network.getRequestBody("PUT", request_upload_response["data"]["upload_url"])) self.assertIsNone(self.network.getRequestBody("POST", self.PRINT_URL)) From 222f8e6cdb11c3dfe44cbaa07cec8035ad9424d4 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 18 Feb 2019 10:38:39 +0100 Subject: [PATCH 25/37] Prevent crash for backup plugin if there is no internet connection --- plugins/CuraDrive/src/DriveApiService.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index 7c1f8faa83..6a828e32d6 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -40,10 +40,13 @@ class DriveApiService: if not access_token: Logger.log("w", "Could not get access token.") return [] - - backup_list_request = requests.get(self.BACKUP_URL, headers = { - "Authorization": "Bearer {}".format(access_token) - }) + try: + backup_list_request = requests.get(self.BACKUP_URL, headers = { + "Authorization": "Bearer {}".format(access_token) + }) + except requests.exceptions.ConnectionError: + Logger.log("w", "Unable to connect with the server.") + return [] # HTTP status 300s mean redirection. 400s and 500s are errors. # Technically 300s are not errors, but the use case here relies on "requests" to handle redirects automatically. From 05b5dfa05fea1e5deb12f3b630ad42856257e264 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Tue, 19 Feb 2019 10:13:05 +0100 Subject: [PATCH 26/37] Improve "don't ask me again" checkbox Contributes to CL-1222 --- plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index e57cd15960..942b417b10 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -491,11 +491,12 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): active_machine.setMetaDataEntry("do_not_show_cloud_message", True) return - def _onDontAskMeAgain(self, messageId: str) -> None: + def _onDontAskMeAgain(self, checked: bool) -> None: active_machine = self._application.getMachineManager().activeMachine # type: Optional["GlobalStack"] if active_machine: - active_machine.setMetaDataEntry("do_not_show_cloud_message", True) - Logger.log("d", "Will not ask the user again to cloud connect for current printer.") + active_machine.setMetaDataEntry("do_not_show_cloud_message", checked) + if checked: + Logger.log("d", "Will not ask the user again to cloud connect for current printer.") return def _onCloudFlowStarted(self, messageId: str, actionId: str) -> None: From 70cd7518ed477ac09c7fa66e84ff34b1fa17bf4b Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 19 Feb 2019 10:47:21 +0100 Subject: [PATCH 27/37] Adjust description of Skin Overlap (Percentage). [CURA-6140] --- resources/definitions/fdmprinter.def.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 407923fb4e..8bb6e4983b 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -1792,7 +1792,7 @@ "skin_overlap": { "label": "Skin Overlap Percentage", - "description": "The amount of overlap between the skin and the walls as a percentage of the skin line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall.", + "description": "Adjust the position where skin is extruded relative to the walls, as a percentage of the line widths of the skin lines and the innermost wall. A slight overlap allows the walls to connect firmly to the skin. Note that, given an equal skin and wall line-width, any percentage over 50% may already cause any skin to go past the wall, because at that point the position of the nozzle of the skin-extruder may already reach past the middle of the wall.", "unit": "%", "type": "float", "default_value": 5, @@ -1807,7 +1807,7 @@ "skin_overlap_mm": { "label": "Skin Overlap", - "description": "The amount of overlap between the skin and the walls. A slight overlap allows the walls to connect firmly to the skin.", + "description": "Adjust the position where skin is extruded relative to the walls. A slight overlap allows the walls to connect firmly to the skin. Note that, given an equal skin and wall line-width, any value over half the width of the wall may already cause any skin to go past the wall, because at that point the position of the nozzle of the skin-extruder may already reach past the middle of the wall.", "unit": "mm", "type": "float", "default_value": 0.02, From c35aabd2c40cd65c01f2443927b04722aed93f85 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Feb 2019 13:34:04 +0100 Subject: [PATCH 28/37] Add message to indicate that Cura was unable to connect with account server This happens when you were logged previously, but currently don't have internet acces. CURA-6231 --- cura/API/Account.py | 3 +++ cura/OAuth2/AuthorizationService.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/cura/API/Account.py b/cura/API/Account.py index 8a8b708cfa..30401454b3 100644 --- a/cura/API/Account.py +++ b/cura/API/Account.py @@ -76,6 +76,9 @@ class Account(QObject): self._error_message.hide() self._error_message = Message(error_message, title = i18n_catalog.i18nc("@info:title", "Login failed")) self._error_message.show() + self._logged_in = False + self.loginStateChanged.emit(False) + return if self._logged_in != logged_in: self._logged_in = logged_in diff --git a/cura/OAuth2/AuthorizationService.py b/cura/OAuth2/AuthorizationService.py index a76e8cf304..355db1f1a0 100644 --- a/cura/OAuth2/AuthorizationService.py +++ b/cura/OAuth2/AuthorizationService.py @@ -9,12 +9,16 @@ import requests.exceptions from UM.Logger import Logger +from UM.Message import Message from UM.Signal import Signal from cura.OAuth2.LocalAuthorizationServer import LocalAuthorizationServer from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers, TOKEN_TIMESTAMP_FORMAT from cura.OAuth2.Models import AuthenticationResponse +from UM.i18n import i18nCatalog +i18n_catalog = i18nCatalog("cura") + if TYPE_CHECKING: from cura.OAuth2.Models import UserProfile, OAuth2Settings from UM.Preferences import Preferences @@ -41,6 +45,14 @@ class AuthorizationService: self._preferences = preferences self._server = LocalAuthorizationServer(self._auth_helpers, self._onAuthStateChanged, daemon=True) + self._unable_to_get_data_message = None + + self.onAuthStateChanged.connect(self._authChanged) + + def _authChanged(self, logged_in): + if logged_in and self._unable_to_get_data_message is not None: + self._unable_to_get_data_message.hide() + def initialize(self, preferences: Optional["Preferences"] = None) -> None: if preferences is not None: self._preferences = preferences @@ -162,7 +174,18 @@ class AuthorizationService: preferences_data = json.loads(self._preferences.getValue(self._settings.AUTH_DATA_PREFERENCE_KEY)) if preferences_data: self._auth_data = AuthenticationResponse(**preferences_data) - self.onAuthStateChanged.emit(logged_in=True) + # Also check if we can actually get the user profile information. + user_profile = self.getUserProfile() + if user_profile is not None: + self.onAuthStateChanged.emit(logged_in=True) + else: + if self._unable_to_get_data_message is not None: + self._unable_to_get_data_message.hide() + + self._unable_to_get_data_message = Message(i18n_catalog.i18nc("@info", "Unable to reach the Ultimaker account server."), title = i18n_catalog.i18nc("@info:title", "Warning")) + self._unable_to_get_data_message.addAction("retry", i18n_catalog.i18nc("@action:button", "Retry"), "[no_icon]", "[no_description]") + self._unable_to_get_data_message.actionTriggered.connect(self._onMessageActionTriggered) + self._unable_to_get_data_message.show() except ValueError: Logger.logException("w", "Could not load auth data from preferences") @@ -179,3 +202,7 @@ class AuthorizationService: else: self._user_profile = None self._preferences.resetPreference(self._settings.AUTH_DATA_PREFERENCE_KEY) + + def _onMessageActionTriggered(self, _, action): + if action == "retry": + self.loadAuthDataFromPreferences() From d53bac6eb80f0642028760569cfbb23c0fee2b88 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Feb 2019 15:42:51 +0100 Subject: [PATCH 29/37] Simplify the expensive call to the validation state Since the support angle needs to be between 0 and 90, we don't have to rely on the (expensive) validator. We can just check that ourselves. --- plugins/SolidView/SolidView.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 797d6dabec..ec00329f86 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -69,8 +69,7 @@ class SolidView(View): if support_angle_stack is not None and Application.getInstance().getPreferences().getValue("view/show_overhang"): angle = support_angle_stack.getProperty("support_angle", "value") # Make sure the overhang angle is valid before passing it to the shader - # Note: if the overhang angle is set to its default value, it does not need to get validated (validationState = None) - if angle is not None and global_container_stack.getProperty("support_angle", "validationState") in [None, ValidatorState.Valid]: + if angle is not None and angle >= 0 and angle <= 90: self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - angle))) else: self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) #Overhang angle of 0 causes no area at all to be marked as overhang. From 6143906fa403b1d94876b9d651154047912ec33b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 19 Feb 2019 15:44:31 +0100 Subject: [PATCH 30/37] Add type for message saying it's unable to connect Contributes to issue CURA-6231. --- cura/OAuth2/AuthorizationService.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cura/OAuth2/AuthorizationService.py b/cura/OAuth2/AuthorizationService.py index 355db1f1a0..3c2f66d037 100644 --- a/cura/OAuth2/AuthorizationService.py +++ b/cura/OAuth2/AuthorizationService.py @@ -1,5 +1,6 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. + import json import webbrowser from datetime import datetime, timedelta @@ -45,7 +46,7 @@ class AuthorizationService: self._preferences = preferences self._server = LocalAuthorizationServer(self._auth_helpers, self._onAuthStateChanged, daemon=True) - self._unable_to_get_data_message = None + self._unable_to_get_data_message = None # type: Optional[Message] self.onAuthStateChanged.connect(self._authChanged) From 22b70e1a345361674c9fb34fa97c62d807606062 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Feb 2019 17:06:48 +0100 Subject: [PATCH 31/37] Ignore sceneChange signals if the camera triggered them in some places These places weren't interested in all changes. They can probably be more strict, but the camera is a really safe bet (and already greatly decreases the number of updates, especially when just moving the camera) --- cura/BuildVolume.py | 11 ++++++----- cura/Machines/Models/MultiBuildPlateModel.py | 4 +++- plugins/ModelChecker/ModelChecker.py | 8 +++++++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index fb13a32732..d00a28c2bb 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -1,6 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. - +from UM.Scene.Camera import Camera from cura.Scene.CuraSceneNode import CuraSceneNode from cura.Settings.ExtruderManager import ExtruderManager from UM.Application import Application #To modify the maximum zoom level. @@ -112,8 +112,6 @@ class BuildVolume(SceneNode): self._setting_change_timer.setSingleShot(True) self._setting_change_timer.timeout.connect(self._onSettingChangeTimerFinished) - - # Must be after setting _build_volume_message, apparently that is used in getMachineManager. # activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality. # Therefore this works. @@ -131,7 +129,9 @@ class BuildVolume(SceneNode): def _onSceneChanged(self, source): if self._global_container_stack: - self._scene_change_timer.start() + # Just ignore the camera scene updates. The build volume won't change because of it! + if not isinstance(source, Camera): + self._scene_change_timer.start() def _onSceneChangeTimerFinished(self): root = self._application.getController().getScene().getRoot() @@ -148,7 +148,7 @@ class BuildVolume(SceneNode): if active_extruder_changed is not None: node.callDecoration("getActiveExtruderChangedSignal").disconnect(self._updateDisallowedAreasAndRebuild) node.decoratorsChanged.disconnect(self._updateNodeListeners) - self._updateDisallowedAreasAndRebuild() # make sure we didn't miss anything before we updated the node listeners + self.rebuild() self._scene_objects = new_scene_objects self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered. @@ -667,6 +667,7 @@ class BuildVolume(SceneNode): # ``_updateDisallowedAreas`` method itself shouldn't call ``rebuild``, # since there may be other changes before it needs to be rebuilt, which # would hit performance. + def _updateDisallowedAreasAndRebuild(self): self._updateDisallowedAreas() self._updateRaftThickness() diff --git a/cura/Machines/Models/MultiBuildPlateModel.py b/cura/Machines/Models/MultiBuildPlateModel.py index 958e93837a..22796b4759 100644 --- a/cura/Machines/Models/MultiBuildPlateModel.py +++ b/cura/Machines/Models/MultiBuildPlateModel.py @@ -4,6 +4,7 @@ from PyQt5.QtCore import QTimer, pyqtSignal, pyqtProperty from UM.Application import Application +from UM.Scene.Camera import Camera from UM.Scene.Selection import Selection from UM.Qt.ListModel import ListModel @@ -51,7 +52,8 @@ class MultiBuildPlateModel(ListModel): return self._active_build_plate def _updateSelectedObjectBuildPlateNumbersDelayed(self, *args): - self._update_timer.start() + if not isinstance(args[0], Camera): + self._update_timer.start() def _updateSelectedObjectBuildPlateNumbers(self, *args): result = set() diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index d2c2eefac2..1913f39d96 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -9,6 +9,7 @@ from UM.Application import Application from UM.Extension import Extension from UM.Logger import Logger from UM.Message import Message +from UM.Scene.Camera import Camera from UM.i18n import i18nCatalog from UM.PluginRegistry import PluginRegistry from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator @@ -35,7 +36,12 @@ class ModelChecker(QObject, Extension): ## Pass-through to allow UM.Signal to connect with a pyqtSignal. def _onChanged(self, *args, **kwargs): - self.onChanged.emit() + # Ignore camera updates. + if len(args) == 0: + self.onChanged.emit() + return + if not isinstance(args[0], Camera): + self.onChanged.emit() ## Called when plug-ins are initialized. # From 3ad916b5654725f19374c95ea28d3f6413c6d98d Mon Sep 17 00:00:00 2001 From: Simon Edwards Date: Tue, 19 Feb 2019 17:07:09 +0100 Subject: [PATCH 32/37] Fix the path to the pop up images in the monitor tab. --- plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 942b417b10..bece666829 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -455,7 +455,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._start_cloud_flow_message = Message( text = i18n_catalog.i18nc("@info:status", "Send and monitor print jobs from anywhere using your Ultimaker account."), lifetime = 0, - image_source = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "resources", "svg", + image_source = "file:///" + os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "resources", "svg", "cloud-flow-start.svg"), image_caption = i18n_catalog.i18nc("@info:status", "Connect to Ultimaker Cloud"), option_text = i18n_catalog.i18nc("@action", "Don't ask me again for this printer."), @@ -477,7 +477,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._cloud_flow_complete_message = Message( text = i18n_catalog.i18nc("@info:status", "You can now send and monitor print jobs from anywhere using your Ultimaker account."), lifetime = 30, - image_source = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "resources", "svg", + image_source = "file:///" + os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "resources", "svg", "cloud-flow-completed.svg"), image_caption = i18n_catalog.i18nc("@info:status", "Connected!") ) @@ -522,4 +522,4 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._cloud_flow_complete_message.hide() self._cloud_flow_complete_message = None - self.checkCloudFlowIsPossible() \ No newline at end of file + self.checkCloudFlowIsPossible() From aef89e2bd3fb5f9faff274be648a9bb2a9ce1c98 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Feb 2019 17:40:44 +0100 Subject: [PATCH 33/37] Add few more cases where Camera changes caused un-needed updates --- cura/ObjectsModel.py | 14 ++++++++++---- cura/Scene/CuraSceneController.py | 3 ++- plugins/CuraEngineBackend/CuraEngineBackend.py | 3 ++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/cura/ObjectsModel.py b/cura/ObjectsModel.py index 8354540783..f9f923b31d 100644 --- a/cura/ObjectsModel.py +++ b/cura/ObjectsModel.py @@ -5,6 +5,7 @@ from PyQt5.QtCore import QTimer from UM.Application import Application from UM.Qt.ListModel import ListModel +from UM.Scene.Camera import Camera from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.SceneNode import SceneNode from UM.Scene.Selection import Selection @@ -19,19 +20,24 @@ class ObjectsModel(ListModel): def __init__(self): super().__init__() - Application.getInstance().getController().getScene().sceneChanged.connect(self._updateDelayed) + Application.getInstance().getController().getScene().sceneChanged.connect(self._updateSceneDelayed) Application.getInstance().getPreferences().preferenceChanged.connect(self._updateDelayed) self._update_timer = QTimer() - self._update_timer.setInterval(100) + self._update_timer.setInterval(200) self._update_timer.setSingleShot(True) self._update_timer.timeout.connect(self._update) self._build_plate_number = -1 def setActiveBuildPlate(self, nr): - self._build_plate_number = nr - self._update() + if self._build_plate_number != nr: + self._build_plate_number = nr + self._update() + + def _updateSceneDelayed(self, source): + if not isinstance(source, Camera): + self._update_timer.start() def _updateDelayed(self, *args): self._update_timer.start() diff --git a/cura/Scene/CuraSceneController.py b/cura/Scene/CuraSceneController.py index 4b19271538..9f26ea7cc3 100644 --- a/cura/Scene/CuraSceneController.py +++ b/cura/Scene/CuraSceneController.py @@ -3,6 +3,7 @@ from UM.Logger import Logger from PyQt5.QtCore import Qt, pyqtSlot, QObject from PyQt5.QtWidgets import QApplication +from UM.Scene.Camera import Camera from cura.ObjectsModel import ObjectsModel from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel @@ -33,7 +34,7 @@ class CuraSceneController(QObject): source = args[0] else: source = None - if not isinstance(source, SceneNode): + if not isinstance(source, SceneNode) or isinstance(source, Camera): return max_build_plate = self._calcMaxBuildPlate() changed = False diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index d5531a2773..aac79f6c28 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -10,6 +10,7 @@ from time import time from typing import Any, cast, Dict, List, Optional, Set, TYPE_CHECKING from UM.Backend.Backend import Backend, BackendState +from UM.Scene.Camera import Camera from UM.Scene.SceneNode import SceneNode from UM.Signal import Signal from UM.Logger import Logger @@ -476,7 +477,7 @@ class CuraEngineBackend(QObject, Backend): # # \param source The scene node that was changed. def _onSceneChanged(self, source: SceneNode) -> None: - if not isinstance(source, SceneNode): + if not isinstance(source, SceneNode) or isinstance(source, Camera): return # This case checks if the source node is a node that contains GCode. In this case the From b61bf3df4d7cbfc695459b472927f175f48f8740 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Feb 2019 17:46:29 +0100 Subject: [PATCH 34/37] Only emit signal if the properties actually changed --- cura/Machines/Models/MultiBuildPlateModel.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cura/Machines/Models/MultiBuildPlateModel.py b/cura/Machines/Models/MultiBuildPlateModel.py index 22796b4759..add960a545 100644 --- a/cura/Machines/Models/MultiBuildPlateModel.py +++ b/cura/Machines/Models/MultiBuildPlateModel.py @@ -35,8 +35,9 @@ class MultiBuildPlateModel(ListModel): self._active_build_plate = -1 def setMaxBuildPlate(self, max_build_plate): - self._max_build_plate = max_build_plate - self.maxBuildPlateChanged.emit() + if self._max_build_plate != max_build_plate: + self._max_build_plate = max_build_plate + self.maxBuildPlateChanged.emit() ## Return the highest build plate number @pyqtProperty(int, notify = maxBuildPlateChanged) @@ -44,8 +45,9 @@ class MultiBuildPlateModel(ListModel): return self._max_build_plate def setActiveBuildPlate(self, nr): - self._active_build_plate = nr - self.activeBuildPlateChanged.emit() + if self._active_build_plate != nr: + self._active_build_plate = nr + self.activeBuildPlateChanged.emit() @pyqtProperty(int, notify = activeBuildPlateChanged) def activeBuildPlate(self): From 3f18b0b974ad8cb297119a22217cef7db9c23f4e Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Feb 2019 18:00:20 +0100 Subject: [PATCH 35/37] Increase agressiveness of short circuit logic; Just check if the node is slicable That should also filter out platform, buildVolume and convexHulLNodes --- cura/BuildVolume.py | 4 ++-- cura/PlatformPhysics.py | 4 ++-- plugins/CuraEngineBackend/CuraEngineBackend.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index d00a28c2bb..aa6b7fb63c 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -129,8 +129,8 @@ class BuildVolume(SceneNode): def _onSceneChanged(self, source): if self._global_container_stack: - # Just ignore the camera scene updates. The build volume won't change because of it! - if not isinstance(source, Camera): + # Ignore anything that is not something we can slice in the first place! + if source.callDecoration("isSliceable"): self._scene_change_timer.start() def _onSceneChangeTimerFinished(self): diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py index 8ddcdbfb2f..8fffac4501 100755 --- a/cura/PlatformPhysics.py +++ b/cura/PlatformPhysics.py @@ -17,7 +17,6 @@ from cura.Scene import ZOffsetDecorator import random # used for list shuffling - class PlatformPhysics: def __init__(self, controller, volume): super().__init__() @@ -40,8 +39,9 @@ class PlatformPhysics: Application.getInstance().getPreferences().addPreference("physics/automatic_drop_down", True) def _onSceneChanged(self, source): - if not source.getMeshData(): + if not source.callDecoration("isSliceable"): return + self._change_timer.start() def _onChangeTimerFinished(self): diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index aac79f6c28..ceba5f3006 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -477,7 +477,7 @@ class CuraEngineBackend(QObject, Backend): # # \param source The scene node that was changed. def _onSceneChanged(self, source: SceneNode) -> None: - if not isinstance(source, SceneNode) or isinstance(source, Camera): + if not source.callDecoration("isSliceable"): return # This case checks if the source node is a node that contains GCode. In this case the From 43b1c869201975dd530e083b5661a1cdfb9d347a Mon Sep 17 00:00:00 2001 From: Simon Edwards Date: Wed, 20 Feb 2019 09:31:51 +0100 Subject: [PATCH 36/37] Use QUrl.fromLocalFile() to create the file:// URLs for the images --- plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index bece666829..ceeeec0382 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -455,8 +455,8 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._start_cloud_flow_message = Message( text = i18n_catalog.i18nc("@info:status", "Send and monitor print jobs from anywhere using your Ultimaker account."), lifetime = 0, - image_source = "file:///" + os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "resources", "svg", - "cloud-flow-start.svg"), + image_source = QUrl.fromLocalFile(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", + "resources", "svg", "cloud-flow-start.svg")), image_caption = i18n_catalog.i18nc("@info:status", "Connect to Ultimaker Cloud"), option_text = i18n_catalog.i18nc("@action", "Don't ask me again for this printer."), option_state = False @@ -477,8 +477,8 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._cloud_flow_complete_message = Message( text = i18n_catalog.i18nc("@info:status", "You can now send and monitor print jobs from anywhere using your Ultimaker account."), lifetime = 30, - image_source = "file:///" + os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "resources", "svg", - "cloud-flow-completed.svg"), + image_source = QUrl.fromLocalFile(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", + "resources", "svg", "cloud-flow-completed.svg")), image_caption = i18n_catalog.i18nc("@info:status", "Connected!") ) self._cloud_flow_complete_message.addAction("", i18n_catalog.i18nc("@action", "Review your connection"), "", "", 1) # TODO: Icon From 1608adde000dc71910a73e95c3aedcb776e7f0a6 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 20 Feb 2019 09:57:48 +0100 Subject: [PATCH 37/37] Change of wording Skin Overlap (Percentage). [CURA-6140] --- resources/definitions/fdmprinter.def.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 8bb6e4983b..04c69c9497 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -1792,7 +1792,7 @@ "skin_overlap": { "label": "Skin Overlap Percentage", - "description": "Adjust the position where skin is extruded relative to the walls, as a percentage of the line widths of the skin lines and the innermost wall. A slight overlap allows the walls to connect firmly to the skin. Note that, given an equal skin and wall line-width, any percentage over 50% may already cause any skin to go past the wall, because at that point the position of the nozzle of the skin-extruder may already reach past the middle of the wall.", + "description": "Adjust the amount of overlap between the walls and (the endpoints of) the skin-centerlines, as a percentage of the line widths of the skin lines and the innermost wall. A slight overlap allows the walls to connect firmly to the skin. Note that, given an equal skin and wall line-width, any percentage over 50% may already cause any skin to go past the wall, because at that point the position of the nozzle of the skin-extruder may already reach past the middle of the wall.", "unit": "%", "type": "float", "default_value": 5, @@ -1807,7 +1807,7 @@ "skin_overlap_mm": { "label": "Skin Overlap", - "description": "Adjust the position where skin is extruded relative to the walls. A slight overlap allows the walls to connect firmly to the skin. Note that, given an equal skin and wall line-width, any value over half the width of the wall may already cause any skin to go past the wall, because at that point the position of the nozzle of the skin-extruder may already reach past the middle of the wall.", + "description": "Adjust the amount of overlap between the walls and (the endpoints of) the skin-centerlines. A slight overlap allows the walls to connect firmly to the skin. Note that, given an equal skin and wall line-width, any value over half the width of the wall may already cause any skin to go past the wall, because at that point the position of the nozzle of the skin-extruder may already reach past the middle of the wall.", "unit": "mm", "type": "float", "default_value": 0.02,