MultiplyObjectsJob.py 4.7 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. for node in self._objects:
  40. # If object is part of a group, multiply group
  41. current_node = node
  42. while current_node.getParent() and (current_node.getParent().callDecoration("isGroup") or current_node.getParent().callDecoration("isSliceable")):
  43. current_node = current_node.getParent()
  44. if current_node in processed_nodes:
  45. continue
  46. processed_nodes.append(current_node)
  47. node_too_big = False
  48. if node.getBoundingBox().width < machine_width or node.getBoundingBox().depth < machine_depth:
  49. offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset = self._min_offset, scale = scale)
  50. else:
  51. node_too_big = True
  52. found_solution_for_all = True
  53. arranger.resetLastPriority()
  54. for i in range(self._count):
  55. # We do place the nodes one by one, as we want to yield in between.
  56. new_node = copy.deepcopy(node)
  57. solution_found = False
  58. if not node_too_big:
  59. if offset_shape_arr is not None and hull_shape_arr is not None:
  60. solution_found = arranger.findNodePlacement(new_node, offset_shape_arr, hull_shape_arr)
  61. else:
  62. # The node has no shape, so no need to arrange it. The solution is simple: Do nothing.
  63. solution_found = True
  64. if node_too_big or not solution_found:
  65. found_solution_for_all = False
  66. new_location = new_node.getPosition()
  67. new_location = new_location.set(z = - not_fit_count * 20)
  68. new_node.setPosition(new_location)
  69. not_fit_count += 1
  70. # Same build plate
  71. build_plate_number = current_node.callDecoration("getBuildPlateNumber")
  72. new_node.callDecoration("setBuildPlateNumber", build_plate_number)
  73. for child in new_node.getChildren():
  74. child.callDecoration("setBuildPlateNumber", build_plate_number)
  75. nodes.append(new_node)
  76. current_progress += 1
  77. status_message.setProgress((current_progress / total_progress) * 100)
  78. Job.yieldThread()
  79. Job.yieldThread()
  80. if nodes:
  81. op = GroupedOperation()
  82. for new_node in nodes:
  83. op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent()))
  84. op.push()
  85. status_message.hide()
  86. if not found_solution_for_all:
  87. 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"))
  88. no_full_solution_message.show()