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
This commit is contained in:
j.delarago 2022-05-30 17:29:59 +02:00
parent 596c24657d
commit 21d59e9349
3 changed files with 69 additions and 19 deletions

View file

@ -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

View file

@ -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")]

View file

@ -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)