From e9965ab2a630ed4c8717b3e838769a39bd2d9195 Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Thu, 26 Sep 2019 10:42:54 +0200 Subject: [PATCH 01/10] Revert the OneAtATimeIterator to the pre 06-2018 implementation. This seems like a better starting point to fix print head collisions, because we got less bug reports for it compared to the 2018 rewrite. CURA-6785 --- cura/OneAtATimeIterator.py | 227 ++++++++++++++++--------------------- 1 file changed, 95 insertions(+), 132 deletions(-) diff --git a/cura/OneAtATimeIterator.py b/cura/OneAtATimeIterator.py index a08f3ed2bf..ab97534ff4 100644 --- a/cura/OneAtATimeIterator.py +++ b/cura/OneAtATimeIterator.py @@ -1,149 +1,112 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2015 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import sys - -from shapely import affinity -from shapely.geometry import Polygon - -from UM.Scene.Iterator.Iterator import Iterator +from UM.Scene.Iterator import Iterator from UM.Scene.SceneNode import SceneNode +from functools import cmp_to_key +from UM.Application import Application - -# Iterator that determines the object print order when one-at a time mode is enabled. -# -# In one-at-a-time mode, only one extruder can be enabled to print. In order to maximize the number of objects we can -# print, we need to print from the corner that's closest to the extruder that's being used. Here is an illustration: -# -# +--------------------------------+ -# | | -# | | -# | | - Rectangle represents the complete print head including fans, etc. -# | X X | y - X's are the nozzles -# | (1) (2) | ^ -# | | | -# +--------------------------------+ +--> x -# -# In this case, the nozzles are symmetric, nozzle (1) is closer to the bottom left corner while (2) is closer to the -# bottom right. If we use nozzle (1) to print, then we better off printing from the bottom left corner so the print -# head will not collide into an object on its top-right side, which is a very large unused area. Following the same -# logic, if we are printing with nozzle (2), then it's better to print from the bottom-right side. -# -# This iterator determines the print order following the rules above. -# -class OneAtATimeIterator(Iterator): - +## Iterator that returns a list of nodes in the order that they need to be printed +# If there is no solution an empty list is returned. +# Take note that the list of nodes can have children (that may or may not contain mesh data) +class OneAtATimeIterator(Iterator.Iterator): def __init__(self, scene_node): - from cura.CuraApplication import CuraApplication - self._global_stack = CuraApplication.getInstance().getGlobalContainerStack() + super().__init__(scene_node) # Call super to make multiple inheritence work. + self._hit_map = [[]] self._original_node_list = [] - super().__init__(scene_node) # Call super to make multiple inheritance work. - - def getMachineNearestCornerToExtruder(self, global_stack): - head_and_fans_coordinates = global_stack.getHeadAndFansCoordinates() - - used_extruder = None - for extruder in global_stack.extruders.values(): - if extruder.isEnabled: - used_extruder = extruder - break - - extruder_offsets = [used_extruder.getProperty("machine_nozzle_offset_x", "value"), - used_extruder.getProperty("machine_nozzle_offset_y", "value")] - - # find the corner that's closest to the origin - min_distance2 = sys.maxsize - min_coord = None - for coord in head_and_fans_coordinates: - x = coord[0] - extruder_offsets[0] - y = coord[1] - extruder_offsets[1] - - distance2 = x**2 + y**2 - if distance2 <= min_distance2: - min_distance2 = distance2 - min_coord = coord - - return min_coord - - def _checkForCollisions(self) -> bool: - all_nodes = [] - for node in self._scene_node.getChildren(): - if not issubclass(type(node), SceneNode): - continue - convex_hull = node.callDecoration("getConvexHullHead") - if not convex_hull: - continue - - bounding_box = node.getBoundingBox() - if not bounding_box: - continue - from UM.Math.Polygon import Polygon - bounding_box_polygon = Polygon([[bounding_box.left, bounding_box.front], - [bounding_box.left, bounding_box.back], - [bounding_box.right, bounding_box.back], - [bounding_box.right, bounding_box.front]]) - - all_nodes.append({"node": node, - "bounding_box": bounding_box_polygon, - "convex_hull": convex_hull}) - - has_collisions = False - for i, node_dict in enumerate(all_nodes): - for j, other_node_dict in enumerate(all_nodes): - if i == j: - continue - if node_dict["bounding_box"].intersectsPolygon(other_node_dict["convex_hull"]): - has_collisions = True - break - - if has_collisions: - break - - return has_collisions - def _fillStack(self): - min_coord = self.getMachineNearestCornerToExtruder(self._global_stack) - transform_x = -int(round(min_coord[0] / abs(min_coord[0]))) - transform_y = -int(round(min_coord[1] / abs(min_coord[1]))) - - machine_size = [self._global_stack.getProperty("machine_width", "value"), - self._global_stack.getProperty("machine_depth", "value")] - - def flip_x(polygon): - tm2 = [-1, 0, 0, 1, 0, 0] - return affinity.affine_transform(affinity.translate(polygon, xoff = -machine_size[0]), tm2) - - def flip_y(polygon): - tm2 = [1, 0, 0, -1, 0, 0] - return affinity.affine_transform(affinity.translate(polygon, yoff = -machine_size[1]), tm2) - - if self._checkForCollisions(): - self._node_stack = [] - return - node_list = [] for node in self._scene_node.getChildren(): if not issubclass(type(node), SceneNode): continue - convex_hull = node.callDecoration("getConvexHull") - if convex_hull: - xmin = min(x for x, _ in convex_hull._points) - xmax = max(x for x, _ in convex_hull._points) - ymin = min(y for _, y in convex_hull._points) - ymax = max(y for _, y in convex_hull._points) + if node.callDecoration("getConvexHull"): + node_list.append(node) - convex_hull_polygon = Polygon.from_bounds(xmin, ymin, xmax, ymax) - if transform_x < 0: - convex_hull_polygon = flip_x(convex_hull_polygon) - if transform_y < 0: - convex_hull_polygon = flip_y(convex_hull_polygon) - node_list.append({"node": node, - "min_coord": [convex_hull_polygon.bounds[0], convex_hull_polygon.bounds[1]], - }) + if len(node_list) < 2: + self._node_stack = node_list[:] + return - node_list = sorted(node_list, key = lambda d: d["min_coord"]) + # Copy the list + self._original_node_list = node_list[:] + + ## Initialise the hit map (pre-compute all hits between all objects) + self._hit_map = [[self._checkHit(i,j) for i in node_list] for j in node_list] + + # Check if we have to files that block eachother. If this is the case, there is no solution! + for a in range(0,len(node_list)): + for b in range(0,len(node_list)): + if a != b and self._hit_map[a][b] and self._hit_map[b][a]: + return + + # Sort the original list so that items that block the most other objects are at the beginning. + # This does not decrease the worst case running time, but should improve it in most cases. + sorted(node_list, key = cmp_to_key(self._calculateScore)) + + todo_node_list = [_ObjectOrder([], node_list)] + while len(todo_node_list) > 0: + current = todo_node_list.pop() + for node in current.todo: + # Check if the object can be placed with what we have and still allows for a solution in the future + if not self._checkHitMultiple(node, current.order) and not self._checkBlockMultiple(node, current.todo): + # We found a possible result. Create new todo & order list. + new_todo_list = current.todo[:] + new_todo_list.remove(node) + new_order = current.order[:] + [node] + if len(new_todo_list) == 0: + # We have no more nodes to check, so quit looking. + todo_node_list = None + self._node_stack = new_order + + return + todo_node_list.append(_ObjectOrder(new_order, new_todo_list)) + self._node_stack = [] #No result found! + + + # Check if first object can be printed before the provided list (using the hit map) + def _checkHitMultiple(self, node, other_nodes): + node_index = self._original_node_list.index(node) + for other_node in other_nodes: + other_node_index = self._original_node_list.index(other_node) + if self._hit_map[node_index][other_node_index]: + return True + return False + + def _checkBlockMultiple(self, node, other_nodes): + node_index = self._original_node_list.index(node) + for other_node in other_nodes: + other_node_index = self._original_node_list.index(other_node) + if self._hit_map[other_node_index][node_index] and node_index != other_node_index: + return True + return False + + ## Calculate score simply sums the number of other objects it 'blocks' + def _calculateScore(self, a, b): + score_a = sum(self._hit_map[self._original_node_list.index(a)]) + score_b = sum(self._hit_map[self._original_node_list.index(b)]) + return score_a - score_b + + # Checks if A can be printed before B + def _checkHit(self, a, b): + if a == b: + return False + + overlap = a.callDecoration("getConvexHullBoundary").intersectsPolygon(b.callDecoration("getConvexHullHeadFull")) + if overlap: + return True + else: + return False + + +## Internal object used to keep track of a possible order in which to print objects. +class _ObjectOrder(): + def __init__(self, order, todo): + """ + :param order: List of indexes in which to print objects, ordered by printing order. + :param todo: List of indexes which are not yet inserted into the order list. + """ + self.order = order + self.todo = todo - self._node_stack = [d["node"] for d in node_list] From c4c62cbba2228032d1a37d23f4834b0aba9e58bb Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Fri, 27 Sep 2019 14:15:31 +0200 Subject: [PATCH 02/10] Take nozzle offset into account in _getHeadAndFans() CURA-6785 --- cura/OneAtATimeIterator.py | 2 +- cura/Scene/ConvexHullDecorator.py | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cura/OneAtATimeIterator.py b/cura/OneAtATimeIterator.py index ab97534ff4..900eaf2273 100644 --- a/cura/OneAtATimeIterator.py +++ b/cura/OneAtATimeIterator.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from UM.Scene.Iterator import Iterator diff --git a/cura/Scene/ConvexHullDecorator.py b/cura/Scene/ConvexHullDecorator.py index 2d8224eecc..bde7cde807 100644 --- a/cura/Scene/ConvexHullDecorator.py +++ b/cura/Scene/ConvexHullDecorator.py @@ -266,9 +266,13 @@ class ConvexHullDecorator(SceneNodeDecorator): return offset_hull def _getHeadAndFans(self) -> Polygon: - if self._global_stack: - return Polygon(numpy.array(self._global_stack.getHeadAndFansCoordinates(), numpy.float32)) - return Polygon() + if not self._global_stack: + return Polygon() + + polygon = Polygon(numpy.array(self._global_stack.getHeadAndFansCoordinates(), numpy.float32)) + offset_x = self._getSettingProperty("machine_nozzle_offset_x", "value") + offset_y = self._getSettingProperty("machine_nozzle_offset_y", "value") + return polygon.translate(-offset_x, -offset_y) def _compute2DConvexHeadFull(self) -> Optional[Polygon]: convex_hull = self._compute2DConvexHull() From 69028bf27948e77fe9a4e50f5555fab41bc876af Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 30 Sep 2019 16:24:53 +0200 Subject: [PATCH 03/10] Remove unused import Contributes to issue CURA-6785. --- cura/OneAtATimeIterator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cura/OneAtATimeIterator.py b/cura/OneAtATimeIterator.py index 900eaf2273..b66866a131 100644 --- a/cura/OneAtATimeIterator.py +++ b/cura/OneAtATimeIterator.py @@ -4,7 +4,6 @@ from UM.Scene.Iterator import Iterator from UM.Scene.SceneNode import SceneNode from functools import cmp_to_key -from UM.Application import Application ## Iterator that returns a list of nodes in the order that they need to be printed # If there is no solution an empty list is returned. From 7bf2fa3b43d1ffe30af1f6161d37ceb8ed09d905 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 30 Sep 2019 16:50:35 +0200 Subject: [PATCH 04/10] Add typing and documentation and remove unused code Contributes to issue CURA-6785. --- cura/OneAtATimeIterator.py | 48 +++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/cura/OneAtATimeIterator.py b/cura/OneAtATimeIterator.py index b66866a131..a61ce492a9 100644 --- a/cura/OneAtATimeIterator.py +++ b/cura/OneAtATimeIterator.py @@ -1,6 +1,8 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from typing import List + from UM.Scene.Iterator import Iterator from UM.Scene.SceneNode import SceneNode from functools import cmp_to_key @@ -9,12 +11,14 @@ from functools import cmp_to_key # If there is no solution an empty list is returned. # Take note that the list of nodes can have children (that may or may not contain mesh data) class OneAtATimeIterator(Iterator.Iterator): - def __init__(self, scene_node): - super().__init__(scene_node) # Call super to make multiple inheritence work. - self._hit_map = [[]] - self._original_node_list = [] + def __init__(self, scene_node) -> None: + super().__init__(scene_node) # Call super to make multiple inheritance work. + self._hit_map = [[]] # type: List[List[bool]] # For each node, which other nodes this hits. A grid of booleans on which nodes hit which. + self._original_node_list = [] # type: List[SceneNode] # The nodes that need to be checked for collisions. - def _fillStack(self): + ## Fills the ``_node_stack`` with a list of scene nodes that need to be + # printed in order. + def _fillStack(self) -> None: node_list = [] for node in self._scene_node.getChildren(): if not issubclass(type(node), SceneNode): @@ -34,9 +38,9 @@ class OneAtATimeIterator(Iterator.Iterator): ## Initialise the hit map (pre-compute all hits between all objects) self._hit_map = [[self._checkHit(i,j) for i in node_list] for j in node_list] - # Check if we have to files that block eachother. If this is the case, there is no solution! - for a in range(0,len(node_list)): - for b in range(0,len(node_list)): + # Check if we have to files that block each other. If this is the case, there is no solution! + for a in range(0, len(node_list)): + for b in range(0, len(node_list)): if a != b and self._hit_map[a][b] and self._hit_map[b][a]: return @@ -56,16 +60,14 @@ class OneAtATimeIterator(Iterator.Iterator): new_order = current.order[:] + [node] if len(new_todo_list) == 0: # We have no more nodes to check, so quit looking. - todo_node_list = None self._node_stack = new_order - return todo_node_list.append(_ObjectOrder(new_order, new_todo_list)) self._node_stack = [] #No result found! # Check if first object can be printed before the provided list (using the hit map) - def _checkHitMultiple(self, node, other_nodes): + def _checkHitMultiple(self, node: SceneNode, other_nodes: List[SceneNode]) -> bool: node_index = self._original_node_list.index(node) for other_node in other_nodes: other_node_index = self._original_node_list.index(other_node) @@ -73,7 +75,10 @@ class OneAtATimeIterator(Iterator.Iterator): return True return False - def _checkBlockMultiple(self, node, other_nodes): + ## Check for a node whether it hits any of the other nodes. + # \param node The node to check whether it collides with the other nodes. + # \param other_nodes The nodes to check for collisions. + def _checkBlockMultiple(self, node: SceneNode, other_nodes: List[SceneNode]) -> bool: node_index = self._original_node_list.index(node) for other_node in other_nodes: other_node_index = self._original_node_list.index(other_node) @@ -82,13 +87,13 @@ class OneAtATimeIterator(Iterator.Iterator): return False ## Calculate score simply sums the number of other objects it 'blocks' - def _calculateScore(self, a, b): + def _calculateScore(self, a: SceneNode, b: SceneNode) -> int: score_a = sum(self._hit_map[self._original_node_list.index(a)]) score_b = sum(self._hit_map[self._original_node_list.index(b)]) return score_a - score_b # Checks if A can be printed before B - def _checkHit(self, a, b): + def _checkHit(self, a: SceneNode, b: SceneNode) -> bool: if a == b: return False @@ -99,13 +104,12 @@ class OneAtATimeIterator(Iterator.Iterator): return False -## Internal object used to keep track of a possible order in which to print objects. -class _ObjectOrder(): - def __init__(self, order, todo): - """ - :param order: List of indexes in which to print objects, ordered by printing order. - :param todo: List of indexes which are not yet inserted into the order list. - """ +## Internal object used to keep track of a possible order in which to print objects. +class _ObjectOrder: + ## Creates the _ObjectOrder instance. + # \param order List of indices in which to print objects, ordered by printing + # order. + # \param todo: List of indices which are not yet inserted into the order list. + def __init__(self, order: List[SceneNode], todo: List[SceneNode]): self.order = order self.todo = todo - From 672fc58930052749c497862242f7871c888ac223 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 2 Oct 2019 12:59:30 +0200 Subject: [PATCH 05/10] Allow down to half the layer height for infill layer thickness This is possible because CuraEngine rounds these to the nearest layer thickness. So if it's more than half the layer height it gets rounded up and it's still properly one layer. Contributes to issue #6465. --- resources/definitions/fdmprinter.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 3dbada96f5..122d0e3f5e 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -1886,7 +1886,7 @@ "unit": "mm", "type": "float", "default_value": 0.1, - "minimum_value": "resolveOrValue('layer_height') if infill_line_distance > 0 else -999999", + "minimum_value": "resolveOrValue('layer_height') / 2 if infill_line_distance > 0 else -999999", "maximum_value_warning": "0.75 * machine_nozzle_size", "maximum_value": "resolveOrValue('layer_height') * (1.45 if spaghetti_infill_enabled else 8) if infill_line_distance > 0 else 999999", "value": "resolveOrValue('layer_height')", From f5f91c9c3a3086b5b576fc2e2907454c8e1d892d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 2 Oct 2019 13:06:27 +0200 Subject: [PATCH 06/10] Ensure that profiles are cleaned up if one of the set is incorrect --- cura/Settings/CuraContainerRegistry.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index c1a9ba9ead..3c93888b66 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -292,6 +292,7 @@ class CuraContainerRegistry(ContainerRegistry): profile_or_list.append(profile) # Import all profiles + profile_ids_added = [] # type: List[str] for profile_index, profile in enumerate(profile_or_list): if profile_index == 0: # This is assumed to be the global profile @@ -312,11 +313,15 @@ class CuraContainerRegistry(ContainerRegistry): result = self._configureProfile(profile, profile_id, new_name, expected_machine_definition) if result is not None: + # Remove any profiles that did got added. + for profile_id in profile_ids_added: + self.removeContainer(profile_id) + return {"status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tag !", "Failed to import profile from {0}:", file_name) + " " + result} - + profile_ids_added.append(profile.getId()) return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())} # This message is throw when the profile reader doesn't find any profile in the file From 95120300601c5a8391a3909c7291f95248d35052 Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Wed, 2 Oct 2019 13:07:11 +0200 Subject: [PATCH 07/10] Check for adhesion area collisions in one-at-a-time ordering CURA-6785 --- cura/OneAtATimeIterator.py | 16 ++++++++++++++-- cura/Scene/ConvexHullDecorator.py | 13 ++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/cura/OneAtATimeIterator.py b/cura/OneAtATimeIterator.py index a61ce492a9..4d420f6d05 100644 --- a/cura/OneAtATimeIterator.py +++ b/cura/OneAtATimeIterator.py @@ -92,12 +92,24 @@ class OneAtATimeIterator(Iterator.Iterator): score_b = sum(self._hit_map[self._original_node_list.index(b)]) return score_a - score_b - # Checks if A can be printed before B + ## Checks if A can be printed before B def _checkHit(self, a: SceneNode, b: SceneNode) -> bool: if a == b: return False - overlap = a.callDecoration("getConvexHullBoundary").intersectsPolygon(b.callDecoration("getConvexHullHeadFull")) + a_hit_hull = a.callDecoration("getConvexHullBoundary") + b_hit_hull = b.callDecoration("getConvexHullHeadFull") + overlap = a_hit_hull.intersectsPolygon(b_hit_hull) + + if overlap: + return True + + # Adhesion areas must never overlap, regardless of printing order + # This would cause over-extrusion + a_hit_hull = a.callDecoration("getAdhesionArea") + b_hit_hull = b.callDecoration("getAdhesionArea") + overlap = a_hit_hull.intersectsPolygon(b_hit_hull) + if overlap: return True else: diff --git a/cura/Scene/ConvexHullDecorator.py b/cura/Scene/ConvexHullDecorator.py index bde7cde807..c263726d07 100644 --- a/cura/Scene/ConvexHullDecorator.py +++ b/cura/Scene/ConvexHullDecorator.py @@ -76,7 +76,18 @@ class ConvexHullDecorator(SceneNodeDecorator): def __deepcopy__(self, memo): return ConvexHullDecorator() - ## Get the unmodified 2D projected convex hull of the node (if any) + + ## The polygon representing the 2D adhesion area. + # If no adhesion is used, the regular convex hull is returned + def getAdhesionArea(self) -> Optional[Polygon]: + if self._node is None: + return None + + hull = self._compute2DConvexHull() + return self._add2DAdhesionMargin(hull) + + + ## Get the unmodified 2D projected convex hull with 2D adhesion area of the node (if any) def getConvexHull(self) -> Optional[Polygon]: if self._node is None: return None From 1b1029a3e07816b46c9476c06048a63ccecb8a44 Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Wed, 2 Oct 2019 13:13:32 +0200 Subject: [PATCH 08/10] Use 2 leading spaces for doxygen documentation CURA-6785 --- cura/OneAtATimeIterator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/OneAtATimeIterator.py b/cura/OneAtATimeIterator.py index 4d420f6d05..b77e1f3982 100644 --- a/cura/OneAtATimeIterator.py +++ b/cura/OneAtATimeIterator.py @@ -66,7 +66,7 @@ class OneAtATimeIterator(Iterator.Iterator): self._node_stack = [] #No result found! - # Check if first object can be printed before the provided list (using the hit map) + # Check if first object can be printed before the provided list (using the hit map) def _checkHitMultiple(self, node: SceneNode, other_nodes: List[SceneNode]) -> bool: node_index = self._original_node_list.index(node) for other_node in other_nodes: @@ -92,7 +92,7 @@ class OneAtATimeIterator(Iterator.Iterator): score_b = sum(self._hit_map[self._original_node_list.index(b)]) return score_a - score_b - ## Checks if A can be printed before B + ## Checks if A can be printed before B def _checkHit(self, a: SceneNode, b: SceneNode) -> bool: if a == b: return False From 5ec6b2fdf792cf6ad3a76450f56cce68c34efb0d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 2 Oct 2019 15:48:10 +0200 Subject: [PATCH 09/10] Fix typing CURA-6785 --- cura/Scene/ConvexHullDecorator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cura/Scene/ConvexHullDecorator.py b/cura/Scene/ConvexHullDecorator.py index c263726d07..72e95c9299 100644 --- a/cura/Scene/ConvexHullDecorator.py +++ b/cura/Scene/ConvexHullDecorator.py @@ -76,7 +76,6 @@ class ConvexHullDecorator(SceneNodeDecorator): def __deepcopy__(self, memo): return ConvexHullDecorator() - ## The polygon representing the 2D adhesion area. # If no adhesion is used, the regular convex hull is returned def getAdhesionArea(self) -> Optional[Polygon]: @@ -84,8 +83,10 @@ class ConvexHullDecorator(SceneNodeDecorator): return None hull = self._compute2DConvexHull() - return self._add2DAdhesionMargin(hull) + if hull is None: + return None + return self._add2DAdhesionMargin(hull) ## Get the unmodified 2D projected convex hull with 2D adhesion area of the node (if any) def getConvexHull(self) -> Optional[Polygon]: From 61a1b61756e4d977719884091dd26b0c53c2de46 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 4 Oct 2019 10:27:35 +0200 Subject: [PATCH 10/10] Remove unnecessary activeQualityName property CURA-6846 --- cura/Settings/MachineManager.py | 8 -------- .../qml/PrintSetupSelector/Custom/CustomPrintSetup.qml | 2 +- .../qml/PrintSetupSelector/PrintSetupSelectorHeader.qml | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 37789b23a1..4518304680 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -656,7 +656,6 @@ class MachineManager(QObject): return result - ## Returns whether there is anything unsupported in the current set-up. # # The current set-up signifies the global stack and all extruder stacks, @@ -1673,13 +1672,6 @@ class MachineManager(QObject): return global_container_stack.qualityChanges.getName() return global_container_stack.quality.getName() - @pyqtProperty(str, notify = activeQualityChanged) - def activeQualityName(self) -> str: - global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() - if global_stack is None: - return empty_quality_container.getName() - return global_stack.quality.getName() - @pyqtProperty(bool, notify = activeQualityGroupChanged) def hasNotSupportedQuality(self) -> bool: global_container_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() diff --git a/resources/qml/PrintSetupSelector/Custom/CustomPrintSetup.qml b/resources/qml/PrintSetupSelector/Custom/CustomPrintSetup.qml index 33e2888fbb..55ae33b134 100644 --- a/resources/qml/PrintSetupSelector/Custom/CustomPrintSetup.qml +++ b/resources/qml/PrintSetupSelector/Custom/CustomPrintSetup.qml @@ -97,7 +97,7 @@ Item { result += " - " + Cura.MachineManager.activeIntentName } - result += " - " + Cura.MachineManager.activeQualityName + result += " - " + Cura.MachineManager.activeQualityGroup.getName() } if (Cura.MachineManager.isActiveQualityExperimental) diff --git a/resources/qml/PrintSetupSelector/PrintSetupSelectorHeader.qml b/resources/qml/PrintSetupSelector/PrintSetupSelectorHeader.qml index 9340f64d89..affe514bd8 100644 --- a/resources/qml/PrintSetupSelector/PrintSetupSelectorHeader.qml +++ b/resources/qml/PrintSetupSelector/PrintSetupSelectorHeader.qml @@ -29,7 +29,7 @@ RowLayout { text += " - " + Cura.MachineManager.activeIntentName } - text += " - " + Cura.MachineManager.activeQualityName + text += " - " + Cura.MachineManager.activeQualityGroup.getName() } if (!Cura.MachineManager.hasNotSupportedQuality)