diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 51f524d0a1..00e4229f16 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -4,7 +4,7 @@ import os import sys import time -from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any +from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any, Dict import numpy from PyQt5.QtCore import QObject, QTimer, QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS @@ -1382,22 +1382,29 @@ class CuraApplication(QtApplication): if not nodes: return + objects_in_filename = {} # type: Dict[str, List[CuraSceneNode]] for node in nodes: mesh_data = node.getMeshData() - if mesh_data: file_name = mesh_data.getFileName() if file_name: - job = ReadMeshJob(file_name) - job._node = node # type: ignore - job.finished.connect(self._reloadMeshFinished) - if has_merged_nodes: - job.finished.connect(self.updateOriginOfMergedMeshes) - - job.start() + if file_name not in objects_in_filename: + objects_in_filename[file_name] = [] + if file_name in objects_in_filename: + objects_in_filename[file_name].append(node) else: Logger.log("w", "Unable to reload data because we don't have a filename.") + for file_name, nodes in objects_in_filename.items(): + for node in nodes: + job = ReadMeshJob(file_name) + job._node = node # type: ignore + job.finished.connect(self._reloadMeshFinished) + if has_merged_nodes: + job.finished.connect(self.updateOriginOfMergedMeshes) + + job.start() + @pyqtSlot("QStringList") def setExpandedCategories(self, categories: List[str]) -> None: categories = list(set(categories)) @@ -1572,13 +1579,30 @@ class CuraApplication(QtApplication): fileLoaded = pyqtSignal(str) fileCompleted = pyqtSignal(str) - def _reloadMeshFinished(self, job): - # TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh! - job_result = job.getResult() + def _reloadMeshFinished(self, job) -> None: + """ + Function called whenever a ReadMeshJob finishes in the background. It reloads a specific node object in the + scene from its source file. The function gets all the nodes that exist in the file through the job result, and + then finds the scene node that it wants to refresh by its object id. Each job refreshes only one node. + + :param job: The ReadMeshJob running in the background that reads all the meshes in a file + :return: None + """ + job_result = job.getResult() # nodes that exist inside the file read by this job if len(job_result) == 0: Logger.log("e", "Reloading the mesh failed.") return - mesh_data = job_result[0].getMeshData() + object_found = False + mesh_data = None + # Find the node to be refreshed based on its id + for job_result_node in job_result: + if job_result_node.getId() == job._node.getId(): + mesh_data = job_result_node.getMeshData() + object_found = True + break + if not object_found: + Logger.warning("The object with id {} no longer exists! Keeping the old version in the scene.".format(job_result_node.getId())) + return if not mesh_data: Logger.log("w", "Could not find a mesh in reloaded node.") return diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index 670049802d..bad1f0af5d 100755 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -1,31 +1,28 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import List, Optional, Union, TYPE_CHECKING import os.path import zipfile - -import numpy +from typing import List, Optional, Union, TYPE_CHECKING import Savitar +import numpy from UM.Logger import Logger from UM.Math.Matrix import Matrix from UM.Math.Vector import Vector from UM.Mesh.MeshBuilder import MeshBuilder from UM.Mesh.MeshReader import MeshReader -from UM.Scene.GroupDecorator import GroupDecorator -from UM.Scene.SceneNode import SceneNode #For typing. from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType - +from UM.Scene.GroupDecorator import GroupDecorator +from UM.Scene.SceneNode import SceneNode # For typing. from cura.CuraApplication import CuraApplication from cura.Machines.ContainerTree import ContainerTree -from cura.Settings.ExtruderManager import ExtruderManager -from cura.Scene.CuraSceneNode import CuraSceneNode from cura.Scene.BuildPlateDecorator import BuildPlateDecorator +from cura.Scene.CuraSceneNode import CuraSceneNode from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator from cura.Scene.ZOffsetDecorator import ZOffsetDecorator - +from cura.Settings.ExtruderManager import ExtruderManager try: if not TYPE_CHECKING: @@ -52,7 +49,6 @@ class ThreeMFReader(MeshReader): self._root = None self._base_name = "" self._unit = None - self._object_count = 0 # Used to name objects as there is no node name yet. def _createMatrixFromTransformationString(self, transformation: str) -> Matrix: if transformation == "": @@ -87,17 +83,20 @@ class ThreeMFReader(MeshReader): ## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node. # \returns Scene node. def _convertSavitarNodeToUMNode(self, savitar_node: Savitar.SceneNode, file_name: str = "") -> Optional[SceneNode]: - self._object_count += 1 - node_name = savitar_node.getName() + node_id = savitar_node.getId() if node_name == "": - node_name = "Object %s" % self._object_count + if file_name != "": + node_name = os.path.basename(file_name) + else: + node_name = "Object {}".format(node_id) active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate um_node = CuraSceneNode() # This adds a SettingOverrideDecorator um_node.addDecorator(BuildPlateDecorator(active_build_plate)) um_node.setName(node_name) + um_node.setId(node_id) transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation()) um_node.setTransformation(transformation) mesh_builder = MeshBuilder() @@ -169,7 +168,6 @@ class ThreeMFReader(MeshReader): def _read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]: result = [] - self._object_count = 0 # Used to name objects as there is no node name yet. # The base object of 3mf is a zipped archive. try: archive = zipfile.ZipFile(file_name, "r")