OneAtATimeIterator.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. # Copyright (c) 2018 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import sys
  4. from shapely import affinity
  5. from shapely.geometry import Polygon
  6. from UM.Scene.Iterator.Iterator import Iterator
  7. from UM.Scene.SceneNode import SceneNode
  8. # Iterator that determines the object print order when one-at a time mode is enabled.
  9. #
  10. # In one-at-a-time mode, only one extruder can be enabled to print. In order to maximize the number of objects we can
  11. # print, we need to print from the corner that's closest to the extruder that's being used. Here is an illustration:
  12. #
  13. # +--------------------------------+
  14. # | |
  15. # | |
  16. # | | - Rectangle represents the complete print head including fans, etc.
  17. # | X X | y - X's are the nozzles
  18. # | (1) (2) | ^
  19. # | | |
  20. # +--------------------------------+ +--> x
  21. #
  22. # In this case, the nozzles are symmetric, nozzle (1) is closer to the bottom left corner while (2) is closer to the
  23. # bottom right. If we use nozzle (1) to print, then we better off printing from the bottom left corner so the print
  24. # head will not collide into an object on its top-right side, which is a very large unused area. Following the same
  25. # logic, if we are printing with nozzle (2), then it's better to print from the bottom-right side.
  26. #
  27. # This iterator determines the print order following the rules above.
  28. #
  29. class OneAtATimeIterator(Iterator):
  30. def __init__(self, scene_node):
  31. from cura.CuraApplication import CuraApplication
  32. self._global_stack = CuraApplication.getInstance().getGlobalContainerStack()
  33. self._original_node_list = []
  34. super().__init__(scene_node) # Call super to make multiple inheritance work.
  35. def getMachineNearestCornerToExtruder(self, global_stack):
  36. head_and_fans_coordinates = global_stack.getHeadAndFansCoordinates()
  37. used_extruder = None
  38. for extruder in global_stack.extruders.values():
  39. if extruder.isEnabled:
  40. used_extruder = extruder
  41. break
  42. extruder_offsets = [used_extruder.getProperty("machine_nozzle_offset_x", "value"),
  43. used_extruder.getProperty("machine_nozzle_offset_y", "value")]
  44. # find the corner that's closest to the origin
  45. min_distance2 = sys.maxsize
  46. min_coord = None
  47. for coord in head_and_fans_coordinates:
  48. x = coord[0] - extruder_offsets[0]
  49. y = coord[1] - extruder_offsets[1]
  50. distance2 = x**2 + y**2
  51. if distance2 <= min_distance2:
  52. min_distance2 = distance2
  53. min_coord = coord
  54. return min_coord
  55. def _checkForCollisions(self) -> bool:
  56. all_nodes = []
  57. for node in self._scene_node.getChildren():
  58. if not issubclass(type(node), SceneNode):
  59. continue
  60. convex_hull = node.callDecoration("getConvexHullHead")
  61. if not convex_hull:
  62. continue
  63. bounding_box = node.getBoundingBox()
  64. if not bounding_box:
  65. continue
  66. from UM.Math.Polygon import Polygon
  67. bounding_box_polygon = Polygon([[bounding_box.left, bounding_box.front],
  68. [bounding_box.left, bounding_box.back],
  69. [bounding_box.right, bounding_box.back],
  70. [bounding_box.right, bounding_box.front]])
  71. all_nodes.append({"node": node,
  72. "bounding_box": bounding_box_polygon,
  73. "convex_hull": convex_hull})
  74. has_collisions = False
  75. for i, node_dict in enumerate(all_nodes):
  76. for j, other_node_dict in enumerate(all_nodes):
  77. if i == j:
  78. continue
  79. if node_dict["bounding_box"].intersectsPolygon(other_node_dict["convex_hull"]):
  80. has_collisions = True
  81. break
  82. if has_collisions:
  83. break
  84. return has_collisions
  85. def _fillStack(self):
  86. min_coord = self.getMachineNearestCornerToExtruder(self._global_stack)
  87. transform_x = -int(round(min_coord[0] / abs(min_coord[0])))
  88. transform_y = -int(round(min_coord[1] / abs(min_coord[1])))
  89. machine_size = [self._global_stack.getProperty("machine_width", "value"),
  90. self._global_stack.getProperty("machine_depth", "value")]
  91. def flip_x(polygon):
  92. tm2 = [-1, 0, 0, 1, 0, 0]
  93. return affinity.affine_transform(affinity.translate(polygon, xoff = -machine_size[0]), tm2)
  94. def flip_y(polygon):
  95. tm2 = [1, 0, 0, -1, 0, 0]
  96. return affinity.affine_transform(affinity.translate(polygon, yoff = -machine_size[1]), tm2)
  97. if self._checkForCollisions():
  98. self._node_stack = []
  99. return
  100. node_list = []
  101. for node in self._scene_node.getChildren():
  102. if not issubclass(type(node), SceneNode):
  103. continue
  104. convex_hull = node.callDecoration("getConvexHull")
  105. if convex_hull:
  106. xmin = min(x for x, _ in convex_hull._points)
  107. xmax = max(x for x, _ in convex_hull._points)
  108. ymin = min(y for _, y in convex_hull._points)
  109. ymax = max(y for _, y in convex_hull._points)
  110. convex_hull_polygon = Polygon.from_bounds(xmin, ymin, xmax, ymax)
  111. if transform_x < 0:
  112. convex_hull_polygon = flip_x(convex_hull_polygon)
  113. if transform_y < 0:
  114. convex_hull_polygon = flip_y(convex_hull_polygon)
  115. node_list.append({"node": node,
  116. "min_coord": [convex_hull_polygon.bounds[0], convex_hull_polygon.bounds[1]],
  117. })
  118. node_list = sorted(node_list, key = lambda d: d["min_coord"])
  119. self._node_stack = [d["node"] for d in node_list]