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 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) coord_initial_leftover_x = self._build_volume_bounding_box.right + 2 * self._grid_width coord_initial_leftover_y = (self._build_volume_bounding_box.back + self._build_volume_bounding_box.front) * 0.5 self._initial_leftover_grid_x, self._initial_leftover_grid_y = self.coordSpaceToGridSpace(coord_initial_leftover_x, coord_initial_leftover_y) self._initial_leftover_grid_x = math.floor(self._initial_leftover_grid_x) self._initial_leftover_grid_y = math.floor(self._initial_leftover_grid_y) def arrange(self)-> bool: grouped_operation, not_fit_count = self.createGroupOperationForArrange() grouped_operation.push() return not_fit_count == 0 def createGroupOperationForArrange(self) -> Tuple[GroupedOperation, int]: # 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 operation = self.moveNodeOnGrid(node, grid_x, grid_y) grouped_operation.addOperation(operation) leftover_nodes = self._nodes_to_arrange[len(sequence):] left_over_grid_y = self._initial_leftover_grid_y for node in leftover_nodes: grouped_operation.addOperation(AddSceneNodeOperation(node, scene_root)) # find the first next grid position that isn't occupied by a fixed node while (self._initial_leftover_grid_x, left_over_grid_y) in fixed_nodes_grid_ids: left_over_grid_y = left_over_grid_y - 1 operation = self.moveNodeOnGrid(node, self._initial_leftover_grid_x, left_over_grid_y) grouped_operation.addOperation(operation) left_over_grid_y = left_over_grid_y - 1 self.drawDebugSvg() return grouped_operation, len(leftover_nodes) def moveNodeOnGrid(self, node: "SceneNode", grid_x: int, grid_y: int) -> "Operation.Operation": 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 return TranslateOperation(node, Vector(delta_x, 0, delta_y)) 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"")