import math from typing import List, TYPE_CHECKING, Optional, Tuple, Set from UM.Application import Application from UM.Math import AxisAlignedBox from UM.Math.Vector import Vector from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.TranslateOperation import TranslateOperation class GridArrange: offset_x: float = 10 offset_y: float = 10 _grid_width: float _grid_height: float _nodes_to_arrange: List["SceneNode"] _fixed_nodes: List["SceneNode"] _build_volume_bounding_box = AxisAlignedBox def __init__(self, nodes_to_arrange: List["SceneNode"], build_volume: "BuildVolume", fixed_nodes: List["SceneNode"] = []): print("len(nodes_to_arrange)", len(nodes_to_arrange)) self._nodes_to_arrange = nodes_to_arrange self._build_volume_bounding_box = build_volume.getBoundingBox() self._fixed_nodes = fixed_nodes def arrange(self) -> Tuple[GroupedOperation, int]: self._grid_width = 0 self._grid_height = 0 for node in self._nodes_to_arrange: bounding_box = node.getBoundingBox() self._grid_width = max(self._grid_width, bounding_box.width) self._grid_height = max(self._grid_height, bounding_box.depth) # Find grid indexes that intersect with fixed objects fixed_nodes_grid_ids = set() for node in self._fixed_nodes: fixed_nodes_grid_ids = fixed_nodes_grid_ids.union(self.intersectingGridIdxInclusive(node.getBoundingBox())) build_plate_grid_ids = self.intersectingGridIdxExclusive(self._build_volume_bounding_box) allowed_grid_idx = build_plate_grid_ids.difference(fixed_nodes_grid_ids) # Find the sequence in which items are placed coord_build_plate_center_x = self._build_volume_bounding_box.width * 0.5 + self._build_volume_bounding_box.left coord_build_plate_center_y = self._build_volume_bounding_box.depth * 0.5 + self._build_volume_bounding_box.back grid_build_plate_center_x, grid_build_plate_center_y = self.coordSpaceToGridSpace(coord_build_plate_center_x, coord_build_plate_center_y) def distToCenter(grid_id: Tuple[int, int]) -> float: grid_x, grid_y = grid_id distance_squared = (grid_build_plate_center_x - grid_x) ** 2 + (grid_build_plate_center_y - grid_y) ** 2 return distance_squared sequence: List[Tuple[int, int]] = list(allowed_grid_idx) sequence.sort(key=distToCenter) scene_root = Application.getInstance().getController().getScene().getRoot() grouped_operation = GroupedOperation() for grid_id, node in zip(sequence, self._nodes_to_arrange): grouped_operation.addOperation(AddSceneNodeOperation(node, scene_root)) grid_x, grid_y = grid_id coord_grid_x, coord_grid_y = self.gridSpaceToCoordSpace(grid_x, grid_y) center_grid_x = coord_grid_x+(0.5 * self._grid_width) center_grid_y = coord_grid_y+(0.5 * self._grid_height) bounding_box = node.getBoundingBox() center_node_x = (bounding_box.left + bounding_box.right) * 0.5 center_node_y = (bounding_box.back + bounding_box.front) * 0.5 delta_x = center_grid_x - center_node_x delta_y = center_grid_y - center_node_y grouped_operation.addOperation(TranslateOperation(node, Vector(delta_x, 0, delta_y))) self.drawDebugSvg() return grouped_operation, 0 def getGridCornerPoints(self, bounding_box: "BoundingVolume") -> Tuple[float, float, float, float]: coord_x1 = bounding_box.left coord_x2 = bounding_box.right coord_y1 = bounding_box.back coord_y2 = bounding_box.front grid_x1, grid_y1 = self.coordSpaceToGridSpace(coord_x1, coord_y1) grid_x2, grid_y2 = self.coordSpaceToGridSpace(coord_x2, coord_y2) return grid_x1, grid_y1, grid_x2, grid_y2 def intersectingGridIdxInclusive(self, bounding_box: "BoundingVolume") -> Set[Tuple[int, int]]: grid_x1, grid_y1, grid_x2, grid_y2 = self.getGridCornerPoints(bounding_box) grid_idx = set() for grid_x in range(math.floor(grid_x1), math.ceil(grid_x2)): for grid_y in range(math.floor(grid_y1), math.ceil(grid_y2)): grid_idx.add((grid_x, grid_y)) return grid_idx def intersectingGridIdxExclusive(self, bounding_box: "BoundingVolume") -> Set[Tuple[int, int]]: grid_x1, grid_y1, grid_x2, grid_y2 = self.getGridCornerPoints(bounding_box) grid_idx = set() for grid_x in range(math.ceil(grid_x1), math.floor(grid_x2)): for grid_y in range(math.ceil(grid_y1), math.floor(grid_y2)): grid_idx.add((grid_x, grid_y)) return grid_idx def gridSpaceToCoordSpace(self, x: float, y: float) -> Tuple[float, float]: grid_x = x * (self._grid_width + self.offset_x) + self._build_volume_bounding_box.left grid_y = y * (self._grid_height + self.offset_y) + self._build_volume_bounding_box.back return grid_x, grid_y def coordSpaceToGridSpace(self, grid_x: float, grid_y: float) -> Tuple[float, float]: coord_x = (grid_x - self._build_volume_bounding_box.left) / (self._grid_width + self.offset_x) coord_y = (grid_y - self._build_volume_bounding_box.back) / (self._grid_height + self.offset_y) return coord_x, coord_y def drawDebugSvg(self): with open("Builvolume_test.svg", "w") as f: build_volume_bounding_box = self._build_volume_bounding_box f.write( f"\n") f.write( f""" """) for grid_x in range(-10, 10): for grid_y in range(-10, 10): # if (grid_x, grid_y) in intersecting_grid_idx: # fill_color = "red" # elif (grid_x, grid_y) in build_plate_grid_idx: # fill_color = "green" # else: # fill_color = "orange" coord_grid_x, coord_grid_y = self.gridSpaceToCoordSpace(grid_x, grid_y) f.write( f""" """) f.write(f""" {grid_x},{grid_y} """) for node in self._fixed_nodes: bounding_box = node.getBoundingBox() f.write(f""" """) for node in self._nodes_to_arrange: bounding_box = node.getBoundingBox() f.write(f""" """) f.write(f"")