ArrangeObjectsJob.py 4.9 KB

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