diff --git a/cura/CuraActions.py b/cura/CuraActions.py index aef26c7082..338c18a6b9 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -7,8 +7,6 @@ from PyQt6.QtCore import QObject, QUrl, QMimeData from PyQt6.QtGui import QDesktopServices from PyQt6.QtWidgets import QApplication -import pySavitar as Savitar - from UM.Event import CallFunctionEvent from UM.FlameProfiler import pyqtSlot from UM.Math.Vector import Vector @@ -194,18 +192,13 @@ class CuraActions(QObject): @pyqtSlot() def copy(self) -> None: - # Convert all selected objects to a Savitar scene - savitar_scene = Savitar.Scene() mesh_writer = cura.CuraApplication.CuraApplication.getInstance().getMeshFileHandler().getWriter("3MFWriter") - for scene_node in Selection.getAllSelectedObjects(): - savitar_node = mesh_writer._convertUMNodeToSavitarNode(scene_node) - savitar_scene.addSceneNode(savitar_node) - # Convert the scene to a string - parser = Savitar.ThreeMFParser() - scene_string = parser.sceneToString(savitar_scene) - - # Copy the scene to the clipboard + # Get the selected nodes + selected_objects = Selection.getAllSelectedObjects() + # Serialize the nodes to a string + scene_string = mesh_writer.sceneNodesToString(selected_objects) + # Put the string on the clipboard QApplication.clipboard().setText(scene_string) @pyqtSlot() @@ -214,17 +207,9 @@ class CuraActions(QObject): # Parse the scene from the clipboard scene_string = QApplication.clipboard().text() - parser = Savitar.ThreeMFParser() - scene = parser.parse(scene_string) - # Convert the scene to scene nodes - nodes = [] mesh_reader = application.getMeshFileHandler().getReaderForFile(".3mf") - for savitar_node in scene.getSceneNodes(): - scene_node = mesh_reader._convertSavitarNodeToUMNode(savitar_node, "file_name") - if scene_node is None: - continue - nodes.append(scene_node) + nodes = mesh_reader.stringToSceneNodes(scene_string) # Find all fixed nodes, these are the nodes that should be avoided when arranging fixed_nodes = [] diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index e8b6a54e46..e06e9dcf4e 100755 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -56,7 +56,8 @@ class ThreeMFReader(MeshReader): def emptyFileHintSet(self) -> bool: return self._empty_project - def _createMatrixFromTransformationString(self, transformation: str) -> Matrix: + @staticmethod + def _createMatrixFromTransformationString(transformation: str) -> Matrix: if transformation == "": return Matrix() @@ -90,7 +91,8 @@ class ThreeMFReader(MeshReader): return temp_mat - def _convertSavitarNodeToUMNode(self, savitar_node: Savitar.SceneNode, file_name: str = "") -> Optional[SceneNode]: + @staticmethod + def _convertSavitarNodeToUMNode(savitar_node: Savitar.SceneNode, file_name: str = "") -> Optional[SceneNode]: """Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node. :returns: Scene node. @@ -119,7 +121,7 @@ class ThreeMFReader(MeshReader): pass um_node.setName(node_name) um_node.setId(node_id) - transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation()) + transformation = ThreeMFReader._createMatrixFromTransformationString(savitar_node.getTransformation()) um_node.setTransformation(transformation) mesh_builder = MeshBuilder() @@ -138,7 +140,7 @@ class ThreeMFReader(MeshReader): um_node.setMeshData(mesh_data) for child in savitar_node.getChildren(): - child_node = self._convertSavitarNodeToUMNode(child) + child_node = ThreeMFReader._convertSavitarNodeToUMNode(child) if child_node: um_node.addChild(child_node) @@ -214,7 +216,7 @@ class ThreeMFReader(MeshReader): CuraApplication.getInstance().getController().getScene().setMetaDataEntry(key, value) for node in scene_3mf.getSceneNodes(): - um_node = self._convertSavitarNodeToUMNode(node, file_name) + um_node = ThreeMFReader._convertSavitarNodeToUMNode(node, file_name) if um_node is None: continue @@ -300,8 +302,23 @@ class ThreeMFReader(MeshReader): if unit is None: unit = "millimeter" elif unit not in conversion_to_mm: - Logger.log("w", "Unrecognised unit {unit} used. Assuming mm instead.".format(unit = unit)) + Logger.log("w", "Unrecognised unit {unit} used. Assuming mm instead.".format(unit=unit)) unit = "millimeter" scale = conversion_to_mm[unit] return Vector(scale, scale, scale) + + @staticmethod + def stringToSceneNodes(scene_string: str) -> List[SceneNode]: + parser = Savitar.ThreeMFParser() + scene = parser.parse(scene_string) + + # Convert the scene to scene nodes + nodes = [] + for savitar_node in scene.getSceneNodes(): + scene_node = ThreeMFReader._convertSavitarNodeToUMNode(savitar_node, "file_name") + if scene_node is None: + continue + nodes.append(scene_node) + + return nodes diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py index 57c667145e..3f6fef7201 100644 --- a/plugins/3MFWriter/ThreeMFWriter.py +++ b/plugins/3MFWriter/ThreeMFWriter.py @@ -55,11 +55,12 @@ class ThreeMFWriter(MeshWriter): "cura": "http://software.ultimaker.com/xml/cura/3mf/2015/10" } - self._unit_matrix_string = self._convertMatrixToString(Matrix()) + self._unit_matrix_string = ThreeMFWriter._convertMatrixToString(Matrix()) self._archive: Optional[zipfile.ZipFile] = None self._store_archive = False - def _convertMatrixToString(self, matrix): + @staticmethod + def _convertMatrixToString(matrix): result = "" result += str(matrix._data[0, 0]) + " " result += str(matrix._data[1, 0]) + " " @@ -83,7 +84,8 @@ class ThreeMFWriter(MeshWriter): """ self._store_archive = store_archive - def _convertUMNodeToSavitarNode(self, um_node, transformation = Matrix()): + @staticmethod + def _convertUMNodeToSavitarNode(um_node, transformation=Matrix()): """Convenience function that converts an Uranium SceneNode object to a SavitarSceneNode :returns: Uranium Scene node. @@ -100,7 +102,7 @@ class ThreeMFWriter(MeshWriter): node_matrix = um_node.getLocalTransformation() - matrix_string = self._convertMatrixToString(node_matrix.preMultiply(transformation)) + matrix_string = ThreeMFWriter._convertMatrixToString(node_matrix.preMultiply(transformation)) savitar_node.setTransformation(matrix_string) mesh_data = um_node.getMeshData() @@ -133,7 +135,7 @@ class ThreeMFWriter(MeshWriter): # only save the nodes on the active build plate if child_node.callDecoration("getBuildPlateNumber") != active_build_plate_nr: continue - savitar_child_node = self._convertUMNodeToSavitarNode(child_node) + savitar_child_node = ThreeMFWriter._convertUMNodeToSavitarNode(child_node) if savitar_child_node is not None: savitar_node.addChild(savitar_child_node) @@ -221,7 +223,7 @@ class ThreeMFWriter(MeshWriter): for node in nodes: if node == root_node: for root_child in node.getChildren(): - savitar_node = self._convertUMNodeToSavitarNode(root_child, transformation_matrix) + savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(root_child, transformation_matrix) if savitar_node: savitar_scene.addSceneNode(savitar_node) else: @@ -303,9 +305,19 @@ class ThreeMFWriter(MeshWriter): Logger.log("w", "Can't create snapshot when renderer not initialized.") return None try: - snapshot = Snapshot.snapshot(width = 300, height = 300) + snapshot = Snapshot.snapshot(width=300, height=300) except: Logger.logException("w", "Failed to create snapshot image") return None return snapshot + + @staticmethod + def sceneNodesToString(scene_nodes: [SceneNode]) -> str: + savitar_scene = Savitar.Scene() + for scene_node in scene_nodes: + savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(scene_node) + savitar_scene.addSceneNode(savitar_node) + parser = Savitar.ThreeMFParser() + scene_string = parser.sceneToString(savitar_scene) + return scene_string