MultiplyObjectsJob.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. # Copyright (c) 2018 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import copy
  4. from typing import List
  5. from UM.Job import Job
  6. from UM.Operations.GroupedOperation import GroupedOperation
  7. from UM.Message import Message
  8. from UM.Scene.SceneNode import SceneNode
  9. from UM.i18n import i18nCatalog
  10. i18n_catalog = i18nCatalog("cura")
  11. from cura.Arranging.Arrange import Arrange
  12. from cura.Arranging.ShapeArray import ShapeArray
  13. from UM.Application import Application
  14. from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
  15. class MultiplyObjectsJob(Job):
  16. def __init__(self, objects, count, min_offset = 8):
  17. super().__init__()
  18. self._objects = objects
  19. self._count = count
  20. self._min_offset = min_offset
  21. def run(self) -> None:
  22. status_message = Message(i18n_catalog.i18nc("@info:status", "Multiplying and placing objects"), lifetime=0,
  23. dismissable=False, progress=0, title = i18n_catalog.i18nc("@info:title", "Placing Objects"))
  24. status_message.show()
  25. scene = Application.getInstance().getController().getScene()
  26. total_progress = len(self._objects) * self._count
  27. current_progress = 0
  28. global_container_stack = Application.getInstance().getGlobalContainerStack()
  29. if global_container_stack is None:
  30. return # We can't do anything in this case.
  31. machine_width = global_container_stack.getProperty("machine_width", "value")
  32. machine_depth = global_container_stack.getProperty("machine_depth", "value")
  33. root = scene.getRoot()
  34. scale = 0.5
  35. arranger = Arrange.create(x = machine_width, y = machine_depth, scene_root = root, scale = scale, min_offset = self._min_offset)
  36. processed_nodes = [] # type: List[SceneNode]
  37. nodes = []
  38. not_fit_count = 0
  39. found_solution_for_all = False
  40. for node in self._objects:
  41. # If object is part of a group, multiply group
  42. current_node = node
  43. while current_node.getParent() and (current_node.getParent().callDecoration("isGroup") or current_node.getParent().callDecoration("isSliceable")):
  44. current_node = current_node.getParent()
  45. if current_node in processed_nodes:
  46. continue
  47. processed_nodes.append(current_node)
  48. node_too_big = False
  49. if node.getBoundingBox().width < machine_width or node.getBoundingBox().depth < machine_depth:
  50. offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset = self._min_offset, scale = scale)
  51. else:
  52. node_too_big = True
  53. found_solution_for_all = True
  54. arranger.resetLastPriority()
  55. for _ in range(self._count):
  56. # We do place the nodes one by one, as we want to yield in between.
  57. new_node = copy.deepcopy(node)
  58. solution_found = False
  59. if not node_too_big:
  60. if offset_shape_arr is not None and hull_shape_arr is not None:
  61. solution_found = arranger.findNodePlacement(new_node, offset_shape_arr, hull_shape_arr)
  62. else:
  63. # The node has no shape, so no need to arrange it. The solution is simple: Do nothing.
  64. solution_found = True
  65. if node_too_big or not solution_found:
  66. found_solution_for_all = False
  67. new_location = new_node.getPosition()
  68. new_location = new_location.set(z = - not_fit_count * 20)
  69. new_node.setPosition(new_location)
  70. not_fit_count += 1
  71. # Same build plate
  72. build_plate_number = current_node.callDecoration("getBuildPlateNumber")
  73. new_node.callDecoration("setBuildPlateNumber", build_plate_number)
  74. for child in new_node.getChildren():
  75. child.callDecoration("setBuildPlateNumber", build_plate_number)
  76. nodes.append(new_node)
  77. current_progress += 1
  78. status_message.setProgress((current_progress / total_progress) * 100)
  79. Job.yieldThread()
  80. Job.yieldThread()
  81. if nodes:
  82. operation = GroupedOperation()
  83. for new_node in nodes:
  84. operation.addOperation(AddSceneNodeOperation(new_node, current_node.getParent()))
  85. operation.push()
  86. status_message.hide()
  87. if not found_solution_for_all:
  88. no_full_solution_message = Message(i18n_catalog.i18nc("@info:status", "Unable to find a location within the build volume for all objects"), title = i18n_catalog.i18nc("@info:title", "Placing Object"))
  89. no_full_solution_message.show()