diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 5bfc27f334..348ce6a49c 100644 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -19,6 +19,7 @@ from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") import time +import os class MachineManager(QObject): def __init__(self, parent = None): @@ -502,7 +503,7 @@ class MachineManager(QObject): containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = material_id) if not containers or not self._active_container_stack: return - + Logger.log("d", "Attempting to change the active material to %s", material_id) old_variant = self._active_container_stack.findContainer({"type": "variant"}) old_material = self._active_container_stack.findContainer({"type": "material"}) old_quality = self._active_container_stack.findContainer({"type": "quality"}) @@ -535,6 +536,7 @@ class MachineManager(QObject): containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = variant_id) if not containers or not self._active_container_stack: return + Logger.log("d", "Attempting to change the active variant to %s", variant_id) old_variant = self._active_container_stack.findContainer({"type": "variant"}) old_material = self._active_container_stack.findContainer({"type": "material"}) if old_variant: @@ -554,6 +556,9 @@ class MachineManager(QObject): containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = quality_id) if not containers or not self._global_container_stack: return + + Logger.log("d", "Attempting to change the active quality to %s", quality_id) + self.blurSettings.emit() quality_container = None quality_changes_container = self._empty_quality_changes_container @@ -829,7 +834,22 @@ class MachineManager(QObject): return containers[0] if "material" in search_criteria: - # If a quality for this specific material cannot be found, try finding qualities for a generic version of the material + # First check if we can solve our material not found problem by checking if we can find quality containers + # that are assigned to the parents of this material profile. + try: + inherited_files = material_container.getInheritedFiles() + if inherited_files: + for inherited_file in inherited_files: + # Extract the ID from the path we used to load the file. + search_criteria["material"] = os.path.basename(inherited_file).split(".")[0] + containers = container_registry.findInstanceContainers(**search_criteria) + if containers: + return containers[0] + except AttributeError: # Material_container does not support inheritance. + pass + + # We still weren't able to find a quality for this specific material. + # Try to find qualities for a generic version of the material. material_search_criteria = { "type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic" } if definition.getMetaDataEntry("has_machine_quality"): if material_container: diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 77f775ee27..b5337258e5 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -7,8 +7,10 @@ import io import xml.etree.ElementTree as ET import uuid +from UM.Resources import Resources from UM.Logger import Logger from UM.Util import parseBool +from cura.CuraApplication import CuraApplication import UM.Dictionary @@ -18,6 +20,7 @@ import UM.Settings class XmlMaterialProfile(UM.Settings.InstanceContainer): def __init__(self, container_id, *args, **kwargs): super().__init__(container_id, *args, **kwargs) + self._inherited_files = [] ## Overridden from InstanceContainer def duplicate(self, new_id, new_name = None): @@ -48,6 +51,9 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): result.setMetaDataEntry("base_file", result.id) return result + def getInheritedFiles(self): + return self._inherited_files + ## Overridden from InstanceContainer def setReadOnly(self, read_only): super().setReadOnly(read_only) @@ -246,6 +252,50 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): return stream.getvalue() + # Recursively resolve loading inherited files + def _resolveInheritance(self, file_name): + xml = self._loadFile(file_name) + + inherits = xml.find("./um:inherits", self.__namespaces) + if inherits is not None: + inherited = self._resolveInheritance(inherits.text) + xml = self._mergeXML(inherited, xml) + + return xml + + def _loadFile(self, file_name): + path = Resources.getPath(CuraApplication.getInstance().ResourceTypes.MaterialInstanceContainer, file_name + ".xml.fdm_material") + + with open(path, encoding="utf-8") as f: + contents = f.read() + + self._inherited_files.append(path) + return ET.fromstring(contents) + + def _mergeXML(self, first, second): + result = copy.deepcopy(first) + self._combineElement(result, second) + return result + + # Recursively merges XML elements. Updates either the text or children if another element is found in first. + # If it does not exist, copies it from second. + def _combineElement(self, first, second): + # Create a mapping from tag name to element. + mapping = {el.tag: el for el in first} + for el in second: + if len(el): # Check if element has children. + try: + self._combineElement(mapping[el.tag], el) # Multiple elements, handle those. + except KeyError: + mapping[el.tag] = el + first.append(el) + else: + try: + mapping[el.tag].text = el.text + except KeyError: # Not in the mapping, so simply add it + mapping[el.tag] = el + first.append(el) + ## Overridden from InstanceContainer def deserialize(self, serialized): data = ET.fromstring(serialized) @@ -255,6 +305,11 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): # TODO: Add material verfication self.addMetaDataEntry("status", "unknown") + #for inherit in data.findall("./um:inherits", self.__namespaces): + inherits = data.find("./um:inherits", self.__namespaces) + if inherits is not None: + inherited = self._resolveInheritance(inherits.text) + data = self._mergeXML(inherited, data) metadata = data.iterfind("./um:metadata/*", self.__namespaces) for entry in metadata: