MultiplyObjectsJob.py 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. # Copyright (c) 2017 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. from UM.Job import Job
  4. from UM.Scene.SceneNode import SceneNode
  5. from UM.Math.Vector import Vector
  6. from UM.Operations.SetTransformOperation import SetTransformOperation
  7. from UM.Operations.TranslateOperation import TranslateOperation
  8. from UM.Operations.GroupedOperation import GroupedOperation
  9. from UM.Logger import Logger
  10. from UM.Message import Message
  11. from UM.i18n import i18nCatalog
  12. i18n_catalog = i18nCatalog("cura")
  13. from cura.ZOffsetDecorator import ZOffsetDecorator
  14. from cura.Arrange import Arrange
  15. from cura.ShapeArray import ShapeArray
  16. from typing import List
  17. from UM.Application import Application
  18. from UM.Scene.Selection import Selection
  19. from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
  20. class MultiplyObjectsJob(Job):
  21. def __init__(self, object_id, count, min_offset = 8):
  22. super().__init__()
  23. self._object_id = object_id
  24. self._count = count
  25. self._min_offset = min_offset
  26. def run(self):
  27. status_message = Message(i18n_catalog.i18nc("@info:status", "Multiplying and placing objects"), lifetime=0,
  28. dismissable=False, progress=0)
  29. status_message.show()
  30. scene = Application.getInstance().getController().getScene()
  31. node = scene.findObject(self._object_id)
  32. if not node and self._object_id != 0: # Workaround for tool handles overlapping the selected object
  33. node = Selection.getSelectedObject(0)
  34. # If object is part of a group, multiply group
  35. current_node = node
  36. while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
  37. current_node = current_node.getParent()
  38. root = scene.getRoot()
  39. arranger = Arrange.create(scene_root=root)
  40. node_too_big = False
  41. if node.getBoundingBox().width < 300 or node.getBoundingBox().depth < 300:
  42. offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset=self._min_offset)
  43. else:
  44. node_too_big = True
  45. nodes = []
  46. found_solution_for_all = True
  47. for i in range(self._count):
  48. # We do place the nodes one by one, as we want to yield in between.
  49. if not node_too_big:
  50. node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr)
  51. if node_too_big or not solution_found:
  52. found_solution_for_all = False
  53. new_location = node.getPosition()
  54. new_location = new_location.set(z = 100 - i * 20)
  55. node.setPosition(new_location)
  56. nodes.append(node)
  57. Job.yieldThread()
  58. status_message.setProgress((i + 1) / self._count * 100)
  59. if nodes:
  60. op = GroupedOperation()
  61. for new_node in nodes:
  62. op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent()))
  63. op.push()
  64. status_message.hide()
  65. if not found_solution_for_all:
  66. no_full_solution_message = Message(i18n_catalog.i18nc("@info:status", "Unable to find a location within the build volume for all objects"))
  67. no_full_solution_message.show()