diff --git a/cura/ArrangeObjectsJob.py b/cura/ArrangeObjectsJob.py new file mode 100644 index 0000000000..8e99cfe3dd --- /dev/null +++ b/cura/ArrangeObjectsJob.py @@ -0,0 +1,63 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from UM.Job import Job +from UM.Scene.SceneNode import SceneNode +from UM.Math.Vector import Vector +from UM.Operations.SetTransformOperation import SetTransformOperation +from UM.Message import Message + +from cura.ZOffsetDecorator import ZOffsetDecorator +from cura.Arrange import Arrange +from cura.ShapeArray import ShapeArray + +from typing import List + + +class ArrangeObjectsJob(Job): + def __init__(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode], min_offset = 8): + super().__init__() + self._nodes = nodes + self._fixed_nodes = fixed_nodes + self._min_offset = min_offset + + def run(self): + arranger = Arrange.create(fixed_nodes = self._fixed_nodes) + + # Collect nodes to be placed + nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr) + for node in self._nodes: + offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset) + nodes_arr.append((offset_shape_arr.arr.shape[0] * offset_shape_arr.arr.shape[1], node, offset_shape_arr, hull_shape_arr)) + + # Sort the nodes with the biggest area first. + nodes_arr.sort(key=lambda item: item[0]) + nodes_arr.reverse() + + # Place nodes one at a time + start_priority = 0 + last_priority = start_priority + last_size = None + for size, node, offset_shape_arr, hull_shape_arr in nodes_arr: + # For performance reasons, we assume that when a location does not fit, + # it will also not fit for the next object (while what can be untrue). + # We also skip possibilities by slicing through the possibilities (step = 10) + if last_size == size: # This optimization works if many of the objects have the same size + start_priority = last_priority + else: + start_priority = 0 + best_spot = arranger.bestSpot(offset_shape_arr, start_prio=start_priority, step=10) + x, y = best_spot.x, best_spot.y + last_size = size + last_priority = best_spot.priority + if x is not None: # We could find a place + arranger.place(x, y, hull_shape_arr) # take place before the next one + + node.removeDecorator(ZOffsetDecorator) + if node.getBoundingBox(): + center_y = node.getWorldPosition().y - node.getBoundingBox().bottom + else: + center_y = 0 + + transformation_operation = SetTransformOperation(node, Vector(x, center_y, y)) + transformation_operation.push() diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 8bfa5627f7..d407ae1b85 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -38,6 +38,8 @@ from cura.SetParentOperation import SetParentOperation from cura.SliceableObjectDecorator import SliceableObjectDecorator from cura.BlockSlicingDecorator import BlockSlicingDecorator +from cura.ArrangeObjectsJob import ArrangeObjectsJob + from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.SettingFunction import SettingFunction @@ -1020,51 +1022,12 @@ class CuraApplication(QtApplication): fixed_nodes.append(node) self.arrange(nodes, fixed_nodes) - ## Arrange the nodes, given fixed nodes + ## Arrange a set of nodes given a set of fixed nodes # \param nodes nodes that we have to place # \param fixed_nodes nodes that are placed in the arranger before finding spots for nodes def arrange(self, nodes, fixed_nodes): - min_offset = 8 - - arranger = Arrange.create(fixed_nodes = fixed_nodes) - - # Collect nodes to be placed - nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr) - for node in nodes: - offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset) - nodes_arr.append((offset_shape_arr.arr.shape[0] * offset_shape_arr.arr.shape[1], node, offset_shape_arr, hull_shape_arr)) - - # Sort nodes biggest area first - nodes_arr.sort(key = lambda item: item[0]) - nodes_arr.reverse() - - # Place nodes one at a time - start_prio = 0 - last_size = None - for size, node, offset_shape_arr, hull_shape_arr in nodes_arr: - # For performance reasons, we assume that when a location does not fit, - # it will also not fit for the next object (while what can be untrue). - # We also skip possibilities by slicing through the possibilities (step = 10) - if last_size == size: # This optimization works if many of the objects have the same size - start_prio = last_priority - else: - start_prio = 0 - best_spot = arranger.bestSpot(offset_shape_arr, start_prio = start_prio, step = 10) - x, y = best_spot.x, best_spot.y - last_size = size - last_priority = best_spot.priority - if x is not None: # We could find a place - arranger.place(x, y, hull_shape_arr) # take place before the next one - - node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator) - if node.getBoundingBox(): - center_y = node.getWorldPosition().y - node.getBoundingBox().bottom - else: - center_y = 0 - - op = GroupedOperation() - op.addOperation(SetTransformOperation(node, Vector(x, center_y, y))) - op.push() + job = ArrangeObjectsJob(nodes, fixed_nodes) + job.start() ## Reload all mesh data on the screen from file. @pyqtSlot()