MultiplyObjectsJob.py 4.9 KB

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