GridArrange.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import math
  2. from typing import List, TYPE_CHECKING, Optional, Tuple, Set
  3. from UM.Application import Application
  4. from UM.Math import AxisAlignedBox
  5. from UM.Math.Vector import Vector
  6. from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
  7. from UM.Operations.GroupedOperation import GroupedOperation
  8. from UM.Operations.TranslateOperation import TranslateOperation
  9. class GridArrange:
  10. offset_x: float = 10
  11. offset_y: float = 10
  12. _grid_width: float
  13. _grid_height: float
  14. _nodes_to_arrange: List["SceneNode"]
  15. _fixed_nodes: List["SceneNode"]
  16. _build_volume_bounding_box = AxisAlignedBox
  17. def __init__(self, nodes_to_arrange: List["SceneNode"], build_volume: "BuildVolume", fixed_nodes: List["SceneNode"] = []):
  18. print("len(nodes_to_arrange)", len(nodes_to_arrange))
  19. self._nodes_to_arrange = nodes_to_arrange
  20. self._build_volume_bounding_box = build_volume.getBoundingBox()
  21. self._fixed_nodes = fixed_nodes
  22. def arrange(self)-> bool:
  23. grouped_operation, not_fit_count = self.createGroupOperationForArrange()
  24. grouped_operation.push()
  25. return not_fit_count == 0
  26. def createGroupOperationForArrange(self) -> Tuple[GroupedOperation, int]:
  27. self._grid_width = 0
  28. self._grid_height = 0
  29. for node in self._nodes_to_arrange:
  30. bounding_box = node.getBoundingBox()
  31. self._grid_width = max(self._grid_width, bounding_box.width)
  32. self._grid_height = max(self._grid_height, bounding_box.depth)
  33. # Find grid indexes that intersect with fixed objects
  34. fixed_nodes_grid_ids = set()
  35. for node in self._fixed_nodes:
  36. fixed_nodes_grid_ids = fixed_nodes_grid_ids.union(self.intersectingGridIdxInclusive(node.getBoundingBox()))
  37. build_plate_grid_ids = self.intersectingGridIdxExclusive(self._build_volume_bounding_box)
  38. allowed_grid_idx = build_plate_grid_ids.difference(fixed_nodes_grid_ids)
  39. # Find the sequence in which items are placed
  40. coord_build_plate_center_x = self._build_volume_bounding_box.width * 0.5 + self._build_volume_bounding_box.left
  41. coord_build_plate_center_y = self._build_volume_bounding_box.depth * 0.5 + self._build_volume_bounding_box.back
  42. grid_build_plate_center_x, grid_build_plate_center_y = self.coordSpaceToGridSpace(coord_build_plate_center_x, coord_build_plate_center_y)
  43. def distToCenter(grid_id: Tuple[int, int]) -> float:
  44. grid_x, grid_y = grid_id
  45. distance_squared = (grid_build_plate_center_x - grid_x) ** 2 + (grid_build_plate_center_y - grid_y) ** 2
  46. return distance_squared
  47. sequence: List[Tuple[int, int]] = list(allowed_grid_idx)
  48. sequence.sort(key=distToCenter)
  49. scene_root = Application.getInstance().getController().getScene().getRoot()
  50. grouped_operation = GroupedOperation()
  51. for grid_id, node in zip(sequence, self._nodes_to_arrange):
  52. grouped_operation.addOperation(AddSceneNodeOperation(node, scene_root))
  53. grid_x, grid_y = grid_id
  54. coord_grid_x, coord_grid_y = self.gridSpaceToCoordSpace(grid_x, grid_y)
  55. center_grid_x = coord_grid_x+(0.5 * self._grid_width)
  56. center_grid_y = coord_grid_y+(0.5 * self._grid_height)
  57. bounding_box = node.getBoundingBox()
  58. center_node_x = (bounding_box.left + bounding_box.right) * 0.5
  59. center_node_y = (bounding_box.back + bounding_box.front) * 0.5
  60. delta_x = center_grid_x - center_node_x
  61. delta_y = center_grid_y - center_node_y
  62. grouped_operation.addOperation(TranslateOperation(node, Vector(delta_x, 0, delta_y)))
  63. self.drawDebugSvg()
  64. return grouped_operation, 0
  65. def getGridCornerPoints(self, bounding_box: "BoundingVolume") -> Tuple[float, float, float, float]:
  66. coord_x1 = bounding_box.left
  67. coord_x2 = bounding_box.right
  68. coord_y1 = bounding_box.back
  69. coord_y2 = bounding_box.front
  70. grid_x1, grid_y1 = self.coordSpaceToGridSpace(coord_x1, coord_y1)
  71. grid_x2, grid_y2 = self.coordSpaceToGridSpace(coord_x2, coord_y2)
  72. return grid_x1, grid_y1, grid_x2, grid_y2
  73. def intersectingGridIdxInclusive(self, bounding_box: "BoundingVolume") -> Set[Tuple[int, int]]:
  74. grid_x1, grid_y1, grid_x2, grid_y2 = self.getGridCornerPoints(bounding_box)
  75. grid_idx = set()
  76. for grid_x in range(math.floor(grid_x1), math.ceil(grid_x2)):
  77. for grid_y in range(math.floor(grid_y1), math.ceil(grid_y2)):
  78. grid_idx.add((grid_x, grid_y))
  79. return grid_idx
  80. def intersectingGridIdxExclusive(self, bounding_box: "BoundingVolume") -> Set[Tuple[int, int]]:
  81. grid_x1, grid_y1, grid_x2, grid_y2 = self.getGridCornerPoints(bounding_box)
  82. grid_idx = set()
  83. for grid_x in range(math.ceil(grid_x1), math.floor(grid_x2)):
  84. for grid_y in range(math.ceil(grid_y1), math.floor(grid_y2)):
  85. grid_idx.add((grid_x, grid_y))
  86. return grid_idx
  87. def gridSpaceToCoordSpace(self, x: float, y: float) -> Tuple[float, float]:
  88. grid_x = x * (self._grid_width + self.offset_x) + self._build_volume_bounding_box.left
  89. grid_y = y * (self._grid_height + self.offset_y) + self._build_volume_bounding_box.back
  90. return grid_x, grid_y
  91. def coordSpaceToGridSpace(self, grid_x: float, grid_y: float) -> Tuple[float, float]:
  92. coord_x = (grid_x - self._build_volume_bounding_box.left) / (self._grid_width + self.offset_x)
  93. coord_y = (grid_y - self._build_volume_bounding_box.back) / (self._grid_height + self.offset_y)
  94. return coord_x, coord_y
  95. def drawDebugSvg(self):
  96. with open("Builvolume_test.svg", "w") as f:
  97. build_volume_bounding_box = self._build_volume_bounding_box
  98. f.write(
  99. f"<svg xmlns='http://www.w3.org/2000/svg' viewBox='{build_volume_bounding_box.left - 100} {build_volume_bounding_box.back - 100} {build_volume_bounding_box.width + 200} {build_volume_bounding_box.depth + 200}'>\n")
  100. f.write(
  101. f"""
  102. <rect
  103. x='{build_volume_bounding_box.left}'
  104. y='{build_volume_bounding_box.back}'
  105. width='{build_volume_bounding_box.width}'
  106. height='{build_volume_bounding_box.depth}'
  107. fill=\"lightgrey\"
  108. />
  109. """)
  110. for grid_x in range(-10, 10):
  111. for grid_y in range(-10, 10):
  112. # if (grid_x, grid_y) in intersecting_grid_idx:
  113. # fill_color = "red"
  114. # elif (grid_x, grid_y) in build_plate_grid_idx:
  115. # fill_color = "green"
  116. # else:
  117. # fill_color = "orange"
  118. coord_grid_x, coord_grid_y = self.gridSpaceToCoordSpace(grid_x, grid_y)
  119. f.write(
  120. f"""
  121. <rect
  122. x="{coord_grid_x}"
  123. y="{coord_grid_y}"
  124. width="{self._grid_width}"
  125. height="{self._grid_height}"
  126. fill="green"
  127. stroke="black"
  128. />
  129. """)
  130. f.write(f"""
  131. <text
  132. x="{coord_grid_x + self._grid_width * 0.5}"
  133. y="{coord_grid_y + self._grid_height * 0.5}"
  134. >
  135. {grid_x},{grid_y}
  136. </text>
  137. """)
  138. for node in self._fixed_nodes:
  139. bounding_box = node.getBoundingBox()
  140. f.write(f"""
  141. <rect
  142. x="{bounding_box.left}"
  143. y="{bounding_box.back}"
  144. width="{bounding_box.width}"
  145. height="{bounding_box.depth}"
  146. fill="red"
  147. />
  148. """)
  149. for node in self._nodes_to_arrange:
  150. bounding_box = node.getBoundingBox()
  151. f.write(f"""
  152. <rect
  153. x="{bounding_box.left}"
  154. y="{bounding_box.back}"
  155. width="{bounding_box.width}"
  156. height="{bounding_box.depth}"
  157. fill="rgba(0,0,0,0.1)"
  158. stroke="blue"
  159. stroke-width="3"
  160. />
  161. """)
  162. f.write(f"</svg>")