multiplying objects place them in a grid

Co-authored-by: Casper Lamboo <c.lamboo@ultimaker.com>

CURA-7951
This commit is contained in:
saumya.jain 2023-08-21 18:51:08 +02:00
parent df69ccadb5
commit b91ebcbb36
2 changed files with 197 additions and 6 deletions

View file

@ -0,0 +1,185 @@
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"<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>")

View file

@ -14,6 +14,7 @@ from UM.Operations.TranslateOperation import TranslateOperation
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from cura.Arranging.GridArrange import GridArrange
from cura.Arranging.Nest2DArrange import arrange, createGroupOperationForArrange from cura.Arranging.Nest2DArrange import arrange, createGroupOperationForArrange
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")
@ -77,12 +78,17 @@ class MultiplyObjectsJob(Job):
found_solution_for_all = True found_solution_for_all = True
group_operation = GroupedOperation() group_operation = GroupedOperation()
if nodes: if nodes:
group_operation, not_fit_count = createGroupOperationForArrange(nodes, grid_arrange = GridArrange(nodes,Application.getInstance().getBuildVolume(),
Application.getInstance().getBuildVolume(), fixed_nodes)
fixed_nodes,
factor=10000, group_operation, not_fit_count = grid_arrange.arrange()
add_new_nodes_in_scene=True, print("group_operation", group_operation)
lock_rotation=self._lock_rotation) # group_operation, not_fit_count = createGroupOperationForArrange(nodes,
# Application.getInstance().getBuildVolume(),
# fixed_nodes,
# factor=10000,
# add_new_nodes_in_scene=True,
# lock_rotation=self._lock_rotation)
found_solution_for_all = not_fit_count == 0 found_solution_for_all = not_fit_count == 0
if nodes_to_add_without_arrange: if nodes_to_add_without_arrange: