From 55bdc0c85340478547273d35a02fe0964f45b70e Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 6 Feb 2018 16:27:01 +0100 Subject: [PATCH] WIP: Create VariantManager --- cura/CuraApplication.py | 11 +++ cura/Machines/ContainerNode.py | 34 ++++++++ cura/Machines/VariantManager.py | 81 +++++++++++++++++++ cura/Machines/__init__.py | 0 cura/Settings/MachineManager.py | 20 +++++ cura/Settings/NozzleModel.py | 47 +++++++++++ .../XmlMaterialProfile/XmlMaterialProfile.py | 20 +++-- resources/qml/Menus/NozzleMenu.qml | 51 ++++++------ 8 files changed, 228 insertions(+), 36 deletions(-) create mode 100644 cura/Machines/ContainerNode.py create mode 100644 cura/Machines/VariantManager.py create mode 100644 cura/Machines/__init__.py create mode 100644 cura/Settings/NozzleModel.py diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 07b3aab60c..d209b90909 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -59,6 +59,8 @@ from cura.Settings.SettingInheritanceManager import SettingInheritanceManager from cura.Settings.UserProfilesModel import UserProfilesModel from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager +from cura.Machines.VariantManager import VariantManager + from . import PlatformPhysics from . import BuildVolume @@ -233,6 +235,9 @@ class CuraApplication(QtApplication): if kwargs["parsed_command_line"].get("trigger_early_crash", False): assert not "This crash is triggered by the trigger_early_crash command line argument." + # new stuff + self._variant_manager = VariantManager(ContainerRegistry.getInstance()) + self.default_theme = "cura-light" self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png"))) @@ -707,6 +712,9 @@ class CuraApplication(QtApplication): return False return True + def getVariantManager(self): + return self._variant_manager + def preRun(self): # Last check for unknown commandline arguments parser = self.getCommandlineParser() @@ -723,6 +731,9 @@ class CuraApplication(QtApplication): def run(self): self.preRun() + container_registry = ContainerRegistry.getInstance() + self._variant_manager.initialize() + # Check if we should run as single instance or not self._setUpSingleInstanceServer() diff --git a/cura/Machines/ContainerNode.py b/cura/Machines/ContainerNode.py new file mode 100644 index 0000000000..b9ebaa698a --- /dev/null +++ b/cura/Machines/ContainerNode.py @@ -0,0 +1,34 @@ +from typing import Optional + +from collections import OrderedDict + +from UM.Logger import Logger + + +## A metadata / container combination. Use getContainer to get the container corresponding to the metadata +class ContainerNode: + def __init__(self, metadata = None): + self.metadata = metadata + self.container = None + self.children_map = OrderedDict() + + def getChildNode(self, child_key: str) -> Optional["QualityNode"]: + return self.children_map.get(child_key) + + def getContainer(self) -> "InstanceContainer": + if self.metadata is None: + raise RuntimeError("Cannot get container for a QualityNode without metadata") + + if self.container is None: + container_id = self.metadata["id"] + Logger.log("d", "Lazy-loading container [%s]", container_id) + from UM.Settings.ContainerRegistry import ContainerRegistry + container_list = ContainerRegistry.getInstance().findInstanceContainers(id = container_id) + if not container_list: + raise RuntimeError("Failed to lazy-load container [%s], cannot find it" % container_id) + self.container = container_list[0] + + return self.container + + def __str__(self): + return "ContainerNode[%s]" % self.metadata.get("id") diff --git a/cura/Machines/VariantManager.py b/cura/Machines/VariantManager.py new file mode 100644 index 0000000000..1f9f0288ac --- /dev/null +++ b/cura/Machines/VariantManager.py @@ -0,0 +1,81 @@ +from typing import Optional + +from UM.Logger import Logger +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.InstanceContainer import InstanceContainer + +from cura.Machines.ContainerNode import ContainerNode +from cura.Settings.GlobalStack import GlobalStack + + +class VariantType: + BUILD_PLATE = "buildplate" + NOZZLE = "nozzle" + + +ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE) + + +# +# VariantManager is THE place to look for a specific variant. It maintains a variant lookup table with the following +# structure: +# +# [machine_definition_id] -> [variant_type] -> [variant_name] -> ContainerNode(metadata / container) +# Example: "ultimaker3" -> "buildplate" -> "Glass" (if present) -> ContainerNode +# -> ... +# -> "nozzle" -> "AA 0.4" +# -> "BB 0.8" +# -> ... +# +# Note that the "container" field is not loaded in the beginning because it would defeat the purpose of lazy-loading. +# A container is loaded when getVariant() is called to load a variant InstanceContainer. +# +class VariantManager: + + def __init__(self, container_registry): + self._container_registry = container_registry # type: ContainerRegistry + + self._machine_to_variant_dict_map = {} # -> + + self._exclude_variant_id_list = ["empty_variant"] + + # + # Initializes the VariantManager including: + # - initializing the variant lookup table based on the metadata in ContainerRegistry. + # + def initialize(self): + # Cache all variants from the container registry to a variant map for better searching and organization. + variant_metadata_list = self._container_registry.findContainersMetadata(type = "variant") + for variant_metadata in variant_metadata_list: + if variant_metadata["id"] in self._exclude_variant_id_list: + Logger.log("d", "Exclude variant [%s]", variant_metadata["id"]) + continue + + variant_name = variant_metadata["name"] + variant_definition = variant_metadata["definition"] + if variant_definition not in self._machine_to_variant_dict_map: + self._machine_to_variant_dict_map[variant_definition] = {} + #for variant_type in ALL_VARIANT_TYPES: + # self._machine_to_variant_dict_map[variant_definition][variant_type] = {} + + variant_type = variant_metadata["hardware_type"] + #variant_dict = self._machine_to_variant_dict_map[variant_definition][variant_type] + variant_dict = self._machine_to_variant_dict_map[variant_definition] + if variant_name in variant_dict: + # ERROR: duplicated variant name. + raise RuntimeError("Found duplicated variant name [%s], type [%s] for machine [%s]" % + (variant_name, variant_type, variant_definition)) + + variant_dict[variant_name] = ContainerNode(metadata = variant_metadata) + + # + # Gets the variant InstanceContainer with the given information. + # Almost the same as getVariantMetadata() except that this returns an InstanceContainer if present. + # + def getVariant(self, machine_type_name: str, variant_name: str, + variant_type: Optional[str] = None) -> Optional["InstanceContainer"]: + return self._machine_to_variant_dict_map[machine_type_name].get(variant_name) + + def getVariantNodes(self, machine: "GlobalStack"): + machine_type_name = machine.definition.getId() + return self._machine_to_variant_dict_map.get(machine_type_name) diff --git a/cura/Machines/__init__.py b/cura/Machines/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index cc5c4aa539..719f90bdd1 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1490,3 +1490,23 @@ class MachineManager(QObject): stacks = ExtruderManager.getInstance().getActiveExtruderStacks() stacks.append(self._global_container_stack) return [ s.containersChanged for s in stacks ] + + # New + @pyqtSlot(str, "QVariant") + def setVariantGroup(self, position, container_node): + Logger.log("d", "---------------- container = [%s]", container_node) + position = str(position) + self.blurSettings.emit() + with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): + self._global_container_stack.extruders[position].variant = container_node.getContainer() + + @pyqtSlot("QVariant") + def handleQualityGroup(self, quality_group): + Logger.log("d", "---------------- qg = [%s]", quality_group.name) + self.blurSettings.emit() + with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): + self._global_container_stack.quality = quality_group.node_for_global.getContainer() + self._global_container_stack.qualityChanges = self._empty_quality_changes_container + for position, node in quality_group.nodes_for_extruders.items(): + self._global_container_stack.extruders[position].quality = node.getContainer() + self._global_container_stack.extruders[position].qualityChanges = self._empty_quality_changes_container diff --git a/cura/Settings/NozzleModel.py b/cura/Settings/NozzleModel.py new file mode 100644 index 0000000000..3ad28f3287 --- /dev/null +++ b/cura/Settings/NozzleModel.py @@ -0,0 +1,47 @@ +from PyQt5.QtCore import Qt + +from UM.Application import Application +from UM.Qt.ListModel import ListModel + + +class NozzleModel(ListModel): + IdRole = Qt.UserRole + 1 + HotendNameRole = Qt.UserRole + 2 + ContainerNodeRole = Qt.UserRole + 3 + + def __init__(self, parent = None): + super().__init__(parent) + + self.addRoleName(self.IdRole, "id") + self.addRoleName(self.HotendNameRole, "hotend_name") + self.addRoleName(self.ContainerNodeRole, "container_node") + + Application.getInstance().globalContainerStackChanged.connect(self._update) + Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update) + Application.getInstance().getMachineManager().activeStackChanged.connect(self._update) + Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update) + + def _update(self): + self.items.clear() + + variant_manager = Application.getInstance()._variant_manager + active_global_stack = Application.getInstance().getMachineManager()._global_container_stack + if active_global_stack is None: + self.setItems([]) + return + + variant_group_dict = variant_manager.getVariantNodes(active_global_stack) + if not variant_group_dict: + self.setItems([]) + return + + item_list = [] + for hotend_name, container_node in sorted(variant_group_dict.items(), key = lambda i: i[0]): + item = {"id": hotend_name, + "hotend_name": hotend_name, + "container_node": container_node + } + + item_list.append(item) + + self.setItems(item_list) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index a7d9a7be26..54aaedb8f9 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -590,15 +590,14 @@ class XmlMaterialProfile(InstanceContainer): if buildplate_id is None: continue - variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata( - id = buildplate_id) - if not variant_containers: - # It is not really properly defined what "ID" is so also search for variants by name. - variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata( - definition = machine_id, name = buildplate_id) - - if not variant_containers: + from cura.Machines.VariantManager import VariantType + variant_manager = CuraApplication.getInstance().getVariantManager() + variant_node = variant_manager.getVariant(machine_id, VariantType.BUILD_PLATE, buildplate_id) + if not variant_node: continue + variant_metadata = variant_node.metadata + + # TODO: check if build plate variant exists buildplate_compatibility = machine_compatibility buildplate_recommended = machine_compatibility @@ -623,6 +622,11 @@ class XmlMaterialProfile(InstanceContainer): if hotend_name is None: continue + variant_manager = CuraApplication.getInstance().getVariantManager() + variant_node = variant_manager.getVariant(machine_id, hotend_name) + if not variant_node: + continue + hotend_compatibility = machine_compatibility hotend_setting_values = {} settings = hotend.iterfind("./um:setting", self.__namespaces) diff --git a/resources/qml/Menus/NozzleMenu.qml b/resources/qml/Menus/NozzleMenu.qml index 81db20a79d..6d554218a1 100644 --- a/resources/qml/Menus/NozzleMenu.qml +++ b/resources/qml/Menus/NozzleMenu.qml @@ -29,38 +29,33 @@ Menu return true; } - MenuItem + // TODO: single instance?? + Cura.NozzleModel { - id: automaticNozzle - text: - { - if(visible) - { - var nozzleName = Cura.MachineManager.printerOutputDevices[0].hotendIds[extruderIndex]; - return catalog.i18nc("@title:menuitem %1 is the nozzle currently loaded in the printer", "Automatic: %1").arg(nozzleName); - } - return ""; - } - visible: printerConnected && Cura.MachineManager.printerOutputDevices[0].hotendIds != undefined && Cura.MachineManager.printerOutputDevices[0].hotendIds.length > extruderIndex && !isClusterPrinter - onTriggered: - { - var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex; - Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex); - var hotendId = Cura.MachineManager.printerOutputDevices[0].hotendIds[extruderIndex]; - var itemIndex = nozzleInstantiator.model.find("name", hotendId); - if(itemIndex > -1) - { - Cura.MachineManager.setActiveVariant(nozzleInstantiator.model.getItem(itemIndex).id); - } - Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); - } + id: nozzleModel } - MenuSeparator + Instantiator { - visible: automaticNozzle.visible - } + model: nozzleModel + MenuItem + { + text: model.hotend_name + checkable: true + checked: Cura.MachineManager.activeVariantId == model.hotend_name + exclusiveGroup: group + onTriggered: { + var position = Cura.ExtruderManager.activeExtruderIndex; + Cura.MachineManager.setVariantGroup(position, model.container_node); + } + visible: true + } + + onObjectAdded: menu.insertItem(index, object); + onObjectRemoved: menu.removeItem(object); + } + /* Instantiator { id: nozzleInstantiator @@ -96,7 +91,7 @@ Menu } onObjectAdded: menu.insertItem(index, object) onObjectRemoved: menu.removeItem(object) - } + } */ ExclusiveGroup { id: group } }