diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py index 80ecf72c0a..122c38804c 100644 --- a/cura/Machines/Models/BaseMaterialsModel.py +++ b/cura/Machines/Models/BaseMaterialsModel.py @@ -2,51 +2,54 @@ # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty - -from UM.Application import Application from UM.Qt.ListModel import ListModel -# -# This is the base model class for GenericMaterialsModel and BrandMaterialsModel -# Those 2 models are used by the material drop down menu to show generic materials and branded materials separately. -# The extruder position defined here is being used to bound a menu to the correct extruder. This is used in the top -# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu -# +## This is the base model class for GenericMaterialsModel and MaterialBrandsModel. +# Those 2 models are used by the material drop down menu to show generic materials and branded materials separately. +# The extruder position defined here is being used to bound a menu to the correct extruder. This is used in the top +# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu 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 - ColorCodeRole = Qt.UserRole + 8 - GUIDRole = Qt.UserRole + 9 - IsFavoriteRole = Qt.UserRole + 10 extruderPositionChanged = pyqtSignal() def __init__(self, parent = None): super().__init__(parent) - self._application = Application.getInstance() - self._machine_manager = self._application.getMachineManager() - self.addRoleName(self.RootMaterialIdRole, "root_material_id") - self.addRoleName(self.IdRole, "id") - self.addRoleName(self.GUIDRole, "GUID") - self.addRoleName(self.NameRole, "name") - self.addRoleName(self.BrandRole, "brand") - self.addRoleName(self.MaterialRole, "material") - self.addRoleName(self.ColorRole, "color_name") - self.addRoleName(self.ColorCodeRole, "color_code") - self.addRoleName(self.ContainerNodeRole, "container_node") - self.addRoleName(self.IsFavoriteRole, "is_favorite") + from cura.CuraApplication import CuraApplication + + self._application = CuraApplication.getInstance() + + # Make these managers available to all material models + self._extruder_manager = self._application.getExtruderManager() + self._machine_manager = self._application.getMachineManager() + self._material_manager = self._application.getMaterialManager() + + # Update the stack and the model data when the machine changes + self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack) + + # Update this model when switching machines + self._machine_manager.activeStackChanged.connect(self._update) + + # Update this model when list of materials changes + self._material_manager.materialsUpdated.connect(self._update) + + self.addRoleName(Qt.UserRole + 1, "root_material_id") + self.addRoleName(Qt.UserRole + 2, "id") + self.addRoleName(Qt.UserRole + 3, "GUID") + self.addRoleName(Qt.UserRole + 4, "name") + self.addRoleName(Qt.UserRole + 5, "brand") + self.addRoleName(Qt.UserRole + 6, "material") + self.addRoleName(Qt.UserRole + 7, "color_name") + self.addRoleName(Qt.UserRole + 8, "color_code") + self.addRoleName(Qt.UserRole + 9, "container_node") + self.addRoleName(Qt.UserRole + 10, "is_favorite") self._extruder_position = 0 self._extruder_stack = None - # Update the stack and the model data when the machine changes - self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack) + + self._available_materials = None + self._favorite_ids = None def _updateExtruderStack(self): global_stack = self._machine_manager.activeMachine @@ -71,8 +74,30 @@ class BaseMaterialsModel(ListModel): def extruderPosition(self) -> int: return self._extruder_position - # - # This is an abstract method that needs to be implemented by - # + ## This is an abstract method that needs to be implemented by the specific + # models themselves. def _update(self): pass + + ## This method is used by all material models in the beginning of the + # _update() method in order to prevent errors. It's the same in all models + # so it's placed here for easy access. + def _canUpdate(self): + global_stack = self._machine_manager.activeMachine + + if global_stack is None: + return False + + extruder_position = str(self._extruder_position) + + if extruder_position not in global_stack.extruders: + return False + + extruder_stack = global_stack.extruders[extruder_position] + + self._available_materials = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack, extruder_stack) + if self._available_materials is None: + return False + + return True + diff --git a/cura/Machines/Models/BrandMaterialsModel.py b/cura/Machines/Models/BrandMaterialsModel.py deleted file mode 100644 index 154019d028..0000000000 --- a/cura/Machines/Models/BrandMaterialsModel.py +++ /dev/null @@ -1,174 +0,0 @@ -# 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 UM.Logger import Logger -from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel - - -# -# This is an intermediate model to group materials with different colours for a same brand and type. -# -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") - - -# -# This model is used to show branded materials in the material drop down menu. -# The structure of the menu looks like this: -# Brand -> Material Type -> list of materials -# -# To illustrate, a branded material menu may look like this: -# Ultimaker -> PLA -> Yellow PLA -# -> Black PLA -# -> ... -# -> ABS -> White ABS -# ... -# -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 - self._extruder_stack = None - - from cura.CuraApplication import CuraApplication - self._container_registry = CuraApplication.getInstance().getContainerRegistry() - self._machine_manager = CuraApplication.getInstance().getMachineManager() - self._extruder_manager = CuraApplication.getInstance().getExtruderManager() - self._material_manager = CuraApplication.getInstance().getMaterialManager() - - self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack) - self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines. - self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes. - # self._material_manager.favoritesUpdated.connect(self._update) # Update when favorites are changed - self._update() - - def _updateExtruderStack(self): - global_stack = self._machine_manager.activeMachine - if global_stack is None: - return - - if self._extruder_stack is not None: - self._extruder_stack.pyqtContainersChanged.disconnect(self._update) - self._extruder_stack = global_stack.extruders.get(str(self._extruder_position)) - if self._extruder_stack is not None: - self._extruder_stack.pyqtContainersChanged.connect(self._update) - # Force update the model when the extruder stack changes - self._update() - - def setExtruderPosition(self, position: int): - if self._extruder_stack is None or self._extruder_position != position: - self._extruder_position = position - self._updateExtruderStack() - self.extruderPositionChanged.emit() - - @pyqtProperty(int, fset=setExtruderPosition, notify=extruderPositionChanged) - def extruderPosition(self) -> int: - return self._extruder_position - - def _update(self): - Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) - global_stack = self._machine_manager.activeMachine - if global_stack is None: - self.setItems([]) - return - extruder_position = str(self._extruder_position) - if extruder_position not in global_stack.extruders: - self.setItems([]) - return - extruder_stack = global_stack.extruders[str(self._extruder_position)] - - available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack, - extruder_stack) - if available_material_dict is None: - self.setItems([]) - return - - brand_item_list = [] - brand_group_dict = {} - for root_material_id, container_node in available_material_dict.items(): - metadata = container_node.metadata - - favorites = self._material_manager.getFavorites() - - # Do not include the materials from a to-be-removed package - if bool(metadata.get("removed", False)): - continue - - # Skip generic materials, and add brands we haven't seen yet to the dict - brand = metadata["brand"] - if brand.lower() == "generic": - continue - if brand not in brand_group_dict: - brand_group_dict[brand] = {} - - # Add material types we haven't seen yet to the dict - material_type = metadata["material"] - if material_type not in brand_group_dict[brand]: - brand_group_dict[brand][material_type] = [] - - # Now handle the individual materials - item = { - "root_material_id": root_material_id, - "id": metadata["id"], - "container_id": metadata["id"], # TODO: Remove duplicate in material manager qml - "GUID": metadata["GUID"], - "name": metadata["name"], - "brand": metadata["brand"], - "description": metadata["description"], - "material": metadata["material"], - "color_name": metadata["color_name"], - "color_code": metadata["color_code"], - "density": metadata.get("properties", {}).get("density", ""), - "diameter": metadata.get("properties", {}).get("diameter", ""), - "approximate_diameter": metadata["approximate_diameter"], - "adhesion_info": metadata["adhesion_info"], - "is_read_only": self._container_registry.isReadOnly(metadata["id"]), - "container_node": container_node, - "is_favorite": root_material_id in favorites - } - 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() - - # Sort materials by name - material_list = sorted(material_list, key = lambda x: x["name"].upper()) - material_type_item["colors"].setItems(material_list) - - material_type_item_list.append(material_type_item) - - # Sort material type by name - material_type_item_list = sorted(material_type_item_list, key = lambda x: x["name"].upper()) - brand_item["materials"].setItems(material_type_item_list) - - brand_item_list.append(brand_item) - - # Sort brand by name - brand_item_list = sorted(brand_item_list, key = lambda x: x["name"].upper()) - self.setItems(brand_item_list) diff --git a/cura/Machines/Models/FavoriteMaterialsModel.py b/cura/Machines/Models/FavoriteMaterialsModel.py index 35a5229da1..34e148e2fd 100644 --- a/cura/Machines/Models/FavoriteMaterialsModel.py +++ b/cura/Machines/Models/FavoriteMaterialsModel.py @@ -8,51 +8,32 @@ class FavoriteMaterialsModel(BaseMaterialsModel): def __init__(self, parent = None): super().__init__(parent) - - from cura.CuraApplication import CuraApplication - self._preferences = CuraApplication.getInstance().getPreferences() - self._machine_manager = CuraApplication.getInstance().getMachineManager() - self._extruder_manager = CuraApplication.getInstance().getExtruderManager() - self._material_manager = CuraApplication.getInstance().getMaterialManager() - - self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines. - self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes. self._material_manager.favoritesUpdated.connect(self._update) # Update when favorites are changed self._update() def _update(self): - Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) - global_stack = self._machine_manager.activeMachine - if global_stack is None: + # Perform standard check and reset if the check fails + if not self._canUpdate(): self.setItems([]) return - extruder_position = str(self._extruder_position) - if extruder_position not in global_stack.extruders: - self.setItems([]) - return - extruder_stack = global_stack.extruders[extruder_position] - - available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack, extruder_stack) - if available_material_dict is None: - self.setItems([]) - return - - favorite_ids = self._material_manager.getFavorites() + # Get updated list of favorites + self._favorite_ids = self._material_manager.getFavorites() item_list = [] - for root_material_id, container_node in available_material_dict.items(): - metadata = container_node.metadata - # Only add results for favorite materials - if root_material_id not in favorite_ids: - continue + for root_material_id, container_node in self._available_materials.items(): + metadata = container_node.metadata # Do not include the materials from a to-be-removed package if bool(metadata.get("removed", False)): continue + # Only add results for favorite materials + if root_material_id not in self._favorite_ids: + continue + item = { "root_material_id": root_material_id, "id": metadata["id"], @@ -63,11 +44,11 @@ class FavoriteMaterialsModel(BaseMaterialsModel): "color_name": metadata["color_name"], "color_code": metadata["color_code"], "container_node": container_node, - "is_favorite": True + "is_favorite": True # Don't need to set since we only include favorites anyway } item_list.append(item) - # Sort the item list by material name alphabetically + # Sort the item list alphabetically by name item_list = sorted(item_list, key = lambda d: d["brand"].upper()) self.setItems(item_list) diff --git a/cura/Machines/Models/GenericMaterialsModel.py b/cura/Machines/Models/GenericMaterialsModel.py index a8f5bfbd35..3432f3e35c 100644 --- a/cura/Machines/Models/GenericMaterialsModel.py +++ b/cura/Machines/Models/GenericMaterialsModel.py @@ -4,44 +4,25 @@ from UM.Logger import Logger from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel - 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.activeStackChanged.connect(self._update) #Update when switching machines. - self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes. self._update() def _update(self): - Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) - global_stack = self._machine_manager.activeMachine - if global_stack is None: - self.setItems([]) - return - extruder_position = str(self._extruder_position) - if extruder_position not in global_stack.extruders: - self.setItems([]) - return - extruder_stack = global_stack.extruders[extruder_position] - - available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack, - extruder_stack) - if available_material_dict is None: + # Perform standard check and reset if the check fails + if not self._canUpdate(): self.setItems([]) return - favorites = self._material_manager.getFavorites() + # Get updated list of favorites + self._favorite_ids = self._material_manager.getFavorites() item_list = [] - for root_material_id, container_node in available_material_dict.items(): + + for root_material_id, container_node in self._available_materials.items(): metadata = container_node.metadata # Only add results for generic materials @@ -54,19 +35,19 @@ class GenericMaterialsModel(BaseMaterialsModel): item = { "root_material_id": root_material_id, - "id": metadata["id"], - "GUID": metadata["GUID"], - "name": metadata["name"], - "brand": metadata["brand"], - "material": metadata["material"], - "color_name": metadata["color_name"], - "color_code": metadata["color_code"], - "container_node": container_node, - "is_favorite": root_material_id in favorites + "id": metadata["id"], + "GUID": metadata["GUID"], + "name": metadata["name"], + "brand": metadata["brand"], + "material": metadata["material"], + "color_name": metadata["color_name"], + "color_code": metadata["color_code"], + "container_node": container_node, + "is_favorite": root_material_id in self._favorite_ids } item_list.append(item) - # Sort the item list by material name alphabetically + # Sort the item list alphabetically by name item_list = sorted(item_list, key = lambda d: d["name"].upper()) self.setItems(item_list) diff --git a/cura/Machines/Models/MaterialBrandsModel.py b/cura/Machines/Models/MaterialBrandsModel.py new file mode 100644 index 0000000000..89d9fc7bfd --- /dev/null +++ b/cura/Machines/Models/MaterialBrandsModel.py @@ -0,0 +1,129 @@ +# 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 UM.Logger import Logger +from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel + +class MaterialTypesModel(ListModel): + + def __init__(self, parent = None): + super().__init__(parent) + + self.addRoleName(Qt.UserRole + 1, "name") + self.addRoleName(Qt.UserRole + 2, "colors") + +class MaterialBrandsModel(BaseMaterialsModel): + + extruderPositionChanged = pyqtSignal() + + def __init__(self, parent = None): + super().__init__(parent) + + from cura.CuraApplication import CuraApplication + + self.addRoleName(Qt.UserRole + 1, "name") + self.addRoleName(Qt.UserRole + 2, "material_types") + + self._container_registry = CuraApplication.getInstance().getContainerRegistry() + + self._update() + + def _update(self): + + # Perform standard check and reset if the check fails + if not self._canUpdate(): + self.setItems([]) + return + + # Get updated list of favorites + self._favorite_ids = self._material_manager.getFavorites() + + brand_item_list = [] + brand_group_dict = {} + + # Part 1: Generate the entire tree of brands -> material types -> spcific materials + for root_material_id, container_node in self._available_materials.items(): + metadata = container_node.metadata + + # Do not include the materials from a to-be-removed package + if bool(metadata.get("removed", False)): + continue + + # Add brands we haven't seen yet to the dict, skipping generics + brand = metadata["brand"] + if brand.lower() == "generic": + continue + if brand not in brand_group_dict: + brand_group_dict[brand] = {} + + # Add material types we haven't seen yet to the dict + material_type = metadata["material"] + if material_type not in brand_group_dict[brand]: + brand_group_dict[brand][material_type] = [] + + # Now handle the individual materials + item = { + "root_material_id": root_material_id, + "id": metadata["id"], + "container_id": metadata["id"], # TODO: Remove duplicate in material manager qml + "GUID": metadata["GUID"], + "name": metadata["name"], + "brand": metadata["brand"], + "description": metadata["description"], + "material": metadata["material"], + "color_name": metadata["color_name"], + "color_code": metadata["color_code"], + "density": metadata.get("properties", {}).get("density", ""), + "diameter": metadata.get("properties", {}).get("diameter", ""), + "approximate_diameter": metadata["approximate_diameter"], + "adhesion_info": metadata["adhesion_info"], + "is_read_only": self._container_registry.isReadOnly(metadata["id"]), + "container_node": container_node, + "is_favorite": root_material_id in self._favorite_ids + } + brand_group_dict[brand][material_type].append(item) + + # Part 2: Organize the tree into models + # + # Normally, the structure of the menu looks like this: + # Brand -> Material Type -> Specific Material + # + # To illustrate, a branded material menu may look like this: + # Ultimaker ┳ PLA ┳ Yellow PLA + # ┃ ┣ Black PLA + # ┃ ┗ ... + # ┃ + # ┗ ABS ┳ White ABS + # ┗ ... + for brand, material_dict in brand_group_dict.items(): + + material_type_item_list = [] + brand_item = { + "name": brand, + "material_types": MaterialTypesModel(self) + } + + for material_type, material_list in material_dict.items(): + material_type_item = { + "name": material_type, + "colors": BaseMaterialsModel(self) + } + material_type_item["colors"].clear() + + # Sort materials by name + material_list = sorted(material_list, key = lambda x: x["name"].upper()) + material_type_item["colors"].setItems(material_list) + + material_type_item_list.append(material_type_item) + + # Sort material type by name + material_type_item_list = sorted(material_type_item_list, key = lambda x: x["name"].upper()) + brand_item["material_types"].setItems(material_type_item_list) + + brand_item_list.append(brand_item) + + # Sort brand by name + brand_item_list = sorted(brand_item_list, key = lambda x: x["name"].upper()) + self.setItems(brand_item_list) diff --git a/resources/qml/Menus/MaterialMenu.qml b/resources/qml/Menus/MaterialMenu.qml index a53bc72a94..186c5d1d2a 100644 --- a/resources/qml/Menus/MaterialMenu.qml +++ b/resources/qml/Menus/MaterialMenu.qml @@ -26,7 +26,7 @@ Menu extruderPosition: menu.extruderIndex } - Cura.BrandMaterialsModel + Cura.MaterialBrandsModel { id: brandModel extruderPosition: menu.extruderIndex @@ -80,7 +80,7 @@ Menu id: brandMenu title: brandName property string brandName: model.name - property var brandMaterials: model.materials + property var brandMaterials: model.material_types Instantiator { diff --git a/resources/qml/Preferences/Materials/MaterialsList.qml b/resources/qml/Preferences/Materials/MaterialsList.qml index a051f55cac..220ce0134d 100644 --- a/resources/qml/Preferences/Materials/MaterialsList.qml +++ b/resources/qml/Preferences/Materials/MaterialsList.qml @@ -18,7 +18,7 @@ Item // Children UM.I18nCatalog { id: catalog; name: "cura"; } - Cura.BrandMaterialsModel { id: materialsModel } + Cura.MaterialBrandsModel { id: materialsModel } Cura.FavoriteMaterialsModel { id: favoriteMaterialsModel } Cura.GenericMaterialsModel { id: genericMaterialsModel } Column @@ -186,7 +186,7 @@ Item { id: brand_section property var expanded: true - property var types_model: model.materials + property var types_model: model.material_types height: childrenRect.height width: parent.width Rectangle diff --git a/resources/qml/Preferences/Materials/MaterialsPage.qml b/resources/qml/Preferences/Materials/MaterialsPage.qml index ce7203f34b..4df6049b3f 100644 --- a/resources/qml/Preferences/Materials/MaterialsPage.qml +++ b/resources/qml/Preferences/Materials/MaterialsPage.qml @@ -31,7 +31,7 @@ Item id: catalog name: "cura" } - Cura.BrandMaterialsModel + Cura.MaterialBrandsModel { id: materialsModel }