123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708 |
- import copy
- import os.path
- import urllib.parse
- import uuid
- from typing import Dict, Union
- from PyQt5.QtCore import QObject, QUrl, QVariant
- from UM.FlameProfiler import pyqtSlot
- from PyQt5.QtWidgets import QMessageBox
- from UM.PluginRegistry import PluginRegistry
- from UM.SaveFile import SaveFile
- from UM.Platform import Platform
- from UM.MimeTypeDatabase import MimeTypeDatabase
- from UM.Logger import Logger
- from UM.Application import Application
- from UM.Settings.ContainerStack import ContainerStack
- from UM.Settings.DefinitionContainer import DefinitionContainer
- from UM.Settings.InstanceContainer import InstanceContainer
- from UM.MimeTypeDatabase import MimeTypeNotFoundError
- from UM.Settings.ContainerRegistry import ContainerRegistry
- from UM.i18n import i18nCatalog
- from cura.Settings.ExtruderManager import ExtruderManager
- from cura.Settings.ExtruderStack import ExtruderStack
- from cura.Machines.MachineTools import getMachineDefinitionIDForQualitySearch
- catalog = i18nCatalog("cura")
- class ContainerManager(QObject):
- def __init__(self, parent = None):
- super().__init__(parent)
- self._application = Application.getInstance()
- self._container_registry = ContainerRegistry.getInstance()
- self._machine_manager = self._application.getMachineManager()
- self._material_manager = self._application.getMaterialManager()
- self._container_name_filters = {}
- @pyqtSlot(str, str, result=str)
- def getContainerMetaDataEntry(self, container_id, entry_name):
- metadatas = self._container_registry.findContainersMetadata(id = container_id)
- if not metadatas:
- Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id)
- return ""
- return str(metadatas[0].get(entry_name, ""))
-
-
-
-
-
-
-
-
-
-
-
-
-
- @pyqtSlot("QVariant", str, str)
- def setContainerMetaDataEntry(self, container_node, entry_name, entry_value):
- root_material_id = container_node.metadata["base_file"]
- if self._container_registry.isReadOnly(root_material_id):
- Logger.log("w", "Cannot set metadata of read-only container %s.", root_material_id)
- return False
- material_group = self._material_manager.getMaterialGroup(root_material_id)
- entries = entry_name.split("/")
- entry_name = entries.pop()
- sub_item_changed = False
- if entries:
- root_name = entries.pop(0)
- root = material_group.root_material_node.metadata.get(root_name)
- item = root
- for _ in range(len(entries)):
- item = item.get(entries.pop(0), { })
- if item[entry_name] != entry_value:
- sub_item_changed = True
- item[entry_name] = entry_value
- entry_name = root_name
- entry_value = root
- container = material_group.root_material_node.getContainer()
- container.setMetaDataEntry(entry_name, entry_value)
- if sub_item_changed:
- container.metaDataChanged.emit(container)
-
-
-
-
-
-
-
-
-
-
-
-
- @pyqtSlot(str, str, str, str, result = bool)
- def setContainerProperty(self, container_id, setting_key, property_name, property_value):
- if self._container_registry.isReadOnly(container_id):
- Logger.log("w", "Cannot set properties of read-only container %s.", container_id)
- return False
- containers = self._container_registry.findContainers(id = container_id)
- if not containers:
- Logger.log("w", "Could not set properties of container %s because it was not found.", container_id)
- return False
- container = containers[0]
- container.setProperty(setting_key, property_name, property_value)
- basefile = container.getMetaDataEntry("base_file", container_id)
- for sibbling_container in ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile):
- if sibbling_container != container:
- sibbling_container.setProperty(setting_key, property_name, property_value)
- return True
-
-
-
-
-
-
-
-
-
-
-
-
-
- @pyqtSlot(str, str, str, result = QVariant)
- def getContainerProperty(self, container_id: str, setting_key: str, property_name: str):
- containers = self._container_registry.findContainers(id = container_id)
- if not containers:
- Logger.log("w", "Could not get properties of container %s because it was not found.", container_id)
- return ""
- container = containers[0]
- return container.getProperty(setting_key, property_name)
-
- @pyqtSlot("QVariant", str)
- def setMaterialName(self, material_node, new_name):
- 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._material_manager.getMaterialGroup(root_material_id)
- material_group.root_material_node.getContainer().setName(new_name)
- @pyqtSlot(str, result = str)
- def makeUniqueName(self, original_name):
- return self._container_registry.uniqueName(original_name)
-
-
-
-
-
-
-
-
-
-
- @pyqtSlot(str, result = "QStringList")
- def getContainerNameFilters(self, type_name):
- if not self._container_name_filters:
- self._updateContainerNameFilters()
- filters = []
- for filter_string, entry in self._container_name_filters.items():
- if not type_name or entry["type"] == type_name:
- filters.append(filter_string)
- filters.append("All Files (*)")
- return filters
-
-
-
-
-
-
-
-
-
- @pyqtSlot(str, str, QUrl, result = "QVariantMap")
- def exportContainer(self, container_id: str, file_type: str, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
- if not container_id or not file_type or not file_url_or_string:
- return {"status": "error", "message": "Invalid arguments"}
- if isinstance(file_url_or_string, QUrl):
- file_url = file_url_or_string.toLocalFile()
- else:
- file_url = file_url_or_string
- if not file_url:
- return {"status": "error", "message": "Invalid path"}
- mime_type = None
- if file_type not in self._container_name_filters:
- try:
- mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url)
- except MimeTypeNotFoundError:
- return {"status": "error", "message": "Unknown File Type"}
- else:
- mime_type = self._container_name_filters[file_type]["mime"]
- containers = self._container_registry.findContainers(id = container_id)
- if not containers:
- return {"status": "error", "message": "Container not found"}
- container = containers[0]
- if Platform.isOSX() and "." in file_url:
- file_url = file_url[:file_url.rfind(".")]
- for suffix in mime_type.suffixes:
- if file_url.endswith(suffix):
- break
- else:
- file_url += "." + mime_type.preferredSuffix
- if not Platform.isWindows():
- if os.path.exists(file_url):
- result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
- catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_url))
- if result == QMessageBox.No:
- return {"status": "cancelled", "message": "User cancelled"}
- try:
- contents = container.serialize()
- except NotImplementedError:
- return {"status": "error", "message": "Unable to serialize container"}
- if contents is None:
- return {"status": "error", "message": "Serialization returned None. Unable to write to file"}
- with SaveFile(file_url, "w") as f:
- f.write(contents)
- return {"status": "success", "message": "Successfully exported container", "path": file_url}
-
-
-
-
-
-
- @pyqtSlot(QUrl, result = "QVariantMap")
- def importMaterialContainer(self, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
- if not file_url_or_string:
- return {"status": "error", "message": "Invalid path"}
- if isinstance(file_url_or_string, QUrl):
- file_url = file_url_or_string.toLocalFile()
- else:
- file_url = file_url_or_string
- if not file_url or not os.path.exists(file_url):
- return {"status": "error", "message": "Invalid path"}
- try:
- mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url)
- except MimeTypeNotFoundError:
- return {"status": "error", "message": "Could not determine mime type of file"}
- container_type = self._container_registry.getContainerForMimeType(mime_type)
- if not container_type:
- return {"status": "error", "message": "Could not find a container to handle the specified file."}
- container_id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(file_url)))
- container_id = self._container_registry.uniqueName(container_id)
- container = container_type(container_id)
- try:
- with open(file_url, "rt", encoding = "utf-8") as f:
- container.deserialize(f.read())
- except PermissionError:
- return {"status": "error", "message": "Permission denied when trying to read the file"}
- except Exception as ex:
- return {"status": "error", "message": str(ex)}
- container.setDirty(True)
- self._container_registry.addContainer(container)
- return {"status": "success", "message": "Successfully imported container {0}".format(container.getName())}
-
-
-
-
-
-
- @pyqtSlot(result = bool)
- def updateQualityChanges(self):
- global_stack = Application.getInstance().getGlobalContainerStack()
- if not global_stack:
- return False
- self._machine_manager.blurSettings.emit()
- for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
-
- quality_changes = stack.qualityChanges
- if not quality_changes or self._container_registry.isReadOnly(quality_changes.getId()):
- Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId())
- continue
- self._performMerge(quality_changes, stack.getTop())
- self._machine_manager.activeQualityChangesGroupChanged.emit()
- return True
-
- @pyqtSlot()
- def clearUserContainers(self) -> None:
- self._machine_manager.blurSettings.emit()
- send_emits_containers = []
-
- for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
- container = stack.getTop()
- container.clear()
- send_emits_containers.append(container)
- for container in send_emits_containers:
- container.sendPostponedEmits()
-
-
-
-
-
-
-
- @pyqtSlot(str)
- def createQualityChanges(self, base_name):
- global_stack = Application.getInstance().getGlobalContainerStack()
- if not global_stack:
- return
- active_quality_name = self._machine_manager.activeQualityOrQualityChangesName
- if active_quality_name == "":
- Logger.log("w", "No quality container found in stack %s, cannot create profile", global_stack.getId())
- return
- self._machine_manager.blurSettings.emit()
- if base_name is None or base_name == "":
- base_name = active_quality_name
- unique_name = self._container_registry.uniqueName(base_name)
-
- for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
- user_container = stack.userChanges
- quality_container = stack.quality
- quality_changes_container = stack.qualityChanges
- if not quality_container or not quality_changes_container:
- Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId())
- continue
- extruder_definition_id = None
- if isinstance(stack, ExtruderStack):
- extruder_definition_id = stack.definition.getId()
- quality_type = quality_container.getMetaDataEntry("quality_type")
- new_changes = self._createQualityChanges(quality_type, unique_name, global_stack, extruder_definition_id)
- self._performMerge(new_changes, quality_changes_container, clear_settings = False)
- self._performMerge(new_changes, user_container)
- self._container_registry.addContainer(new_changes)
-
-
-
- @pyqtSlot(QObject)
- def removeQualityChangesGroup(self, quality_changes_group):
- Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name)
- for node in quality_changes_group.getAllNodes():
- self._container_registry.removeContainer(node.metadata["id"])
-
-
-
- @pyqtSlot(QObject, str, result = str)
- def renameQualityChangesGroup(self, quality_changes_group, new_name) -> str:
- Logger.log("i", "Renaming QualityChangesGroup[%s] to [%s]", quality_changes_group.name, new_name)
- self._machine_manager.blurSettings.emit()
- if new_name == quality_changes_group.name:
- Logger.log("i", "QualityChangesGroup name [%s] unchanged.", quality_changes_group.name)
- return new_name
- new_name = self._container_registry.uniqueName(new_name)
- for node in quality_changes_group.getAllNodes():
- node.getContainer().setName(new_name)
- self._machine_manager.activeQualityChanged.emit()
- self._machine_manager.activeQualityGroupChanged.emit()
- return new_name
- @pyqtSlot(str, "QVariantMap")
- def duplicateQualityChanges(self, quality_changes_name, quality_model_item):
- global_stack = Application.getInstance().getGlobalContainerStack()
- quality_group = quality_model_item["quality_group"]
- quality_changes_group = quality_model_item["quality_changes_group"]
- if quality_changes_group is None:
-
- new_quality_changes = self._createQualityChanges(quality_group.quality_type, quality_changes_name,
- global_stack, extruder_id = None)
- self._container_registry.addContainer(new_quality_changes)
- else:
- new_name = self._container_registry.uniqueName(quality_changes_name)
- for node in quality_changes_group.getAllNodes():
- container = node.getContainer()
- new_id = self._container_registry.uniqueName(container.getId())
- self._container_registry.addContainer(container.duplicate(new_id, new_name))
- @pyqtSlot("QVariant")
- def removeMaterial(self, material_node):
- root_material_id = material_node.metadata["base_file"]
- material_group = self._material_manager.getMaterialGroup(root_material_id)
- if not material_group:
- Logger.log("d", "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", result = str)
- def duplicateMaterial(self, material_node, new_base_id = None, new_metadata = None):
- root_material_id = material_node.metadata["base_file"]
- material_group = self._material_manager.getMaterialGroup(root_material_id)
- if not material_group:
- Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", root_material_id)
- return
- base_container = material_group.root_material_node.getContainer()
- containers_to_copy = []
- for node in material_group.derived_material_node_list:
- containers_to_copy.append(node.getContainer())
-
- Application.getInstance().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 container_to_copy in containers_to_copy:
-
- 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)
- ContainerRegistry.getInstance().addContainer(container_to_add)
- return new_base_id
-
-
-
- @pyqtSlot(result = str)
- def createMaterial(self):
-
- Application.getInstance().saveSettings()
- global_stack = Application.getInstance().getGlobalContainerStack()
- approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value")))
- root_material_id = "generic_pla"
- root_material_id = self._material_manager.getRootMaterialIDForDiameter(root_material_id, approximate_diameter)
- material_group = self._material_manager.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
-
-
-
-
- @pyqtSlot("QVariant", result = "QStringList")
- def getLinkedMaterials(self, material_node):
- guid = material_node.metadata["GUID"]
- material_group_list = self._material_manager.getMaterialGroupListByGUID(guid)
- linked_material_names = []
- if material_group_list:
- for material_group in material_group_list:
- linked_material_names.append(material_group.root_material_node.metadata["name"])
- return linked_material_names
-
-
- @pyqtSlot("QVariant")
- def unlinkMaterial(self, material_node):
-
- material_group = self._material_manager.getMaterialGroup(material_node.metadata["base_file"])
-
- new_guid = str(uuid.uuid4())
-
-
-
- container = material_group.root_material_node.getContainer()
- container.setMetaDataEntry("GUID", new_guid)
-
- @classmethod
- def getInstance(cls) -> "ContainerManager":
-
- if ContainerManager.__instance is None:
- ContainerManager.__instance = cls()
- return ContainerManager.__instance
- __instance = None
-
- @staticmethod
- def createContainerManager(engine, js_engine):
- return ContainerManager.getInstance()
- def _performMerge(self, merge_into, merge, clear_settings = True):
- assert isinstance(merge, type(merge_into))
- if merge == merge_into:
- return
- for key in merge.getAllKeys():
- merge_into.setProperty(key, "value", merge.getProperty(key, "value"))
- if clear_settings:
- merge.clear()
- def _updateContainerNameFilters(self) -> None:
- self._container_name_filters = {}
- for plugin_id, container_type in self._container_registry.getContainerTypes():
-
- if container_type in (InstanceContainer, ContainerStack, DefinitionContainer):
- continue
- serialize_type = ""
- try:
- plugin_metadata = PluginRegistry.getInstance().getMetaData(plugin_id)
- if plugin_metadata:
- serialize_type = plugin_metadata["settings_container"]["type"]
- else:
- continue
- except KeyError as e:
- continue
- mime_type = self._container_registry.getMimeTypeForContainer(container_type)
- entry = {
- "type": serialize_type,
- "mime": mime_type,
- "container": container_type
- }
- suffix = mime_type.preferredSuffix
- if Platform.isOSX() and "." in suffix:
-
- suffix = suffix[suffix.index(".") + 1:]
- suffix_list = "*." + suffix
- for suffix in mime_type.suffixes:
- if suffix == mime_type.preferredSuffix:
- continue
- if Platform.isOSX() and "." in suffix:
-
- suffix = suffix[suffix.index("."):]
- suffix_list += ", *." + suffix
- name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
- self._container_name_filters[name_filter] = entry
-
-
-
-
-
-
-
-
-
-
- def _createUniqueId(self, stack_id, container_name):
- result = stack_id + "_" + container_name
- result = result.lower()
- result.replace(" ", "_")
- return result
-
-
-
-
-
-
-
-
- def _createQualityChanges(self, quality_type, new_name, machine, extruder_id):
- base_id = machine.definition.getId() if extruder_id is None else extruder_id
-
- quality_changes = InstanceContainer(self._createUniqueId(base_id, new_name))
- quality_changes.setName(new_name)
- quality_changes.addMetaDataEntry("type", "quality_changes")
- quality_changes.addMetaDataEntry("quality_type", quality_type)
-
- if extruder_id is not None:
- quality_changes.addMetaDataEntry("extruder", extruder_id)
-
- machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
- quality_changes.setDefinition(machine_definition_id)
- from cura.CuraApplication import CuraApplication
- quality_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
- return quality_changes
-
- @pyqtSlot(QUrl, result="QVariantMap")
- def importProfile(self, file_url):
- if not file_url.isValid():
- return
- path = file_url.toLocalFile()
- if not path:
- return
- return self._container_registry.importProfile(path)
- @pyqtSlot(QObject, QUrl, str)
- def exportQualityChangesGroup(self, quality_changes_group, file_url: QUrl, file_type: str):
- if not file_url.isValid():
- return
- path = file_url.toLocalFile()
- if not path:
- return
- container_list = [n.getContainer() for n in quality_changes_group.getAllNodes()]
- self._container_registry.exportQualityProfile(container_list, path, file_type)
|