Use the node id as identifier

Now that libSavitar allows us to read the object id from the 3mf file,
this id will be propagated as an id inside CuraSceneNodes and it will
be used as an identifier to find the object that has to be refreshed.

CURA-7333
This commit is contained in:
Kostas Karmas 2020-04-03 11:05:38 +02:00
parent 5bdcc2e5ba
commit 8eb48672e1
2 changed files with 27 additions and 49 deletions

View file

@ -1397,9 +1397,8 @@ class CuraApplication(QtApplication):
Logger.log("w", "Unable to reload data because we don't have a filename.") Logger.log("w", "Unable to reload data because we don't have a filename.")
for file_name, nodes in objects_in_filename.items(): for file_name, nodes in objects_in_filename.items():
for object_index, node in enumerate(nodes): for node in nodes:
job = ReadMeshJob(file_name) job = ReadMeshJob(file_name)
job.object_to_be_reloaded = self._getObjectIndexInFile(file_name, node.getName()) # The object index in file to be loaded by this specific job
job._node = node # type: ignore job._node = node # type: ignore
job.finished.connect(self._reloadMeshFinished) job.finished.connect(self._reloadMeshFinished)
if has_merged_nodes: if has_merged_nodes:
@ -1407,41 +1406,6 @@ class CuraApplication(QtApplication):
job.start() job.start()
@staticmethod
def _getObjectIndexInFile(file_name: str, node_name: str) -> int:
"""
This function extracts the index of the object inside a file. This is achieved by looking into the name
of the node. There are two possibilities:
* The node is named as filename.ext, filename.ext(1), filename.ext(2), etc, which maps to indices 0, 1, 2, ...
* The node is named as Object 1, Object 2, Object 3 etc, which maps to indices 0, 1, 2 ...
:param file_name: The name of the file where the node has been retrieved from
:param node_name: The name of the node as presented in the Scene
:return: The index of the node inside the file_name
"""
file_name = file_name.split("/")[-1] # Keep only the filename, without the path
node_int_index = 0
if file_name in node_name:
# if the file_name exists inside the node_name, remove it along with all parenthesis and spaces
node_str_index = re.sub(r'[() ]', '', node_name.replace(file_name, ""))
if node_str_index == "":
node_str_index = "0"
try:
node_int_index = int(node_str_index)
except ValueError:
Logger.warning("Object '{}' has an incorrect index '{}'.".format(node_name, node_str_index))
return 0
elif "Object " in node_name:
# if the nodes are named as Object 1, Object 2, etc, remove 'Object ' and keep only the number
node_str_index = node_name.replace("Object ", "")
try:
node_int_index = int(node_str_index) - 1
except ValueError:
Logger.warning("Object '{}' has an incorrect index '{}'.".format(node_name, node_str_index))
return 0
return node_int_index
@pyqtSlot("QStringList") @pyqtSlot("QStringList")
def setExpandedCategories(self, categories: List[str]) -> None: def setExpandedCategories(self, categories: List[str]) -> None:
categories = list(set(categories)) categories = list(set(categories))
@ -1616,16 +1580,29 @@ class CuraApplication(QtApplication):
fileLoaded = pyqtSignal(str) fileLoaded = pyqtSignal(str)
fileCompleted = pyqtSignal(str) fileCompleted = pyqtSignal(str)
def _reloadMeshFinished(self, job): def _reloadMeshFinished(self, job: ReadMeshJob) -> None:
job_result = job.getResult() """
object_to_be_reloaded = job.object_to_be_reloaded 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: if len(job_result) == 0:
Logger.log("e", "Reloading the mesh failed.") Logger.log("e", "Reloading the mesh failed.")
return return
try: # In case the object has disappeared after reloading, log a warning and keep the old mesh in the scene object_found = False
mesh_data = job_result[object_to_be_reloaded].getMeshData() mesh_data = None
except IndexError: # Find the node to be refreshed based on its id
Logger.warning("Object at index {} no longer exists! Keeping the old version in the scene.".format(object_to_be_reloaded)) 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 return
if not mesh_data: if not mesh_data:
Logger.log("w", "Could not find a mesh in reloaded node.") Logger.log("w", "Could not find a mesh in reloaded node.")

View file

@ -52,7 +52,6 @@ class ThreeMFReader(MeshReader):
self._root = None self._root = None
self._base_name = "" self._base_name = ""
self._unit = None self._unit = None
self._object_count = 0 # Used to name objects as there is no node name yet.
def _createMatrixFromTransformationString(self, transformation: str) -> Matrix: def _createMatrixFromTransformationString(self, transformation: str) -> Matrix:
if transformation == "": if transformation == "":
@ -87,17 +86,20 @@ class ThreeMFReader(MeshReader):
## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node. ## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node.
# \returns Scene node. # \returns Scene node.
def _convertSavitarNodeToUMNode(self, savitar_node: Savitar.SceneNode, file_name: str = "") -> Optional[SceneNode]: def _convertSavitarNodeToUMNode(self, savitar_node: Savitar.SceneNode, file_name: str = "") -> Optional[SceneNode]:
self._object_count += 1
node_name = savitar_node.getName() node_name = savitar_node.getName()
node_id = savitar_node.getId()
if node_name == "": 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 active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
um_node = CuraSceneNode() # This adds a SettingOverrideDecorator um_node = CuraSceneNode() # This adds a SettingOverrideDecorator
um_node.addDecorator(BuildPlateDecorator(active_build_plate)) um_node.addDecorator(BuildPlateDecorator(active_build_plate))
um_node.setName(node_name) um_node.setName(node_name)
um_node.setId(node_id)
transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation()) transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation())
um_node.setTransformation(transformation) um_node.setTransformation(transformation)
mesh_builder = MeshBuilder() mesh_builder = MeshBuilder()
@ -169,7 +171,6 @@ class ThreeMFReader(MeshReader):
def _read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]: def _read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]:
result = [] 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. # The base object of 3mf is a zipped archive.
try: try:
archive = zipfile.ZipFile(file_name, "r") archive = zipfile.ZipFile(file_name, "r")