ArrangeObjectsJob.py 4.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. # Copyright (c) 2017 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from UM.Job import Job
  4. from UM.Scene.SceneNode import SceneNode
  5. from UM.Math.Vector import Vector
  6. from UM.Operations.TranslateOperation import TranslateOperation
  7. from UM.Operations.GroupedOperation import GroupedOperation
  8. from UM.Logger import Logger
  9. from UM.Message import Message
  10. from UM.i18n import i18nCatalog
  11. i18n_catalog = i18nCatalog("cura")
  12. from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
  13. from cura.Arranging.Arrange import Arrange
  14. from cura.Arranging.ShapeArray import ShapeArray
  15. from typing import List
  16. class ArrangeObjectsJob(Job):
  17. def __init__(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode], min_offset = 8):
  18. super().__init__()
  19. self._nodes = nodes
  20. self._fixed_nodes = fixed_nodes
  21. self._min_offset = min_offset
  22. def run(self):
  23. status_message = Message(i18n_catalog.i18nc("@info:status", "Finding new location for objects"),
  24. lifetime = 0,
  25. dismissable=False,
  26. progress = 0,
  27. title = i18n_catalog.i18nc("@info:title", "Finding Location"))
  28. status_message.show()
  29. arranger = Arrange.create(fixed_nodes = self._fixed_nodes)
  30. # Collect nodes to be placed
  31. nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr)
  32. for node in self._nodes:
  33. offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset)
  34. nodes_arr.append((offset_shape_arr.arr.shape[0] * offset_shape_arr.arr.shape[1], node, offset_shape_arr, hull_shape_arr))
  35. # Sort the nodes with the biggest area first.
  36. nodes_arr.sort(key=lambda item: item[0])
  37. nodes_arr.reverse()
  38. # Place nodes one at a time
  39. start_priority = 0
  40. last_priority = start_priority
  41. last_size = None
  42. grouped_operation = GroupedOperation()
  43. found_solution_for_all = True
  44. for idx, (size, node, offset_shape_arr, hull_shape_arr) in enumerate(nodes_arr):
  45. # For performance reasons, we assume that when a location does not fit,
  46. # it will also not fit for the next object (while what can be untrue).
  47. # We also skip possibilities by slicing through the possibilities (step = 10)
  48. if last_size == size: # This optimization works if many of the objects have the same size
  49. start_priority = last_priority
  50. else:
  51. start_priority = 0
  52. best_spot = arranger.bestSpot(offset_shape_arr, start_prio=start_priority, step=10)
  53. x, y = best_spot.x, best_spot.y
  54. node.removeDecorator(ZOffsetDecorator)
  55. if node.getBoundingBox():
  56. center_y = node.getWorldPosition().y - node.getBoundingBox().bottom
  57. else:
  58. center_y = 0
  59. if x is not None: # We could find a place
  60. last_size = size
  61. last_priority = best_spot.priority
  62. arranger.place(x, y, hull_shape_arr) # take place before the next one
  63. grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True))
  64. else:
  65. Logger.log("d", "Arrange all: could not find spot!")
  66. found_solution_for_all = False
  67. grouped_operation.addOperation(TranslateOperation(node, Vector(200, center_y, - idx * 20), set_position = True))
  68. status_message.setProgress((idx + 1) / len(nodes_arr) * 100)
  69. Job.yieldThread()
  70. grouped_operation.push()
  71. status_message.hide()
  72. if not found_solution_for_all:
  73. no_full_solution_message = Message(i18n_catalog.i18nc("@info:status", "Unable to find a location within the build volume for all objects"),
  74. title = i18n_catalog.i18nc("@info:title", "Can't Find Location"))
  75. no_full_solution_message.show()
  76. self.finished.emit(self)