diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py index 57c667145e..cbbb635d21 100644 --- a/plugins/3MFWriter/ThreeMFWriter.py +++ b/plugins/3MFWriter/ThreeMFWriter.py @@ -1,8 +1,9 @@ # Copyright (c) 2015-2022 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import json +import re -from typing import Optional, cast, List, Dict +from typing import Optional, cast, List, Dict, Pattern, Set from UM.Mesh.MeshWriter import MeshWriter from UM.Math.Vector import Vector @@ -17,6 +18,7 @@ from UM.Settings.EmptyInstanceContainer import EmptyInstanceContainer from cura.CuraApplication import CuraApplication from cura.CuraPackageManager import CuraPackageManager +from cura.Settings import CuraContainerStack from cura.Utils.Threading import call_on_qt_thread from cura.Snapshot import Snapshot @@ -175,13 +177,15 @@ class ThreeMFWriter(MeshWriter): 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") + 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 = "/" + THUMBNAIL_PATH, 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) + packages_metadata = self._getMaterialPackageMetadata() + self._getPluginPackageMetadata() + self._storeMetadataJson({"packages": packages_metadata}, archive, PACKAGE_METADATA_PATH) savitar_scene = Savitar.Scene() @@ -253,7 +257,64 @@ class ThreeMFWriter(MeshWriter): 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)) + archive.writestr(metadata_file, + json.dumps(metadata, separators=(", ", ": "), indent=4, skipkeys=True, ensure_ascii=False)) + + @staticmethod + def _getPluginPackageMetadata() -> List[Dict[str, str]]: + """Get metadata for all backend plugins that are used in the project. + + :return: List of material metadata dictionaries. + """ + + backend_plugin_enum_value_regex = re.compile( + r"PLUGIN::(?P\w+)@(?P\d+.\d+.\d+)::(?P\w+)") + # This regex parses enum values to find if they contain custom + # backend engine values. These custom enum values are in the format + # PLUGIN::@:: + # where + # - plugin_id is the id of the plugin + # - version is in the semver format + # - value is the value of the enum + + plugin_ids = set() + + def add_plugin_ids_in_stack(stack: CuraContainerStack): + for key in stack.getAllKeys(): + value = str(stack.getProperty(key, "value")) + for plugin_id, _version, _value in backend_plugin_enum_value_regex.findall(value): + plugin_ids.add(plugin_id) + + # go through all stacks and find all the plugin id contained in the project + global_stack = Application.getInstance().getMachineManager().activeMachine + add_plugin_ids_in_stack(global_stack) + + for container in global_stack.getContainers(): + add_plugin_ids_in_stack(container) + + for extruder_stack in global_stack.extruderList: + add_plugin_ids_in_stack(extruder_stack) + + for container in extruder_stack.getContainers(): + add_plugin_ids_in_stack(container) + + metadata = {} + + package_manager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager()) + for plugin_id in plugin_ids: + package_data = package_manager.getInstalledPackageInfo(plugin_id) + + metadata[plugin_id] = { + "id": plugin_id, + "display_name": package_data.get("display_name") if package_data.get("display_name") 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 "", + "type": "backend_plugin", + } + + # Storing in a dict and fetching values to avoid duplicates + return list(metadata.values()) @staticmethod def _getMaterialPackageMetadata() -> List[Dict[str, str]]: @@ -278,7 +339,8 @@ class ThreeMFWriter(MeshWriter): # Don't export bundled materials continue - package_id = package_manager.getMaterialFilePackageId(extruder.material.getFileName(), extruder.material.getMetaDataEntry("GUID")) + package_id = package_manager.getMaterialFilePackageId(extruder.material.getFileName(), + extruder.material.getMetaDataEntry("GUID")) package_data = package_manager.getInstalledPackageInfo(package_id) # We failed to find the package for this material @@ -286,10 +348,14 @@ class ThreeMFWriter(MeshWriter): Logger.info(f"Could not find package for material in extruder {extruder.id}, skipping.") continue - material_metadata = {"id": package_id, - "display_name": package_data.get("display_name") if package_data.get("display_name") 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 ""} + material_metadata = { + "id": package_id, + "display_name": package_data.get("display_name") if package_data.get("display_name") 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 "", + "type": "material", + } metadata[package_id] = material_metadata