OneAtATimeIterator.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import List
  4. from UM.Scene.Iterator import Iterator
  5. from UM.Scene.SceneNode import SceneNode
  6. from functools import cmp_to_key
  7. class OneAtATimeIterator(Iterator.Iterator):
  8. """Iterator that returns a list of nodes in the order that they need to be printed
  9. If there is no solution an empty list is returned.
  10. Take note that the list of nodes can have children (that may or may not contain mesh data)
  11. """
  12. def __init__(self, scene_node) -> None:
  13. super().__init__(scene_node) # Call super to make multiple inheritance work.
  14. self._hit_map = [[]] # type: List[List[bool]] # For each node, which other nodes this hits. A grid of booleans on which nodes hit which.
  15. self._original_node_list = [] # type: List[SceneNode] # The nodes that need to be checked for collisions.
  16. def _fillStack(self) -> None:
  17. """Fills the ``_node_stack`` with a list of scene nodes that need to be printed in order. """
  18. node_list = []
  19. for node in self._scene_node.getChildren():
  20. if not issubclass(type(node), SceneNode):
  21. continue
  22. # Node can't be printed, so don't bother sending it.
  23. if getattr(node, "_outside_buildarea", False):
  24. continue
  25. if node.callDecoration("getConvexHull"):
  26. node_list.append(node)
  27. if len(node_list) < 2:
  28. self._node_stack = node_list[:]
  29. return
  30. # Copy the list
  31. self._original_node_list = node_list[:]
  32. # Initialise the hit map (pre-compute all hits between all objects)
  33. self._hit_map = [[self._checkHit(i, j) for i in node_list] for j in node_list]
  34. # Check if we have to files that block each other. If this is the case, there is no solution!
  35. for a in range(0, len(node_list)):
  36. for b in range(0, len(node_list)):
  37. if a != b and self._hit_map[a][b] and self._hit_map[b][a]:
  38. return
  39. # Sort the original list so that items that block the most other objects are at the beginning.
  40. # This does not decrease the worst case running time, but should improve it in most cases.
  41. sorted(node_list, key = cmp_to_key(self._calculateScore))
  42. todo_node_list = [_ObjectOrder([], node_list)]
  43. while len(todo_node_list) > 0:
  44. current = todo_node_list.pop()
  45. for node in current.todo:
  46. # Check if the object can be placed with what we have and still allows for a solution in the future
  47. if not self._checkHitMultiple(node, current.order) and not self._checkBlockMultiple(node, current.todo):
  48. # We found a possible result. Create new todo & order list.
  49. new_todo_list = current.todo[:]
  50. new_todo_list.remove(node)
  51. new_order = current.order[:] + [node]
  52. if len(new_todo_list) == 0:
  53. # We have no more nodes to check, so quit looking.
  54. self._node_stack = new_order
  55. return
  56. todo_node_list.append(_ObjectOrder(new_order, new_todo_list))
  57. self._node_stack = [] #No result found!
  58. # Check if first object can be printed before the provided list (using the hit map)
  59. def _checkHitMultiple(self, node: SceneNode, other_nodes: List[SceneNode]) -> bool:
  60. node_index = self._original_node_list.index(node)
  61. for other_node in other_nodes:
  62. other_node_index = self._original_node_list.index(other_node)
  63. if self._hit_map[node_index][other_node_index]:
  64. return True
  65. return False
  66. def _checkBlockMultiple(self, node: SceneNode, other_nodes: List[SceneNode]) -> bool:
  67. """Check for a node whether it hits any of the other nodes.
  68. :param node: The node to check whether it collides with the other nodes.
  69. :param other_nodes: The nodes to check for collisions.
  70. :return: returns collision between nodes
  71. """
  72. node_index = self._original_node_list.index(node)
  73. for other_node in other_nodes:
  74. other_node_index = self._original_node_list.index(other_node)
  75. if self._hit_map[other_node_index][node_index] and node_index != other_node_index:
  76. return True
  77. return False
  78. def _calculateScore(self, a: SceneNode, b: SceneNode) -> int:
  79. """Calculate score simply sums the number of other objects it 'blocks'
  80. :param a: node
  81. :param b: node
  82. :return: sum of the number of other objects
  83. """
  84. score_a = sum(self._hit_map[self._original_node_list.index(a)])
  85. score_b = sum(self._hit_map[self._original_node_list.index(b)])
  86. return score_a - score_b
  87. def _checkHit(self, a: SceneNode, b: SceneNode) -> bool:
  88. """Checks if a can be printed before b
  89. :param a: node
  90. :param b: node
  91. :return: true if a can be printed before b
  92. """
  93. if a == b:
  94. return False
  95. a_hit_hull = a.callDecoration("getConvexHullBoundary")
  96. b_hit_hull = b.callDecoration("getConvexHullHeadFull")
  97. overlap = a_hit_hull.intersectsPolygon(b_hit_hull)
  98. if overlap:
  99. return True
  100. # Adhesion areas must never overlap, regardless of printing order
  101. # This would cause over-extrusion
  102. a_hit_hull = a.callDecoration("getAdhesionArea")
  103. b_hit_hull = b.callDecoration("getAdhesionArea")
  104. overlap = a_hit_hull.intersectsPolygon(b_hit_hull)
  105. if overlap:
  106. return True
  107. else:
  108. return False
  109. class _ObjectOrder:
  110. """Internal object used to keep track of a possible order in which to print objects."""
  111. def __init__(self, order: List[SceneNode], todo: List[SceneNode]) -> None:
  112. """Creates the _ObjectOrder instance.
  113. :param order: List of indices in which to print objects, ordered by printing order.
  114. :param todo: List of indices which are not yet inserted into the order list.
  115. """
  116. self.order = order
  117. self.todo = todo