Arrange.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  2. from UM.Logger import Logger
  3. from cura.ShapeArray import ShapeArray
  4. import numpy
  5. import copy
  6. ## The Arrange classed is used together with ShapeArray. The class tries to find
  7. # good locations for objects that you try to put on a build place.
  8. # Different priority schemes can be defined so it alters the behavior while using
  9. # the same logic.
  10. class Arrange:
  11. def __init__(self, x, y, offset_x, offset_y, scale=1):
  12. self.shape = (y, x)
  13. self._priority = numpy.zeros((x, y), dtype=numpy.int32)
  14. self._priority_unique_values = []
  15. self._occupied = numpy.zeros((x, y), dtype=numpy.int32)
  16. self._scale = scale # convert input coordinates to arrange coordinates
  17. self._offset_x = offset_x
  18. self._offset_y = offset_y
  19. ## Helper to create an Arranger instance
  20. #
  21. # Either fill in scene_root and create will find all sliceable nodes by itself,
  22. # or use fixed_nodes to provide the nodes yourself.
  23. # \param scene_root
  24. # \param fixed_nodes
  25. @classmethod
  26. def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5):
  27. arranger = Arrange(220, 220, 110, 110, scale = scale)
  28. arranger.centerFirst()
  29. if fixed_nodes is None:
  30. fixed_nodes = []
  31. for node_ in DepthFirstIterator(scene_root):
  32. # Only count sliceable objects
  33. if node_.callDecoration("isSliceable"):
  34. fixed_nodes.append(node_)
  35. # place all objects fixed nodes
  36. for fixed_node in fixed_nodes:
  37. vertices = fixed_node.callDecoration("getConvexHull")
  38. points = copy.deepcopy(vertices._points)
  39. shape_arr = ShapeArray.fromPolygon(points, scale = scale)
  40. arranger.place(0, 0, shape_arr)
  41. return arranger
  42. ## Find placement for a node (using offset shape) and place it (using hull shape)
  43. #
  44. def findNodePlacements(self, node, offset_shape_arr, hull_shape_arr, count = 1, step = 1):
  45. # offset_shape_arr, hull_shape_arr, arranger -> nodes, arranger
  46. nodes = []
  47. start_prio = 0
  48. for i in range(count):
  49. new_node = copy.deepcopy(node)
  50. x, y, penalty_points, start_prio = self.bestSpot(
  51. offset_shape_arr, start_prio = start_prio, step = step)
  52. transformation = new_node._transformation
  53. if x is not None: # We could find a place
  54. transformation._data[0][3] = x
  55. transformation._data[2][3] = y
  56. self.place(x, y, hull_shape_arr) # take place before the next one
  57. else:
  58. Logger.log("d", "Could not find spot!")
  59. transformation._data[0][3] = 200
  60. transformation._data[2][3] = 100 + i * 20
  61. nodes.append(new_node)
  62. return nodes
  63. ## Fill priority, take offset as center. lower is better
  64. def centerFirst(self):
  65. # Distance x + distance y: creates diamond shape
  66. #self._priority = numpy.fromfunction(
  67. # lambda i, j: abs(self._offset_x-i)+abs(self._offset_y-j), self.shape, dtype=numpy.int32)
  68. # Square distance: creates a more round shape
  69. self._priority = numpy.fromfunction(
  70. lambda i, j: (self._offset_x - i) ** 2 + (self._offset_y - j) ** 2, self.shape, dtype=numpy.int32)
  71. self._priority_unique_values = numpy.unique(self._priority)
  72. self._priority_unique_values.sort()
  73. def backFirst(self):
  74. self._priority = numpy.fromfunction(
  75. lambda i, j: 10 * j + abs(self._offset_x - i), self.shape, dtype=numpy.int32)
  76. self._priority_unique_values = numpy.unique(self._priority)
  77. self._priority_unique_values.sort()
  78. ## Return the amount of "penalty points" for polygon, which is the sum of priority
  79. # 999999 if occupied
  80. def checkShape(self, x, y, shape_arr):
  81. x = int(self._scale * x)
  82. y = int(self._scale * y)
  83. offset_x = x + self._offset_x + shape_arr.offset_x
  84. offset_y = y + self._offset_y + shape_arr.offset_y
  85. occupied_slice = self._occupied[
  86. offset_y:offset_y + shape_arr.arr.shape[0],
  87. offset_x:offset_x + shape_arr.arr.shape[1]]
  88. try:
  89. if numpy.any(occupied_slice[numpy.where(shape_arr.arr == 1)]):
  90. return 999999
  91. except IndexError: # out of bounds if you try to place an object outside
  92. return 999999
  93. prio_slice = self._priority[
  94. offset_y:offset_y + shape_arr.arr.shape[0],
  95. offset_x:offset_x + shape_arr.arr.shape[1]]
  96. return numpy.sum(prio_slice[numpy.where(shape_arr.arr == 1)])
  97. ## Find "best" spot for ShapeArray
  98. def bestSpot(self, shape_arr, start_prio = 0, step = 1):
  99. start_idx_list = numpy.where(self._priority_unique_values == start_prio)
  100. if start_idx_list:
  101. start_idx = start_idx_list[0]
  102. else:
  103. start_idx = 0
  104. for prio in self._priority_unique_values[start_idx::step]:
  105. tryout_idx = numpy.where(self._priority == prio)
  106. for idx in range(len(tryout_idx[0])):
  107. x = tryout_idx[0][idx]
  108. y = tryout_idx[1][idx]
  109. projected_x = x - self._offset_x
  110. projected_y = y - self._offset_y
  111. # array to "world" coordinates
  112. penalty_points = self.checkShape(projected_x, projected_y, shape_arr)
  113. if penalty_points != 999999:
  114. return projected_x, projected_y, penalty_points, prio
  115. return None, None, None, prio # No suitable location found :-(
  116. ## Place the object
  117. def place(self, x, y, shape_arr):
  118. x = int(self._scale * x)
  119. y = int(self._scale * y)
  120. offset_x = x + self._offset_x + shape_arr.offset_x
  121. offset_y = y + self._offset_y + shape_arr.offset_y
  122. shape_y, shape_x = self._occupied.shape
  123. min_x = min(max(offset_x, 0), shape_x - 1)
  124. min_y = min(max(offset_y, 0), shape_y - 1)
  125. max_x = min(max(offset_x + shape_arr.arr.shape[1], 0), shape_x - 1)
  126. max_y = min(max(offset_y + shape_arr.arr.shape[0], 0), shape_y - 1)
  127. occupied_slice = self._occupied[min_y:max_y, min_x:max_x]
  128. # we use a slice of shape because it can be out of bounds
  129. occupied_slice[numpy.where(shape_arr.arr[
  130. min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)] = 1
  131. # Set priority to low (= high number), so it won't get picked at trying out.
  132. prio_slice = self._priority[min_y:max_y, min_x:max_x]
  133. prio_slice[numpy.where(shape_arr.arr[
  134. min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)] = 999