Cura/cura/Arranging/GridArrange.py
saumya.jain 2089462cd8 place objects outside grid
Co-authored-by: Casper Lamboo <c.lamboo@ultimaker.com>

CURA-7951
2023-08-22 11:16:02 +02:00

218 lines
9.9 KiB
Python

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"<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")
f.write(
f"""
<rect
x='{build_volume_bounding_box.left}'
y='{build_volume_bounding_box.back}'
width='{build_volume_bounding_box.width}'
height='{build_volume_bounding_box.depth}'
fill=\"lightgrey\"
/>
""")
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"""
<rect
x="{coord_grid_x}"
y="{coord_grid_y}"
width="{self._grid_width}"
height="{self._grid_height}"
fill="green"
stroke="black"
/>
""")
f.write(f"""
<text
x="{coord_grid_x + self._grid_width * 0.5}"
y="{coord_grid_y + self._grid_height * 0.5}"
>
{grid_x},{grid_y}
</text>
""")
for node in self._fixed_nodes:
bounding_box = node.getBoundingBox()
f.write(f"""
<rect
x="{bounding_box.left}"
y="{bounding_box.back}"
width="{bounding_box.width}"
height="{bounding_box.depth}"
fill="red"
/>
""")
for node in self._nodes_to_arrange:
bounding_box = node.getBoundingBox()
f.write(f"""
<rect
x="{bounding_box.left}"
y="{bounding_box.back}"
width="{bounding_box.width}"
height="{bounding_box.depth}"
fill="rgba(0,0,0,0.1)"
stroke="blue"
stroke-width="3"
/>
""")
f.write(f"</svg>")