Cleaner code

I was hoping to completely nix the generic materials model (since it's basically just a brand "Generic", but then in the QML it has to be have the same in terms of sub-menus or fold-outs and that looked stupid (Generic -> ABS -> ABS)). So we keep that one for now. It is cleaner though.

Contributes to CURA-5162, CURA-5378
This commit is contained in:
Ian Paschal 2018-08-23 16:39:40 +02:00
parent 8da7773600
commit 56a5f59964
8 changed files with 222 additions and 280 deletions

View file

@ -2,51 +2,54 @@
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
from UM.Application import Application
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
# ## This is the base model class for GenericMaterialsModel and MaterialBrandsModel.
# 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.
# 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
# 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
# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu
#
class BaseMaterialsModel(ListModel): 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() extruderPositionChanged = pyqtSignal()
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)
self._application = Application.getInstance()
self._machine_manager = self._application.getMachineManager()
self.addRoleName(self.RootMaterialIdRole, "root_material_id") from cura.CuraApplication import CuraApplication
self.addRoleName(self.IdRole, "id")
self.addRoleName(self.GUIDRole, "GUID") self._application = CuraApplication.getInstance()
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.BrandRole, "brand") # Make these managers available to all material models
self.addRoleName(self.MaterialRole, "material") self._extruder_manager = self._application.getExtruderManager()
self.addRoleName(self.ColorRole, "color_name") self._machine_manager = self._application.getMachineManager()
self.addRoleName(self.ColorCodeRole, "color_code") self._material_manager = self._application.getMaterialManager()
self.addRoleName(self.ContainerNodeRole, "container_node")
self.addRoleName(self.IsFavoriteRole, "is_favorite") # 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_position = 0
self._extruder_stack = None 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): def _updateExtruderStack(self):
global_stack = self._machine_manager.activeMachine global_stack = self._machine_manager.activeMachine
@ -71,8 +74,30 @@ class BaseMaterialsModel(ListModel):
def extruderPosition(self) -> int: def extruderPosition(self) -> int:
return self._extruder_position return self._extruder_position
# ## This is an abstract method that needs to be implemented by the specific
# This is an abstract method that needs to be implemented by # models themselves.
#
def _update(self): def _update(self):
pass 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

View file

@ -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)

View file

@ -8,51 +8,32 @@ class FavoriteMaterialsModel(BaseMaterialsModel):
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) 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._material_manager.favoritesUpdated.connect(self._update) # Update when favorites are changed
self._update() self._update()
def _update(self): def _update(self):
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
global_stack = self._machine_manager.activeMachine # Perform standard check and reset if the check fails
if global_stack is None: if not self._canUpdate():
self.setItems([]) self.setItems([])
return return
extruder_position = str(self._extruder_position) # Get updated list of favorites
if extruder_position not in global_stack.extruders: self._favorite_ids = self._material_manager.getFavorites()
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()
item_list = [] item_list = []
for root_material_id, container_node in available_material_dict.items():
metadata = container_node.metadata
# Only add results for favorite materials for root_material_id, container_node in self._available_materials.items():
if root_material_id not in favorite_ids: metadata = container_node.metadata
continue
# Do not include the materials from a to-be-removed package # Do not include the materials from a to-be-removed package
if bool(metadata.get("removed", False)): if bool(metadata.get("removed", False)):
continue continue
# Only add results for favorite materials
if root_material_id not in self._favorite_ids:
continue
item = { item = {
"root_material_id": root_material_id, "root_material_id": root_material_id,
"id": metadata["id"], "id": metadata["id"],
@ -63,11 +44,11 @@ class FavoriteMaterialsModel(BaseMaterialsModel):
"color_name": metadata["color_name"], "color_name": metadata["color_name"],
"color_code": metadata["color_code"], "color_code": metadata["color_code"],
"container_node": container_node, "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) 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()) item_list = sorted(item_list, key = lambda d: d["brand"].upper())
self.setItems(item_list) self.setItems(item_list)

View file

@ -4,44 +4,25 @@
from UM.Logger import Logger from UM.Logger import Logger
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
class GenericMaterialsModel(BaseMaterialsModel): class GenericMaterialsModel(BaseMaterialsModel):
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) 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() self._update()
def _update(self): def _update(self):
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
global_stack = self._machine_manager.activeMachine # Perform standard check and reset if the check fails
if global_stack is None: 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([]) self.setItems([])
return return
favorites = self._material_manager.getFavorites() # Get updated list of favorites
self._favorite_ids = self._material_manager.getFavorites()
item_list = [] 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 metadata = container_node.metadata
# Only add results for generic materials # Only add results for generic materials
@ -54,19 +35,19 @@ class GenericMaterialsModel(BaseMaterialsModel):
item = { item = {
"root_material_id": root_material_id, "root_material_id": root_material_id,
"id": metadata["id"], "id": metadata["id"],
"GUID": metadata["GUID"], "GUID": metadata["GUID"],
"name": metadata["name"], "name": metadata["name"],
"brand": metadata["brand"], "brand": metadata["brand"],
"material": metadata["material"], "material": metadata["material"],
"color_name": metadata["color_name"], "color_name": metadata["color_name"],
"color_code": metadata["color_code"], "color_code": metadata["color_code"],
"container_node": container_node, "container_node": container_node,
"is_favorite": root_material_id in favorites "is_favorite": root_material_id in self._favorite_ids
} }
item_list.append(item) 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()) item_list = sorted(item_list, key = lambda d: d["name"].upper())
self.setItems(item_list) self.setItems(item_list)

View file

@ -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)

View file

@ -26,7 +26,7 @@ Menu
extruderPosition: menu.extruderIndex extruderPosition: menu.extruderIndex
} }
Cura.BrandMaterialsModel Cura.MaterialBrandsModel
{ {
id: brandModel id: brandModel
extruderPosition: menu.extruderIndex extruderPosition: menu.extruderIndex
@ -80,7 +80,7 @@ Menu
id: brandMenu id: brandMenu
title: brandName title: brandName
property string brandName: model.name property string brandName: model.name
property var brandMaterials: model.materials property var brandMaterials: model.material_types
Instantiator Instantiator
{ {

View file

@ -18,7 +18,7 @@ Item
// Children // Children
UM.I18nCatalog { id: catalog; name: "cura"; } UM.I18nCatalog { id: catalog; name: "cura"; }
Cura.BrandMaterialsModel { id: materialsModel } Cura.MaterialBrandsModel { id: materialsModel }
Cura.FavoriteMaterialsModel { id: favoriteMaterialsModel } Cura.FavoriteMaterialsModel { id: favoriteMaterialsModel }
Cura.GenericMaterialsModel { id: genericMaterialsModel } Cura.GenericMaterialsModel { id: genericMaterialsModel }
Column Column
@ -186,7 +186,7 @@ Item
{ {
id: brand_section id: brand_section
property var expanded: true property var expanded: true
property var types_model: model.materials property var types_model: model.material_types
height: childrenRect.height height: childrenRect.height
width: parent.width width: parent.width
Rectangle Rectangle

View file

@ -31,7 +31,7 @@ Item
id: catalog id: catalog
name: "cura" name: "cura"
} }
Cura.BrandMaterialsModel Cura.MaterialBrandsModel
{ {
id: materialsModel id: materialsModel
} }