PrintOrderManager.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. from typing import List, Callable, Optional, Any
  2. from PyQt6.QtCore import pyqtProperty, pyqtSignal, QObject, pyqtSlot
  3. from UM.Application import Application
  4. from UM.Scene.Selection import Selection
  5. from cura.Scene.CuraSceneNode import CuraSceneNode
  6. class PrintOrderManager(QObject):
  7. """Allows to order the object list to set the print sequence manually"""
  8. def __init__(self, get_nodes: Callable[[], List[CuraSceneNode]]) -> None:
  9. super().__init__()
  10. self._get_nodes = get_nodes
  11. self._configureEvents()
  12. _settingsChanged = pyqtSignal()
  13. _uiActionsOutdated = pyqtSignal()
  14. printOrderChanged = pyqtSignal()
  15. @pyqtSlot()
  16. def swapSelectedAndPreviousNodes(self) -> None:
  17. selected_node, previous_node, next_node = self._getSelectedAndNeighborNodes()
  18. self._swapPrintOrders(selected_node, previous_node)
  19. @pyqtSlot()
  20. def swapSelectedAndNextNodes(self) -> None:
  21. selected_node, previous_node, next_node = self._getSelectedAndNeighborNodes()
  22. self._swapPrintOrders(selected_node, next_node)
  23. @pyqtProperty(str, notify=_uiActionsOutdated)
  24. def previousNodeName(self) -> str:
  25. selected_node, previous_node, next_node = self._getSelectedAndNeighborNodes()
  26. return self._getNodeName(previous_node)
  27. @pyqtProperty(str, notify=_uiActionsOutdated)
  28. def nextNodeName(self) -> str:
  29. selected_node, previous_node, next_node = self._getSelectedAndNeighborNodes()
  30. return self._getNodeName(next_node)
  31. @pyqtProperty(bool, notify=_uiActionsOutdated)
  32. def shouldEnablePrintBeforeAction(self) -> bool:
  33. selected_node, previous_node, next_node = self._getSelectedAndNeighborNodes()
  34. can_swap_with_previous_node = selected_node is not None and previous_node is not None
  35. return can_swap_with_previous_node
  36. @pyqtProperty(bool, notify=_uiActionsOutdated)
  37. def shouldEnablePrintAfterAction(self) -> bool:
  38. selected_node, previous_node, next_node = self._getSelectedAndNeighborNodes()
  39. can_swap_with_next_node = selected_node is not None and next_node is not None
  40. return can_swap_with_next_node
  41. @pyqtProperty(bool, notify=_settingsChanged)
  42. def shouldShowEditPrintOrderActions(self) -> bool:
  43. return PrintOrderManager.isUserDefinedPrintOrderEnabled()
  44. @staticmethod
  45. def isUserDefinedPrintOrderEnabled() -> bool:
  46. stack = Application.getInstance().getGlobalContainerStack()
  47. is_enabled = stack and \
  48. stack.getProperty("print_sequence", "value") == "one_at_a_time" and \
  49. stack.getProperty("user_defined_print_order_enabled", "value")
  50. return bool(is_enabled)
  51. @staticmethod
  52. def initializePrintOrders(nodes: List[CuraSceneNode]) -> None:
  53. """Just created (loaded from file) nodes have print order 0.
  54. This method initializes print orders with max value to put nodes at the end of object list"""
  55. max_print_order = max(map(lambda n: n.printOrder, nodes), default=0)
  56. for node in nodes:
  57. if node.printOrder == 0:
  58. max_print_order += 1
  59. node.printOrder = max_print_order
  60. @staticmethod
  61. def updatePrintOrdersAfterGroupOperation(
  62. all_nodes: List[CuraSceneNode],
  63. group_node: CuraSceneNode,
  64. grouped_nodes: List[CuraSceneNode]
  65. ) -> None:
  66. group_node.printOrder = min(map(lambda n: n.printOrder, grouped_nodes))
  67. all_nodes.append(group_node)
  68. for node in grouped_nodes:
  69. all_nodes.remove(node)
  70. # reassign print orders so there won't be gaps like 1 2 5 6 7
  71. sorted_nodes = sorted(all_nodes, key=lambda n: n.printOrder)
  72. for i, node in enumerate(sorted_nodes):
  73. node.printOrder = i + 1
  74. @staticmethod
  75. def updatePrintOrdersAfterUngroupOperation(
  76. all_nodes: List[CuraSceneNode],
  77. group_node: CuraSceneNode,
  78. ungrouped_nodes: List[CuraSceneNode]
  79. ) -> None:
  80. all_nodes.remove(group_node)
  81. nodes_to_update_print_order = filter(lambda n: n.printOrder > group_node.printOrder, all_nodes)
  82. for node in nodes_to_update_print_order:
  83. node.printOrder += len(ungrouped_nodes) - 1
  84. for i, child in enumerate(ungrouped_nodes):
  85. child.printOrder = group_node.printOrder + i
  86. all_nodes.append(child)
  87. def _swapPrintOrders(self, node1: CuraSceneNode, node2: CuraSceneNode) -> None:
  88. if node1 and node2:
  89. node1.printOrder, node2.printOrder = node2.printOrder, node1.printOrder # swap print orders
  90. self.printOrderChanged.emit() # update object list first
  91. self._uiActionsOutdated.emit() # then update UI actions
  92. def _getSelectedAndNeighborNodes(self
  93. ) -> (Optional[CuraSceneNode], Optional[CuraSceneNode], Optional[CuraSceneNode]):
  94. nodes = self._get_nodes()
  95. ordered_nodes = sorted(nodes, key=lambda n: n.printOrder)
  96. for i, node in enumerate(ordered_nodes, 1):
  97. node.printOrder = i
  98. selected_node = PrintOrderManager._getSingleSelectedNode()
  99. if selected_node and selected_node in ordered_nodes:
  100. selected_node_index = ordered_nodes.index(selected_node)
  101. else:
  102. selected_node_index = None
  103. if selected_node_index is not None and selected_node_index - 1 >= 0:
  104. previous_node = ordered_nodes[selected_node_index - 1]
  105. else:
  106. previous_node = None
  107. if selected_node_index is not None and selected_node_index + 1 < len(ordered_nodes):
  108. next_node = ordered_nodes[selected_node_index + 1]
  109. else:
  110. next_node = None
  111. return selected_node, previous_node, next_node
  112. @staticmethod
  113. def _getNodeName(node: CuraSceneNode, max_length: int = 30) -> str:
  114. node_name = node.getName() if node else ""
  115. truncated_node_name = node_name[:max_length]
  116. return truncated_node_name
  117. @staticmethod
  118. def _getSingleSelectedNode() -> Optional[CuraSceneNode]:
  119. if len(Selection.getAllSelectedObjects()) == 1:
  120. selected_node = Selection.getSelectedObject(0)
  121. return selected_node
  122. return None
  123. def _configureEvents(self) -> None:
  124. Selection.selectionChanged.connect(self._onSelectionChanged)
  125. self._global_stack = None
  126. Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
  127. self._onGlobalStackChanged()
  128. def _onGlobalStackChanged(self) -> None:
  129. if self._global_stack:
  130. self._global_stack.propertyChanged.disconnect(self._onSettingsChanged)
  131. self._global_stack.containersChanged.disconnect(self._onSettingsChanged)
  132. self._global_stack = Application.getInstance().getGlobalContainerStack()
  133. if self._global_stack:
  134. self._global_stack.propertyChanged.connect(self._onSettingsChanged)
  135. self._global_stack.containersChanged.connect(self._onSettingsChanged)
  136. def _onSettingsChanged(self, *args: Any) -> None:
  137. self._settingsChanged.emit()
  138. def _onSelectionChanged(self) -> None:
  139. self._uiActionsOutdated.emit()