123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- from typing import List, cast
- from PyQt6.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty
- from PyQt6.QtGui import QDesktopServices
- from PyQt6.QtWidgets import QApplication
- from UM.Application import Application
- from UM.Event import CallFunctionEvent
- from UM.FlameProfiler import pyqtSlot
- from UM.Math.Vector import Vector
- from UM.Scene.Selection import Selection
- from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
- from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
- from UM.Operations.GroupedOperation import GroupedOperation
- from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
- from UM.Operations.TranslateOperation import TranslateOperation
- import cura.CuraApplication
- from cura.Operations.SetParentOperation import SetParentOperation
- from cura.MultiplyObjectsJob import MultiplyObjectsJob
- from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation
- from cura.Settings.ExtruderManager import ExtruderManager
- from cura.Arranging.GridArrange import GridArrange
- from cura.Arranging.Nest2DArrange import Nest2DArrange
- from cura.Operations.SetBuildPlateNumberOperation import SetBuildPlateNumberOperation
- from UM.Logger import Logger
- from UM.Scene.SceneNode import SceneNode
- class CuraActions(QObject):
- def __init__(self, parent: QObject = None) -> None:
- super().__init__(parent)
- self._operation_stack = Application.getInstance().getOperationStack()
- self._operation_stack.changed.connect(self._onUndoStackChanged)
- undoStackChanged = pyqtSignal()
- @pyqtSlot()
- def openDocumentation(self) -> None:
-
-
-
- event = CallFunctionEvent(self._openUrl, [QUrl("https://ultimaker.com/en/resources/manuals/software?utm_source=cura&utm_medium=software&utm_campaign=dropdown-documentation")], {})
- cura.CuraApplication.CuraApplication.getInstance().functionEvent(event)
- @pyqtProperty(bool, notify=undoStackChanged)
- def canUndo(self):
- return self._operation_stack.canUndo()
- @pyqtProperty(bool, notify=undoStackChanged)
- def canRedo(self):
- return self._operation_stack.canRedo()
- @pyqtSlot()
- def undo(self):
- self._operation_stack.undo()
- @pyqtSlot()
- def redo(self):
- self._operation_stack.redo()
- def _onUndoStackChanged(self):
- self.undoStackChanged.emit()
- @pyqtSlot()
- def openBugReportPage(self) -> None:
- event = CallFunctionEvent(self._openUrl, [QUrl("https://github.com/Ultimaker/Cura/issues/new/choose")], {})
- cura.CuraApplication.CuraApplication.getInstance().functionEvent(event)
- @pyqtSlot()
- def homeCamera(self) -> None:
- """Reset camera position and direction to default"""
- scene = cura.CuraApplication.CuraApplication.getInstance().getController().getScene()
- camera = scene.getActiveCamera()
- if camera:
- diagonal_size = cura.CuraApplication.CuraApplication.getInstance().getBuildVolume().getDiagonalSize()
- camera.setPosition(Vector(-80, 250, 700) * diagonal_size / 375)
- camera.setPerspective(True)
- camera.lookAt(Vector(0, 0, 0))
- @pyqtSlot()
- def centerSelection(self) -> None:
- """Center all objects in the selection"""
- operation = GroupedOperation()
- for node in Selection.getAllSelectedObjects():
- current_node = node
- parent_node = current_node.getParent()
- while parent_node and parent_node.callDecoration("isGroup"):
- current_node = parent_node
- parent_node = current_node.getParent()
-
- bbox = current_node.getBoundingBox()
- if bbox:
- center_y = current_node.getWorldPosition().y - bbox.bottom
- else:
- center_y = 0
-
- center_operation = TranslateOperation(current_node, Vector(0, center_y, 0), set_position = True)
- operation.addOperation(center_operation)
- operation.push()
- @pyqtSlot(int)
- def multiplySelection(self, count: int) -> None:
- """Multiply all objects in the selection
- :param count: The number of times to multiply the selection.
- """
- min_offset = cura.CuraApplication.CuraApplication.getInstance().getBuildVolume().getEdgeDisallowedSize() + 2
- job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, min_offset = max(min_offset, 8))
- job.start()
- @pyqtSlot(int)
- def multiplySelectionToGrid(self, count: int) -> None:
- """Multiply all objects in the selection
- :param count: The number of times to multiply the selection.
- """
- min_offset = cura.CuraApplication.CuraApplication.getInstance().getBuildVolume().getEdgeDisallowedSize() + 2
- job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, min_offset=max(min_offset, 8),
- grid_arrange=True)
- job.start()
- @pyqtSlot()
- def deleteSelection(self) -> None:
- """Delete all selected objects."""
- if not cura.CuraApplication.CuraApplication.getInstance().getController().getToolsEnabled():
- return
- removed_group_nodes = []
- op = GroupedOperation()
- nodes = Selection.getAllSelectedObjects()
- for node in nodes:
- op.addOperation(RemoveSceneNodeOperation(node))
- group_node = node.getParent()
- if group_node and group_node.callDecoration("isGroup") and group_node not in removed_group_nodes:
- remaining_nodes_in_group = list(set(group_node.getChildren()) - set(nodes))
- if len(remaining_nodes_in_group) == 1:
- removed_group_nodes.append(group_node)
- op.addOperation(SetParentOperation(remaining_nodes_in_group[0], group_node.getParent()))
- op.addOperation(RemoveSceneNodeOperation(group_node))
-
- cura.CuraApplication.CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)
- op.push()
- @pyqtSlot(str)
- def setExtruderForSelection(self, extruder_id: str) -> None:
- """Set the extruder that should be used to print the selection.
- :param extruder_id: The ID of the extruder stack to use for the selected objects.
- """
- operation = GroupedOperation()
- nodes_to_change = []
- for node in Selection.getAllSelectedObjects():
-
- if node.callDecoration("isGroup"):
- for grouped_node in BreadthFirstIterator(node):
- if grouped_node.callDecoration("getActiveExtruder") == extruder_id:
- continue
- if grouped_node.callDecoration("isGroup"):
- continue
- nodes_to_change.append(grouped_node)
- continue
-
- if node.callDecoration("getActiveExtruder") == extruder_id:
- continue
- nodes_to_change.append(node)
- if not nodes_to_change:
-
-
-
- ExtruderManager.getInstance().resetSelectedObjectExtruders()
- return
- for node in nodes_to_change:
- operation.addOperation(SetObjectExtruderOperation(node, extruder_id))
- operation.push()
- @pyqtSlot(int)
- def setBuildPlateForSelection(self, build_plate_nr: int) -> None:
- Logger.log("d", "Setting build plate number... %d" % build_plate_nr)
- operation = GroupedOperation()
- root = cura.CuraApplication.CuraApplication.getInstance().getController().getScene().getRoot()
- nodes_to_change = []
- for node in Selection.getAllSelectedObjects():
- parent_node = node
- while parent_node.getParent() != root:
- parent_node = cast(SceneNode, parent_node.getParent())
- for single_node in BreadthFirstIterator(parent_node):
- nodes_to_change.append(single_node)
- if not nodes_to_change:
- Logger.log("d", "Nothing to change.")
- return
- for node in nodes_to_change:
- operation.addOperation(SetBuildPlateNumberOperation(node, build_plate_nr))
- operation.push()
- Selection.clear()
- @pyqtSlot()
- def cut(self) -> None:
- self.copy()
- self.deleteSelection()
- @pyqtSlot()
- def copy(self) -> None:
- mesh_writer = cura.CuraApplication.CuraApplication.getInstance().getMeshFileHandler().getWriter("3MFWriter")
- if not mesh_writer:
- Logger.log("e", "No 3MF writer found, unable to copy.")
- return
-
- selected_objects = Selection.getAllSelectedObjects()
-
- scene_string = mesh_writer.sceneNodesToString(selected_objects)
-
- QApplication.clipboard().setText(scene_string)
- @pyqtSlot()
- def paste(self) -> None:
- application = cura.CuraApplication.CuraApplication.getInstance()
- mesh_reader = application.getMeshFileHandler().getReaderForFile(".3mf")
- if not mesh_reader:
- Logger.log("e", "No 3MF reader found, unable to paste.")
- return
-
- scene_string = QApplication.clipboard().text()
- nodes = mesh_reader.stringToSceneNodes(scene_string)
- if not nodes:
-
- return
-
- fixed_nodes = []
- root = application.getController().getScene().getRoot()
- for node in DepthFirstIterator(root):
-
- if node.callDecoration("isSliceable"):
- fixed_nodes.append(node)
-
- arranger = GridArrange(nodes, application.getBuildVolume(), fixed_nodes)
- group_operation, not_fit_count = arranger.createGroupOperationForArrange(add_new_nodes_in_scene = True)
- group_operation.push()
-
- for node in Selection.getAllSelectedObjects():
- Selection.remove(node)
- numberOfFixedNodes = len(fixed_nodes)
- for node in nodes:
- numberOfFixedNodes += 1
- node.printOrder = numberOfFixedNodes
- Selection.add(node)
- def _openUrl(self, url: QUrl) -> None:
- QDesktopServices.openUrl(url)
|