diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 02b708f58c..833f43e29c 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -67,9 +67,9 @@ from cura.Machines.Models.NozzleModel import NozzleModel from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel -from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel +from cura.Machines.Models.FavoriteMaterialsModel import FavoriteMaterialsModel from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel -from cura.Machines.Models.BrandMaterialsModel import BrandMaterialsModel +from cura.Machines.Models.MaterialBrandsModel import MaterialBrandsModel from cura.Machines.Models.QualityManagementModel import QualityManagementModel from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel from cura.Machines.Models.MachineManagementModel import MachineManagementModel @@ -480,7 +480,9 @@ class CuraApplication(QtApplication): preferences.addPreference("view/filter_current_build_plate", False) preferences.addPreference("cura/sidebar_collapsed", False) - self._need_to_show_user_agreement = not self.getPreferences().getValue("general/accepted_user_agreement") + preferences.addPreference("cura/favorite_materials", ";".join([])) + + self._need_to_show_user_agreement = not preferences.getValue("general/accepted_user_agreement") for key in [ "dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin @@ -911,9 +913,9 @@ class CuraApplication(QtApplication): qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer") qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") + qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel") qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel") - qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel") - qmlRegisterType(MaterialManagementModel, "Cura", 1, 0, "MaterialManagementModel") + qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel") qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel") qmlRegisterType(MachineManagementModel, "Cura", 1, 0, "MachineManagementModel") diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index d5a7d5d089..1463f2e40e 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -4,6 +4,7 @@ from collections import defaultdict, OrderedDict import copy import uuid +import json from typing import Dict, Optional, TYPE_CHECKING from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot @@ -38,7 +39,8 @@ if TYPE_CHECKING: # class MaterialManager(QObject): - materialsUpdated = pyqtSignal() # Emitted whenever the material lookup tables are updated. + materialsUpdated = pyqtSignal() # Emitted whenever the material lookup tables are updated. + favoritesUpdated = pyqtSignal() # Emitted whenever the favorites are changed def __init__(self, container_registry, parent = None): super().__init__(parent) @@ -75,6 +77,8 @@ class MaterialManager(QObject): self._container_registry.containerAdded.connect(self._onContainerMetadataChanged) self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged) + self._favorites = set() + def initialize(self): # Find all materials and put them in a matrix for quick search. material_metadatas = {metadata["id"]: metadata for metadata in @@ -194,6 +198,11 @@ class MaterialManager(QObject): self.materialsUpdated.emit() + favorites = self._application.getPreferences().getValue("cura/favorite_materials") + for item in favorites.split(";"): + self._favorites.add(item) + self.favoritesUpdated.emit() + def __addMaterialMetadataIntoLookupTree(self, material_metadata: dict) -> None: material_id = material_metadata["id"] @@ -608,3 +617,25 @@ class MaterialManager(QObject): new_base_id = new_id, new_metadata = new_metadata) return new_id + + @pyqtSlot(str) + def addFavorite(self, root_material_id: str): + self._favorites.add(root_material_id) + self.favoritesUpdated.emit() + + # Ensure all settings are saved. + self._application.getPreferences().setValue("cura/favorite_materials", ";".join(list(self._favorites))) + self._application.saveSettings() + + @pyqtSlot(str) + def removeFavorite(self, root_material_id: str): + self._favorites.remove(root_material_id) + self.favoritesUpdated.emit() + + # Ensure all settings are saved. + self._application.getPreferences().setValue("cura/favorite_materials", ";".join(list(self._favorites))) + self._application.saveSettings() + + @pyqtSlot() + def getFavorites(self): + return self._favorites \ No newline at end of file diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py index 4759c8b5b0..43b42c55ff 100644 --- a/cura/Machines/Models/BaseMaterialsModel.py +++ b/cura/Machines/Models/BaseMaterialsModel.py @@ -2,45 +2,63 @@ # 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 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.NameRole, "name") - self.addRoleName(self.BrandRole, "brand") - self.addRoleName(self.MaterialRole, "material") - self.addRoleName(self.ColorRole, "color_name") - self.addRoleName(self.ContainerNodeRole, "container_node") + from cura.CuraApplication import CuraApplication + + self._application = CuraApplication.getInstance() + + # Make these managers available to all material models + self._container_registry = self._application.getInstance().getContainerRegistry() + 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) + + # Update this model when list of favorites changes + self._material_manager.favoritesUpdated.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, "description") + self.addRoleName(Qt.UserRole + 7, "material") + self.addRoleName(Qt.UserRole + 8, "color_name") + self.addRoleName(Qt.UserRole + 9, "color_code") + self.addRoleName(Qt.UserRole + 10, "density") + self.addRoleName(Qt.UserRole + 11, "diameter") + self.addRoleName(Qt.UserRole + 12, "approximate_diameter") + self.addRoleName(Qt.UserRole + 13, "adhesion_info") + self.addRoleName(Qt.UserRole + 14, "is_read_only") + self.addRoleName(Qt.UserRole + 15, "container_node") + self.addRoleName(Qt.UserRole + 16, "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 @@ -65,8 +83,55 @@ 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 + + ## This is another convenience function which is shared by all material + # models so it's put here to avoid having so much duplicated code. + def _createMaterialItem(self, root_material_id, container_node): + metadata = container_node.metadata + 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 + } + return item + diff --git a/cura/Machines/Models/BrandMaterialsModel.py b/cura/Machines/Models/BrandMaterialsModel.py deleted file mode 100644 index ad48b3ea21..0000000000 --- a/cura/Machines/Models/BrandMaterialsModel.py +++ /dev/null @@ -1,157 +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._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._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 - brand = metadata["brand"] - # Only add results for generic materials - if brand.lower() == "generic": - continue - - # Do not include the materials from a to-be-removed package - if bool(metadata.get("removed", False)): - 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() - - # 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 new file mode 100644 index 0000000000..be3f0f605f --- /dev/null +++ b/cura/Machines/Models/FavoriteMaterialsModel.py @@ -0,0 +1,42 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from UM.Logger import Logger +from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel + +class FavoriteMaterialsModel(BaseMaterialsModel): + + def __init__(self, parent = None): + super().__init__(parent) + 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() + + item_list = [] + + 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 = self._createMaterialItem(root_material_id, container_node) + item_list.append(item) + + # 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 f14b039c91..27e6fdfd7c 100644 --- a/cura/Machines/Models/GenericMaterialsModel.py +++ b/cura/Machines/Models/GenericMaterialsModel.py @@ -4,63 +4,39 @@ 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: + # 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 + # 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 generic materials - if metadata["brand"].lower() != "generic": - 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 - 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 - } + # Only add results for generic materials + if metadata["brand"].lower() != "generic": + continue + + item = self._createMaterialItem(root_material_id, container_node) 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..3f917abb16 --- /dev/null +++ b/cura/Machines/Models/MaterialBrandsModel.py @@ -0,0 +1,107 @@ +# 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) + + self.addRoleName(Qt.UserRole + 1, "name") + self.addRoleName(Qt.UserRole + 2, "material_types") + + 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 = self._createMaterialItem(root_material_id, container_node) + 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/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py deleted file mode 100644 index 46e9cb887a..0000000000 --- a/cura/Machines/Models/MaterialManagementModel.py +++ /dev/null @@ -1,104 +0,0 @@ -# Copyright (c) 2018 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -from PyQt5.QtCore import Qt - -from UM.Logger import Logger -from UM.Qt.ListModel import ListModel - - -# -# 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() - 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): - 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 - active_extruder_stack = self._machine_manager.activeStack - - available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack, - active_extruder_stack) - if available_material_dict is None: - self.setItems([]) - return - - material_list = [] - for root_material_id, container_node in available_material_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"].upper(), k["name"].upper())) - self.setItems(material_list) diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 046a887a71..cd8a122bd2 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -541,7 +541,7 @@ UM.MainWindow insertPage(2, catalog.i18nc("@title:tab", "Printers"), Qt.resolvedUrl("Preferences/MachinesPage.qml")); - insertPage(3, catalog.i18nc("@title:tab", "Materials"), Qt.resolvedUrl("Preferences/MaterialsPage.qml")); + insertPage(3, catalog.i18nc("@title:tab", "Materials"), Qt.resolvedUrl("Preferences/Materials/MaterialsPage.qml")); insertPage(4, catalog.i18nc("@title:tab", "Profiles"), Qt.resolvedUrl("Preferences/ProfilesPage.qml")); diff --git a/resources/qml/Menus/MaterialMenu.qml b/resources/qml/Menus/MaterialMenu.qml index 64b3130724..186c5d1d2a 100644 --- a/resources/qml/Menus/MaterialMenu.qml +++ b/resources/qml/Menus/MaterialMenu.qml @@ -14,24 +14,64 @@ Menu property int extruderIndex: 0 + Cura.FavoriteMaterialsModel + { + id: favoriteMaterialsModel + extruderPosition: menu.extruderIndex + } + + Cura.GenericMaterialsModel + { + id: genericMaterialsModel + extruderPosition: menu.extruderIndex + } + + Cura.MaterialBrandsModel + { + id: brandModel + extruderPosition: menu.extruderIndex + } + Instantiator { - model: genericMaterialsModel - MenuItem + model: favoriteMaterialsModel + delegate: MenuItem { - text: model.name + text: model.brand + " " + model.name checkable: true checked: model.root_material_id == Cura.MachineManager.currentRootMaterialId[extruderIndex] + onTriggered: Cura.MachineManager.setMaterial(extruderIndex, model.container_node) exclusiveGroup: group - onTriggered: - { - Cura.MachineManager.setMaterial(extruderIndex, model.container_node); - } } onObjectAdded: menu.insertItem(index, object) - onObjectRemoved: menu.removeItem(object) + onObjectRemoved: menu.removeItem(object) // TODO: This ain't gonna work, removeItem() takes an index, not object } - MenuSeparator { } + + MenuSeparator {} + + Menu + { + id: genericMenu + title: "Generic" + + Instantiator + { + model: genericMaterialsModel + delegate: MenuItem + { + text: model.name + checkable: true + checked: model.root_material_id == Cura.MachineManager.currentRootMaterialId[extruderIndex] + exclusiveGroup: group + onTriggered: Cura.MachineManager.setMaterial(extruderIndex, model.container_node) + } + onObjectAdded: genericMenu.insertItem(index, object) + onObjectRemoved: genericMenu.removeItem(object) // TODO: This ain't gonna work, removeItem() takes an index, not object + } + } + + MenuSeparator {} + Instantiator { model: brandModel @@ -40,12 +80,12 @@ Menu id: brandMenu title: brandName property string brandName: model.name - property var brandMaterials: model.materials + property var brandMaterials: model.material_types Instantiator { model: brandMaterials - Menu + delegate: Menu { id: brandMaterialsMenu title: materialName @@ -55,16 +95,13 @@ Menu Instantiator { model: brandMaterialColors - MenuItem + delegate: MenuItem { text: model.name checkable: true checked: model.id == Cura.MachineManager.allActiveMaterialIds[Cura.ExtruderManager.extruderIds[extruderIndex]] exclusiveGroup: group - onTriggered: - { - Cura.MachineManager.setMaterial(extruderIndex, model.container_node); - } + onTriggered: Cura.MachineManager.setMaterial(extruderIndex, model.container_node) } onObjectAdded: brandMaterialsMenu.insertItem(index, object) onObjectRemoved: brandMaterialsMenu.removeItem(object) @@ -78,21 +115,14 @@ Menu onObjectRemoved: menu.removeItem(object) } - Cura.GenericMaterialsModel - { - id: genericMaterialsModel - extruderPosition: menu.extruderIndex + ExclusiveGroup { + id: group } - Cura.BrandMaterialsModel + MenuSeparator {} + + MenuItem { - id: brandModel - extruderPosition: menu.extruderIndex + action: Cura.Actions.manageMaterials } - - ExclusiveGroup { id: group } - - MenuSeparator { } - - MenuItem { action: Cura.Actions.manageMaterials } } diff --git a/resources/qml/Preferences/Materials/MaterialsBrandSection.qml b/resources/qml/Preferences/Materials/MaterialsBrandSection.qml new file mode 100644 index 0000000000..1077cbff6f --- /dev/null +++ b/resources/qml/Preferences/Materials/MaterialsBrandSection.qml @@ -0,0 +1,100 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Uranium is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.2 + +import UM 1.2 as UM +import Cura 1.0 as Cura + +Rectangle +{ + id: brand_section + property var expanded: base.collapsed_brands.indexOf(model.name) > -1 + property var types_model: model.material_types + height: childrenRect.height + width: parent.width + Rectangle + { + id: brand_header_background + color: UM.Theme.getColor("favorites_header_bar") + anchors.fill: brand_header + } + Row + { + id: brand_header + width: parent.width + Label + { + id: brand_name + text: model.name + height: UM.Theme.getSize("favorites_row").height + width: parent.width - UM.Theme.getSize("favorites_button").width + verticalAlignment: Text.AlignVCenter + leftPadding: 4 + } + Button + { + text: "" + implicitWidth: UM.Theme.getSize("favorites_button").width + implicitHeight: UM.Theme.getSize("favorites_button").height + UM.RecolorImage { + anchors + { + verticalCenter: parent.verticalCenter + horizontalCenter: parent.horizontalCenter + } + width: UM.Theme.getSize("standard_arrow").width + height: UM.Theme.getSize("standard_arrow").height + sourceSize.width: width + sourceSize.height: height + color: "black" + source: brand_section.expanded ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_left") + } + style: ButtonStyle + { + background: Rectangle + { + anchors.fill: parent + color: "transparent" + } + } + } + } + MouseArea + { + anchors.fill: brand_header + onPressed: + { + const i = base.collapsed_brands.indexOf(model.name) + if (i > -1) + { + // Remove it + base.collapsed_brands.splice(i, 1) + brand_section.expanded = false + } + else + { + // Add it + base.collapsed_brands.push(model.name) + brand_section.expanded = true + } + } + } + Column + { + anchors.top: brand_header.bottom + width: parent.width + anchors.left: parent.left + height: brand_section.expanded ? childrenRect.height : 0 + visible: brand_section.expanded + Repeater + { + model: types_model + delegate: MaterialsTypeSection {} + } + } +} \ No newline at end of file diff --git a/resources/qml/Preferences/Materials/MaterialsDetailsPanel.qml b/resources/qml/Preferences/Materials/MaterialsDetailsPanel.qml new file mode 100644 index 0000000000..ad9f0e3766 --- /dev/null +++ b/resources/qml/Preferences/Materials/MaterialsDetailsPanel.qml @@ -0,0 +1,102 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Uranium is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.2 + +import UM 1.2 as UM +import Cura 1.0 as Cura + +Item +{ + id: detailsPanel + + property var currentItem: base.currentItem + + onCurrentItemChanged: { updateMaterialPropertiesObject(currentItem) } + + function updateMaterialPropertiesObject( currentItem ) + { + materialProperties.name = currentItem.name || "Unknown" + materialProperties.guid = currentItem.GUID; + materialProperties.container_id = currentItem.id + materialProperties.brand = currentItem.brand || "Unknown" + materialProperties.material = currentItem.material || "Unknown" + materialProperties.color_name = currentItem.color_name || "Yellow" + materialProperties.color_code = currentItem.color_code || "yellow" + materialProperties.description = currentItem.description || "" + materialProperties.adhesion_info = currentItem.adhesion_info || ""; + materialProperties.density = currentItem.density || 0.0 + materialProperties.diameter = currentItem.diameter || 0.0 + materialProperties.approximate_diameter = currentItem.approximate_diameter || "0" + } + + Item + { + anchors.fill: parent + + Item // Material title Label + { + id: profileName + + width: parent.width + height: childrenRect.height + + Label { + text: materialProperties.name + font: UM.Theme.getFont("large") + } + } + + MaterialsView // Material detailed information view below the title Label + { + id: materialDetailsView + anchors + { + left: parent.left + right: parent.right + top: profileName.bottom + topMargin: UM.Theme.getSize("default_margin").height + bottom: parent.bottom + } + + editingEnabled: base.currentItem != null && !base.currentItem.is_read_only + + properties: materialProperties + containerId: base.currentItem != null ? base.currentItem.id : "" + currentMaterialNode: base.currentItem.container_node + + + } + + QtObject + { + id: materialProperties + + property string guid: "00000000-0000-0000-0000-000000000000" + property string container_id: "Unknown"; + property string name: "Unknown"; + property string profile_type: "Unknown"; + property string brand: "Unknown"; + property string material: "Unknown"; // This needs to be named as "material" to be consistent with + // the material container's metadata entry + + property string color_name: "Yellow"; + property color color_code: "yellow"; + + property real density: 0.0; + property real diameter: 0.0; + property string approximate_diameter: "0"; + + property real spool_cost: 0.0; + property real spool_weight: 0.0; + property real spool_length: 0.0; + property real cost_per_meter: 0.0; + + property string description: ""; + property string adhesion_info: ""; + } + } +} \ No newline at end of file diff --git a/resources/qml/Preferences/Materials/MaterialsList.qml b/resources/qml/Preferences/Materials/MaterialsList.qml new file mode 100644 index 0000000000..4a1a330ed6 --- /dev/null +++ b/resources/qml/Preferences/Materials/MaterialsList.qml @@ -0,0 +1,199 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Uranium is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.2 + +import UM 1.2 as UM +import Cura 1.0 as Cura + +Item +{ + id: materialList + width: materialScrollView.width - 17 + height: childrenRect.height + + // Children + UM.I18nCatalog { id: catalog; name: "cura"; } + Cura.MaterialBrandsModel { id: materialsModel } + Cura.FavoriteMaterialsModel { id: favoriteMaterialsModel } + Cura.GenericMaterialsModel { id: genericMaterialsModel } + Column + { + Rectangle + { + property var expanded: true + + id: favorites_section + height: childrenRect.height + width: materialList.width + Rectangle + { + id: favorites_header_background + color: UM.Theme.getColor("favorites_header_bar") + anchors.fill: favorites_header + } + Row + { + id: favorites_header + Label + { + id: favorites_name + text: "Favorites" + height: UM.Theme.getSize("favorites_row").height + width: materialList.width - UM.Theme.getSize("favorites_button").width + verticalAlignment: Text.AlignVCenter + leftPadding: 4 + } + Button + { + text: "" + implicitWidth: UM.Theme.getSize("favorites_button").width + implicitHeight: UM.Theme.getSize("favorites_button").height + UM.RecolorImage { + anchors + { + verticalCenter: parent.verticalCenter + horizontalCenter: parent.horizontalCenter + } + width: UM.Theme.getSize("standard_arrow").width + height: UM.Theme.getSize("standard_arrow").height + sourceSize.width: width + sourceSize.height: height + color: "black" + source: favorites_section.expanded ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_left") + } + style: ButtonStyle + { + background: Rectangle + { + anchors.fill: parent + color: "transparent" + } + } + } + } + MouseArea + { + anchors.fill: favorites_header + onPressed: + { + favorites_section.expanded = !favorites_section.expanded + } + } + Column + { + anchors.top: favorites_header.bottom + anchors.left: parent.left + width: materialList.width + height: favorites_section.expanded ? childrenRect.height : 0 + visible: favorites_section.expanded + Repeater + { + model: favoriteMaterialsModel + delegate: MaterialsSlot { + material: model + } + } + } + } + Rectangle + { + property var expanded: base.collapsed_brands.indexOf("Generic") > -1 + + id: generic_section + height: childrenRect.height + width: materialList.width + Rectangle + { + id: generic_header_background + color: UM.Theme.getColor("favorites_header_bar") + anchors.fill: generic_header + } + Row + { + id: generic_header + Label + { + id: generic_name + text: "Generic" + height: UM.Theme.getSize("favorites_row").height + width: materialList.width - UM.Theme.getSize("favorites_button").width + verticalAlignment: Text.AlignVCenter + leftPadding: 4 + } + Button + { + text: "" + implicitWidth: UM.Theme.getSize("favorites_button").width + implicitHeight: UM.Theme.getSize("favorites_button").height + UM.RecolorImage { + anchors + { + verticalCenter: parent.verticalCenter + horizontalCenter: parent.horizontalCenter + } + width: UM.Theme.getSize("standard_arrow").width + height: UM.Theme.getSize("standard_arrow").height + sourceSize.width: width + sourceSize.height: height + color: "black" + source: generic_section.expanded ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_left") + } + style: ButtonStyle + { + background: Rectangle + { + anchors.fill: parent + color: "transparent" + } + } + } + } + MouseArea + { + anchors.fill: generic_header + onPressed: + { + const i = base.collapsed_brands.indexOf("Generic") + if (i > -1) + { + // Remove it + base.collapsed_brands.splice(i, 1) + generic_section.expanded = false + } + else + { + // Add it + base.collapsed_brands.push("Generic") + generic_section.expanded = true + } + } + } + Column + { + anchors.top: generic_header.bottom + width: materialList.width + anchors.left: parent.left + height: generic_section.expanded ? childrenRect.height : 0 + visible: generic_section.expanded + Repeater + { + model: genericMaterialsModel + delegate: MaterialsSlot { + material: model + } + } + } + } + Repeater + { + id: brand_list + model: materialsModel + delegate: MaterialsBrandSection {} + } + } +} \ No newline at end of file diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/Materials/MaterialsPage.qml similarity index 56% rename from resources/qml/Preferences/MaterialsPage.qml rename to resources/qml/Preferences/Materials/MaterialsPage.qml index e2e3edec2f..0b81df5fa1 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/Materials/MaterialsPage.qml @@ -9,25 +9,110 @@ import QtQuick.Dialogs 1.2 import UM 1.2 as UM import Cura 1.0 as Cura - Item { id: base property QtObject materialManager: CuraApplication.getMaterialManager() - property var resetEnabled: false // Keep PreferencesDialog happy - - UM.I18nCatalog { id: catalog; name: "cura"; } - - Cura.MaterialManagementModel + // Keep PreferencesDialog happy + property var resetEnabled: false + property var currentItem: null + property var isCurrentItemActivated: { - id: materialsModel + const extruder_position = Cura.ExtruderManager.activeExtruderIndex; + const root_material_id = Cura.MachineManager.currentRootMaterialId[extruder_position]; + return base.currentItem.root_material_id == root_material_id; + } + property string newRootMaterialIdToSwitchTo: "" + property bool toActivateNewMaterial: false + + // TODO: Save these to preferences + property var collapsed_brands: [] + property var collapsed_types: [] + + UM.I18nCatalog + { + id: catalog + name: "cura" + } + Cura.MaterialBrandsModel { id: materialsModel } + + function findModelByRootId( search_root_id ) + { + for (var i = 0; i < materialsModel.rowCount(); i++) + { + var types_model = materialsModel.getItem(i).material_types; + for (var j = 0; j < types_model.rowCount(); j++) + { + var colors_model = types_model.getItem(j).colors; + for (var k = 0; k < colors_model.rowCount(); k++) + { + var material = colors_model.getItem(k); + if (material.root_material_id == search_root_id) + { + return material + } + } + } + } + } + Component.onCompleted: + { + // Select the activated material when this page shows up + const extruder_position = Cura.ExtruderManager.activeExtruderIndex; + const active_root_material_id = Cura.MachineManager.currentRootMaterialId[extruder_position]; + console.log("goign to search for", active_root_material_id) + base.currentItem = findModelByRootId(active_root_material_id) } + onCurrentItemChanged: { MaterialsDetailsPanel.currentItem = currentItem } + Connections + { + target: materialsModel + onItemsChanged: + { + var currentItemId = base.currentItem == null ? "" : base.currentItem.root_material_id; + var position = Cura.ExtruderManager.activeExtruderIndex; + + // try to pick the currently selected item; it may have been moved + if (base.newRootMaterialIdToSwitchTo == "") + { + base.newRootMaterialIdToSwitchTo = currentItemId; + } + + for (var idx = 0; idx < materialsModel.rowCount(); ++idx) + { + var item = materialsModel.getItem(idx); + if (item.root_material_id == base.newRootMaterialIdToSwitchTo) + { + // Switch to the newly created profile if needed + materialListView.currentIndex = idx; + materialListView.activateDetailsWithIndex(materialListView.currentIndex); + if (base.toActivateNewMaterial) + { + Cura.MachineManager.setMaterial(position, item.container_node); + } + base.newRootMaterialIdToSwitchTo = ""; + base.toActivateNewMaterial = false; + return + } + } + + materialListView.currentIndex = 0; + materialListView.activateDetailsWithIndex(materialListView.currentIndex); + if (base.toActivateNewMaterial) + { + Cura.MachineManager.setMaterial(position, materialsModel.getItem(0).container_node); + } + base.newRootMaterialIdToSwitchTo = ""; + base.toActivateNewMaterial = false; + } + } + + // Main layout Label { id: titleLabel - anchors { top: parent.top @@ -35,45 +120,12 @@ Item right: parent.right margins: 5 * screenScaleFactor } - font.pointSize: 18 text: catalog.i18nc("@title:tab", "Materials") } - property var hasCurrentItem: materialListView.currentItem != null - - property var currentItem: - { // is soon to be overwritten - var current_index = materialListView.currentIndex; - return materialsModel.getItem(current_index); - } - - property var isCurrentItemActivated: - { - const extruder_position = Cura.ExtruderManager.activeExtruderIndex; - const root_material_id = Cura.MachineManager.currentRootMaterialId[extruder_position]; - return base.currentItem.root_material_id == root_material_id; - } - - Component.onCompleted: - { - // Select the activated material when this page shows up - const extruder_position = Cura.ExtruderManager.activeExtruderIndex; - const active_root_material_id = Cura.MachineManager.currentRootMaterialId[extruder_position]; - var itemIndex = -1; - for (var i = 0; i < materialsModel.rowCount(); ++i) - { - var item = materialsModel.getItem(i); - if (item.root_material_id == active_root_material_id) - { - itemIndex = i; - break; - } - } - materialListView.currentIndex = itemIndex; - } - - Row // Button Row + // Button Row + Row { id: buttonRow anchors @@ -166,63 +218,102 @@ Item } } - property string newRootMaterialIdToSwitchTo: "" - property bool toActivateNewMaterial: false - - // This connection makes sure that we will switch to the new - Connections - { - target: materialsModel - onItemsChanged: + Item { + id: contentsItem + anchors { - var currentItemId = base.currentItem == null ? "" : base.currentItem.root_material_id; - var position = Cura.ExtruderManager.activeExtruderIndex; + top: titleLabel.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + margins: 5 * screenScaleFactor + bottomMargin: 0 + } + clip: true + } - // try to pick the currently selected item; it may have been moved - if (base.newRootMaterialIdToSwitchTo == "") + Item + { + anchors + { + top: buttonRow.bottom + topMargin: UM.Theme.getSize("default_margin").height + left: parent.left + right: parent.right + bottom: parent.bottom + } + + SystemPalette { id: palette } + + Label + { + id: captionLabel + anchors { - base.newRootMaterialIdToSwitchTo = currentItemId; + top: parent.top + left: parent.left } - - for (var idx = 0; idx < materialsModel.rowCount(); ++idx) + visible: text != "" + text: { - var item = materialsModel.getItem(idx); - if (item.root_material_id == base.newRootMaterialIdToSwitchTo) + var caption = catalog.i18nc("@action:label", "Printer") + ": " + Cura.MachineManager.activeMachineName; + if (Cura.MachineManager.hasVariants) { - // Switch to the newly created profile if needed - materialListView.currentIndex = idx; - materialListView.activateDetailsWithIndex(materialListView.currentIndex); - if (base.toActivateNewMaterial) - { - Cura.MachineManager.setMaterial(position, item.container_node); - } - base.newRootMaterialIdToSwitchTo = ""; - base.toActivateNewMaterial = false; - return + caption += ", " + Cura.MachineManager.activeDefinitionVariantsName + ": " + Cura.MachineManager.activeVariantName; } + return caption; + } + width: materialScrollView.width + elide: Text.ElideRight + } + + ScrollView + { + id: materialScrollView + anchors + { + top: captionLabel.visible ? captionLabel.bottom : parent.top + topMargin: captionLabel.visible ? UM.Theme.getSize("default_margin").height : 0 + bottom: parent.bottom + left: parent.left } - materialListView.currentIndex = 0; - materialListView.activateDetailsWithIndex(materialListView.currentIndex); - if (base.toActivateNewMaterial) + Rectangle { - Cura.MachineManager.setMaterial(position, materialsModel.getItem(0).container_node); + parent: viewport + anchors.fill: parent + color: palette.light + } + + width: true ? (parent.width * 0.4) | 0 : parent.width + frameVisible: true + verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn + + MaterialsList {} + } + + MaterialsDetailsPanel + { + anchors + { + left: materialScrollView.right + leftMargin: UM.Theme.getSize("default_margin").width + top: parent.top + bottom: parent.bottom + right: parent.right } - base.newRootMaterialIdToSwitchTo = ""; - base.toActivateNewMaterial = false; } } + // Dialogs MessageDialog { id: confirmRemoveMaterialDialog - icon: StandardIcon.Question; title: catalog.i18nc("@title:window", "Confirm Remove") text: catalog.i18nc("@label (%1 is object name)", "Are you sure you wish to remove %1? This cannot be undone!").arg(base.currentItem.name) standardButtons: StandardButton.Yes | StandardButton.No modality: Qt.ApplicationModal - onYes: { base.materialManager.removeMaterial(base.currentItem.container_node); @@ -292,281 +383,4 @@ Item { id: messageDialog } - - - Item { - id: contentsItem - - anchors - { - top: titleLabel.bottom - left: parent.left - right: parent.right - bottom: parent.bottom - margins: 5 * screenScaleFactor - bottomMargin: 0 - } - - clip: true - } - - Item - { - anchors - { - top: buttonRow.bottom - topMargin: UM.Theme.getSize("default_margin").height - left: parent.left - right: parent.right - bottom: parent.bottom - } - - SystemPalette { id: palette } - - Label - { - id: captionLabel - anchors - { - top: parent.top - left: parent.left - } - visible: text != "" - text: - { - var caption = catalog.i18nc("@action:label", "Printer") + ": " + Cura.MachineManager.activeMachineName; - if (Cura.MachineManager.hasVariants) - { - caption += ", " + Cura.MachineManager.activeDefinitionVariantsName + ": " + Cura.MachineManager.activeVariantName; - } - return caption; - } - width: materialScrollView.width - elide: Text.ElideRight - } - - ScrollView - { - id: materialScrollView - anchors - { - top: captionLabel.visible ? captionLabel.bottom : parent.top - topMargin: captionLabel.visible ? UM.Theme.getSize("default_margin").height : 0 - bottom: parent.bottom - left: parent.left - } - - Rectangle - { - parent: viewport - anchors.fill: parent - color: palette.light - } - - width: true ? (parent.width * 0.4) | 0 : parent.width - frameVisible: true - - ListView - { - id: materialListView - - model: materialsModel - - section.property: "brand" - section.criteria: ViewSection.FullString - section.delegate: Rectangle - { - width: materialScrollView.width - height: childrenRect.height - color: palette.light - - Label - { - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("default_lining").width - text: section - font.bold: true - color: palette.text - } - } - - delegate: Rectangle - { - width: materialScrollView.width - height: childrenRect.height - color: ListView.isCurrentItem ? palette.highlight : (model.index % 2) ? palette.base : palette.alternateBase - - Row - { - id: materialRow - spacing: (UM.Theme.getSize("default_margin").width / 2) | 0 - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("default_margin").width - anchors.right: parent.right - - property bool isCurrentItem: parent.ListView.isCurrentItem - - property bool isItemActivated: - { - const extruder_position = Cura.ExtruderManager.activeExtruderIndex; - const root_material_id = Cura.MachineManager.currentRootMaterialId[extruder_position]; - return model.root_material_id == root_material_id; - } - - Rectangle - { - width: Math.floor(parent.height * 0.8) - height: Math.floor(parent.height * 0.8) - color: model.color_code - border.color: materialRow.isCurrentItem ? palette.highlightedText : palette.text; - anchors.verticalCenter: parent.verticalCenter - } - Label - { - width: Math.floor((parent.width * 0.3)) - text: model.material - elide: Text.ElideRight - font.italic: materialRow.isItemActivated - color: materialRow.isCurrentItem ? palette.highlightedText : palette.text; - } - Label - { - text: (model.name != model.material) ? model.name : "" - elide: Text.ElideRight - font.italic: materialRow.isItemActivated - color: materialRow.isCurrentItem ? palette.highlightedText : palette.text; - } - } - - MouseArea - { - anchors.fill: parent - onClicked: - { - parent.ListView.view.currentIndex = model.index; - } - } - } - - function activateDetailsWithIndex(index) - { - var model = materialsModel.getItem(index); - base.currentItem = model; - materialDetailsView.containerId = model.container_id; - materialDetailsView.currentMaterialNode = model.container_node; - - detailsPanel.updateMaterialPropertiesObject(); - } - - onCurrentIndexChanged: - { - forceActiveFocus(); // causes the changed fields to be saved - activateDetailsWithIndex(currentIndex); - } - } - } - - - Item - { - id: detailsPanel - - anchors - { - left: materialScrollView.right - leftMargin: UM.Theme.getSize("default_margin").width - top: parent.top - bottom: parent.bottom - right: parent.right - } - - function updateMaterialPropertiesObject() - { - var currentItem = materialsModel.getItem(materialListView.currentIndex); - - materialProperties.name = currentItem.name ? currentItem.name : "Unknown"; - materialProperties.guid = currentItem.guid; - materialProperties.container_id = currentItem.container_id; - - materialProperties.brand = currentItem.brand ? currentItem.brand : "Unknown"; - materialProperties.material = currentItem.material ? currentItem.material : "Unknown"; - materialProperties.color_name = currentItem.color_name ? currentItem.color_name : "Yellow"; - materialProperties.color_code = currentItem.color_code ? currentItem.color_code : "yellow"; - - materialProperties.description = currentItem.description ? currentItem.description : ""; - materialProperties.adhesion_info = currentItem.adhesion_info ? currentItem.adhesion_info : ""; - - materialProperties.density = currentItem.density ? currentItem.density : 0.0; - materialProperties.diameter = currentItem.diameter ? currentItem.diameter : 0.0; - materialProperties.approximate_diameter = currentItem.approximate_diameter ? currentItem.approximate_diameter : "0"; - } - - Item - { - anchors.fill: parent - - Item // Material title Label - { - id: profileName - - width: parent.width - height: childrenRect.height - - Label { - text: materialProperties.name - font: UM.Theme.getFont("large") - } - } - - MaterialView // Material detailed information view below the title Label - { - id: materialDetailsView - anchors - { - left: parent.left - right: parent.right - top: profileName.bottom - topMargin: UM.Theme.getSize("default_margin").height - bottom: parent.bottom - } - - editingEnabled: base.currentItem != null && !base.currentItem.is_read_only - - properties: materialProperties - containerId: base.currentItem != null ? base.currentItem.container_id : "" - currentMaterialNode: base.currentItem.container_node - - property alias pane: base - } - - QtObject - { - id: materialProperties - - property string guid: "00000000-0000-0000-0000-000000000000" - property string container_id: "Unknown"; - property string name: "Unknown"; - property string profile_type: "Unknown"; - property string brand: "Unknown"; - property string material: "Unknown"; // This needs to be named as "material" to be consistent with - // the material container's metadata entry - - property string color_name: "Yellow"; - property color color_code: "yellow"; - - property real density: 0.0; - property real diameter: 0.0; - property string approximate_diameter: "0"; - - property real spool_cost: 0.0; - property real spool_weight: 0.0; - property real spool_length: 0.0; - property real cost_per_meter: 0.0; - - property string description: ""; - property string adhesion_info: ""; - } - } - } - } } diff --git a/resources/qml/Preferences/Materials/MaterialsSlot.qml b/resources/qml/Preferences/Materials/MaterialsSlot.qml new file mode 100644 index 0000000000..ab0dd23750 --- /dev/null +++ b/resources/qml/Preferences/Materials/MaterialsSlot.qml @@ -0,0 +1,120 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Uranium is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.2 + +import UM 1.2 as UM +import Cura 1.0 as Cura + +Rectangle +{ + id: material_slot + property var material + property var hovered: false + property var is_favorite: material.is_favorite + + height: UM.Theme.getSize("favorites_row").height + width: parent.width + color: base.currentItem == model ? UM.Theme.getColor("favorites_row_selected") : "transparent" + + Item + { + height: parent.height + width: parent.width + Rectangle + { + id: swatch + color: material.color_code + border.width: UM.Theme.getSize("default_lining").width + border.color: "black" + width: UM.Theme.getSize("favorites_button_icon").width + height: UM.Theme.getSize("favorites_button_icon").height + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + } + Label + { + text: material.brand + " " + material.name + verticalAlignment: Text.AlignVCenter + height: parent.height + anchors.left: swatch.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: UM.Theme.getSize("narrow_margin").width + } + MouseArea + { + anchors.fill: parent + onClicked: { base.currentItem = material } + hoverEnabled: true + onEntered: { material_slot.hovered = true } + onExited: { material_slot.hovered = false } + } + Button + { + id: favorite_button + text: "" + implicitWidth: UM.Theme.getSize("favorites_button").width + implicitHeight: UM.Theme.getSize("favorites_button").height + visible: material_slot.hovered || material_slot.is_favorite || favorite_button.hovered + anchors + { + right: parent.right + verticalCenter: parent.verticalCenter + } + onClicked: + { + if (material_slot.is_favorite) { + base.materialManager.removeFavorite(material.root_material_id) + material_slot.is_favorite = false + return + } + base.materialManager.addFavorite(material.root_material_id) + material_slot.is_favorite = true + return + } + style: ButtonStyle + { + background: Rectangle + { + anchors.fill: parent + color: "transparent" + } + } + UM.RecolorImage { + anchors + { + verticalCenter: parent.verticalCenter + horizontalCenter: parent.horizontalCenter + } + width: UM.Theme.getSize("favorites_button_icon").width + height: UM.Theme.getSize("favorites_button_icon").height + sourceSize.width: width + sourceSize.height: height + color: + { + if (favorite_button.hovered) + { + return UM.Theme.getColor("primary_hover") + } + else + { + if (material_slot.is_favorite) + { + return UM.Theme.getColor("primary") + } + else + { + UM.Theme.getColor("text_inactive") + } + } + } + source: material_slot.is_favorite ? UM.Theme.getIcon("favorites_star_full") : UM.Theme.getIcon("favorites_star_empty") + } + } + } +} \ No newline at end of file diff --git a/resources/qml/Preferences/Materials/MaterialsTypeSection.qml b/resources/qml/Preferences/Materials/MaterialsTypeSection.qml new file mode 100644 index 0000000000..11bf2385e1 --- /dev/null +++ b/resources/qml/Preferences/Materials/MaterialsTypeSection.qml @@ -0,0 +1,109 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Uranium is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.2 + +import UM 1.2 as UM +import Cura 1.0 as Cura + +Rectangle +{ + id: material_type_section + property var expanded: base.collapsed_types.indexOf(model.brand + "_" + model.name) > -1 + property var colors_model: model.colors + height: childrenRect.height + width: parent.width + Rectangle + { + id: material_type_header_background + color: UM.Theme.getColor("lining") + anchors.bottom: material_type_header.bottom + anchors.left: material_type_header.left + height: UM.Theme.getSize("default_lining").height + width: material_type_header.width + } + Row + { + id: material_type_header + width: parent.width - 8 + anchors + { + left: parent.left + leftMargin: 8 + } + Label + { + text: model.name + height: UM.Theme.getSize("favorites_row").height + width: parent.width - UM.Theme.getSize("favorites_button").width + id: material_type_name + verticalAlignment: Text.AlignVCenter + } + Button + { + text: "" + implicitWidth: UM.Theme.getSize("favorites_button").width + implicitHeight: UM.Theme.getSize("favorites_button").height + UM.RecolorImage { + anchors + { + verticalCenter: parent.verticalCenter + horizontalCenter: parent.horizontalCenter + } + width: UM.Theme.getSize("standard_arrow").width + height: UM.Theme.getSize("standard_arrow").height + sourceSize.width: width + sourceSize.height: height + color: "black" + source: material_type_section.expanded ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_left") + } + style: ButtonStyle + { + background: Rectangle + { + anchors.fill: parent + color: "transparent" + } + } + } + } + MouseArea + { + anchors.fill: material_type_header + onPressed: + { + const i = base.collapsed_types.indexOf(model.brand + "_" + model.name) + if (i > -1) + { + // Remove it + base.collapsed_types.splice(i, 1) + material_type_section.expanded = false + } + else + { + // Add it + base.collapsed_types.push(model.brand + "_" + model.name) + material_type_section.expanded = true + } + } + } + Column + { + height: material_type_section.expanded ? childrenRect.height : 0 + visible: material_type_section.expanded + width: parent.width + anchors.top: material_type_header.bottom + anchors.left: parent.left + Repeater + { + model: colors_model + delegate: MaterialsSlot { + material: model + } + } + } +} \ No newline at end of file diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/Materials/MaterialsView.qml similarity index 99% rename from resources/qml/Preferences/MaterialView.qml rename to resources/qml/Preferences/Materials/MaterialsView.qml index 97184ab558..a03e5c48d7 100644 --- a/resources/qml/Preferences/MaterialView.qml +++ b/resources/qml/Preferences/Materials/MaterialsView.qml @@ -8,6 +8,8 @@ import QtQuick.Dialogs 1.2 import UM 1.2 as UM import Cura 1.0 as Cura +import ".." // Access to ReadOnlyTextArea.qml + TabView { id: base @@ -38,7 +40,7 @@ TabView { return "" } - var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.currentMaterialNode, true); + var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.currentItem.container_node, true); if (linkedMaterials.length == 0) { return "" diff --git a/resources/themes/cura-light/icons/favorites_star_empty.svg b/resources/themes/cura-light/icons/favorites_star_empty.svg new file mode 100644 index 0000000000..bb1205e7a7 --- /dev/null +++ b/resources/themes/cura-light/icons/favorites_star_empty.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/resources/themes/cura-light/icons/favorites_star_full.svg b/resources/themes/cura-light/icons/favorites_star_full.svg new file mode 100644 index 0000000000..aad45c5d02 --- /dev/null +++ b/resources/themes/cura-light/icons/favorites_star_full.svg @@ -0,0 +1,6 @@ + + + + + diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 7bcdafce98..c408146669 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -315,7 +315,13 @@ "tab_status_disconnected": [200, 200, 200, 255], "printer_config_matched": [12, 169, 227, 255], - "printer_config_mismatch": [127, 127, 127, 255] + "printer_config_mismatch": [127, 127, 127, 255], + + "favorites_header_bar": [245, 245, 245, 255], + "favorites_header_hover": [245, 245, 245, 255], + "favorites_header_text": [31, 36, 39, 255], + "favorites_header_text_hover": [31, 36, 39, 255], + "favorites_row_selected": [196, 239, 255, 255] }, "sizes": { @@ -372,6 +378,10 @@ "small_button": [2, 2], "small_button_icon": [1.5, 1.5], + "favorites_row": [2, 2], + "favorites_button": [2, 2], + "favorites_button_icon": [1.2, 1.2], + "printer_status_icon": [1.8, 1.8], "printer_sync_icon": [1.2, 1.2],