diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index b78b9b91a2..97c849144d 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,9 +1,12 @@ diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index eb09f1ed82..a4e86626dc 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -62,7 +62,10 @@ from cura.Machines.Models.CustomQualityProfilesModel import CustomQualityProfile from cura.Machines.Models.Other.MultiBuildPlateModel import MultiBuildPlateModel -from cura.Machines.Models.MaterialsModel import BrandMaterialsModel, GenericMaterialsModel, MaterialsModel +from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel +from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel +from cura.Machines.Models.BrandMaterialsModel import BrandMaterialsModel + from cura.Settings.SettingInheritanceManager import SettingInheritanceManager from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager @@ -954,7 +957,7 @@ class CuraApplication(QtApplication): qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel") qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel") - qmlRegisterType(MaterialsModel, "Cura", 1, 0, "MaterialsModel") + qmlRegisterType(MaterialManagementModel, "Cura", 1, 0, "MaterialManagementModel") qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel") qmlRegisterSingletonType(QualityProfilesModel, "Cura", 1, 0, "QualityProfilesModel", self.getQualityProfileModel) @@ -1046,7 +1049,7 @@ class CuraApplication(QtApplication): count = 0 scene_bounding_box = None is_block_slicing_node = False - active_build_plate = self._multi_build_plate_model.activeBuildPlate + active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate for node in DepthFirstIterator(self.getController().getScene().getRoot()): if ( not issubclass(type(node), CuraSceneNode) or @@ -1295,7 +1298,7 @@ class CuraApplication(QtApplication): @pyqtSlot() def arrangeAll(self): nodes = [] - active_build_plate = self._multi_build_plate_model.activeBuildPlate + active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate for node in DepthFirstIterator(self.getController().getScene().getRoot()): if not isinstance(node, SceneNode): continue @@ -1444,7 +1447,7 @@ class CuraApplication(QtApplication): group_decorator = GroupDecorator() group_node.addDecorator(group_decorator) group_node.addDecorator(ConvexHullDecorator()) - group_node.addDecorator(BuildPlateDecorator(self._multi_build_plate_model.activeBuildPlate)) + group_node.addDecorator(BuildPlateDecorator(self.getMultiBuildPlateModel().activeBuildPlate)) group_node.setParent(self.getController().getScene().getRoot()) group_node.setSelectable(True) center = Selection.getSelectionCenter() @@ -1589,7 +1592,7 @@ class CuraApplication(QtApplication): arrange_objects_on_load = ( not Preferences.getInstance().getValue("cura/use_multi_build_plate") or not Preferences.getInstance().getValue("cura/not_arrange_objects_on_load")) - target_build_plate = self._multi_build_plate_model.activeBuildPlate if arrange_objects_on_load else -1 + target_build_plate = self.getMultiBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1 root = self.getController().getScene().getRoot() fixed_nodes = [] diff --git a/cura/Machines/ContainerGroup.py b/cura/Machines/ContainerGroup.py deleted file mode 100644 index b7ad634f3c..0000000000 --- a/cura/Machines/ContainerGroup.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import List, Optional - -from PyQt5.Qt import QObject, pyqtSlot - -from cura.Machines.ContainerNode import ContainerNode - - -class ContainerGroup(QObject): - - def __init__(self, name: str, parent = None): - super().__init__(parent) - self.name = name - self.node_for_global = None # type: Optional[ContainerNode] - self.nodes_for_extruders = dict() - - @pyqtSlot(result = str) - def getName(self) -> str: - return self.name - - def getAllKeys(self) -> set: - result = set() - for node in [self.node_for_global] + list(self.nodes_for_extruders.values()): - if node is None: - continue - for key in node.getContainer().getAllKeys(): - result.add(key) - return result - - def getAllNodes(self) -> List[ContainerNode]: - result = [] - if self.node_for_global is not None: - result.append(self.node_for_global) - for extruder_node in self.nodes_for_extruders.values(): - result.append(extruder_node) - return result diff --git a/cura/Machines/ContainerNode.py b/cura/Machines/ContainerNode.py index 4d8d663284..6a839fb921 100644 --- a/cura/Machines/ContainerNode.py +++ b/cura/Machines/ContainerNode.py @@ -1,3 +1,6 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + from typing import Optional from collections import OrderedDict diff --git a/cura/Machines/MachineTools.py b/cura/Machines/MachineTools.py deleted file mode 100644 index 91daa2490b..0000000000 --- a/cura/Machines/MachineTools.py +++ /dev/null @@ -1,25 +0,0 @@ -from UM.Util import parseBool - - -# -# Gets the machine definition ID that can be used to search for Quality containers that are suitable for the given -# machine. The rule is as follows: -# 1. By default, the machine definition ID for quality container search will be "fdmprinter", which is the generic -# machine. -# 2. If a machine has its own machine quality (with "has_machine_quality = True"), we should use the given machine's -# own machine definition ID for quality search. -# Example: for an Ultimaker 3, the definition ID should be "ultimaker3". -# 3. When condition (2) is met, AND the machine has "quality_definition" defined in its definition file, then the -# definition ID specified in "quality_definition" should be used. -# Example: for an Ultimaker 3 Extended, it has "quality_definition = ultimaker3". This means Ultimaker 3 Extended -# shares the same set of qualities profiles as Ultimaker 3. -# -def getMachineDefinitionIDForQualitySearch(machine: "GlobalStack", default_definition_id: str = "fdmprinter") -> str: - machine_definition_id = default_definition_id - if parseBool(machine.getMetaDataEntry("has_machine_quality", False)): - # Only use the machine's own quality definition ID if this machine has machine quality. - machine_definition_id = machine.getMetaDataEntry("quality_definition") - if machine_definition_id is None: - machine_definition_id = machine.definition.getId() - - return machine_definition_id diff --git a/cura/Machines/MaterialGroup.py b/cura/Machines/MaterialGroup.py new file mode 100644 index 0000000000..9111cc3a80 --- /dev/null +++ b/cura/Machines/MaterialGroup.py @@ -0,0 +1,25 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +# +# A MaterialGroup represents a group of material InstanceContainers that are derived from a single material profile. +# The main InstanceContainer which has the ID of the material profile file name is called the "root_material". For +# example: "generic_abs" is the root material (ID) of "generic_abs_ultimaker3" and "generic_abs_ultimaker3_AA_0.4", +# and "generic_abs_ultimaker3" and "generic_abs_ultimaker3_AA_0.4" are derived materials of "generic_abs". +# +# Using "generic_abs" as an example, the MaterialGroup for "generic_abs" will contain the following information: +# - name: "generic_abs", root_material_id +# - root_material_node: MaterialNode of "generic_abs" +# - derived_material_node_list: A list of MaterialNodes that are derived from "generic_abs", +# so "generic_abs_ultimaker3", "generic_abs_ultimaker3_AA_0.4", etc. +# +class MaterialGroup: + __slots__ = ("name", "root_material_node", "derived_material_node_list") + + def __init__(self, name: str): + self.name = name + self.root_material_node = None + self.derived_material_node_list = [] + + def __str__(self) -> str: + return "%s[%s]" % (self.__class__.__name__, self.name) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index cbfc822303..ffb796a705 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -1,3 +1,6 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + from collections import defaultdict, OrderedDict from typing import Optional @@ -6,30 +9,20 @@ from PyQt5.Qt import QTimer, QObject, pyqtSignal from UM.Logger import Logger from UM.Settings import ContainerRegistry -from cura.Machines.ContainerNode import ContainerNode - - -class MaterialGroup: - __slots__ = ("name", "root_material_node", "derived_material_node_list") - - def __init__(self, name: str): - self.name = name - self.root_material_node = None - self.derived_material_node_list = [] - - def __str__(self) -> str: - return "%s[%s]" % (self.__class__.__name__, self.name) - - -class MaterialNode(ContainerNode): - __slots__ = ("material_map", "children_map") - - def __init__(self, metadata: Optional[dict] = None): - super().__init__(metadata = metadata) - self.material_map = {} - self.children_map = {} +from .MaterialNode import MaterialNode +from .MaterialGroup import MaterialGroup +# +# MaterialManager maintains a number of maps and trees for material lookup. +# The models GUI and QML use are now only dependent on the MaterialManager. That means as long as the data in +# MaterialManager gets updated correctly, the GUI models should be updated correctly too, and the same goes for GUI. +# +# For now, updating the lookup maps and trees here is very simple: we discard the old data completely and recreate them +# again. This means the update is exactly the same as initialization. There are performance concerns about this approach +# but so far the creation of the tables and maps is very fast and there is no noticeable slowness, we keep it like this +# because it's simple. +# class MaterialManager(QObject): materialsUpdated = pyqtSignal() # Emitted whenever the material lookup tables are updated. @@ -40,12 +33,12 @@ class MaterialManager(QObject): self._fallback_materials_map = dict() # material_type -> generic material metadata self._material_group_map = dict() # root_material_id -> MaterialGroup - self._diameter_machine_variant_material_map = dict() # diameter -> dict(machine_definition_id -> MaterialNode) + self._diameter_machine_variant_material_map = dict() # approximate diameter str -> dict(machine_definition_id -> MaterialNode) # We're using these two maps to convert between the specific diameter material id and the generic material id # because the generic material ids are used in qualities and definitions, while the specific diameter material is meant # i.e. generic_pla -> generic_pla_175 - self._material_diameter_map = defaultdict(dict) # root_material_id -> diameter -> root_material_id for that diameter + self._material_diameter_map = defaultdict(dict) # root_material_id -> approximate diameter str -> root_material_id for that diameter self._diameter_material_map = dict() # material id including diameter (generic_pla_175) -> material root id (generic_pla) # This is used in Legacy UM3 send material function and the material management page. @@ -56,6 +49,9 @@ class MaterialManager(QObject): self._default_machine_definition_id = "fdmprinter" self._default_approximate_diameter_for_quality_search = "3" + # When a material gets added/imported, there can be more than one InstanceContainers. In those cases, we don't + # want to react on every container/metadata changed signal. The timer here is to buffer it a bit so we don't + # react too many time. self._update_timer = QTimer(self) self._update_timer.setInterval(300) self._update_timer.setSingleShot(True) @@ -233,7 +229,8 @@ class MaterialManager(QObject): # # Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup. # - def getAvailableMaterials(self, machine_definition_id: str, variant_name: Optional[str], diameter: float) -> dict: + def getAvailableMaterials(self, machine_definition_id: str, extruder_variant_name: Optional[str], + diameter: float) -> dict: # round the diameter to get the approximate diameter rounded_diameter = str(round(diameter)) if rounded_diameter not in self._diameter_machine_variant_material_map: @@ -245,8 +242,8 @@ class MaterialManager(QObject): 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 variant_name is not None and machine_node is not None: - variant_node = machine_node.getChildNode(variant_name) + 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] @@ -269,7 +266,8 @@ class MaterialManager(QObject): # 1. the given machine doesn't have materials; # 2. cannot find any material InstanceContainers with the given settings. # - def getMaterialNode(self, machine_definition_id: str, variant_name: Optional[str], diameter: float, root_material_id: str) -> Optional["InstanceContainer"]: + def getMaterialNode(self, machine_definition_id: str, extruder_variant_name: Optional[str], + diameter: float, root_material_id: str) -> Optional["InstanceContainer"]: # round the diameter to get the approximate diameter rounded_diameter = str(round(diameter)) if rounded_diameter not in self._diameter_machine_variant_material_map: @@ -285,8 +283,8 @@ class MaterialManager(QObject): # Fallback for "fdmprinter" if the machine-specific materials cannot be found if machine_node is None: machine_node = machine_variant_material_map.get(self._default_machine_definition_id) - if machine_node is not None and variant_name is not None: - variant_node = machine_node.getChildNode(variant_name) + if machine_node is not None and extruder_variant_name is not None: + variant_node = machine_node.getChildNode(extruder_variant_name) # Fallback mechanism of finding materials: # 1. variant-specific material @@ -309,10 +307,15 @@ class MaterialManager(QObject): # For materials such as ultimaker_pla_orange, no quality profiles may be found, so we should fall back to use # the generic material IDs to search for qualities. # + # An example would be, suppose we have machine with preferred material set to "filo3d_pla" (1.75mm), but its + # extruders only use 2.85mm materials, then we won't be able to find the preferred material for this machine. + # A fallback would be to fetch a generic material of the same type "PLA" as "filo3d_pla", and in this case it will + # be "generic_pla". This function is intended to get a generic fallback material for the given material type. + # # This function returns the generic root material ID for the given material type, where material types are "PLA", # "ABS", etc. # - def getFallbackMaterialId(self, material_type: str) -> str: + def getFallbackMaterialIdByMaterialType(self, material_type: str) -> str: # For safety if material_type not in self._fallback_materials_map: Logger.log("w", "The material type [%s] does not have a fallback material" % material_type) diff --git a/cura/Machines/MaterialNode.py b/cura/Machines/MaterialNode.py new file mode 100644 index 0000000000..fde11186c2 --- /dev/null +++ b/cura/Machines/MaterialNode.py @@ -0,0 +1,21 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from typing import Optional + +from .ContainerNode import ContainerNode + + +# +# A MaterialNode is a node in the material lookup tree/map/table. It contains 2 (extra) fields: +# - material_map: a one-to-one map of "material_root_id" to material_node. +# - children_map: the key-value map for child nodes of this node. This is used in a lookup tree. +# +# +class MaterialNode(ContainerNode): + __slots__ = ("material_map", "children_map") + + def __init__(self, metadata: Optional[dict] = None): + super().__init__(metadata = metadata) + self.material_map = {} # material_root_id -> material_node + self.children_map = {} # mapping for the child nodes diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py new file mode 100644 index 0000000000..1b06fa5d1a --- /dev/null +++ b/cura/Machines/Models/BaseMaterialsModel.py @@ -0,0 +1,70 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from typing import Optional +from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty + +from UM.Logger import Logger +from UM.Qt.ListModel import ListModel + + +def getAvailableMaterials(extruder_position: Optional[int] = None): + from cura.CuraApplication import CuraApplication + machine_manager = CuraApplication.getInstance().getMachineManager() + extruder_manager = CuraApplication.getInstance().getExtruderManager() + material_manager = CuraApplication.getInstance().getMaterialManager() + + active_global_stack = machine_manager.activeMachine + extruder_stack = extruder_manager.getActiveExtruderStack() + if extruder_position is not None: + if active_global_stack is not None: + extruder_stack = active_global_stack.extruders.get(str(extruder_position)) + + if active_global_stack is None or extruder_stack is None: + Logger.log("d", "Active global stack [%s] or extruder stack [%s] is None, setting material list to empty.", + active_global_stack, extruder_stack) + return + + machine_definition_id = active_global_stack.definition.getId() + variant_name = None + if extruder_stack.variant.getId() != "empty_variant": + variant_name = extruder_stack.variant.getName() + diameter = extruder_stack.approximateMaterialDiameter + + # Fetch the available materials (ContainerNode) for the current active machine and extruder setup. + result_dict = material_manager.getAvailableMaterials(machine_definition_id, variant_name, diameter) + return result_dict + + +class BaseMaterialsModel(ListModel): + RootMaterialIdRole = Qt.UserRole + 1 + IdRole = Qt.UserRole + 2 + NameRole = Qt.UserRole + 3 + BrandRole = Qt.UserRole + 4 + MaterialRole = Qt.UserRole + 5 + ColorRole = Qt.UserRole + 6 + ContainerNodeRole = Qt.UserRole + 7 + + extruderPositionChanged = pyqtSignal() + + def __init__(self, parent = None): + super().__init__(parent) + + self.addRoleName(self.RootMaterialIdRole, "root_material_id") + self.addRoleName(self.IdRole, "id") + self.addRoleName(self.NameRole, "name") + self.addRoleName(self.BrandRole, "brand") + self.addRoleName(self.MaterialRole, "material") + self.addRoleName(self.ColorRole, "color_name") + self.addRoleName(self.ContainerNodeRole, "container_node") + + self._extruder_position = 0 + + def setExtruderPosition(self, position: int): + if self._extruder_position != position: + self._extruder_position = position + self.extruderPositionChanged.emit() + + @pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged) + def extruderPosition(self) -> int: + return self._extruder_positoin diff --git a/cura/Machines/Models/BrandMaterialsModel.py b/cura/Machines/Models/BrandMaterialsModel.py new file mode 100644 index 0000000000..1edef672d4 --- /dev/null +++ b/cura/Machines/Models/BrandMaterialsModel.py @@ -0,0 +1,112 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty + +from UM.Qt.ListModel import ListModel + +from .BaseMaterialsModel import BaseMaterialsModel, getAvailableMaterials + + +class MaterialsModelGroupedByType(ListModel): + NameRole = Qt.UserRole + 1 + ColorsRole = Qt.UserRole + 2 + + def __init__(self, parent = None): + super().__init__(parent) + + self.addRoleName(self.NameRole, "name") + self.addRoleName(self.ColorsRole, "colors") + + +## Brand --> Material Type -> list of materials +class BrandMaterialsModel(ListModel): + NameRole = Qt.UserRole + 1 + MaterialsRole = Qt.UserRole + 2 + + extruderPositionChanged = pyqtSignal() + + def __init__(self, parent = None): + super().__init__(parent) + + self.addRoleName(self.NameRole, "name") + self.addRoleName(self.MaterialsRole, "materials") + + self._extruder_position = 0 + + from cura.CuraApplication import CuraApplication + self._machine_manager = CuraApplication.getInstance().getMachineManager() + extruder_manager = CuraApplication.getInstance().getExtruderManager() + material_manager = CuraApplication.getInstance().getMaterialManager() + + self._machine_manager.globalContainerChanged.connect(self._update) + extruder_manager.activeExtruderChanged.connect(self._update) + material_manager.materialsUpdated.connect(self._update) + + self._update() + + def setExtruderPosition(self, position: int): + if self._extruder_position != position: + self._extruder_position = position + self.extruderPositionChanged.emit() + + @pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged) + def extruderPosition(self) -> int: + return self._extruder_position + + def _update(self): + global_stack = self._machine_manager.activeMachine + if global_stack is None: + self.setItems([]) + return + + result_dict = getAvailableMaterials(self._extruder_position) + if result_dict is None: + self.setItems([]) + return + + brand_item_list = [] + brand_group_dict = {} + for root_material_id, container_node in result_dict.items(): + metadata = container_node.metadata + brand = metadata["brand"] + # Only add results for generic materials + if brand.lower() == "generic": + continue + + if brand not in brand_group_dict: + brand_group_dict[brand] = {} + + material_type = metadata["material"] + if material_type not in brand_group_dict[brand]: + brand_group_dict[brand][material_type] = [] + + item = {"root_material_id": root_material_id, + "id": metadata["id"], + "name": metadata["name"], + "brand": metadata["brand"], + "material": metadata["material"], + "color_name": metadata["color_name"], + "container_node": container_node + } + brand_group_dict[brand][material_type].append(item) + + for brand, material_dict in brand_group_dict.items(): + brand_item = {"name": brand, + "materials": MaterialsModelGroupedByType(self)} + + material_type_item_list = [] + for material_type, material_list in material_dict.items(): + material_type_item = {"name": material_type, + "colors": BaseMaterialsModel(self)} + material_type_item["colors"].clear() + material_type_item["colors"].setItems(material_list) + + material_type_item_list.append(material_type_item) + + brand_item["materials"].setItems(material_type_item_list) + + brand_item_list.append(brand_item) + + self.setItems(brand_item_list) + diff --git a/cura/Machines/Models/BuildPlateModel.py b/cura/Machines/Models/BuildPlateModel.py index fccf4fa44e..1cb94216a6 100644 --- a/cura/Machines/Models/BuildPlateModel.py +++ b/cura/Machines/Models/BuildPlateModel.py @@ -1,3 +1,6 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + from PyQt5.QtCore import Qt from UM.Application import Application diff --git a/cura/Machines/Models/CustomQualityProfilesModel.py b/cura/Machines/Models/CustomQualityProfilesModel.py index 71a6b6c0f7..4c88d83fdc 100644 --- a/cura/Machines/Models/CustomQualityProfilesModel.py +++ b/cura/Machines/Models/CustomQualityProfilesModel.py @@ -7,12 +7,15 @@ from UM.Logger import Logger from cura.Machines.Models.QualityProfilesModel import QualityProfilesModel +# +# This model is used for the custom profile items in the profile drop down menu. +# class CustomQualityProfilesModel(QualityProfilesModel): def _update(self): Logger.log("d", "Updating %s ...", self.__class__.__name__) - active_global_stack = Application.getInstance().getMachineManager()._global_container_stack + active_global_stack = Application.getInstance().getMachineManager().activeMachine if active_global_stack is None: self.setItems([]) Logger.log("d", "No active GlobalStack, set %s as empty.", self.__class__.__name__) diff --git a/cura/Machines/Models/GenericMaterialsModel.py b/cura/Machines/Models/GenericMaterialsModel.py new file mode 100644 index 0000000000..c15f88d59a --- /dev/null +++ b/cura/Machines/Models/GenericMaterialsModel.py @@ -0,0 +1,54 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from .BaseMaterialsModel import BaseMaterialsModel, getAvailableMaterials + + +class GenericMaterialsModel(BaseMaterialsModel): + + def __init__(self, parent = None): + super().__init__(parent) + + from cura.CuraApplication import CuraApplication + self._machine_manager = CuraApplication.getInstance().getMachineManager() + self._extruder_manager = CuraApplication.getInstance().getExtruderManager() + self._material_manager = CuraApplication.getInstance().getMaterialManager() + + self._machine_manager.globalContainerChanged.connect(self._update) + self._extruder_manager.activeExtruderChanged.connect(self._update) + self._material_manager.materialsUpdated.connect(self._update) + + self._update() + + def _update(self): + global_stack = self._machine_manager.activeMachine + if global_stack is None: + self.setItems([]) + return + + result_dict = getAvailableMaterials(self._extruder_position) + if result_dict is None: + self.setItems([]) + return + + item_list = [] + for root_material_id, container_node in result_dict.items(): + metadata = container_node.metadata + # Only add results for generic materials + if metadata["brand"].lower() != "generic": + continue + + item = {"root_material_id": root_material_id, + "id": metadata["id"], + "name": metadata["name"], + "brand": metadata["brand"], + "material": metadata["material"], + "color_name": metadata["color_name"], + "container_node": container_node + } + item_list.append(item) + + # Sort the item list by material name alphabetically + item_list = sorted(item_list, key = lambda d: d["name"]) + + self.setItems(item_list) diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py new file mode 100644 index 0000000000..cd1aa12427 --- /dev/null +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -0,0 +1,101 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from PyQt5.QtCore import Qt, pyqtProperty + +from UM.Qt.ListModel import ListModel + +from .BaseMaterialsModel import getAvailableMaterials + + +# +# This model is for the Material management page. +# +class MaterialManagementModel(ListModel): + RootMaterialIdRole = Qt.UserRole + 1 + DisplayNameRole = Qt.UserRole + 2 + BrandRole = Qt.UserRole + 3 + MaterialTypeRole = Qt.UserRole + 4 + ColorNameRole = Qt.UserRole + 5 + ColorCodeRole = Qt.UserRole + 6 + ContainerNodeRole = Qt.UserRole + 7 + ContainerIdRole = Qt.UserRole + 8 + + DescriptionRole = Qt.UserRole + 9 + AdhesionInfoRole = Qt.UserRole + 10 + ApproximateDiameterRole = Qt.UserRole + 11 + GuidRole = Qt.UserRole + 12 + DensityRole = Qt.UserRole + 13 + DiameterRole = Qt.UserRole + 14 + IsReadOnlyRole = Qt.UserRole + 15 + + def __init__(self, parent = None): + super().__init__(parent) + + self.addRoleName(self.RootMaterialIdRole, "root_material_id") + self.addRoleName(self.DisplayNameRole, "name") + self.addRoleName(self.BrandRole, "brand") + self.addRoleName(self.MaterialTypeRole, "material") + self.addRoleName(self.ColorNameRole, "color_name") + self.addRoleName(self.ColorCodeRole, "color_code") + self.addRoleName(self.ContainerNodeRole, "container_node") + self.addRoleName(self.ContainerIdRole, "container_id") + + self.addRoleName(self.DescriptionRole, "description") + self.addRoleName(self.AdhesionInfoRole, "adhesion_info") + self.addRoleName(self.ApproximateDiameterRole, "approximate_diameter") + self.addRoleName(self.GuidRole, "guid") + self.addRoleName(self.DensityRole, "density") + self.addRoleName(self.DiameterRole, "diameter") + self.addRoleName(self.IsReadOnlyRole, "is_read_only") + + from cura.CuraApplication import CuraApplication + self._container_registry = CuraApplication.getInstance().getContainerRegistry() + self._machine_manager = CuraApplication.getInstance().getMachineManager() + extruder_manager = CuraApplication.getInstance().getExtruderManager() + material_manager = CuraApplication.getInstance().getMaterialManager() + + self._machine_manager.globalContainerChanged.connect(self._update) + extruder_manager.activeExtruderChanged.connect(self._update) + material_manager.materialsUpdated.connect(self._update) + + self._update() + + def _update(self): + global_stack = self._machine_manager.activeMachine + if global_stack is None: + self.setItems([]) + return + + result_dict = getAvailableMaterials() + if result_dict is None: + self.setItems([]) + return + + material_list = [] + for root_material_id, container_node in result_dict.items(): + keys_to_fetch = ("name", + "brand", + "material", + "color_name", + "color_code", + "description", + "adhesion_info", + "approximate_diameter",) + + item = {"root_material_id": container_node.metadata["base_file"], + "container_node": container_node, + "guid": container_node.metadata["GUID"], + "container_id": container_node.metadata["id"], + "density": container_node.metadata.get("properties", {}).get("density", ""), + "diameter": container_node.metadata.get("properties", {}).get("diameter", ""), + "is_read_only": self._container_registry.isReadOnly(container_node.metadata["id"]), + } + + for key in keys_to_fetch: + item[key] = container_node.metadata.get(key, "") + + material_list.append(item) + + material_list = sorted(material_list, key = lambda k: (k["brand"].lower(), k["name"])) + self.setItems(material_list) diff --git a/cura/Machines/Models/MaterialsModel.py b/cura/Machines/Models/MaterialsModel.py deleted file mode 100644 index 5ef5845371..0000000000 --- a/cura/Machines/Models/MaterialsModel.py +++ /dev/null @@ -1,317 +0,0 @@ -# Copyright (c) 2018 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -from typing import Optional -from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty - -from UM.Logger import Logger -from UM.Qt.ListModel import ListModel - - -def getAvailableMaterials(extruder_position: Optional[int] = None): - from cura.CuraApplication import CuraApplication - machine_manager = CuraApplication.getInstance().getMachineManager() - extruder_manager = CuraApplication.getInstance().getExtruderManager() - - material_manager = CuraApplication.getInstance()._material_manager - - active_global_stack = machine_manager._global_container_stack - extruder_stack = extruder_manager.getActiveExtruderStack() - if extruder_position is not None: - if active_global_stack is not None: - extruder_stack = active_global_stack.extruders.get(str(extruder_position)) - - if active_global_stack is None or extruder_stack is None: - Logger.log("d", "Active global stack [%s] or extruder stack [%s] is None, setting material list to empty.", - active_global_stack, extruder_stack) - return - - machine_definition_id = active_global_stack.definition.getId() - variant_name = None - if extruder_stack.variant.getId() != "empty_variant": - variant_name = extruder_stack.variant.getName() - diameter = extruder_stack.approximateMaterialDiameter - - # Fetch the available materials (ContainerNode) for the current active machine and extruder setup. - result_dict = material_manager.getAvailableMaterials(machine_definition_id, variant_name, diameter) - return result_dict - - -class BaseMaterialsModel(ListModel): - RootMaterialIdRole = Qt.UserRole + 1 - IdRole = Qt.UserRole + 2 - NameRole = Qt.UserRole + 3 - BrandRole = Qt.UserRole + 4 - MaterialRole = Qt.UserRole + 5 - ColorRole = Qt.UserRole + 6 - ContainerNodeRole = Qt.UserRole + 7 - - extruderPositionChanged = pyqtSignal() - - def __init__(self, parent = None): - super().__init__(parent) - - self.addRoleName(self.RootMaterialIdRole, "root_material_id") - self.addRoleName(self.IdRole, "id") - self.addRoleName(self.NameRole, "name") - self.addRoleName(self.BrandRole, "brand") - self.addRoleName(self.MaterialRole, "material") - self.addRoleName(self.ColorRole, "color_name") - self.addRoleName(self.ContainerNodeRole, "container_node") - - self._extruder_position = 0 - - def setExtruderPosition(self, position: int): - if self._extruder_position != position: - self._extruder_position = position - self.extruderPositionChanged.emit() - - @pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged) - def extruderPosition(self) -> int: - return self._extruder_positoin - - -class GenericMaterialsModel(BaseMaterialsModel): - - def __init__(self, parent = None): - super().__init__(parent) - - from cura.CuraApplication import CuraApplication - self._machine_manager = CuraApplication.getInstance().getMachineManager() - self._extruder_manager = CuraApplication.getInstance().getExtruderManager() - self._material_manager = CuraApplication.getInstance()._material_manager - - self._machine_manager.globalContainerChanged.connect(self._update) - self._extruder_manager.activeExtruderChanged.connect(self._update) - self._material_manager.materialsUpdated.connect(self._update) - - self._update() - - def _update(self): - global_stack = self._machine_manager.activeMachine - if global_stack is None: - self.setItems([]) - return - - result_dict = getAvailableMaterials(self._extruder_position) - if result_dict is None: - self.setItems([]) - return - - item_list = [] - for root_material_id, container_node in result_dict.items(): - metadata = container_node.metadata - # Only add results for generic materials - if metadata["brand"].lower() != "generic": - continue - - item = {"root_material_id": root_material_id, - "id": metadata["id"], - "name": metadata["name"], - "brand": metadata["brand"], - "material": metadata["material"], - "color_name": metadata["color_name"], - "container_node": container_node - } - item_list.append(item) - - # Sort the item list by material name alphabetically - item_list = sorted(item_list, key = lambda d: d["name"]) - - self.setItems(item_list) - - -class MaterialsModelGroupedByType(ListModel): - NameRole = Qt.UserRole + 1 - ColorsRole = Qt.UserRole + 2 - - def __init__(self, parent = None): - super().__init__(parent) - - self.addRoleName(self.NameRole, "name") - self.addRoleName(self.ColorsRole, "colors") - - -## Brand --> Material Type -> list of materials -class BrandMaterialsModel(ListModel): - NameRole = Qt.UserRole + 1 - MaterialsRole = Qt.UserRole + 2 - - extruderPositionChanged = pyqtSignal() - - def __init__(self, parent = None): - super().__init__(parent) - - self.addRoleName(self.NameRole, "name") - self.addRoleName(self.MaterialsRole, "materials") - - self._extruder_position = 0 - - from cura.CuraApplication import CuraApplication - self._machine_manager = CuraApplication.getInstance().getMachineManager() - extruder_manager = CuraApplication.getInstance().getExtruderManager() - material_manager = CuraApplication.getInstance()._material_manager - - self._machine_manager.globalContainerChanged.connect(self._update) - extruder_manager.activeExtruderChanged.connect(self._update) - material_manager.materialsUpdated.connect(self._update) - - self._update() - - def setExtruderPosition(self, position: int): - if self._extruder_position != position: - self._extruder_position = position - self.extruderPositionChanged.emit() - - @pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged) - def extruderPosition(self) -> int: - return self._extruder_position - - def _update(self): - global_stack = self._machine_manager.activeMachine - if global_stack is None: - self.setItems([]) - return - - result_dict = getAvailableMaterials(self._extruder_position) - if result_dict is None: - self.setItems([]) - return - - brand_item_list = [] - brand_group_dict = {} - for root_material_id, container_node in result_dict.items(): - metadata = container_node.metadata - brand = metadata["brand"] - # Only add results for generic materials - if brand.lower() == "generic": - continue - - if brand not in brand_group_dict: - brand_group_dict[brand] = {} - - material_type = metadata["material"] - if material_type not in brand_group_dict[brand]: - brand_group_dict[brand][material_type] = [] - - item = {"root_material_id": root_material_id, - "id": metadata["id"], - "name": metadata["name"], - "brand": metadata["brand"], - "material": metadata["material"], - "color_name": metadata["color_name"], - "container_node": container_node - } - brand_group_dict[brand][material_type].append(item) - - for brand, material_dict in brand_group_dict.items(): - brand_item = {"name": brand, - "materials": MaterialsModelGroupedByType(self)} - - material_type_item_list = [] - for material_type, material_list in material_dict.items(): - material_type_item = {"name": material_type, - "colors": BaseMaterialsModel(self)} - material_type_item["colors"].clear() - material_type_item["colors"].setItems(material_list) - - material_type_item_list.append(material_type_item) - - brand_item["materials"].setItems(material_type_item_list) - - brand_item_list.append(brand_item) - - self.setItems(brand_item_list) - - -# -# This model is for the Material management page. -# -class MaterialsModel(ListModel): - RootMaterialIdRole = Qt.UserRole + 1 - DisplayNameRole = Qt.UserRole + 2 - BrandRole = Qt.UserRole + 3 - MaterialTypeRole = Qt.UserRole + 4 - ColorNameRole = Qt.UserRole + 5 - ColorCodeRole = Qt.UserRole + 6 - ContainerNodeRole = Qt.UserRole + 7 - ContainerIdRole = Qt.UserRole + 8 - - DescriptionRole = Qt.UserRole + 9 - AdhesionInfoRole = Qt.UserRole + 10 - ApproximateDiameterRole = Qt.UserRole + 11 - GuidRole = Qt.UserRole + 12 - DensityRole = Qt.UserRole + 13 - DiameterRole = Qt.UserRole + 14 - IsReadOnlyRole = Qt.UserRole + 15 - - def __init__(self, parent = None): - super().__init__(parent) - - self.addRoleName(self.RootMaterialIdRole, "root_material_id") - self.addRoleName(self.DisplayNameRole, "name") - self.addRoleName(self.BrandRole, "brand") - self.addRoleName(self.MaterialTypeRole, "material") - self.addRoleName(self.ColorNameRole, "color_name") - self.addRoleName(self.ColorCodeRole, "color_code") - self.addRoleName(self.ContainerNodeRole, "container_node") - self.addRoleName(self.ContainerIdRole, "container_id") - - self.addRoleName(self.DescriptionRole, "description") - self.addRoleName(self.AdhesionInfoRole, "adhesion_info") - self.addRoleName(self.ApproximateDiameterRole, "approximate_diameter") - self.addRoleName(self.GuidRole, "guid") - self.addRoleName(self.DensityRole, "density") - self.addRoleName(self.DiameterRole, "diameter") - self.addRoleName(self.IsReadOnlyRole, "is_read_only") - - from cura.CuraApplication import CuraApplication - self._container_registry = CuraApplication.getInstance().getContainerRegistry() - self._machine_manager = CuraApplication.getInstance().getMachineManager() - extruder_manager = CuraApplication.getInstance().getExtruderManager() - material_manager = CuraApplication.getInstance()._material_manager - - self._machine_manager.globalContainerChanged.connect(self._update) - extruder_manager.activeExtruderChanged.connect(self._update) - material_manager.materialsUpdated.connect(self._update) - - self._update() - - def _update(self): - global_stack = self._machine_manager.activeMachine - if global_stack is None: - self.setItems([]) - return - - result_dict = getAvailableMaterials() - if result_dict is None: - self.setItems([]) - return - - material_list = [] - for root_material_id, container_node in result_dict.items(): - keys_to_fetch = ("name", - "brand", - "material", - "color_name", - "color_code", - "description", - "adhesion_info", - "approximate_diameter",) - - item = {"root_material_id": container_node.metadata["base_file"], - "container_node": container_node, - "guid": container_node.metadata["GUID"], - "container_id": container_node.metadata["id"], - "density": container_node.metadata.get("properties", {}).get("density", ""), - "diameter": container_node.metadata.get("properties", {}).get("diameter", ""), - "is_read_only": self._container_registry.isReadOnly(container_node.metadata["id"]), - } - - for key in keys_to_fetch: - item[key] = container_node.metadata.get(key, "") - - material_list.append(item) - - material_list = sorted(material_list, key = lambda k: (k["brand"].lower(), k["name"])) - self.setItems(material_list) diff --git a/cura/Machines/Models/QualitySettingsModel.py b/cura/Machines/Models/QualitySettingsModel.py index 304e2f36f0..a62654ad2c 100644 --- a/cura/Machines/Models/QualitySettingsModel.py +++ b/cura/Machines/Models/QualitySettingsModel.py @@ -8,6 +8,9 @@ from UM.Qt.ListModel import ListModel from UM.Settings.ContainerRegistry import ContainerRegistry +# +# This model is used to show details settings of the selected quality in the quality management page. +# class QualitySettingsModel(ListModel): KeyRole = Qt.UserRole + 1 LabelRole = Qt.UserRole + 2 @@ -33,7 +36,7 @@ class QualitySettingsModel(ListModel): self._quality_manager = self._application._quality_manager self._extruder_position = "" - self._quality_item = None + self._selected_quality_item = None # The selected quality in the quality management page self._i18n_catalog = None self._quality_manager.qualitiesUpdated.connect(self._update) @@ -41,7 +44,7 @@ class QualitySettingsModel(ListModel): self._update() extruderPositionChanged = pyqtSignal() - qualityItemChanged = pyqtSignal() + selectedQualityItemChanged = pyqtSignal() def setExtruderPosition(self, extruder_position): if extruder_position != self._extruder_position: @@ -53,18 +56,18 @@ class QualitySettingsModel(ListModel): def extruderPosition(self): return self._extruder_position - def setQualityItem(self, quality_item): - if quality_item != self._quality_item: - self._quality_item = quality_item - self.qualityItemChanged.emit() + def setSelectedQualityItem(self, selected_quality_item): + if selected_quality_item != self._selected_quality_item: + self._selected_quality_item = selected_quality_item + self.selectedQualityItemChanged.emit() self._update() - @pyqtProperty("QVariantMap", fset = setQualityItem, notify = qualityItemChanged) - def qualityItem(self): - return self._quality_item + @pyqtProperty("QVariantMap", fset = setSelectedQualityItem, notify = selectedQualityItemChanged) + def selectedQualityItem(self): + return self._selected_quality_item def _update(self): - if self._quality_item is None: + if self._selected_quality_item is None: self.setItems([]) return @@ -73,8 +76,8 @@ class QualitySettingsModel(ListModel): global_container_stack = Application.getInstance().getGlobalContainerStack() definition_container = global_container_stack.definition - quality_group = self._quality_item["quality_group"] - quality_changes_group = self._quality_item["quality_changes_group"] + quality_group = self._selected_quality_item["quality_group"] + quality_changes_group = self._selected_quality_item["quality_changes_group"] if self._extruder_position == "": quality_node = quality_group.node_for_global diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 08f565ac66..d5a9b6ea8a 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -1,15 +1,15 @@ -from typing import Optional +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. -from PyQt5.Qt import pyqtSignal -from PyQt5.QtCore import QObject, QTimer +from typing import Optional, List + +from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot from UM.Application import Application from UM.Logger import Logger from UM.Util import parseBool -from cura.Machines.ContainerGroup import ContainerGroup from cura.Machines.ContainerNode import ContainerNode -from cura.Machines.MachineTools import getMachineDefinitionIDForQualitySearch # @@ -27,14 +27,50 @@ from cura.Machines.MachineTools import getMachineDefinitionIDForQualitySearch # + # - -class QualityGroup(ContainerGroup): +# +# A QualityGroup represents a group of containers that must be applied to each ContainerStack when it's used. +# Some concrete examples are Quality and QualityChanges: when we select quality type "normal", this quality type +# must be applied to all stacks in a machine, although each stack can have different containers. Use an Ultimaker 3 +# as an example, suppose we choose quality type "normal", the actual InstanceContainers on each stack may look +# as below: +# GlobalStack ExtruderStack 1 ExtruderStack 2 +# quality container: um3_global_normal um3_aa04_pla_normal um3_aa04_abs_normal +# +# This QualityGroup is mainly used in quality and quality_changes to group the containers that can be applied to +# a machine, so when a quality/custom quality is selected, the container can be directly applied to each stack instead +# of looking them up again. +# +class QualityGroup(QObject): def __init__(self, name: str, quality_type: str, parent = None): - super().__init__(name, parent) + super().__init__(parent) + self.name = name + self.node_for_global = None # type: Optional["QualityGroup"] + self.nodes_for_extruders = dict() # position str -> QualityGroup self.quality_type = quality_type self.is_available = False + @pyqtSlot(result = str) + def getName(self) -> str: + return self.name + + def getAllKeys(self) -> set: + result = set() + for node in [self.node_for_global] + list(self.nodes_for_extruders.values()): + if node is None: + continue + for key in node.getContainer().getAllKeys(): + result.add(key) + return result + + def getAllNodes(self) -> List["QualityGroup"]: + result = [] + if self.node_for_global is not None: + result.append(self.node_for_global) + for extruder_node in self.nodes_for_extruders.values(): + result.append(extruder_node) + return result + class QualityChangesGroup(QualityGroup): @@ -111,6 +147,16 @@ class QualityNode(ContainerNode): quality_changes_group.addNode(QualityNode(metadata)) +# +# Similar to MaterialManager, QualityManager maintains a number of maps and trees for material lookup. +# The models GUI and QML use are now only dependent on the QualityManager. That means as long as the data in +# QualityManager gets updated correctly, the GUI models should be updated correctly too, and the same goes for GUI. +# +# For now, updating the lookup maps and trees here is very simple: we discard the old data completely and recreate them +# again. This means the update is exactly the same as initialization. There are performance concerns about this approach +# but so far the creation of the tables and maps is very fast and there is no noticeable slowness, we keep it like this +# because it's simple. +# class QualityManager(QObject): qualitiesUpdated = pyqtSignal() @@ -133,6 +179,9 @@ class QualityManager(QObject): self._container_registry.containerAdded.connect(self._onContainerMetadataChanged) self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged) + # When a custom quality gets added/imported, there can be more than one InstanceContainers. In those cases, + # we don't want to react on every container/metadata changed signal. The timer here is to buffer it a bit so + # we don't react too many time. self._update_timer = QTimer(self) self._update_timer.setInterval(300) self._update_timer.setSingleShot(True) @@ -327,7 +376,7 @@ class QualityManager(QObject): # Also try to get the fallback material material_type = extruder.material.getMetaDataEntry("material") - fallback_root_material_id = self._material_manager.getFallbackMaterialId(material_type) + fallback_root_material_id = self._material_manager.getFallbackMaterialIdByMaterialType(material_type) if fallback_root_material_id: root_material_id_list.append(fallback_root_material_id) @@ -376,7 +425,7 @@ class QualityManager(QObject): return quality_group_dict - def getQualityGroupsForMachineDefinition(self, machine: str) -> dict: + def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> dict: # Get machine definition ID for quality search machine_definition_id = getMachineDefinitionIDForQualitySearch(machine) @@ -399,3 +448,27 @@ class QualityManager(QObject): break return quality_group_dict + + +# +# Gets the machine definition ID that can be used to search for Quality containers that are suitable for the given +# machine. The rule is as follows: +# 1. By default, the machine definition ID for quality container search will be "fdmprinter", which is the generic +# machine. +# 2. If a machine has its own machine quality (with "has_machine_quality = True"), we should use the given machine's +# own machine definition ID for quality search. +# Example: for an Ultimaker 3, the definition ID should be "ultimaker3". +# 3. When condition (2) is met, AND the machine has "quality_definition" defined in its definition file, then the +# definition ID specified in "quality_definition" should be used. +# Example: for an Ultimaker 3 Extended, it has "quality_definition = ultimaker3". This means Ultimaker 3 Extended +# shares the same set of qualities profiles as Ultimaker 3. +# +def getMachineDefinitionIDForQualitySearch(machine: "GlobalStack", default_definition_id: str = "fdmprinter") -> str: + machine_definition_id = default_definition_id + if parseBool(machine.getMetaDataEntry("has_machine_quality", False)): + # Only use the machine's own quality definition ID if this machine has machine quality. + machine_definition_id = machine.getMetaDataEntry("quality_definition") + if machine_definition_id is None: + machine_definition_id = machine.definition.getId() + + return machine_definition_id diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index d49ec79037..96fff61b0b 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -29,7 +29,7 @@ from UM.i18n import i18nCatalog from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderStack import ExtruderStack -from cura.Machines.MachineTools import getMachineDefinitionIDForQualitySearch +from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch catalog = i18nCatalog("cura") diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index d6faf214dd..e79cfa5335 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -28,7 +28,7 @@ from . import GlobalStack from .ExtruderManager import ExtruderManager from cura.CuraApplication import CuraApplication -from cura.Machines.MachineTools import getMachineDefinitionIDForQualitySearch +from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index cb0ce4ffc5..a46fedd853 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -24,7 +24,7 @@ from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.SettingFunction import SettingFunction from UM.Signal import postponeSignals, CompressTechnique -from cura.Machines.MachineTools import getMachineDefinitionIDForQualitySearch +from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch from cura.PrinterOutputDevice import PrinterOutputDevice from cura.Settings.ExtruderManager import ExtruderManager @@ -826,6 +826,12 @@ class MachineManager(QObject): stacks.append(self._global_container_stack) return [ s.containersChanged for s in stacks ] + @pyqtSlot(str, str, str) + def setSettingForAllExtruders(self, setting_name: str, property_name: str, property_value: str): + for key, extruder in self._global_container_stack.extruders.items(): + container = extruder.userChanges + container.setProperty(setting_name, property_name, property_value) + # # New # diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index 95d8146d32..ec590a0212 100755 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -22,7 +22,7 @@ from cura.Scene.CuraSceneNode import CuraSceneNode from cura.Scene.BuildPlateDecorator import BuildPlateDecorator from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator from cura.Scene.ZOffsetDecorator import ZOffsetDecorator -from cura.Machines.MachineTools import getMachineDefinitionIDForQualitySearch +from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch MYPY = False diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml index 11e4a9cd0e..4b5d2a5708 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml @@ -241,8 +241,8 @@ Item { height: UM.Theme.getSize("setting").height onClicked: { - UM.ActiveTool.triggerAction("unsubscribeForSettingValidation", model.key) addedSettingsModel.setVisible(model.key, false) + UM.ActiveTool.triggerAction("unsubscribeForSettingValidation", model.key) } style: ButtonStyle diff --git a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py index 0b5a0696c6..fa9abb8d4e 100644 --- a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py @@ -396,7 +396,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): color = material_data["color"] brand = material_data["brand"] material_type = material_data["material"] - name = "Unknown" + name = "Empty" if material_data["material"] == "empty" else "Unknown" material = MaterialOutputModel(guid=material_data["guid"], type=material_type, brand=brand, color=color, name=name) diff --git a/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml b/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml index 63b374e6b9..54a34fae46 100644 --- a/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml +++ b/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml @@ -264,6 +264,7 @@ Rectangle case "wait_for_configuration": return catalog.i18nc("@label:status", "Reserved") case "wait_cleanup": + case "wait_user_action": return catalog.i18nc("@label:status", "Finished") case "pre_print": case "sent_to_printer": @@ -278,6 +279,7 @@ Rectangle case "aborted": return catalog.i18nc("@label:status", "Print aborted"); default: + // If print job has unknown status show printer.status return printerStatusText(printer); } } diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index 23c19bece8..f2f33800d0 100644 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -69,7 +69,6 @@ Item property alias configureSettingVisibility: configureSettingVisibilityAction property alias browsePlugins: browsePluginsAction - property alias configurePlugins: configurePluginsAction UM.I18nCatalog{id: catalog; name:"cura"} @@ -425,13 +424,6 @@ Item iconName: "plugins_browse" } - Action - { - id: configurePluginsAction - text: catalog.i18nc("@action:menu", "Installed plugins..."); - iconName: "plugins_configure" - } - Action { id: expandSidebarAction; diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 5764ff0628..4007d43c37 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -268,7 +268,6 @@ UM.MainWindow title: catalog.i18nc("@title:menu menubar:toplevel", "P&lugins") MenuItem { action: Cura.Actions.browsePlugins } - MenuItem { action: Cura.Actions.configurePlugins } } Menu diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml index 671b5c6b9b..1177f20334 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/MaterialsPage.qml @@ -17,7 +17,7 @@ Item UM.I18nCatalog { id: catalog; name: "cura"; } - Cura.MaterialsModel { + Cura.MaterialManagementModel { id: materialsModel } diff --git a/resources/qml/Preferences/ProfileTab.qml b/resources/qml/Preferences/ProfileTab.qml index b4b2299c15..82671f306f 100644 --- a/resources/qml/Preferences/ProfileTab.qml +++ b/resources/qml/Preferences/ProfileTab.qml @@ -86,7 +86,7 @@ Tab { id: qualitySettings extruderPosition: base.extruderPosition - qualityItem: base.qualityItem + selectedQualityItem: base.qualityItem } SystemPalette { id: palette } diff --git a/resources/qml/SidebarSimple.qml b/resources/qml/SidebarSimple.qml index 4181e9df73..222a92465f 100644 --- a/resources/qml/SidebarSimple.qml +++ b/resources/qml/SidebarSimple.qml @@ -516,8 +516,7 @@ Item // Update the slider value to represent the rounded value infillSlider.value = roundedSliderValue - // Explicitly cast to string to make sure the value passed to Python is an integer. - infillDensity.setPropertyValue("value", String(roundedSliderValue)) + Cura.MachineManager.setSettingForAllExtruders("infill_sparse_density", "value", roundedSliderValue) } style: SliderStyle @@ -647,14 +646,20 @@ Item onClicked: { // Set to 90% only when enabling gradual infill + var newInfillDensity; if (parseInt(infillSteps.properties.value) == 0) { previousInfillDensity = parseInt(infillDensity.properties.value) - infillDensity.setPropertyValue("value", String(90)) + newInfillDensity = 90; } else { - infillDensity.setPropertyValue("value", String(previousInfillDensity)) + newInfillDensity = previousInfillDensity; } + Cura.MachineManager.setSettingForAllExtruders("infill_sparse_density", "value", String(newInfillDensity)) - infillSteps.setPropertyValue("value", (parseInt(infillSteps.properties.value) == 0) ? 5 : 0) + var infill_steps_value = 0; + if (parseInt(infillSteps.properties.value) == 0) + infill_steps_value = 5; + + Cura.MachineManager.setSettingForAllExtruders("gradual_infill_steps", "value", infill_steps_value) } onEntered: {