CuraActions.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. # Copyright (c) 2018 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from PyQt5.QtCore import QObject, QUrl
  4. from PyQt5.QtGui import QDesktopServices
  5. from typing import List, Optional, cast
  6. from UM.Event import CallFunctionEvent
  7. from UM.FlameProfiler import pyqtSlot
  8. from UM.Math.Quaternion import Quaternion
  9. from UM.Math.Vector import Vector
  10. from UM.Scene.Selection import Selection
  11. from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
  12. from UM.Operations.GroupedOperation import GroupedOperation
  13. from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
  14. from UM.Operations.RotateOperation import RotateOperation
  15. from UM.Operations.TranslateOperation import TranslateOperation
  16. import cura.CuraApplication
  17. from cura.Operations.SetParentOperation import SetParentOperation
  18. from cura.MultiplyObjectsJob import MultiplyObjectsJob
  19. from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation
  20. from cura.Settings.ExtruderManager import ExtruderManager
  21. from cura.Operations.SetBuildPlateNumberOperation import SetBuildPlateNumberOperation
  22. from UM.Logger import Logger
  23. from UM.Scene.SceneNode import SceneNode
  24. class CuraActions(QObject):
  25. def __init__(self, parent: QObject = None) -> None:
  26. super().__init__(parent)
  27. @pyqtSlot()
  28. def openDocumentation(self) -> None:
  29. # Starting a web browser from a signal handler connected to a menu will crash on windows.
  30. # So instead, defer the call to the next run of the event loop, since that does work.
  31. # Note that weirdly enough, only signal handlers that open a web browser fail like that.
  32. event = CallFunctionEvent(self._openUrl, [QUrl("https://ultimaker.com/en/resources/manuals/software")], {})
  33. cura.CuraApplication.CuraApplication.getInstance().functionEvent(event)
  34. @pyqtSlot()
  35. def openBugReportPage(self) -> None:
  36. event = CallFunctionEvent(self._openUrl, [QUrl("https://github.com/Ultimaker/Cura/issues")], {})
  37. cura.CuraApplication.CuraApplication.getInstance().functionEvent(event)
  38. ## Reset camera position and direction to default
  39. @pyqtSlot()
  40. def homeCamera(self) -> None:
  41. scene = cura.CuraApplication.CuraApplication.getInstance().getController().getScene()
  42. camera = scene.getActiveCamera()
  43. if camera:
  44. diagonal_size = cura.CuraApplication.CuraApplication.getInstance().getBuildVolume().getDiagonalSize()
  45. camera.setPosition(Vector(-80, 250, 700) * diagonal_size / 375)
  46. camera.setPerspective(True)
  47. camera.lookAt(Vector(0, 0, 0))
  48. ## Center all objects in the selection
  49. @pyqtSlot()
  50. def centerSelection(self) -> None:
  51. operation = GroupedOperation()
  52. for node in Selection.getAllSelectedObjects():
  53. current_node = node
  54. parent_node = current_node.getParent()
  55. while parent_node and parent_node.callDecoration("isGroup"):
  56. current_node = parent_node
  57. parent_node = current_node.getParent()
  58. # This was formerly done with SetTransformOperation but because of
  59. # unpredictable matrix deconstruction it was possible that mirrors
  60. # could manifest as rotations. Centering is therefore done by
  61. # moving the node to negative whatever its position is:
  62. center_operation = TranslateOperation(current_node, -current_node._position)
  63. operation.addOperation(center_operation)
  64. operation.push()
  65. ## Multiply all objects in the selection
  66. #
  67. # \param count The number of times to multiply the selection.
  68. @pyqtSlot(int)
  69. def multiplySelection(self, count: int) -> None:
  70. min_offset = cura.CuraApplication.CuraApplication.getInstance().getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors
  71. job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, min_offset = max(min_offset, 8))
  72. job.start()
  73. ## Delete all selected objects.
  74. @pyqtSlot()
  75. def deleteSelection(self) -> None:
  76. if not cura.CuraApplication.CuraApplication.getInstance().getController().getToolsEnabled():
  77. return
  78. removed_group_nodes = [] #type: List[SceneNode]
  79. op = GroupedOperation()
  80. nodes = Selection.getAllSelectedObjects()
  81. for node in nodes:
  82. op.addOperation(RemoveSceneNodeOperation(node))
  83. group_node = node.getParent()
  84. if group_node and group_node.callDecoration("isGroup") and group_node not in removed_group_nodes:
  85. remaining_nodes_in_group = list(set(group_node.getChildren()) - set(nodes))
  86. if len(remaining_nodes_in_group) == 1:
  87. removed_group_nodes.append(group_node)
  88. op.addOperation(SetParentOperation(remaining_nodes_in_group[0], group_node.getParent()))
  89. op.addOperation(RemoveSceneNodeOperation(group_node))
  90. # Reset the print information
  91. cura.CuraApplication.CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)
  92. op.push()
  93. ## Set the extruder that should be used to print the selection.
  94. #
  95. # \param extruder_id The ID of the extruder stack to use for the selected objects.
  96. @pyqtSlot(str)
  97. def setExtruderForSelection(self, extruder_id: str) -> None:
  98. operation = GroupedOperation()
  99. nodes_to_change = []
  100. for node in Selection.getAllSelectedObjects():
  101. # If the node is a group, apply the active extruder to all children of the group.
  102. if node.callDecoration("isGroup"):
  103. for grouped_node in BreadthFirstIterator(node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
  104. if grouped_node.callDecoration("getActiveExtruder") == extruder_id:
  105. continue
  106. if grouped_node.callDecoration("isGroup"):
  107. continue
  108. nodes_to_change.append(grouped_node)
  109. continue
  110. # Do not change any nodes that already have the right extruder set.
  111. if node.callDecoration("getActiveExtruder") == extruder_id:
  112. continue
  113. nodes_to_change.append(node)
  114. if not nodes_to_change:
  115. # If there are no changes to make, we still need to reset the selected extruders.
  116. # This is a workaround for checked menu items being deselected while still being
  117. # selected.
  118. ExtruderManager.getInstance().resetSelectedObjectExtruders()
  119. return
  120. for node in nodes_to_change:
  121. operation.addOperation(SetObjectExtruderOperation(node, extruder_id))
  122. operation.push()
  123. @pyqtSlot(int)
  124. def setBuildPlateForSelection(self, build_plate_nr: int) -> None:
  125. Logger.log("d", "Setting build plate number... %d" % build_plate_nr)
  126. operation = GroupedOperation()
  127. root = cura.CuraApplication.CuraApplication.getInstance().getController().getScene().getRoot()
  128. nodes_to_change = [] # type: List[SceneNode]
  129. for node in Selection.getAllSelectedObjects():
  130. parent_node = node # Find the parent node to change instead
  131. while parent_node.getParent() != root:
  132. parent_node = cast(SceneNode, parent_node.getParent())
  133. for single_node in BreadthFirstIterator(parent_node): # type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
  134. nodes_to_change.append(single_node)
  135. if not nodes_to_change:
  136. Logger.log("d", "Nothing to change.")
  137. return
  138. for node in nodes_to_change:
  139. operation.addOperation(SetBuildPlateNumberOperation(node, build_plate_nr))
  140. operation.push()
  141. Selection.clear()
  142. def _openUrl(self, url: QUrl) -> None:
  143. QDesktopServices.openUrl(url)