123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511 |
- from collections import defaultdict, OrderedDict
- import copy
- import uuid
- from typing import Optional, TYPE_CHECKING
- from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
- from UM.Application import Application
- from UM.Logger import Logger
- from UM.Settings.ContainerRegistry import ContainerRegistry
- from UM.Settings.SettingFunction import SettingFunction
- from UM.Util import parseBool
- from .MaterialNode import MaterialNode
- from .MaterialGroup import MaterialGroup
- if TYPE_CHECKING:
- from cura.Settings.GlobalStack import GlobalStack
- class MaterialManager(QObject):
- materialsUpdated = pyqtSignal()
- def __init__(self, container_registry, parent = None):
- super().__init__(parent)
- self._application = Application.getInstance()
- self._container_registry = container_registry
- self._fallback_materials_map = dict()
- self._material_group_map = dict()
- self._diameter_machine_variant_material_map = dict()
-
-
-
- self._material_diameter_map = defaultdict(dict)
- self._diameter_material_map = dict()
-
- self._guid_material_groups_map = defaultdict(list)
-
-
- self._default_machine_definition_id = "fdmprinter"
- self._default_approximate_diameter_for_quality_search = "3"
-
-
-
- self._update_timer = QTimer(self)
- self._update_timer.setInterval(300)
- self._update_timer.setSingleShot(True)
- self._update_timer.timeout.connect(self._updateMaps)
- self._container_registry.containerMetaDataChanged.connect(self._onContainerMetadataChanged)
- self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
- self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
- def initialize(self):
-
- material_metadatas = {metadata["id"]: metadata for metadata in self._container_registry.findContainersMetadata(type = "material")}
- self._material_group_map = dict()
-
-
- for material_id, material_metadata in material_metadatas.items():
-
- if material_id == "empty_material":
- continue
- root_material_id = material_metadata.get("base_file")
- if root_material_id not in self._material_group_map:
- self._material_group_map[root_material_id] = MaterialGroup(root_material_id, MaterialNode(material_metadatas[root_material_id]))
- self._material_group_map[root_material_id].is_read_only = self._container_registry.isReadOnly(root_material_id)
- group = self._material_group_map[root_material_id]
-
- if material_id != root_material_id:
- new_node = MaterialNode(material_metadata)
- group.derived_material_node_list.append(new_node)
-
- self._material_group_map = OrderedDict(sorted(self._material_group_map.items(), key = lambda x: x[0]))
-
-
- self._guid_material_groups_map = defaultdict(list)
- for root_material_id, material_group in self._material_group_map.items():
- guid = material_group.root_material_node.metadata["GUID"]
- self._guid_material_groups_map[guid].append(material_group)
-
-
- grouped_by_type_dict = dict()
- for root_material_id, material_node in self._material_group_map.items():
- if not self._container_registry.isReadOnly(root_material_id):
- continue
- material_type = material_node.root_material_node.metadata["material"]
- if material_type not in grouped_by_type_dict:
- grouped_by_type_dict[material_type] = {"generic": None,
- "others": []}
- brand = material_node.root_material_node.metadata["brand"]
- if brand.lower() == "generic":
- to_add = True
- if material_type in grouped_by_type_dict:
- diameter = material_node.root_material_node.metadata.get("approximate_diameter")
- if diameter != self._default_approximate_diameter_for_quality_search:
- to_add = False
- if to_add:
- grouped_by_type_dict[material_type] = material_node.root_material_node.metadata
- self._fallback_materials_map = grouped_by_type_dict
-
-
-
-
-
- self._material_diameter_map = defaultdict(dict)
- self._diameter_material_map = dict()
-
- material_group_dict = dict()
- keys_to_fetch = ("name", "material", "brand", "color")
- for root_material_id, machine_node in self._material_group_map.items():
- if not self._container_registry.isReadOnly(root_material_id):
- continue
- root_material_metadata = machine_node.root_material_node.metadata
- key_data = []
- for key in keys_to_fetch:
- key_data.append(root_material_metadata.get(key))
- key_data = tuple(key_data)
- if key_data not in material_group_dict:
- material_group_dict[key_data] = dict()
- approximate_diameter = root_material_metadata.get("approximate_diameter")
- material_group_dict[key_data][approximate_diameter] = root_material_metadata["id"]
-
- for data_dict in material_group_dict.values():
- for root_material_id1 in data_dict.values():
- if root_material_id1 in self._material_diameter_map:
- continue
- diameter_map = data_dict
- for root_material_id2 in data_dict.values():
- self._material_diameter_map[root_material_id2] = diameter_map
- default_root_material_id = data_dict.get(self._default_approximate_diameter_for_quality_search)
- if default_root_material_id is None:
- default_root_material_id = list(data_dict.values())[0]
- for root_material_id in data_dict.values():
- self._diameter_material_map[root_material_id] = default_root_material_id
-
-
-
- self._diameter_machine_variant_material_map = dict()
- for material_metadata in material_metadatas.values():
-
- if material_metadata["id"] == "empty_material":
- continue
- root_material_id = material_metadata["base_file"]
- definition = material_metadata["definition"]
- approximate_diameter = material_metadata["approximate_diameter"]
- if approximate_diameter not in self._diameter_machine_variant_material_map:
- self._diameter_machine_variant_material_map[approximate_diameter] = {}
- machine_variant_material_map = self._diameter_machine_variant_material_map[approximate_diameter]
- if definition not in machine_variant_material_map:
- machine_variant_material_map[definition] = MaterialNode()
- machine_node = machine_variant_material_map[definition]
- variant_name = material_metadata.get("variant_name")
- if not variant_name:
-
- machine_node.material_map[root_material_id] = MaterialNode(material_metadata)
- else:
-
-
- if variant_name not in machine_node.children_map:
- machine_node.children_map[variant_name] = MaterialNode()
- variant_node = machine_node.children_map[variant_name]
- if root_material_id not in variant_node.material_map:
- variant_node.material_map[root_material_id] = MaterialNode(material_metadata)
- else:
-
- raise RuntimeError("Found duplicate variant name [%s] for machine [%s] in material [%s]" %
- (variant_name, definition, material_metadata["id"]))
- self.materialsUpdated.emit()
- def _updateMaps(self):
- Logger.log("i", "Updating material lookup data ...")
- self.initialize()
- def _onContainerMetadataChanged(self, container):
- self._onContainerChanged(container)
- def _onContainerChanged(self, container):
- container_type = container.getMetaDataEntry("type")
- if container_type != "material":
- return
-
- self._update_timer.start()
- def getMaterialGroup(self, root_material_id: str) -> Optional[MaterialGroup]:
- return self._material_group_map.get(root_material_id)
- def getRootMaterialIDForDiameter(self, root_material_id: str, approximate_diameter: str) -> str:
- return self._material_diameter_map.get(root_material_id).get(approximate_diameter, root_material_id)
- def getRootMaterialIDWithoutDiameter(self, root_material_id: str) -> str:
- return self._diameter_material_map.get(root_material_id)
- def getMaterialGroupListByGUID(self, guid: str) -> Optional[list]:
- return self._guid_material_groups_map.get(guid)
-
-
-
- def getAvailableMaterials(self, machine_definition_id: str, extruder_variant_name: Optional[str],
- diameter: float) -> dict:
-
- rounded_diameter = str(round(diameter))
- if rounded_diameter not in self._diameter_machine_variant_material_map:
- Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s])", diameter, rounded_diameter)
- return dict()
-
- machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter]
- machine_node = machine_variant_material_map.get(machine_definition_id)
- default_machine_node = machine_variant_material_map.get(self._default_machine_definition_id)
- variant_node = None
- if extruder_variant_name is not None and machine_node is not None:
- variant_node = machine_node.getChildNode(extruder_variant_name)
- nodes_to_check = [variant_node, machine_node, default_machine_node]
-
-
-
-
- material_id_metadata_dict = dict()
- for node in nodes_to_check:
- if node is not None:
- for material_id, node in node.material_map.items():
- if material_id not in material_id_metadata_dict:
- material_id_metadata_dict[material_id] = node
- return material_id_metadata_dict
-
-
-
- def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack",
- extruder_stack: "ExtruderStack") -> Optional[dict]:
- machine_definition_id = machine.definition.getId()
- variant_name = None
- if extruder_stack.variant.getId() != "empty_variant":
- variant_name = extruder_stack.variant.getName()
- diameter = extruder_stack.approximateMaterialDiameter
-
- return self.getAvailableMaterials(machine_definition_id, variant_name, diameter)
-
-
-
-
-
-
- def getMaterialNode(self, machine_definition_id: str, extruder_variant_name: Optional[str],
- diameter: float, root_material_id: str) -> Optional["InstanceContainer"]:
-
- rounded_diameter = str(round(diameter))
- if rounded_diameter not in self._diameter_machine_variant_material_map:
- Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s]) for root material id [%s]",
- diameter, rounded_diameter, root_material_id)
- return None
-
- machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter]
- machine_node = machine_variant_material_map.get(machine_definition_id)
- variant_node = None
-
- if machine_node is None:
- machine_node = machine_variant_material_map.get(self._default_machine_definition_id)
- if machine_node is not None and extruder_variant_name is not None:
- variant_node = machine_node.getChildNode(extruder_variant_name)
-
-
-
-
- nodes_to_check = [variant_node, machine_node,
- machine_variant_material_map.get(self._default_machine_definition_id)]
- material_node = None
- for node in nodes_to_check:
- if node is not None:
- material_node = node.material_map.get(root_material_id)
- if material_node:
- break
- return material_node
-
-
-
-
-
-
- def getMaterialNodeByType(self, global_stack: "GlobalStack", extruder_variant_name: str, material_guid: str) -> Optional["MaterialNode"]:
- node = None
- machine_definition = global_stack.definition
- if parseBool(machine_definition.getMetaDataEntry("has_materials", False)):
- material_diameter = machine_definition.getProperty("material_diameter", "value")
- if isinstance(material_diameter, SettingFunction):
- material_diameter = material_diameter(global_stack)
-
- root_material_id = None
- for material_group in self._guid_material_groups_map[material_guid]:
- if material_group.is_read_only:
- root_material_id = material_group.root_material_node.metadata["id"]
- break
- if not root_material_id:
- Logger.log("i", "Cannot find materials with guid [%s] ", material_guid)
- return None
- node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name,
- material_diameter, root_material_id)
- return node
-
-
-
-
-
-
-
-
-
-
-
-
-
- def getFallbackMaterialIdByMaterialType(self, material_type: str) -> Optional[str]:
-
- if material_type not in self._fallback_materials_map:
- Logger.log("w", "The material type [%s] does not have a fallback material" % material_type)
- return None
- fallback_material = self._fallback_materials_map[material_type]
- if fallback_material:
- return self.getRootMaterialIDWithoutDiameter(fallback_material["id"])
- else:
- return None
- def getDefaultMaterial(self, global_stack: "GlobalStack", extruder_variant_name: Optional[str]) -> Optional["MaterialNode"]:
- node = None
- machine_definition = global_stack.definition
- if parseBool(global_stack.getMetaDataEntry("has_materials", False)):
- material_diameter = machine_definition.getProperty("material_diameter", "value")
- if isinstance(material_diameter, SettingFunction):
- material_diameter = material_diameter(global_stack)
- approximate_material_diameter = str(round(material_diameter))
- root_material_id = machine_definition.getMetaDataEntry("preferred_material")
- root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_material_diameter)
- node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name,
- material_diameter, root_material_id)
- return node
- def removeMaterialByRootId(self, root_material_id: str):
- material_group = self.getMaterialGroup(root_material_id)
- if not material_group:
- Logger.log("i", "Unable to remove the material with id %s, because it doesn't exist.", root_material_id)
- return
- nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list
- for node in nodes_to_remove:
- self._container_registry.removeContainer(node.metadata["id"])
-
-
-
-
-
-
- @pyqtSlot("QVariant", str)
- def setMaterialName(self, material_node: "MaterialNode", name: str):
- root_material_id = material_node.metadata["base_file"]
- if self._container_registry.isReadOnly(root_material_id):
- Logger.log("w", "Cannot set name of read-only container %s.", root_material_id)
- return
- material_group = self.getMaterialGroup(root_material_id)
- material_group.root_material_node.getContainer().setName(name)
-
-
-
- @pyqtSlot("QVariant")
- def removeMaterial(self, material_node: "MaterialNode"):
- root_material_id = material_node.metadata["base_file"]
- self.removeMaterialByRootId(root_material_id)
-
-
-
-
- @pyqtSlot("QVariant", result = str)
- def duplicateMaterial(self, material_node, new_base_id = None, new_metadata = None) -> Optional[str]:
- root_material_id = material_node.metadata["base_file"]
- material_group = self.getMaterialGroup(root_material_id)
- if not material_group:
- Logger.log("i", "Unable to duplicate the material with id %s, because it doesn't exist.", root_material_id)
- return None
- base_container = material_group.root_material_node.getContainer()
-
- self._application.saveSettings()
-
- new_containers = []
- if new_base_id is None:
- new_base_id = self._container_registry.uniqueName(base_container.getId())
- new_base_container = copy.deepcopy(base_container)
- new_base_container.getMetaData()["id"] = new_base_id
- new_base_container.getMetaData()["base_file"] = new_base_id
- if new_metadata is not None:
- for key, value in new_metadata.items():
- new_base_container.getMetaData()[key] = value
- new_containers.append(new_base_container)
-
- for node in material_group.derived_material_node_list:
- container_to_copy = node.getContainer()
-
- new_id = new_base_id
- if container_to_copy.getMetaDataEntry("definition") != "fdmprinter":
- new_id += "_" + container_to_copy.getMetaDataEntry("definition")
- if container_to_copy.getMetaDataEntry("variant_name"):
- variant_name = container_to_copy.getMetaDataEntry("variant_name")
- new_id += "_" + variant_name.replace(" ", "_")
- new_container = copy.deepcopy(container_to_copy)
- new_container.getMetaData()["id"] = new_id
- new_container.getMetaData()["base_file"] = new_base_id
- if new_metadata is not None:
- for key, value in new_metadata.items():
- new_container.getMetaData()[key] = value
- new_containers.append(new_container)
- for container_to_add in new_containers:
- container_to_add.setDirty(True)
- self._container_registry.addContainer(container_to_add)
- return new_base_id
-
-
-
- @pyqtSlot(result = str)
- def createMaterial(self) -> str:
- from UM.i18n import i18nCatalog
- catalog = i18nCatalog("cura")
-
- self._application.saveSettings()
- global_stack = self._application.getGlobalContainerStack()
- approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value")))
- root_material_id = "generic_pla"
- root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter)
- material_group = self.getMaterialGroup(root_material_id)
-
- new_id = self._container_registry.uniqueName("custom_material")
- new_metadata = {"name": catalog.i18nc("@label", "Custom Material"),
- "brand": catalog.i18nc("@label", "Custom"),
- "GUID": str(uuid.uuid4()),
- }
- self.duplicateMaterial(material_group.root_material_node,
- new_base_id = new_id,
- new_metadata = new_metadata)
- return new_id
|