Arrange.py 7.0 KB

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