MultiplyObjectsJob.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  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.Application import Application
  6. from UM.Job import Job
  7. from UM.Math.Vector import Vector
  8. from UM.Message import Message
  9. from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
  10. from UM.Operations.GroupedOperation import GroupedOperation
  11. from UM.Operations.TranslateOperation import TranslateOperation
  12. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  13. from UM.Scene.SceneNode import SceneNode
  14. from UM.i18n import i18nCatalog
  15. from cura.Arranging.GridArrange import GridArrange
  16. from cura.Arranging.Nest2DArrange import Nest2DArrange
  17. i18n_catalog = i18nCatalog("cura")
  18. class MultiplyObjectsJob(Job):
  19. def __init__(self, objects, count: int, min_offset: int = 8 ,* , grid_arrange: bool = False):
  20. super().__init__()
  21. self._objects = objects
  22. self._count: int = count
  23. self._min_offset: int = min_offset
  24. self._grid_arrange: bool = grid_arrange
  25. def run(self) -> None:
  26. status_message = Message(i18n_catalog.i18nc("@info:status", "Multiplying and placing objects"), lifetime = 0,
  27. dismissable = False, progress = 0,
  28. title = i18n_catalog.i18nc("@info:title", "Placing Objects"))
  29. status_message.show()
  30. scene = Application.getInstance().getController().getScene()
  31. global_container_stack = Application.getInstance().getGlobalContainerStack()
  32. if global_container_stack is None:
  33. return # We can't do anything in this case.
  34. root = scene.getRoot()
  35. processed_nodes: List[SceneNode] = []
  36. nodes = []
  37. fixed_nodes = []
  38. for node_ in DepthFirstIterator(root):
  39. # Only count sliceable objects
  40. if node_.callDecoration("isSliceable"):
  41. fixed_nodes.append(node_)
  42. nodes_to_add_without_arrange = []
  43. for node in self._objects:
  44. # If object is part of a group, multiply group
  45. current_node = node
  46. while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
  47. current_node = current_node.getParent()
  48. if current_node in processed_nodes:
  49. continue
  50. processed_nodes.append(current_node)
  51. for _ in range(self._count):
  52. new_node = copy.deepcopy(node)
  53. # Same build plate
  54. build_plate_number = current_node.callDecoration("getBuildPlateNumber")
  55. new_node.callDecoration("setBuildPlateNumber", build_plate_number)
  56. for child in new_node.getChildren():
  57. child.callDecoration("setBuildPlateNumber", build_plate_number)
  58. if not current_node.getParent().callDecoration("isSliceable"):
  59. nodes.append(new_node)
  60. else:
  61. # The node we're trying to place has another node that is sliceable as a parent.
  62. # As such, we shouldn't arrange it (but it should be added to the scene!)
  63. nodes_to_add_without_arrange.append(new_node)
  64. new_node.setParent(current_node.getParent())
  65. found_solution_for_all = True
  66. group_operation = GroupedOperation()
  67. if nodes:
  68. if self._grid_arrange:
  69. arranger = GridArrange(nodes, Application.getInstance().getBuildVolume(), fixed_nodes)
  70. else:
  71. arranger = Nest2DArrange(nodes, Application.getInstance().getBuildVolume(), fixed_nodes, factor=1000)
  72. group_operation, not_fit_count = arranger.createGroupOperationForArrange(add_new_nodes_in_scene=True)
  73. if nodes_to_add_without_arrange:
  74. for nested_node in nodes_to_add_without_arrange:
  75. group_operation.addOperation(AddSceneNodeOperation(nested_node, nested_node.getParent()))
  76. # Move the node a tiny bit so it doesn't overlap with the existing one.
  77. # This doesn't fix it if someone creates more than one duplicate, but it at least shows that something
  78. # happened (and after moving it, it's clear that there are more underneath)
  79. group_operation.addOperation(TranslateOperation(nested_node, Vector(2.5, 2.5, 2.5)))
  80. group_operation.push()
  81. status_message.hide()
  82. if not found_solution_for_all:
  83. no_full_solution_message = Message(
  84. i18n_catalog.i18nc("@info:status", "Unable to find a location within the build volume for all objects"),
  85. title = i18n_catalog.i18nc("@info:title", "Placing Object"),
  86. message_type = Message.MessageType.WARNING)
  87. no_full_solution_message.show()