From 6aeb59eb1548bb05f40ed525971f7b58888483a5 Mon Sep 17 00:00:00 2001 From: Fernando Pietchaki <6844712+pietchaki@users.noreply.github.com> Date: Tue, 29 Aug 2023 15:06:27 -0300 Subject: [PATCH] Refactor object reload to handle 3mf files Refactor object reload to better handling of 3mf files Make a single file read for all objects from that file, and then replace the objects in scene to the new ones, matching by name. This is also required for an Uranium update to handle the automatic file reload when a file change is detected. 3mf Files can have multiple objects in them. When that is the case, and the file is updated, all objects from that file where being replaced by the first object in the file. --- cura/CuraApplication.py | 67 ++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 0de3ccc7806..566885b7d7c 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1529,15 +1529,14 @@ def reloadAll(self) -> None: 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: - file_path = os.path.normpath(os.path.dirname(file_name)) - job = ReadMeshJob(file_name, add_to_recent_files = file_path != tempfile.gettempdir()) # Don't add temp files to the recent files list - job._node = node # type: ignore - job.finished.connect(self._reloadMeshFinished) - if has_merged_nodes: - job.finished.connect(self.updateOriginOfMergedMeshes) - - job.start() + file_path = os.path.normpath(os.path.dirname(file_name)) + job = ReadMeshJob(file_name, + add_to_recent_files=file_path != tempfile.gettempdir()) # Don't add temp files to the recent files list + job._nodes = nodes # 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: @@ -1709,9 +1708,10 @@ def _onActiveMachineChanged(self): def _reloadMeshFinished(self, job) -> None: """ - Function called whenever a ReadMeshJob finishes in the background. It reloads a specific node object in the + Function called when ReadMeshJob finishes reloading a file in the background, then update node objects 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. + then finds the scene nodes that need to be refreshed by their name. Each job refreshes all nodes of a file. + Nodes that are not present in the updated file are kept in the scene. :param job: The :py:class:`Uranium.UM.ReadMeshJob.ReadMeshJob` running in the background that reads all the meshes in a file @@ -1721,21 +1721,37 @@ def _reloadMeshFinished(self, job) -> None: if len(job_result) == 0: Logger.log("e", "Reloading the mesh failed.") return - object_found = False - mesh_data = None + renamed_nodes = {} # type: Dict[str, int] # 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 - job._node.setMeshData(mesh_data) + mesh_data = job_result_node.getMeshData() + if not mesh_data: + Logger.log("w", "Could not find a mesh in reloaded node.") + continue + + # Solves issues with object naming + result_node_name = job_result_node.getName() + if not result_node_name: + result_node_name = os.path.basename(mesh_data.getFileName()) + if result_node_name in renamed_nodes: # objects may get renamed by ObjectsModel._renameNodes() when loaded + renamed_nodes[result_node_name] += 1 + result_node_name = "{0}({1})".format(result_node_name, renamed_nodes[result_node_name]) + else: + renamed_nodes[job_result_node.getName()] = 0 + + # Find the matching scene node to replace + scene_node = None + for replaced_node in job._nodes: + if replaced_node.getName() == result_node_name: + scene_node = replaced_node + break + + if scene_node: + scene_node.setMeshData(mesh_data) + else: + # Current node is a new one in the file, or it's name has changed + # TODO: Load this mesh into the scene. Also alter the "_reloadJobFinished" action in UM.Scene + Logger.log("w", "Could not find matching node for object '{0}' in the scene.".format(result_node_name)) def _openFile(self, filename): self.readLocalFile(QUrl.fromLocalFile(filename)) @@ -1915,7 +1931,8 @@ def _readMeshFinished(self, job): node.scale(original_node.getScale()) node.setSelectable(True) - node.setName(os.path.basename(file_name)) + if not node.getName(): + node.setName(os.path.basename(file_name)) self.getBuildVolume().checkBoundsAndUpdate(node) is_non_sliceable = "." + file_extension in self._non_sliceable_extensions