From 21d59e93492dc66198312bdd8dd17d91bc44d252 Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Mon, 30 May 2022 17:29:59 +0200 Subject: [PATCH 1/9] Write active material metadata to ufp when saving. Add function to fetch package_id using only information from XmlMaterialProfile material container. The only piece of information associating the material container and the package together is the file_name. To find the package that owns a material we have to search each of the material package paths. It would be great to find a cleaner solution (preferable one that doesn't require invalidating the cached containers). CURA-8610 --- cura/CuraPackageManager.py | 20 ++++++ plugins/UFPWriter/UFPWriter.py | 65 +++++++++++++------ .../XmlMaterialProfile/XmlMaterialProfile.py | 3 + 3 files changed, 69 insertions(+), 19 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 17d6832ac6..b23422fbbd 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -1,5 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import os from typing import Any, cast, Dict, List, Set, Tuple, TYPE_CHECKING, Optional @@ -51,6 +52,25 @@ class CuraPackageManager(PackageManager): super().initialize() + def getMaterialFilePackageId(self, file_name: str, guid: str) -> str: + """Get the id of the material package that contains file_name""" + for material_package in [f for f in os.scandir(self._installation_dirs_dict["materials"]) if f.is_dir()]: + package_id = material_package.name + + for root, _, file_names in os.walk(material_package.path): + if file_name not in file_names: + #File with the name we are looking for is not in this directory + continue + + with open(root + "/" + file_name, encoding="utf-8") as f: + # Make sure the file we found has the same guid as our material + # Parsing this xml would be better but the namespace is needed to search it. + if guid in f.read(): + return package_id + continue + + + def getMachinesUsingPackage(self, package_id: str) -> Tuple[List[Tuple[GlobalStack, str, str]], List[Tuple[GlobalStack, str, str]]]: """Returns a list of where the package is used diff --git a/plugins/UFPWriter/UFPWriter.py b/plugins/UFPWriter/UFPWriter.py index 52dab1efc7..59067b1932 100644 --- a/plugins/UFPWriter/UFPWriter.py +++ b/plugins/UFPWriter/UFPWriter.py @@ -16,13 +16,14 @@ from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType from UM.PluginRegistry import PluginRegistry # To get the g-code writer. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator -from UM.Scene.SceneNode import SceneNode from cura.CuraApplication import CuraApplication +from cura.CuraPackageManager import CuraPackageManager from cura.Utils.Threading import call_on_qt_thread from UM.i18n import i18nCatalog METADATA_OBJECTS_PATH = "metadata/objects" +METADATA_MATERIALS_PATH = "metadata/packages" catalog = i18nCatalog("cura") @@ -49,7 +50,7 @@ class UFPWriter(MeshWriter): archive.openStream(stream, "application/x-ufp", OpenMode.WriteOnly) try: - self._writeObjectList(archive) + self._writeMetadata(archive) # Store the g-code from the scene. archive.addContentType(extension = "gcode", mime_type = "text/x-gcode") @@ -163,30 +164,56 @@ class UFPWriter(MeshWriter): return True @staticmethod - def _writeObjectList(archive): - """Write a json list of object names to the METADATA_OBJECTS_PATH metadata field + def _writeMetadata(archive: VirtualFile): + material_metadata = UFPWriter._getMaterialPackageMetadata() + object_metadata = UFPWriter._getObjectMetadata() - To retrieve, use: `archive.getMetadata(METADATA_OBJECTS_PATH)` - """ + data = {METADATA_MATERIALS_PATH: material_metadata, + METADATA_OBJECTS_PATH: object_metadata} - objects_model = CuraApplication.getInstance().getObjectsModel() - object_metas = [] - - for item in objects_model.items: - object_metas.extend(UFPWriter._getObjectMetadata(item["node"])) - - data = {METADATA_OBJECTS_PATH: object_metas} archive.setMetadata(data) @staticmethod - def _getObjectMetadata(node: SceneNode) -> List[Dict[str, str]]: + def _getObjectMetadata() -> List[Dict[str, str]]: """Get object metadata to write for a Node. :return: List of object metadata dictionaries. - Might contain > 1 element in case of a group node. - Might be empty in case of nonPrintingMesh """ + metadata = [] + + objects_model = CuraApplication.getInstance().getObjectsModel() + + for item in objects_model.items: + for node in DepthFirstIterator(item["node"]): + if node.getMeshData() is not None and not node.callDecoration("isNonPrintingMesh"): + metadata.extend({"name": node.getName()}) + + return metadata + + @staticmethod + def _getMaterialPackageMetadata() -> List[Dict[str, str]]: + """Get metadata for installed materials in active extruder stack, this does not include bundled materials. + + :return: List of material metadata dictionaries. + """ + metadata = [] + + package_manager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager()) + + for extruder in CuraApplication.getInstance().getExtruderManager().getActiveExtruderStacks(): + package_id = package_manager.getMaterialFilePackageId(extruder.material.getFileName(), extruder.material.getMetaDataEntry("GUID")) + package_data = package_manager.getInstalledPackageInfo(package_id) + + if package_data.get("is_bundled"): + continue + + material_metadata = {"id": package_id, + "display_name": package_data.get("display_name"), + "website": package_data.get("website"), + "package_version": package_data.get("package_version"), + "sdk_version_semver": package_data.get("package_version_semver")} + + metadata.append(material_metadata) + + return metadata - return [{"name": item.getName()} - for item in DepthFirstIterator(node) - if item.getMeshData() is not None and not item.callDecoration("isNonPrintingMesh")] diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 1b88272d49..d1da10399a 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -343,6 +343,9 @@ class XmlMaterialProfile(InstanceContainer): return stream.getvalue().decode("utf-8") + def getFileName(self): + return self.getMetaDataEntry("base_file") + ".xml.fdm_material" + # Recursively resolve loading inherited files def _resolveInheritance(self, file_name): xml = self._loadFile(file_name) From 3dec025cb881e171dc89fa293c18afe13c32ed15 Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Tue, 31 May 2022 08:54:08 +0200 Subject: [PATCH 2/9] Update comment to clarify function usage. CURA-8610 --- cura/CuraPackageManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index b23422fbbd..813ce40763 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -53,7 +53,7 @@ class CuraPackageManager(PackageManager): super().initialize() def getMaterialFilePackageId(self, file_name: str, guid: str) -> str: - """Get the id of the material package that contains file_name""" + """Get the id of the installed material package that contains file_name""" for material_package in [f for f in os.scandir(self._installation_dirs_dict["materials"]) if f.is_dir()]: package_id = material_package.name From cdc08b5d54da9f9be7a8269fc549442db445b778 Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Tue, 31 May 2022 10:00:25 +0200 Subject: [PATCH 3/9] Improve GUID check by parsing xml and getting GUID specifically inside the metadata tag. Remove exporting materials from disabled extruders CURA-8610 --- cura/CuraPackageManager.py | 7 +++++-- plugins/UFPWriter/UFPWriter.py | 6 +++++- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 9 +++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 813ce40763..0cb18ccc92 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -10,6 +10,8 @@ from cura.Settings.GlobalStack import GlobalStack from UM.PackageManager import PackageManager # The class we're extending. from UM.Resources import Resources # To find storage paths for some resource types. from UM.i18n import i18nCatalog +from plugins.XmlMaterialProfile.XmlMaterialProfile import XmlMaterialProfile + catalog = i18nCatalog("cura") if TYPE_CHECKING: @@ -59,13 +61,14 @@ class CuraPackageManager(PackageManager): for root, _, file_names in os.walk(material_package.path): if file_name not in file_names: - #File with the name we are looking for is not in this directory + # File with the name we are looking for is not in this directory continue with open(root + "/" + file_name, encoding="utf-8") as f: # Make sure the file we found has the same guid as our material # Parsing this xml would be better but the namespace is needed to search it. - if guid in f.read(): + parsed_guid = XmlMaterialProfile.getMetadataFromSerialized(f.read(), "GUID") + if guid == parsed_guid: return package_id continue diff --git a/plugins/UFPWriter/UFPWriter.py b/plugins/UFPWriter/UFPWriter.py index 59067b1932..c14b5fbfe9 100644 --- a/plugins/UFPWriter/UFPWriter.py +++ b/plugins/UFPWriter/UFPWriter.py @@ -201,10 +201,14 @@ class UFPWriter(MeshWriter): package_manager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager()) for extruder in CuraApplication.getInstance().getExtruderManager().getActiveExtruderStacks(): + if not extruder.isEnabled: + # Don't export materials not in use + continue + package_id = package_manager.getMaterialFilePackageId(extruder.material.getFileName(), extruder.material.getMetaDataEntry("GUID")) package_data = package_manager.getInstalledPackageInfo(package_id) - if package_data.get("is_bundled"): + if not package_data or package_data.get("is_bundled"): continue material_metadata = {"id": package_id, diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index d1da10399a..05ae9a79c9 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -480,6 +480,15 @@ class XmlMaterialProfile(InstanceContainer): return version * 1000000 + setting_version + @classmethod + def getMetadataFromSerialized(cls, serialized: str, property_name: str) -> str: + data = ET.fromstring(serialized) + metadata = data.find("./um:metadata", cls.__namespaces) + property = metadata.find("./um:" + property_name, cls.__namespaces) + + # This is a necessary property != None check, xml library overrides __bool__ to return False in cases when Element is not None. + return property.text if property != None else "" + def deserialize(self, serialized, file_name = None): """Overridden from InstanceContainer""" From 64478fb17d62ec081d431f0d7d359120a3ddc49a Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Tue, 31 May 2022 10:04:23 +0200 Subject: [PATCH 4/9] Fix typo fetching sdk version CURA-8610 --- plugins/UFPWriter/UFPWriter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/UFPWriter/UFPWriter.py b/plugins/UFPWriter/UFPWriter.py index c14b5fbfe9..0ff476e26b 100644 --- a/plugins/UFPWriter/UFPWriter.py +++ b/plugins/UFPWriter/UFPWriter.py @@ -215,7 +215,7 @@ class UFPWriter(MeshWriter): "display_name": package_data.get("display_name"), "website": package_data.get("website"), "package_version": package_data.get("package_version"), - "sdk_version_semver": package_data.get("package_version_semver")} + "sdk_version_semver": package_data.get("sdk_version_semver")} metadata.append(material_metadata) From ec60325a3fc722c5e54ae1e5451d9d6a9fd440d1 Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Tue, 31 May 2022 12:39:00 +0200 Subject: [PATCH 5/9] Move metadata exporting to 3mf CURA-8610 --- plugins/3MFWriter/ThreeMFWriter.py | 72 ++++++++++++++++--- .../XmlMaterialProfile/XmlMaterialProfile.py | 2 +- 2 files changed, 62 insertions(+), 12 deletions(-) diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py index 853aa08513..4bb8ab5881 100644 --- a/plugins/3MFWriter/ThreeMFWriter.py +++ b/plugins/3MFWriter/ThreeMFWriter.py @@ -1,6 +1,8 @@ # Copyright (c) 2015-2022 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Optional +import json + +from typing import Optional, cast, List, Dict from UM.Mesh.MeshWriter import MeshWriter from UM.Math.Vector import Vector @@ -10,6 +12,7 @@ from UM.Application import Application from UM.Scene.SceneNode import SceneNode from cura.CuraApplication import CuraApplication +from cura.CuraPackageManager import CuraPackageManager from cura.Utils.Threading import call_on_qt_thread from cura.Snapshot import Snapshot @@ -34,6 +37,9 @@ import UM.Application from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") +THUMBNAIL_PATH = "Metadata/thumbnail.png" +MODEL_PATH = "3D/3dmodel.model" +PACKAGE_METADATA_PATH = "Metadata/packages.json" class ThreeMFWriter(MeshWriter): def __init__(self): @@ -46,7 +52,7 @@ class ThreeMFWriter(MeshWriter): } self._unit_matrix_string = self._convertMatrixToString(Matrix()) - self._archive = None # type: Optional[zipfile.ZipFile] + self._archive: Optional[zipfile.ZipFile] = None self._store_archive = False def _convertMatrixToString(self, matrix): @@ -132,11 +138,11 @@ class ThreeMFWriter(MeshWriter): def getArchive(self): return self._archive - def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode): + def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode) -> bool: self._archive = None # Reset archive archive = zipfile.ZipFile(stream, "w", compression = zipfile.ZIP_DEFLATED) try: - model_file = zipfile.ZipInfo("3D/3dmodel.model") + model_file = zipfile.ZipInfo(MODEL_PATH) # Because zipfile is stupid and ignores archive-level compression settings when writing with ZipInfo. model_file.compress_type = zipfile.ZIP_DEFLATED @@ -151,7 +157,7 @@ class ThreeMFWriter(MeshWriter): relations_file = zipfile.ZipInfo("_rels/.rels") relations_file.compress_type = zipfile.ZIP_DEFLATED relations_element = ET.Element("Relationships", xmlns = self._namespaces["relationships"]) - model_relation_element = ET.SubElement(relations_element, "Relationship", Target = "/3D/3dmodel.model", Id = "rel0", Type = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel") + model_relation_element = ET.SubElement(relations_element, "Relationship", Target = "/" + MODEL_PATH, Id = "rel0", Type = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel") # Attempt to add a thumbnail snapshot = self._createSnapshot() @@ -160,28 +166,32 @@ class ThreeMFWriter(MeshWriter): thumbnail_buffer.open(QBuffer.OpenModeFlag.ReadWrite) snapshot.save(thumbnail_buffer, "PNG") - thumbnail_file = zipfile.ZipInfo("Metadata/thumbnail.png") + thumbnail_file = zipfile.ZipInfo(THUMBNAIL_PATH) # Don't try to compress snapshot file, because the PNG is pretty much as compact as it will get archive.writestr(thumbnail_file, thumbnail_buffer.data()) # Add PNG to content types file thumbnail_type = ET.SubElement(content_types, "Default", Extension = "png", ContentType = "image/png") # Add thumbnail relation to _rels/.rels file - thumbnail_relation_element = ET.SubElement(relations_element, "Relationship", Target = "/Metadata/thumbnail.png", Id = "rel1", Type = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail") + thumbnail_relation_element = ET.SubElement(relations_element, "Relationship", Target = "/" + THUMBNAIL_PATH, Id = "rel1", Type = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail") + + # Write material metadata + material_metadata = self._getMaterialPackageMetadata() + self._storeMetadataJson({"packages": material_metadata}, archive, PACKAGE_METADATA_PATH) savitar_scene = Savitar.Scene() - metadata_to_store = CuraApplication.getInstance().getController().getScene().getMetaData() + scene_metadata = CuraApplication.getInstance().getController().getScene().getMetaData() - for key, value in metadata_to_store.items(): + for key, value in scene_metadata.items(): savitar_scene.setMetaDataEntry(key, value) current_time_string = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - if "Application" not in metadata_to_store: + if "Application" not in scene_metadata: # This might sound a bit strange, but this field should store the original application that created # the 3mf. So if it was already set, leave it to whatever it was. savitar_scene.setMetaDataEntry("Application", CuraApplication.getInstance().getApplicationDisplayName()) - if "CreationDate" not in metadata_to_store: + if "CreationDate" not in scene_metadata: savitar_scene.setMetaDataEntry("CreationDate", current_time_string) savitar_scene.setMetaDataEntry("ModificationDate", current_time_string) @@ -233,6 +243,46 @@ class ThreeMFWriter(MeshWriter): return True + @staticmethod + def _storeMetadataJson(metadata: Dict[str, List[Dict[str, str]]], archive: zipfile.ZipFile, path: str) -> None: + """Stores metadata inside archive path as json file""" + metadata_file = zipfile.ZipInfo(path) + # We have to set the compress type of each file as well (it doesn't keep the type of the entire archive) + metadata_file.compress_type = zipfile.ZIP_DEFLATED + archive.writestr(metadata_file, json.dumps(metadata, separators=(", ", ": "), indent=4, skipkeys=True, ensure_ascii=False)) + + @staticmethod + def _getMaterialPackageMetadata() -> List[Dict[str, str]]: + """Get metadata for installed materials in active extruder stack, this does not include bundled materials. + + :return: List of material metadata dictionaries. + """ + metadata = {} + + package_manager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager()) + + for extruder in CuraApplication.getInstance().getExtruderManager().getActiveExtruderStacks(): + if not extruder.isEnabled: + # Don't export materials not in use + continue + + package_id = package_manager.getMaterialFilePackageId(extruder.material.getFileName(), extruder.material.getMetaDataEntry("GUID")) + package_data = package_manager.getInstalledPackageInfo(package_id) + + if not package_data or package_data.get("is_bundled"): + continue + + material_metadata = {"id": package_id, + "display_name": package_data.get("display_name") if package_data.get("display_name") else "", + "website": package_data.get("website") if package_data.get("website") else "", + "package_version": package_data.get("package_version") if package_data.get("package_version") else "", + "sdk_version_semver": package_data.get("sdk_version_semver") if package_data.get("sdk_version_semver") else ""} + + metadata[package_id] = material_metadata + + # Storing in a dict and fetching values to avoid duplicates + return list(metadata.values()) + @call_on_qt_thread # must be called from the main thread because of OpenGL def _createSnapshot(self): Logger.log("d", "Creating thumbnail image...") diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 05ae9a79c9..4cc3638f71 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -344,7 +344,7 @@ class XmlMaterialProfile(InstanceContainer): return stream.getvalue().decode("utf-8") def getFileName(self): - return self.getMetaDataEntry("base_file") + ".xml.fdm_material" + return (self.getMetaDataEntry("base_file") + ".xml.fdm_material").replace(" ", "+") # Recursively resolve loading inherited files def _resolveInheritance(self, file_name): From ca9955c7305b433f31eb314099afcc6dd77fd7c8 Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Tue, 31 May 2022 12:43:52 +0200 Subject: [PATCH 6/9] Remove metadata exporting to ufp CURA-8610 --- plugins/UFPWriter/UFPWriter.py | 69 ++++++++++------------------------ 1 file changed, 19 insertions(+), 50 deletions(-) diff --git a/plugins/UFPWriter/UFPWriter.py b/plugins/UFPWriter/UFPWriter.py index 0ff476e26b..52dab1efc7 100644 --- a/plugins/UFPWriter/UFPWriter.py +++ b/plugins/UFPWriter/UFPWriter.py @@ -16,14 +16,13 @@ from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType from UM.PluginRegistry import PluginRegistry # To get the g-code writer. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator +from UM.Scene.SceneNode import SceneNode from cura.CuraApplication import CuraApplication -from cura.CuraPackageManager import CuraPackageManager from cura.Utils.Threading import call_on_qt_thread from UM.i18n import i18nCatalog METADATA_OBJECTS_PATH = "metadata/objects" -METADATA_MATERIALS_PATH = "metadata/packages" catalog = i18nCatalog("cura") @@ -50,7 +49,7 @@ class UFPWriter(MeshWriter): archive.openStream(stream, "application/x-ufp", OpenMode.WriteOnly) try: - self._writeMetadata(archive) + self._writeObjectList(archive) # Store the g-code from the scene. archive.addContentType(extension = "gcode", mime_type = "text/x-gcode") @@ -164,60 +163,30 @@ class UFPWriter(MeshWriter): return True @staticmethod - def _writeMetadata(archive: VirtualFile): - material_metadata = UFPWriter._getMaterialPackageMetadata() - object_metadata = UFPWriter._getObjectMetadata() + def _writeObjectList(archive): + """Write a json list of object names to the METADATA_OBJECTS_PATH metadata field - data = {METADATA_MATERIALS_PATH: material_metadata, - METADATA_OBJECTS_PATH: object_metadata} + To retrieve, use: `archive.getMetadata(METADATA_OBJECTS_PATH)` + """ + objects_model = CuraApplication.getInstance().getObjectsModel() + object_metas = [] + + for item in objects_model.items: + object_metas.extend(UFPWriter._getObjectMetadata(item["node"])) + + data = {METADATA_OBJECTS_PATH: object_metas} archive.setMetadata(data) @staticmethod - def _getObjectMetadata() -> List[Dict[str, str]]: + def _getObjectMetadata(node: SceneNode) -> List[Dict[str, str]]: """Get object metadata to write for a Node. :return: List of object metadata dictionaries. + Might contain > 1 element in case of a group node. + Might be empty in case of nonPrintingMesh """ - metadata = [] - - objects_model = CuraApplication.getInstance().getObjectsModel() - - for item in objects_model.items: - for node in DepthFirstIterator(item["node"]): - if node.getMeshData() is not None and not node.callDecoration("isNonPrintingMesh"): - metadata.extend({"name": node.getName()}) - - return metadata - - @staticmethod - def _getMaterialPackageMetadata() -> List[Dict[str, str]]: - """Get metadata for installed materials in active extruder stack, this does not include bundled materials. - - :return: List of material metadata dictionaries. - """ - metadata = [] - - package_manager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager()) - - for extruder in CuraApplication.getInstance().getExtruderManager().getActiveExtruderStacks(): - if not extruder.isEnabled: - # Don't export materials not in use - continue - - package_id = package_manager.getMaterialFilePackageId(extruder.material.getFileName(), extruder.material.getMetaDataEntry("GUID")) - package_data = package_manager.getInstalledPackageInfo(package_id) - - if not package_data or package_data.get("is_bundled"): - continue - - material_metadata = {"id": package_id, - "display_name": package_data.get("display_name"), - "website": package_data.get("website"), - "package_version": package_data.get("package_version"), - "sdk_version_semver": package_data.get("sdk_version_semver")} - - metadata.append(material_metadata) - - return metadata + return [{"name": item.getName()} + for item in DepthFirstIterator(node) + if item.getMeshData() is not None and not item.callDecoration("isNonPrintingMesh")] From 094c781b02c0d39f999757c5f1ed94b29de01f0c Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Tue, 31 May 2022 15:49:57 +0200 Subject: [PATCH 7/9] Update plugins/XmlMaterialProfile/XmlMaterialProfile.py Co-authored-by: Remco Burema <41987080+rburema@users.noreply.github.com> --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 4cc3638f71..d7d11a1f44 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -343,7 +343,7 @@ class XmlMaterialProfile(InstanceContainer): return stream.getvalue().decode("utf-8") - def getFileName(self): + def getFileName(self) -> str: return (self.getMetaDataEntry("base_file") + ".xml.fdm_material").replace(" ", "+") # Recursively resolve loading inherited files From 29b645070475516484314cc846d63a38500f5ad3 Mon Sep 17 00:00:00 2001 From: "j.delarago" Date: Tue, 31 May 2022 16:29:56 +0200 Subject: [PATCH 8/9] Add some more information for the unhappy route and a message to inform the user. CURA-6990 --- cura/CuraPackageManager.py | 5 +++-- plugins/3MFWriter/ThreeMFWriter.py | 10 +++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 0cb18ccc92..720406fbc6 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -4,6 +4,7 @@ import os from typing import Any, cast, Dict, List, Set, Tuple, TYPE_CHECKING, Optional +from UM.Logger import Logger from cura.CuraApplication import CuraApplication # To find some resource types. from cura.Settings.GlobalStack import GlobalStack @@ -70,9 +71,9 @@ class CuraPackageManager(PackageManager): parsed_guid = XmlMaterialProfile.getMetadataFromSerialized(f.read(), "GUID") if guid == parsed_guid: return package_id - continue - + Logger.error("Could not find package_id for file: {} with GUID: {} ".format(file_name, guid)) + return "" def getMachinesUsingPackage(self, package_id: str) -> Tuple[List[Tuple[GlobalStack, str, str]], List[Tuple[GlobalStack, str, str]]]: """Returns a list of where the package is used diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py index 4bb8ab5881..eb96490fdb 100644 --- a/plugins/3MFWriter/ThreeMFWriter.py +++ b/plugins/3MFWriter/ThreeMFWriter.py @@ -9,6 +9,7 @@ from UM.Math.Vector import Vector from UM.Logger import Logger from UM.Math.Matrix import Matrix from UM.Application import Application +from UM.Message import Message from UM.Scene.SceneNode import SceneNode from cura.CuraApplication import CuraApplication @@ -269,7 +270,14 @@ class ThreeMFWriter(MeshWriter): package_id = package_manager.getMaterialFilePackageId(extruder.material.getFileName(), extruder.material.getMetaDataEntry("GUID")) package_data = package_manager.getInstalledPackageInfo(package_id) - if not package_data or package_data.get("is_bundled"): + if not package_data: + message = Message(catalog.i18nc("@error:uninstall", + "It was not possible to store material package information in project file: {material}. This project may not open correctly on other systems.".format(material=extruder.getName())), + title=catalog.i18nc("@info:title", "Failed to save material package information"), + message_type=Message.MessageType.WARNING) + message.show() + + if package_data.get("is_bundled"): continue material_metadata = {"id": package_id, From aa83c68387e713452a1787c0f3504eec1572e9fd Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 31 May 2022 17:20:51 +0200 Subject: [PATCH 9/9] This error is not about materials, not uninstalling. --- plugins/3MFWriter/ThreeMFWriter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py index eb96490fdb..a4a53dd813 100644 --- a/plugins/3MFWriter/ThreeMFWriter.py +++ b/plugins/3MFWriter/ThreeMFWriter.py @@ -271,7 +271,7 @@ class ThreeMFWriter(MeshWriter): package_data = package_manager.getInstalledPackageInfo(package_id) if not package_data: - message = Message(catalog.i18nc("@error:uninstall", + message = Message(catalog.i18nc("@error:material", "It was not possible to store material package information in project file: {material}. This project may not open correctly on other systems.".format(material=extruder.getName())), title=catalog.i18nc("@info:title", "Failed to save material package information"), message_type=Message.MessageType.WARNING)