Skip to content

Commit

Permalink
Refactor object reload to handle 3mf files
Browse files Browse the repository at this point in the history
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 Require Update to the CuraApplication that currently rename
all objects with the file name.

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.
  • Loading branch information
pietchaki committed Aug 29, 2023
1 parent 6bcb8ae commit 506d731
Showing 1 changed file with 47 additions and 23 deletions.
70 changes: 47 additions & 23 deletions UM/Scene/Scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ def _onFileChanged(self, file_path: str) -> None:

if modified_nodes:
# Hide the message if it was already visible
# Todo: keep one message for each modified file, when multiple had been updated at same time
if self._reload_message is not None:
self._reload_message.hide()

Expand All @@ -197,11 +198,11 @@ def _onFileChanged(self, file_path: str) -> None:
self._reload_message.addAction("reload", i18n_catalog.i18nc("@action:button", "Reload"),
icon = "",
description = i18n_catalog.i18nc("@action:description", "This will trigger the modified files to reload from disk."))
self._reload_callback = functools.partial(self._reloadNodes, modified_nodes)
self._reload_callback = functools.partial(self._reloadNodes, modified_nodes, file_path)
self._reload_message.actionTriggered.connect(self._reload_callback)
self._reload_message.show()

def _reloadNodes(self, nodes: List["SceneNode"], message: str, action: str) -> None:
def _reloadNodes(self, nodes: List["SceneNode"], file_path: str, message: str, action: str) -> None:
"""Reloads a list of nodes after the user pressed the "Reload" button.
:param nodes: The list of nodes that needs to be reloaded.
Expand All @@ -213,31 +214,54 @@ def _reloadNodes(self, nodes: List["SceneNode"], message: str, action: str) -> N
return
if self._reload_message is not None:
self._reload_message.hide()
for node in nodes:
meshdata = node.getMeshData()
if meshdata:
filename = meshdata.getFileName()
if not filename or not os.path.isfile(filename): # File doesn't exist any more.
continue
job = ReadMeshJob(filename)
reload_finished_callback = functools.partial(self._reloadJobFinished, node)

# Store it so it won't get garbage collected. This is a memory leak, but just one partial per reload so
# it's not much.
self._callbacks.add(reload_finished_callback)

job.finished.connect(reload_finished_callback)
job.start()

def _reloadJobFinished(self, replaced_node: SceneNode, job: ReadMeshJob) -> None:

if not file_path or not os.path.isfile(file_path): # File doesn't exist anymore.
return

job = ReadMeshJob(file_path)
reload_finished_callback = functools.partial(self._reloadJobFinished, nodes)

# Store it so it won't get garbage collected. This is a memory leak, but just one partial per reload so
# it's not much.
self._callbacks.add(reload_finished_callback)

job.finished.connect(reload_finished_callback)
job.start()

def _reloadJobFinished(self, replaced_nodes: [SceneNode], job: ReadMeshJob) -> None:
"""Triggered when reloading has finished.
This then puts the resulting mesh data in the node.
This then puts the resulting mesh data in the nodes.
Objects in the scene that are not in the reloaded file will be kept. (same as in the ReloadAll action)
"""
renamed_nodes = {} # type: Dict[str, int]

for node in job.getResult():
mesh_data = node.getMeshData()
if mesh_data:
replaced_node.setMeshData(mesh_data)
else:
if not mesh_data:
Logger.log("w", "Could not find a mesh in reloaded node.")
continue

# Solves issues with object naming
node_name = node.getName()
if not node_name:
node_name = os.path.basename(mesh_data.getFileName())
if node_name in renamed_nodes: # objects may get renamed by Cura.UI.ObjectsModel._renameNodes() when loaded
renamed_nodes[node_name] += 1
node_name = "{0}({1})".format(node.getName(), renamed_nodes[node.getName()])
else:
renamed_nodes[node.getName()] = 0

# Find the matching scene node to replace
scene_node = None
for replaced_node in replaced_nodes:
if replaced_node.getName() == 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 "ReloadAll" action in CuraApplication.
Logger.log("w", "Could not find matching node for object '{0}' in the scene.", node_name)

0 comments on commit 506d731

Please sign in to comment.