mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-10 07:15:03 -06:00
Move models around again
- Move machine related models to cura.Machines.Models - Move printer device related models to cura.PrinterOutput.Models - Other UI/GUI related modules in cura.UI
This commit is contained in:
parent
85b28a0c90
commit
4a171eebf6
48 changed files with 99 additions and 101 deletions
152
cura/Machines/Models/BaseMaterialsModel.py
Normal file
152
cura/Machines/Models/BaseMaterialsModel.py
Normal file
|
@ -0,0 +1,152 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Optional, Dict, Set
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
|
||||
## 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
|
||||
from cura.Machines.MaterialNode import MaterialNode
|
||||
|
||||
|
||||
class BaseMaterialsModel(ListModel):
|
||||
|
||||
extruderPositionChanged = pyqtSignal()
|
||||
enabledChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
self._available_materials = None # type: Optional[Dict[str, MaterialNode]]
|
||||
self._favorite_ids = set() # type: Set[str]
|
||||
self._enabled = True
|
||||
|
||||
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.approximateMaterialDiameterChanged.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)
|
||||
self._extruder_stack.approximateMaterialDiameterChanged.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 setEnabled(self, enabled):
|
||||
if self._enabled != enabled:
|
||||
self._enabled = enabled
|
||||
if self._enabled:
|
||||
# ensure the data is there again.
|
||||
self._update()
|
||||
self.enabledChanged.emit()
|
||||
|
||||
@pyqtProperty(bool, fset=setEnabled, notify=enabledChanged)
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
|
||||
## 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 or not self._enabled:
|
||||
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.getMetadata()
|
||||
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.get("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
|
||||
|
51
cura/Machines/Models/BuildPlateModel.py
Normal file
51
cura/Machines/Models/BuildPlateModel.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Util import parseBool
|
||||
|
||||
from cura.Machines.VariantType import VariantType
|
||||
|
||||
|
||||
class BuildPlateModel(ListModel):
|
||||
NameRole = Qt.UserRole + 1
|
||||
ContainerNodeRole = Qt.UserRole + 2
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.addRoleName(self.NameRole, "name")
|
||||
self.addRoleName(self.ContainerNodeRole, "container_node")
|
||||
|
||||
self._application = Application.getInstance()
|
||||
self._variant_manager = self._application._variant_manager
|
||||
self._machine_manager = self._application.getMachineManager()
|
||||
|
||||
self._machine_manager.globalContainerChanged.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._global_container_stack
|
||||
if not global_stack:
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
has_variants = parseBool(global_stack.getMetaDataEntry("has_variant_buildplates", False))
|
||||
if not has_variants:
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
variant_dict = self._variant_manager.getVariantNodes(global_stack, variant_type = VariantType.BUILD_PLATE)
|
||||
|
||||
item_list = []
|
||||
for name, variant_node in variant_dict.items():
|
||||
item = {"name": name,
|
||||
"container_node": variant_node}
|
||||
item_list.append(item)
|
||||
self.setItems(item_list)
|
|
@ -0,0 +1,37 @@
|
|||
# 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.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
|
||||
|
||||
|
||||
#
|
||||
# This model is used for the custom profile items in the profile drop down menu.
|
||||
#
|
||||
class CustomQualityProfilesDropDownMenuModel(QualityProfilesDropDownMenuModel):
|
||||
|
||||
def _update(self):
|
||||
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
|
||||
|
||||
active_global_stack = self._machine_manager.activeMachine
|
||||
if active_global_stack is None:
|
||||
self.setItems([])
|
||||
Logger.log("d", "No active GlobalStack, set %s as empty.", self.__class__.__name__)
|
||||
return
|
||||
|
||||
quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(active_global_stack)
|
||||
|
||||
item_list = []
|
||||
for key in sorted(quality_changes_group_dict, key = lambda name: name.upper()):
|
||||
quality_changes_group = quality_changes_group_dict[key]
|
||||
|
||||
item = {"name": quality_changes_group.name,
|
||||
"layer_height": "",
|
||||
"layer_height_without_unit": "",
|
||||
"available": quality_changes_group.is_available,
|
||||
"quality_changes_group": quality_changes_group}
|
||||
|
||||
item_list.append(item)
|
||||
|
||||
self.setItems(item_list)
|
140
cura/Machines/Models/DiscoveredPrintersModel.py
Normal file
140
cura/Machines/Models/DiscoveredPrintersModel.py
Normal file
|
@ -0,0 +1,140 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Callable, Dict, List, Optional, TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtProperty, pyqtSignal, QObject
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from PyQt5.QtCore import QObject
|
||||
|
||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice
|
||||
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class DiscoveredPrinter(QObject):
|
||||
|
||||
def __init__(self, ip_address: str, key: str, name: str, create_callback: Callable[[str], None], machine_type: str,
|
||||
device: "NetworkedPrinterOutputDevice", parent: Optional["QObject"] = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self._ip_address = ip_address
|
||||
self._key = key
|
||||
self._name = name
|
||||
self.create_callback = create_callback
|
||||
self._machine_type = machine_type
|
||||
self._device = device
|
||||
|
||||
nameChanged = pyqtSignal()
|
||||
|
||||
def getKey(self) -> str:
|
||||
return self._key
|
||||
|
||||
@pyqtProperty(str, notify = nameChanged)
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
def setName(self, name: str) -> None:
|
||||
if self._name != name:
|
||||
self._name = name
|
||||
self.nameChanged.emit()
|
||||
|
||||
machineTypeChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(str, notify = machineTypeChanged)
|
||||
def machine_type(self) -> str:
|
||||
return self._machine_type
|
||||
|
||||
def setMachineType(self, machine_type: str) -> None:
|
||||
if self._machine_type != machine_type:
|
||||
self._machine_type = machine_type
|
||||
self.machineTypeChanged.emit()
|
||||
|
||||
# Human readable machine type string
|
||||
@pyqtProperty(str, notify = machineTypeChanged)
|
||||
def readable_machine_type(self) -> str:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
readable_type = CuraApplication.getInstance().getMachineManager().getMachineTypeNameFromId(self._machine_type)
|
||||
if not readable_type:
|
||||
readable_type = catalog.i18nc("@label", "Unknown")
|
||||
return readable_type
|
||||
|
||||
@pyqtProperty(bool, notify = machineTypeChanged)
|
||||
def is_unknown_machine_type(self) -> bool:
|
||||
return self.readable_machine_type.lower() == "unknown"
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def device(self) -> "NetworkedPrinterOutputDevice":
|
||||
return self._device
|
||||
|
||||
|
||||
#
|
||||
# Discovered printers are all the printers that were found on the network, which provide a more convenient way
|
||||
# to add networked printers (Plugin finds a bunch of printers, user can select one from the list, plugin can then
|
||||
# add that printer to Cura as the active one).
|
||||
#
|
||||
class DiscoveredPrintersModel(QObject):
|
||||
|
||||
def __init__(self, parent: Optional["QObject"] = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self._discovered_printer_by_ip_dict = dict() # type: Dict[str, DiscoveredPrinter]
|
||||
|
||||
discoveredPrintersChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(list, notify = discoveredPrintersChanged)
|
||||
def discovered_printers(self) -> List["DiscoveredPrinter"]:
|
||||
item_list = list(x for x in self._discovered_printer_by_ip_dict.values())
|
||||
item_list.sort(key = lambda x: x.name)
|
||||
return item_list
|
||||
|
||||
def addDiscoveredPrinter(self, ip_address: str, key: str, name: str, create_callback: Callable[[str], None],
|
||||
machine_type: str, device: "NetworkedPrinterOutputDevice") -> None:
|
||||
if ip_address in self._discovered_printer_by_ip_dict:
|
||||
Logger.log("e", "Printer with ip [%s] has already been added", ip_address)
|
||||
return
|
||||
|
||||
discovered_printer = DiscoveredPrinter(ip_address, key, name, create_callback, machine_type, device, parent = self)
|
||||
self._discovered_printer_by_ip_dict[ip_address] = discovered_printer
|
||||
self.discoveredPrintersChanged.emit()
|
||||
|
||||
def updateDiscoveredPrinter(self, ip_address: str,
|
||||
name: Optional[str] = None,
|
||||
machine_type: Optional[str] = None) -> None:
|
||||
if ip_address not in self._discovered_printer_by_ip_dict:
|
||||
Logger.log("e", "Printer with ip [%s] is not known", ip_address)
|
||||
return
|
||||
|
||||
item = self._discovered_printer_by_ip_dict[ip_address]
|
||||
|
||||
if name is not None:
|
||||
item.setName(name)
|
||||
if machine_type is not None:
|
||||
item.setMachineType(machine_type)
|
||||
|
||||
def removeDiscoveredPrinter(self, ip_address: str) -> None:
|
||||
if ip_address not in self._discovered_printer_by_ip_dict:
|
||||
Logger.log("i", "Key [%s] does not exist in the discovered printers list.", ip_address)
|
||||
return
|
||||
|
||||
del self._discovered_printer_by_ip_dict[ip_address]
|
||||
self.discoveredPrintersChanged.emit()
|
||||
|
||||
# A convenience function for QML to create a machine (GlobalStack) out of the given discovered printer.
|
||||
# This function invokes the given discovered printer's "create_callback" to do this.
|
||||
@pyqtSlot("QVariant")
|
||||
def createMachineFromDiscoveredPrinter(self, discovered_printer: "DiscoveredPrinter") -> None:
|
||||
discovered_printer.create_callback(discovered_printer.getKey())
|
||||
|
||||
@pyqtSlot(str)
|
||||
def createMachineFromDiscoveredPrinterAddress(self, ip_address: str) -> None:
|
||||
if ip_address not in self._discovered_printer_by_ip_dict:
|
||||
Logger.log("i", "Key [%s] does not exist in the discovered printers list.", ip_address)
|
||||
return
|
||||
|
||||
self.createMachineFromDiscoveredPrinter(self._discovered_printer_by_ip_dict[ip_address])
|
222
cura/Machines/Models/ExtrudersModel.py
Normal file
222
cura/Machines/Models/ExtrudersModel.py
Normal file
|
@ -0,0 +1,222 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer
|
||||
from typing import Iterable, TYPE_CHECKING
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Application import Application
|
||||
import UM.FlameProfiler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Settings.ExtruderStack import ExtruderStack # To listen to changes on the extruders.
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
## Model that holds extruders.
|
||||
#
|
||||
# This model is designed for use by any list of extruders, but specifically
|
||||
# intended for drop-down lists of the current machine's extruders in place of
|
||||
# settings.
|
||||
class ExtrudersModel(ListModel):
|
||||
# The ID of the container stack for the extruder.
|
||||
IdRole = Qt.UserRole + 1
|
||||
|
||||
## Human-readable name of the extruder.
|
||||
NameRole = Qt.UserRole + 2
|
||||
|
||||
## Colour of the material loaded in the extruder.
|
||||
ColorRole = Qt.UserRole + 3
|
||||
|
||||
## Index of the extruder, which is also the value of the setting itself.
|
||||
#
|
||||
# An index of 0 indicates the first extruder, an index of 1 the second
|
||||
# one, and so on. This is the value that will be saved in instance
|
||||
# containers.
|
||||
IndexRole = Qt.UserRole + 4
|
||||
|
||||
# The ID of the definition of the extruder.
|
||||
DefinitionRole = Qt.UserRole + 5
|
||||
|
||||
# The material of the extruder.
|
||||
MaterialRole = Qt.UserRole + 6
|
||||
|
||||
# The variant of the extruder.
|
||||
VariantRole = Qt.UserRole + 7
|
||||
StackRole = Qt.UserRole + 8
|
||||
|
||||
MaterialBrandRole = Qt.UserRole + 9
|
||||
ColorNameRole = Qt.UserRole + 10
|
||||
|
||||
## Is the extruder enabled?
|
||||
EnabledRole = Qt.UserRole + 11
|
||||
|
||||
## List of colours to display if there is no material or the material has no known
|
||||
# colour.
|
||||
defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
|
||||
|
||||
## Initialises the extruders model, defining the roles and listening for
|
||||
# changes in the data.
|
||||
#
|
||||
# \param parent Parent QtObject of this list.
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.addRoleName(self.IdRole, "id")
|
||||
self.addRoleName(self.NameRole, "name")
|
||||
self.addRoleName(self.EnabledRole, "enabled")
|
||||
self.addRoleName(self.ColorRole, "color")
|
||||
self.addRoleName(self.IndexRole, "index")
|
||||
self.addRoleName(self.DefinitionRole, "definition")
|
||||
self.addRoleName(self.MaterialRole, "material")
|
||||
self.addRoleName(self.VariantRole, "variant")
|
||||
self.addRoleName(self.StackRole, "stack")
|
||||
self.addRoleName(self.MaterialBrandRole, "material_brand")
|
||||
self.addRoleName(self.ColorNameRole, "color_name")
|
||||
self._update_extruder_timer = QTimer()
|
||||
self._update_extruder_timer.setInterval(100)
|
||||
self._update_extruder_timer.setSingleShot(True)
|
||||
self._update_extruder_timer.timeout.connect(self.__updateExtruders)
|
||||
|
||||
self._active_machine_extruders = [] # type: Iterable[ExtruderStack]
|
||||
self._add_optional_extruder = False
|
||||
|
||||
# Listen to changes
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._extrudersChanged) # When the machine is swapped we must update the active machine extruders
|
||||
Application.getInstance().getExtruderManager().extrudersChanged.connect(self._extrudersChanged) # When the extruders change we must link to the stack-changed signal of the new extruder
|
||||
Application.getInstance().getContainerRegistry().containerMetaDataChanged.connect(self._onExtruderStackContainersChanged) # When meta data from a material container changes we must update
|
||||
self._extrudersChanged() # Also calls _updateExtruders
|
||||
|
||||
addOptionalExtruderChanged = pyqtSignal()
|
||||
|
||||
def setAddOptionalExtruder(self, add_optional_extruder):
|
||||
if add_optional_extruder != self._add_optional_extruder:
|
||||
self._add_optional_extruder = add_optional_extruder
|
||||
self.addOptionalExtruderChanged.emit()
|
||||
self._updateExtruders()
|
||||
|
||||
@pyqtProperty(bool, fset = setAddOptionalExtruder, notify = addOptionalExtruderChanged)
|
||||
def addOptionalExtruder(self):
|
||||
return self._add_optional_extruder
|
||||
|
||||
## Links to the stack-changed signal of the new extruders when an extruder
|
||||
# is swapped out or added in the current machine.
|
||||
#
|
||||
# \param machine_id The machine for which the extruders changed. This is
|
||||
# filled by the ExtruderManager.extrudersChanged signal when coming from
|
||||
# that signal. Application.globalContainerStackChanged doesn't fill this
|
||||
# signal; it's assumed to be the current printer in that case.
|
||||
def _extrudersChanged(self, machine_id = None):
|
||||
machine_manager = Application.getInstance().getMachineManager()
|
||||
if machine_id is not None:
|
||||
if machine_manager.activeMachine is None:
|
||||
# No machine, don't need to update the current machine's extruders
|
||||
return
|
||||
if machine_id != machine_manager.activeMachine.getId():
|
||||
# Not the current machine
|
||||
return
|
||||
|
||||
# Unlink from old extruders
|
||||
for extruder in self._active_machine_extruders:
|
||||
extruder.containersChanged.disconnect(self._onExtruderStackContainersChanged)
|
||||
extruder.enabledChanged.disconnect(self._updateExtruders)
|
||||
|
||||
# Link to new extruders
|
||||
self._active_machine_extruders = []
|
||||
extruder_manager = Application.getInstance().getExtruderManager()
|
||||
for extruder in extruder_manager.getActiveExtruderStacks():
|
||||
if extruder is None: #This extruder wasn't loaded yet. This happens asynchronously while this model is constructed from QML.
|
||||
continue
|
||||
extruder.containersChanged.connect(self._onExtruderStackContainersChanged)
|
||||
extruder.enabledChanged.connect(self._updateExtruders)
|
||||
self._active_machine_extruders.append(extruder)
|
||||
|
||||
self._updateExtruders() # Since the new extruders may have different properties, update our own model.
|
||||
|
||||
def _onExtruderStackContainersChanged(self, container):
|
||||
# Update when there is an empty container or material or variant change
|
||||
if container.getMetaDataEntry("type") in ["material", "variant", None]:
|
||||
# The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name
|
||||
self._updateExtruders()
|
||||
|
||||
modelChanged = pyqtSignal()
|
||||
|
||||
def _updateExtruders(self):
|
||||
self._update_extruder_timer.start()
|
||||
|
||||
## Update the list of extruders.
|
||||
#
|
||||
# This should be called whenever the list of extruders changes.
|
||||
@UM.FlameProfiler.profile
|
||||
def __updateExtruders(self):
|
||||
extruders_changed = False
|
||||
|
||||
if self.count != 0:
|
||||
extruders_changed = True
|
||||
|
||||
items = []
|
||||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
|
||||
# get machine extruder count for verification
|
||||
machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value")
|
||||
|
||||
for extruder in Application.getInstance().getExtruderManager().getActiveExtruderStacks():
|
||||
position = extruder.getMetaDataEntry("position", default = "0")
|
||||
try:
|
||||
position = int(position)
|
||||
except ValueError:
|
||||
# Not a proper int.
|
||||
position = -1
|
||||
if position >= machine_extruder_count:
|
||||
continue
|
||||
|
||||
default_color = self.defaultColors[position] if 0 <= position < len(self.defaultColors) else self.defaultColors[0]
|
||||
color = extruder.material.getMetaDataEntry("color_code", default = default_color) if extruder.material else default_color
|
||||
material_brand = extruder.material.getMetaDataEntry("brand", default = "generic")
|
||||
color_name = extruder.material.getMetaDataEntry("color_name")
|
||||
# construct an item with only the relevant information
|
||||
item = {
|
||||
"id": extruder.getId(),
|
||||
"name": extruder.getName(),
|
||||
"enabled": extruder.isEnabled,
|
||||
"color": color,
|
||||
"index": position,
|
||||
"definition": extruder.getBottom().getId(),
|
||||
"material": extruder.material.getName() if extruder.material else "",
|
||||
"variant": extruder.variant.getName() if extruder.variant else "", # e.g. print core
|
||||
"stack": extruder,
|
||||
"material_brand": material_brand,
|
||||
"color_name": color_name
|
||||
}
|
||||
|
||||
items.append(item)
|
||||
extruders_changed = True
|
||||
|
||||
if extruders_changed:
|
||||
# sort by extruder index
|
||||
items.sort(key = lambda i: i["index"])
|
||||
|
||||
# We need optional extruder to be last, so add it after we do sorting.
|
||||
# This way we can simply interpret the -1 of the index as the last item (which it now always is)
|
||||
if self._add_optional_extruder:
|
||||
item = {
|
||||
"id": "",
|
||||
"name": catalog.i18nc("@menuitem", "Not overridden"),
|
||||
"enabled": True,
|
||||
"color": "#ffffff",
|
||||
"index": -1,
|
||||
"definition": "",
|
||||
"material": "",
|
||||
"variant": "",
|
||||
"stack": None,
|
||||
"material_brand": "",
|
||||
"color_name": "",
|
||||
}
|
||||
items.append(item)
|
||||
if self._items != items:
|
||||
self.setItems(items)
|
||||
self.modelChanged.emit()
|
38
cura/Machines/Models/FavoriteMaterialsModel.py
Normal file
38
cura/Machines/Models/FavoriteMaterialsModel.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
||||
|
||||
## Model that shows the list of favorite materials.
|
||||
class FavoriteMaterialsModel(BaseMaterialsModel):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
self._update()
|
||||
|
||||
def _update(self):
|
||||
if not self._canUpdate():
|
||||
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.getMetadata()
|
||||
|
||||
# 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)
|
38
cura/Machines/Models/GenericMaterialsModel.py
Normal file
38
cura/Machines/Models/GenericMaterialsModel.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
||||
|
||||
class GenericMaterialsModel(BaseMaterialsModel):
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
self._update()
|
||||
|
||||
def _update(self):
|
||||
if not self._canUpdate():
|
||||
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.getMetadata()
|
||||
|
||||
# Do not include the materials from a to-be-removed package
|
||||
if bool(metadata.get("removed", False)):
|
||||
continue
|
||||
|
||||
# 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 alphabetically by name
|
||||
item_list = sorted(item_list, key = lambda d: d["name"].upper())
|
||||
|
||||
self.setItems(item_list)
|
77
cura/Machines/Models/GlobalStacksModel.py
Normal file
77
cura/Machines/Models/GlobalStacksModel.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
|
||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
|
||||
class GlobalStacksModel(ListModel):
|
||||
NameRole = Qt.UserRole + 1
|
||||
IdRole = Qt.UserRole + 2
|
||||
HasRemoteConnectionRole = Qt.UserRole + 3
|
||||
ConnectionTypeRole = Qt.UserRole + 4
|
||||
MetaDataRole = Qt.UserRole + 5
|
||||
DiscoverySourceRole = Qt.UserRole + 6 # For separating local and remote printers in the machine management page
|
||||
|
||||
def __init__(self, parent = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self._catalog = i18nCatalog("cura")
|
||||
|
||||
self.addRoleName(self.NameRole, "name")
|
||||
self.addRoleName(self.IdRole, "id")
|
||||
self.addRoleName(self.HasRemoteConnectionRole, "hasRemoteConnection")
|
||||
self.addRoleName(self.MetaDataRole, "metadata")
|
||||
self.addRoleName(self.DiscoverySourceRole, "discoverySource")
|
||||
|
||||
self._change_timer = QTimer()
|
||||
self._change_timer.setInterval(200)
|
||||
self._change_timer.setSingleShot(True)
|
||||
self._change_timer.timeout.connect(self._update)
|
||||
|
||||
# Listen to changes
|
||||
CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
|
||||
CuraContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerChanged)
|
||||
CuraContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
|
||||
self._updateDelayed()
|
||||
|
||||
## Handler for container added/removed events from registry
|
||||
def _onContainerChanged(self, container) -> None:
|
||||
# We only need to update when the added / removed container GlobalStack
|
||||
if isinstance(container, GlobalStack):
|
||||
self._updateDelayed()
|
||||
|
||||
def _updateDelayed(self) -> None:
|
||||
self._change_timer.start()
|
||||
|
||||
def _update(self) -> None:
|
||||
items = []
|
||||
|
||||
container_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine")
|
||||
|
||||
for container_stack in container_stacks:
|
||||
has_remote_connection = False
|
||||
|
||||
for connection_type in container_stack.configuredConnectionTypes:
|
||||
has_remote_connection |= connection_type in [ConnectionType.NetworkConnection.value,
|
||||
ConnectionType.CloudConnection.value]
|
||||
|
||||
if container_stack.getMetaDataEntry("hidden", False) in ["True", True]:
|
||||
continue
|
||||
|
||||
section_name = "Network enabled printers" if has_remote_connection else "Local printers"
|
||||
section_name = self._catalog.i18nc("@info:title", section_name)
|
||||
|
||||
items.append({"name": container_stack.getMetaDataEntry("group_name", container_stack.getName()),
|
||||
"id": container_stack.getId(),
|
||||
"hasRemoteConnection": has_remote_connection,
|
||||
"metadata": container_stack.getMetaData().copy(),
|
||||
"discoverySource": section_name})
|
||||
items.sort(key = lambda i: not i["hasRemoteConnection"])
|
||||
self.setItems(items)
|
82
cura/Machines/Models/MachineManagementModel.py
Normal file
82
cura/Machines/Models/MachineManagementModel.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
#
|
||||
# This the QML model for the quality management page.
|
||||
#
|
||||
class MachineManagementModel(ListModel):
|
||||
NameRole = Qt.UserRole + 1
|
||||
IdRole = Qt.UserRole + 2
|
||||
MetaDataRole = Qt.UserRole + 3
|
||||
GroupRole = Qt.UserRole + 4
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
self.addRoleName(self.NameRole, "name")
|
||||
self.addRoleName(self.IdRole, "id")
|
||||
self.addRoleName(self.MetaDataRole, "metadata")
|
||||
self.addRoleName(self.GroupRole, "group")
|
||||
self._local_container_stacks = []
|
||||
self._network_container_stacks = []
|
||||
|
||||
# Listen to changes
|
||||
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
|
||||
ContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerChanged)
|
||||
ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
|
||||
self._filter_dict = {}
|
||||
self._update()
|
||||
|
||||
## Handler for container added/removed events from registry
|
||||
def _onContainerChanged(self, container):
|
||||
# We only need to update when the added / removed container is a stack.
|
||||
if isinstance(container, ContainerStack) and container.getMetaDataEntry("type") == "machine":
|
||||
self._update()
|
||||
|
||||
## Private convenience function to reset & repopulate the model.
|
||||
def _update(self):
|
||||
items = []
|
||||
|
||||
# Get first the network enabled printers
|
||||
network_filter_printers = {"type": "machine",
|
||||
"um_network_key": "*",
|
||||
"hidden": "False"}
|
||||
self._network_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**network_filter_printers)
|
||||
self._network_container_stacks.sort(key = lambda i: i.getMetaDataEntry("group_name", ""))
|
||||
|
||||
for container in self._network_container_stacks:
|
||||
metadata = container.getMetaData().copy()
|
||||
if container.getBottom():
|
||||
metadata["definition_name"] = container.getBottom().getName()
|
||||
|
||||
items.append({"name": metadata.get("group_name", ""),
|
||||
"id": container.getId(),
|
||||
"metadata": metadata,
|
||||
"group": catalog.i18nc("@info:title", "Network enabled printers")})
|
||||
|
||||
# Get now the local printers
|
||||
local_filter_printers = {"type": "machine", "um_network_key": None}
|
||||
self._local_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**local_filter_printers)
|
||||
self._local_container_stacks.sort(key = lambda i: i.getName())
|
||||
|
||||
for container in self._local_container_stacks:
|
||||
metadata = container.getMetaData().copy()
|
||||
if container.getBottom():
|
||||
metadata["definition_name"] = container.getBottom().getName()
|
||||
|
||||
items.append({"name": container.getName(),
|
||||
"id": container.getId(),
|
||||
"metadata": metadata,
|
||||
"group": catalog.i18nc("@info:title", "Local printers")})
|
||||
|
||||
self.setItems(items)
|
102
cura/Machines/Models/MaterialBrandsModel.py
Normal file
102
cura/Machines/Models/MaterialBrandsModel.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal
|
||||
from UM.Qt.ListModel import ListModel
|
||||
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, "brand")
|
||||
self.addRoleName(Qt.UserRole + 3, "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):
|
||||
if not self._canUpdate():
|
||||
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():
|
||||
# Do not include the materials from a to-be-removed package
|
||||
if bool(container_node.getMetaDataEntry("removed", False)):
|
||||
continue
|
||||
|
||||
# Add brands we haven't seen yet to the dict, skipping generics
|
||||
brand = container_node.getMetaDataEntry("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 = container_node.getMetaDataEntry("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,
|
||||
"brand": brand,
|
||||
"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)
|
69
cura/Machines/Models/MultiBuildPlateModel.py
Normal file
69
cura/Machines/Models/MultiBuildPlateModel.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QTimer, pyqtSignal, pyqtProperty
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Scene.Camera import Camera
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
|
||||
#
|
||||
# This is the model for multi build plate feature.
|
||||
# This has nothing to do with the build plate types you can choose on the sidebar for a machine.
|
||||
#
|
||||
class MultiBuildPlateModel(ListModel):
|
||||
|
||||
maxBuildPlateChanged = pyqtSignal()
|
||||
activeBuildPlateChanged = pyqtSignal()
|
||||
selectionChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._update_timer = QTimer()
|
||||
self._update_timer.setInterval(100)
|
||||
self._update_timer.setSingleShot(True)
|
||||
self._update_timer.timeout.connect(self._updateSelectedObjectBuildPlateNumbers)
|
||||
|
||||
self._application = Application.getInstance()
|
||||
self._application.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbersDelayed)
|
||||
Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
|
||||
|
||||
self._max_build_plate = 1 # default
|
||||
self._active_build_plate = -1
|
||||
|
||||
def setMaxBuildPlate(self, max_build_plate):
|
||||
if self._max_build_plate != max_build_plate:
|
||||
self._max_build_plate = max_build_plate
|
||||
self.maxBuildPlateChanged.emit()
|
||||
|
||||
## Return the highest build plate number
|
||||
@pyqtProperty(int, notify = maxBuildPlateChanged)
|
||||
def maxBuildPlate(self):
|
||||
return self._max_build_plate
|
||||
|
||||
def setActiveBuildPlate(self, nr):
|
||||
if self._active_build_plate != nr:
|
||||
self._active_build_plate = nr
|
||||
self.activeBuildPlateChanged.emit()
|
||||
|
||||
@pyqtProperty(int, notify = activeBuildPlateChanged)
|
||||
def activeBuildPlate(self):
|
||||
return self._active_build_plate
|
||||
|
||||
def _updateSelectedObjectBuildPlateNumbersDelayed(self, *args):
|
||||
if not isinstance(args[0], Camera):
|
||||
self._update_timer.start()
|
||||
|
||||
def _updateSelectedObjectBuildPlateNumbers(self, *args):
|
||||
result = set()
|
||||
for node in Selection.getAllSelectedObjects():
|
||||
result.add(node.callDecoration("getBuildPlateNumber"))
|
||||
self._selection_build_plates = list(result)
|
||||
self.selectionChanged.emit()
|
||||
|
||||
@pyqtProperty("QVariantList", notify = selectionChanged)
|
||||
def selectionBuildPlates(self):
|
||||
return self._selection_build_plates
|
60
cura/Machines/Models/NozzleModel.py
Normal file
60
cura/Machines/Models/NozzleModel.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Util import parseBool
|
||||
|
||||
from cura.Machines.VariantType import VariantType
|
||||
|
||||
|
||||
class NozzleModel(ListModel):
|
||||
IdRole = Qt.UserRole + 1
|
||||
HotendNameRole = Qt.UserRole + 2
|
||||
ContainerNodeRole = Qt.UserRole + 3
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.addRoleName(self.IdRole, "id")
|
||||
self.addRoleName(self.HotendNameRole, "hotend_name")
|
||||
self.addRoleName(self.ContainerNodeRole, "container_node")
|
||||
|
||||
self._application = Application.getInstance()
|
||||
self._machine_manager = self._application.getMachineManager()
|
||||
self._variant_manager = self._application.getVariantManager()
|
||||
|
||||
self._machine_manager.globalContainerChanged.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
|
||||
|
||||
has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", False))
|
||||
if not has_variants:
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
variant_node_dict = self._variant_manager.getVariantNodes(global_stack, VariantType.NOZZLE)
|
||||
if not variant_node_dict:
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
item_list = []
|
||||
for hotend_name, container_node in sorted(variant_node_dict.items(), key = lambda i: i[0].upper()):
|
||||
item = {"id": hotend_name,
|
||||
"hotend_name": hotend_name,
|
||||
"container_node": container_node
|
||||
}
|
||||
|
||||
item_list.append(item)
|
||||
|
||||
self.setItems(item_list)
|
125
cura/Machines/Models/QualityManagementModel.py
Normal file
125
cura/Machines/Models/QualityManagementModel.py
Normal file
|
@ -0,0 +1,125 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Logger import Logger
|
||||
|
||||
#
|
||||
# This the QML model for the quality management page.
|
||||
#
|
||||
class QualityManagementModel(ListModel):
|
||||
NameRole = Qt.UserRole + 1
|
||||
IsReadOnlyRole = Qt.UserRole + 2
|
||||
QualityGroupRole = Qt.UserRole + 3
|
||||
QualityChangesGroupRole = Qt.UserRole + 4
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.addRoleName(self.NameRole, "name")
|
||||
self.addRoleName(self.IsReadOnlyRole, "is_read_only")
|
||||
self.addRoleName(self.QualityGroupRole, "quality_group")
|
||||
self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group")
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
self._container_registry = CuraApplication.getInstance().getContainerRegistry()
|
||||
self._machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
|
||||
self._quality_manager = CuraApplication.getInstance().getQualityManager()
|
||||
|
||||
self._machine_manager.globalContainerChanged.connect(self._update)
|
||||
self._quality_manager.qualitiesUpdated.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 not global_stack:
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
quality_group_dict = self._quality_manager.getQualityGroups(global_stack)
|
||||
quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(global_stack)
|
||||
|
||||
available_quality_types = set(quality_type for quality_type, quality_group in quality_group_dict.items()
|
||||
if quality_group.is_available)
|
||||
if not available_quality_types and not quality_changes_group_dict:
|
||||
# Nothing to show
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
item_list = []
|
||||
# Create quality group items
|
||||
for quality_group in quality_group_dict.values():
|
||||
if not quality_group.is_available:
|
||||
continue
|
||||
|
||||
item = {"name": quality_group.name,
|
||||
"is_read_only": True,
|
||||
"quality_group": quality_group,
|
||||
"quality_changes_group": None}
|
||||
item_list.append(item)
|
||||
# Sort by quality names
|
||||
item_list = sorted(item_list, key = lambda x: x["name"].upper())
|
||||
|
||||
# Create quality_changes group items
|
||||
quality_changes_item_list = []
|
||||
for quality_changes_group in quality_changes_group_dict.values():
|
||||
quality_group = quality_group_dict.get(quality_changes_group.quality_type)
|
||||
item = {"name": quality_changes_group.name,
|
||||
"is_read_only": False,
|
||||
"quality_group": quality_group,
|
||||
"quality_changes_group": quality_changes_group}
|
||||
quality_changes_item_list.append(item)
|
||||
|
||||
# Sort quality_changes items by names and append to the item list
|
||||
quality_changes_item_list = sorted(quality_changes_item_list, key = lambda x: x["name"].upper())
|
||||
item_list += quality_changes_item_list
|
||||
|
||||
self.setItems(item_list)
|
||||
|
||||
# TODO: Duplicated code here from InstanceContainersModel. Refactor and remove this later.
|
||||
#
|
||||
## Gets a list of the possible file filters that the plugins have
|
||||
# registered they can read or write. The convenience meta-filters
|
||||
# "All Supported Types" and "All Files" are added when listing
|
||||
# readers, but not when listing writers.
|
||||
#
|
||||
# \param io_type \type{str} name of the needed IO type
|
||||
# \return A list of strings indicating file name filters for a file
|
||||
# dialog.
|
||||
@pyqtSlot(str, result = "QVariantList")
|
||||
def getFileNameFilters(self, io_type):
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("uranium")
|
||||
#TODO: This function should be in UM.Resources!
|
||||
filters = []
|
||||
all_types = []
|
||||
for plugin_id, meta_data in self._getIOPlugins(io_type):
|
||||
for io_plugin in meta_data[io_type]:
|
||||
filters.append(io_plugin["description"] + " (*." + io_plugin["extension"] + ")")
|
||||
all_types.append("*.{0}".format(io_plugin["extension"]))
|
||||
|
||||
if "_reader" in io_type:
|
||||
# if we're listing readers, add the option to show all supported files as the default option
|
||||
filters.insert(0, catalog.i18nc("@item:inlistbox", "All Supported Types ({0})", " ".join(all_types)))
|
||||
filters.append(catalog.i18nc("@item:inlistbox", "All Files (*)")) # Also allow arbitrary files, if the user so prefers.
|
||||
return filters
|
||||
|
||||
## Gets a list of profile reader or writer plugins
|
||||
# \return List of tuples of (plugin_id, meta_data).
|
||||
def _getIOPlugins(self, io_type):
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
pr = PluginRegistry.getInstance()
|
||||
active_plugin_ids = pr.getActivePlugins()
|
||||
|
||||
result = []
|
||||
for plugin_id in active_plugin_ids:
|
||||
meta_data = pr.getMetaData(plugin_id)
|
||||
if io_type in meta_data:
|
||||
result.append( (plugin_id, meta_data) )
|
||||
return result
|
125
cura/Machines/Models/QualityProfilesDropDownMenuModel.py
Normal file
125
cura/Machines/Models/QualityProfilesDropDownMenuModel.py
Normal file
|
@ -0,0 +1,125 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
|
||||
from cura.Machines.QualityManager import QualityGroup
|
||||
|
||||
|
||||
#
|
||||
# QML Model for all built-in quality profiles. This model is used for the drop-down quality menu.
|
||||
#
|
||||
class QualityProfilesDropDownMenuModel(ListModel):
|
||||
NameRole = Qt.UserRole + 1
|
||||
QualityTypeRole = Qt.UserRole + 2
|
||||
LayerHeightRole = Qt.UserRole + 3
|
||||
LayerHeightUnitRole = Qt.UserRole + 4
|
||||
AvailableRole = Qt.UserRole + 5
|
||||
QualityGroupRole = Qt.UserRole + 6
|
||||
QualityChangesGroupRole = Qt.UserRole + 7
|
||||
IsExperimentalRole = Qt.UserRole + 8
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.addRoleName(self.NameRole, "name")
|
||||
self.addRoleName(self.QualityTypeRole, "quality_type")
|
||||
self.addRoleName(self.LayerHeightRole, "layer_height")
|
||||
self.addRoleName(self.LayerHeightUnitRole, "layer_height_unit")
|
||||
self.addRoleName(self.AvailableRole, "available") #Whether the quality profile is available in our current nozzle + material.
|
||||
self.addRoleName(self.QualityGroupRole, "quality_group")
|
||||
self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group")
|
||||
self.addRoleName(self.IsExperimentalRole, "is_experimental")
|
||||
|
||||
self._application = Application.getInstance()
|
||||
self._machine_manager = self._application.getMachineManager()
|
||||
self._quality_manager = Application.getInstance().getQualityManager()
|
||||
|
||||
self._application.globalContainerStackChanged.connect(self._onChange)
|
||||
self._machine_manager.activeQualityGroupChanged.connect(self._onChange)
|
||||
self._machine_manager.extruderChanged.connect(self._onChange)
|
||||
self._quality_manager.qualitiesUpdated.connect(self._onChange)
|
||||
|
||||
self._layer_height_unit = "" # This is cached
|
||||
|
||||
self._update_timer = QTimer() # type: QTimer
|
||||
self._update_timer.setInterval(100)
|
||||
self._update_timer.setSingleShot(True)
|
||||
self._update_timer.timeout.connect(self._update)
|
||||
|
||||
self._onChange()
|
||||
|
||||
def _onChange(self) -> None:
|
||||
self._update_timer.start()
|
||||
|
||||
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([])
|
||||
Logger.log("d", "No active GlobalStack, set quality profile model as empty.")
|
||||
return
|
||||
|
||||
# Check for material compatibility
|
||||
if not self._machine_manager.activeMaterialsCompatible():
|
||||
Logger.log("d", "No active material compatibility, set quality profile model as empty.")
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
quality_group_dict = self._quality_manager.getQualityGroups(global_stack)
|
||||
|
||||
item_list = []
|
||||
for key in sorted(quality_group_dict):
|
||||
quality_group = quality_group_dict[key]
|
||||
|
||||
layer_height = self._fetchLayerHeight(quality_group)
|
||||
|
||||
item = {"name": quality_group.name,
|
||||
"quality_type": quality_group.quality_type,
|
||||
"layer_height": layer_height,
|
||||
"layer_height_unit": self._layer_height_unit,
|
||||
"available": quality_group.is_available,
|
||||
"quality_group": quality_group,
|
||||
"is_experimental": quality_group.is_experimental}
|
||||
|
||||
item_list.append(item)
|
||||
|
||||
# Sort items based on layer_height
|
||||
item_list = sorted(item_list, key = lambda x: x["layer_height"])
|
||||
|
||||
self.setItems(item_list)
|
||||
|
||||
def _fetchLayerHeight(self, quality_group: "QualityGroup") -> float:
|
||||
global_stack = self._machine_manager.activeMachine
|
||||
if not self._layer_height_unit:
|
||||
unit = global_stack.definition.getProperty("layer_height", "unit")
|
||||
if not unit:
|
||||
unit = ""
|
||||
self._layer_height_unit = unit
|
||||
|
||||
default_layer_height = global_stack.definition.getProperty("layer_height", "value")
|
||||
|
||||
# Get layer_height from the quality profile for the GlobalStack
|
||||
if quality_group.node_for_global is None:
|
||||
return float(default_layer_height)
|
||||
container = quality_group.node_for_global.getContainer()
|
||||
|
||||
layer_height = default_layer_height
|
||||
if container and container.hasProperty("layer_height", "value"):
|
||||
layer_height = container.getProperty("layer_height", "value")
|
||||
else:
|
||||
# Look for layer_height in the GlobalStack from material -> definition
|
||||
container = global_stack.definition
|
||||
if container and container.hasProperty("layer_height", "value"):
|
||||
layer_height = container.getProperty("layer_height", "value")
|
||||
|
||||
if isinstance(layer_height, SettingFunction):
|
||||
layer_height = layer_height(global_stack)
|
||||
|
||||
return float(layer_height)
|
162
cura/Machines/Models/QualitySettingsModel.py
Normal file
162
cura/Machines/Models/QualitySettingsModel.py
Normal file
|
@ -0,0 +1,162 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
|
||||
|
||||
#
|
||||
# This model is used to show details settings of the selected quality in the quality management page.
|
||||
#
|
||||
class QualitySettingsModel(ListModel):
|
||||
KeyRole = Qt.UserRole + 1
|
||||
LabelRole = Qt.UserRole + 2
|
||||
UnitRole = Qt.UserRole + 3
|
||||
ProfileValueRole = Qt.UserRole + 4
|
||||
ProfileValueSourceRole = Qt.UserRole + 5
|
||||
UserValueRole = Qt.UserRole + 6
|
||||
CategoryRole = Qt.UserRole + 7
|
||||
|
||||
GLOBAL_STACK_POSITION = -1
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent = parent)
|
||||
|
||||
self.addRoleName(self.KeyRole, "key")
|
||||
self.addRoleName(self.LabelRole, "label")
|
||||
self.addRoleName(self.UnitRole, "unit")
|
||||
self.addRoleName(self.ProfileValueRole, "profile_value")
|
||||
self.addRoleName(self.ProfileValueSourceRole, "profile_value_source")
|
||||
self.addRoleName(self.UserValueRole, "user_value")
|
||||
self.addRoleName(self.CategoryRole, "category")
|
||||
|
||||
self._container_registry = ContainerRegistry.getInstance()
|
||||
self._application = Application.getInstance()
|
||||
self._quality_manager = self._application.getQualityManager()
|
||||
|
||||
self._selected_position = self.GLOBAL_STACK_POSITION #Must be either GLOBAL_STACK_POSITION or an extruder position (0, 1, etc.)
|
||||
self._selected_quality_item = None # The selected quality in the quality management page
|
||||
self._i18n_catalog = None
|
||||
|
||||
self._quality_manager.qualitiesUpdated.connect(self._update)
|
||||
|
||||
self._update()
|
||||
|
||||
selectedPositionChanged = pyqtSignal()
|
||||
selectedQualityItemChanged = pyqtSignal()
|
||||
|
||||
def setSelectedPosition(self, selected_position):
|
||||
if selected_position != self._selected_position:
|
||||
self._selected_position = selected_position
|
||||
self.selectedPositionChanged.emit()
|
||||
self._update()
|
||||
|
||||
@pyqtProperty(int, fset = setSelectedPosition, notify = selectedPositionChanged)
|
||||
def selectedPosition(self):
|
||||
return self._selected_position
|
||||
|
||||
def setSelectedQualityItem(self, selected_quality_item):
|
||||
if selected_quality_item != self._selected_quality_item:
|
||||
self._selected_quality_item = selected_quality_item
|
||||
self.selectedQualityItemChanged.emit()
|
||||
self._update()
|
||||
|
||||
@pyqtProperty("QVariantMap", fset = setSelectedQualityItem, notify = selectedQualityItemChanged)
|
||||
def selectedQualityItem(self):
|
||||
return self._selected_quality_item
|
||||
|
||||
def _update(self):
|
||||
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
|
||||
|
||||
if not self._selected_quality_item:
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
items = []
|
||||
|
||||
global_container_stack = self._application.getGlobalContainerStack()
|
||||
definition_container = global_container_stack.definition
|
||||
|
||||
quality_group = self._selected_quality_item["quality_group"]
|
||||
quality_changes_group = self._selected_quality_item["quality_changes_group"]
|
||||
|
||||
quality_node = None
|
||||
settings_keys = set()
|
||||
if quality_group:
|
||||
if self._selected_position == self.GLOBAL_STACK_POSITION:
|
||||
quality_node = quality_group.node_for_global
|
||||
else:
|
||||
quality_node = quality_group.nodes_for_extruders.get(str(self._selected_position))
|
||||
settings_keys = quality_group.getAllKeys()
|
||||
quality_containers = []
|
||||
if quality_node is not None and quality_node.getContainer() is not None:
|
||||
quality_containers.append(quality_node.getContainer())
|
||||
|
||||
# Here, if the user has selected a quality changes, then "quality_changes_group" will not be None, and we fetch
|
||||
# the settings in that quality_changes_group.
|
||||
if quality_changes_group is not None:
|
||||
if self._selected_position == self.GLOBAL_STACK_POSITION:
|
||||
quality_changes_node = quality_changes_group.node_for_global
|
||||
else:
|
||||
quality_changes_node = quality_changes_group.nodes_for_extruders.get(str(self._selected_position))
|
||||
if quality_changes_node is not None and quality_changes_node.getContainer() is not None: # it can be None if number of extruders are changed during runtime
|
||||
quality_containers.insert(0, quality_changes_node.getContainer())
|
||||
settings_keys.update(quality_changes_group.getAllKeys())
|
||||
|
||||
# We iterate over all definitions instead of settings in a quality/qualtiy_changes group is because in the GUI,
|
||||
# the settings are grouped together by categories, and we had to go over all the definitions to figure out
|
||||
# which setting belongs in which category.
|
||||
current_category = ""
|
||||
for definition in definition_container.findDefinitions():
|
||||
if definition.type == "category":
|
||||
current_category = definition.label
|
||||
if self._i18n_catalog:
|
||||
current_category = self._i18n_catalog.i18nc(definition.key + " label", definition.label)
|
||||
continue
|
||||
|
||||
profile_value = None
|
||||
profile_value_source = ""
|
||||
for quality_container in quality_containers:
|
||||
new_value = quality_container.getProperty(definition.key, "value")
|
||||
|
||||
if new_value is not None:
|
||||
profile_value_source = quality_container.getMetaDataEntry("type")
|
||||
profile_value = new_value
|
||||
|
||||
# Global tab should use resolve (if there is one)
|
||||
if self._selected_position == self.GLOBAL_STACK_POSITION:
|
||||
resolve_value = global_container_stack.getProperty(definition.key, "resolve")
|
||||
if resolve_value is not None and definition.key in settings_keys:
|
||||
profile_value = resolve_value
|
||||
|
||||
if profile_value is not None:
|
||||
break
|
||||
|
||||
if self._selected_position == self.GLOBAL_STACK_POSITION:
|
||||
user_value = global_container_stack.userChanges.getProperty(definition.key, "value")
|
||||
else:
|
||||
extruder_stack = global_container_stack.extruders[str(self._selected_position)]
|
||||
user_value = extruder_stack.userChanges.getProperty(definition.key, "value")
|
||||
|
||||
if profile_value is None and user_value is None:
|
||||
continue
|
||||
|
||||
label = definition.label
|
||||
if self._i18n_catalog:
|
||||
label = self._i18n_catalog.i18nc(definition.key + " label", label)
|
||||
|
||||
items.append({
|
||||
"key": definition.key,
|
||||
"label": label,
|
||||
"unit": definition.unit,
|
||||
"profile_value": "" if profile_value is None else str(profile_value), # it is for display only
|
||||
"profile_value_source": profile_value_source,
|
||||
"user_value": "" if user_value is None else str(user_value),
|
||||
"category": current_category
|
||||
})
|
||||
|
||||
self.setItems(items)
|
168
cura/Machines/Models/SettingVisibilityPresetsModel.py
Normal file
168
cura/Machines/Models/SettingVisibilityPresetsModel.py
Normal file
|
@ -0,0 +1,168 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional, List
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Resources import Resources
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from cura.Settings.SettingVisibilityPreset import SettingVisibilityPreset
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class SettingVisibilityPresetsModel(QObject):
|
||||
onItemsChanged = pyqtSignal()
|
||||
activePresetChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, preferences: Preferences, parent = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self._items = [] # type: List[SettingVisibilityPreset]
|
||||
self._custom_preset = SettingVisibilityPreset(preset_id = "custom", name = "Custom selection", weight = -100)
|
||||
|
||||
self._populate()
|
||||
|
||||
basic_item = self.getVisibilityPresetById("basic")
|
||||
if basic_item is not None:
|
||||
basic_visibile_settings = ";".join(basic_item.settings)
|
||||
else:
|
||||
Logger.log("w", "Unable to find the basic visiblity preset.")
|
||||
basic_visibile_settings = ""
|
||||
|
||||
self._preferences = preferences
|
||||
|
||||
# Preference to store which preset is currently selected
|
||||
self._preferences.addPreference("cura/active_setting_visibility_preset", "basic")
|
||||
|
||||
# Preference that stores the "custom" set so it can always be restored (even after a restart)
|
||||
self._preferences.addPreference("cura/custom_visible_settings", basic_visibile_settings)
|
||||
self._preferences.preferenceChanged.connect(self._onPreferencesChanged)
|
||||
|
||||
self._active_preset_item = self.getVisibilityPresetById(self._preferences.getValue("cura/active_setting_visibility_preset"))
|
||||
|
||||
# Initialize visible settings if it is not done yet
|
||||
visible_settings = self._preferences.getValue("general/visible_settings")
|
||||
|
||||
if not visible_settings:
|
||||
new_visible_settings = self._active_preset_item.settings if self._active_preset_item is not None else []
|
||||
self._preferences.setValue("general/visible_settings", ";".join(new_visible_settings))
|
||||
else:
|
||||
self._onPreferencesChanged("general/visible_settings")
|
||||
|
||||
self.activePresetChanged.emit()
|
||||
|
||||
def getVisibilityPresetById(self, item_id: str) -> Optional[SettingVisibilityPreset]:
|
||||
result = None
|
||||
for item in self._items:
|
||||
if item.presetId == item_id:
|
||||
result = item
|
||||
break
|
||||
return result
|
||||
|
||||
def _populate(self) -> None:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
items = [] # type: List[SettingVisibilityPreset]
|
||||
items.append(self._custom_preset)
|
||||
for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.SettingVisibilityPreset):
|
||||
setting_visibility_preset = SettingVisibilityPreset()
|
||||
try:
|
||||
setting_visibility_preset.loadFromFile(file_path)
|
||||
except Exception:
|
||||
Logger.logException("e", "Failed to load setting preset %s", file_path)
|
||||
|
||||
items.append(setting_visibility_preset)
|
||||
|
||||
# Sort them on weight (and if that fails, use ID)
|
||||
items.sort(key = lambda k: (int(k.weight), k.presetId))
|
||||
|
||||
self.setItems(items)
|
||||
|
||||
@pyqtProperty("QVariantList", notify = onItemsChanged)
|
||||
def items(self) -> List[SettingVisibilityPreset]:
|
||||
return self._items
|
||||
|
||||
def setItems(self, items: List[SettingVisibilityPreset]) -> None:
|
||||
if self._items != items:
|
||||
self._items = items
|
||||
self.onItemsChanged.emit()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setActivePreset(self, preset_id: str) -> None:
|
||||
if self._active_preset_item is not None and preset_id == self._active_preset_item.presetId:
|
||||
Logger.log("d", "Same setting visibility preset [%s] selected, do nothing.", preset_id)
|
||||
return
|
||||
|
||||
preset_item = self.getVisibilityPresetById(preset_id)
|
||||
if preset_item is None:
|
||||
Logger.log("w", "Tried to set active preset to unknown id [%s]", preset_id)
|
||||
return
|
||||
|
||||
need_to_save_to_custom = self._active_preset_item is None or (self._active_preset_item.presetId == "custom" and preset_id != "custom")
|
||||
if need_to_save_to_custom:
|
||||
# Save the current visibility settings to custom
|
||||
current_visibility_string = self._preferences.getValue("general/visible_settings")
|
||||
if current_visibility_string:
|
||||
self._preferences.setValue("cura/custom_visible_settings", current_visibility_string)
|
||||
|
||||
new_visibility_string = ";".join(preset_item.settings)
|
||||
if preset_id == "custom":
|
||||
# Get settings from the stored custom data
|
||||
new_visibility_string = self._preferences.getValue("cura/custom_visible_settings")
|
||||
if new_visibility_string is None:
|
||||
new_visibility_string = self._preferences.getValue("general/visible_settings")
|
||||
self._preferences.setValue("general/visible_settings", new_visibility_string)
|
||||
|
||||
self._preferences.setValue("cura/active_setting_visibility_preset", preset_id)
|
||||
self._active_preset_item = preset_item
|
||||
self.activePresetChanged.emit()
|
||||
|
||||
@pyqtProperty(str, notify = activePresetChanged)
|
||||
def activePreset(self) -> str:
|
||||
if self._active_preset_item is not None:
|
||||
return self._active_preset_item.presetId
|
||||
return ""
|
||||
|
||||
def _onPreferencesChanged(self, name: str) -> None:
|
||||
if name != "general/visible_settings":
|
||||
return
|
||||
|
||||
# Find the preset that matches with the current visible settings setup
|
||||
visibility_string = self._preferences.getValue("general/visible_settings")
|
||||
if not visibility_string:
|
||||
return
|
||||
|
||||
visibility_set = set(visibility_string.split(";"))
|
||||
matching_preset_item = None
|
||||
for item in self._items:
|
||||
if item.presetId == "custom":
|
||||
continue
|
||||
if set(item.settings) == visibility_set:
|
||||
matching_preset_item = item
|
||||
break
|
||||
|
||||
item_to_set = self._active_preset_item
|
||||
if matching_preset_item is None:
|
||||
# The new visibility setup is "custom" should be custom
|
||||
if self._active_preset_item is None or self._active_preset_item.presetId == "custom":
|
||||
# We are already in custom, just save the settings
|
||||
self._preferences.setValue("cura/custom_visible_settings", visibility_string)
|
||||
else:
|
||||
# We need to move to custom preset.
|
||||
item_to_set = self.getVisibilityPresetById("custom")
|
||||
else:
|
||||
item_to_set = matching_preset_item
|
||||
|
||||
# If we didn't find a matching preset, fallback to custom.
|
||||
if item_to_set is None:
|
||||
item_to_set = self._custom_preset
|
||||
|
||||
if self._active_preset_item is None or self._active_preset_item.presetId != item_to_set.presetId:
|
||||
self._active_preset_item = item_to_set
|
||||
if self._active_preset_item is not None:
|
||||
self._preferences.setValue("cura/active_setting_visibility_preset", self._active_preset_item.presetId)
|
||||
self.activePresetChanged.emit()
|
131
cura/Machines/Models/UserChangesModel.py
Normal file
131
cura/Machines/Models/UserChangesModel.py
Normal file
|
@ -0,0 +1,131 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, Qt
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
|
||||
class UserChangesModel(ListModel):
|
||||
KeyRole = Qt.UserRole + 1
|
||||
LabelRole = Qt.UserRole + 2
|
||||
ExtruderRole = Qt.UserRole + 3
|
||||
OriginalValueRole = Qt.UserRole + 4
|
||||
UserValueRole = Qt.UserRole + 6
|
||||
CategoryRole = Qt.UserRole + 7
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent = parent)
|
||||
self.addRoleName(self.KeyRole, "key")
|
||||
self.addRoleName(self.LabelRole, "label")
|
||||
self.addRoleName(self.ExtruderRole, "extruder")
|
||||
self.addRoleName(self.OriginalValueRole, "original_value")
|
||||
self.addRoleName(self.UserValueRole, "user_value")
|
||||
self.addRoleName(self.CategoryRole, "category")
|
||||
|
||||
self._i18n_catalog = None
|
||||
|
||||
self._update()
|
||||
|
||||
@pyqtSlot()
|
||||
def forceUpdate(self):
|
||||
self._update()
|
||||
|
||||
def _update(self):
|
||||
application = Application.getInstance()
|
||||
machine_manager = application.getMachineManager()
|
||||
cura_formula_functions = application.getCuraFormulaFunctions()
|
||||
|
||||
item_dict = OrderedDict()
|
||||
item_list = []
|
||||
global_stack = machine_manager.activeMachine
|
||||
if not global_stack:
|
||||
return
|
||||
|
||||
stacks = [global_stack]
|
||||
stacks.extend(global_stack.extruders.values())
|
||||
|
||||
# Check if the definition container has a translation file and ensure it's loaded.
|
||||
definition = global_stack.getBottom()
|
||||
|
||||
definition_suffix = ContainerRegistry.getMimeTypeForContainer(type(definition)).preferredSuffix
|
||||
catalog = i18nCatalog(os.path.basename(definition.getId() + "." + definition_suffix))
|
||||
|
||||
if catalog.hasTranslationLoaded():
|
||||
self._i18n_catalog = catalog
|
||||
|
||||
for file_name in definition.getInheritedFiles():
|
||||
catalog = i18nCatalog(os.path.basename(file_name))
|
||||
if catalog.hasTranslationLoaded():
|
||||
self._i18n_catalog = catalog
|
||||
|
||||
for stack in stacks:
|
||||
# Make a list of all containers in the stack.
|
||||
containers = []
|
||||
latest_stack = stack
|
||||
while latest_stack:
|
||||
containers.extend(latest_stack.getContainers())
|
||||
latest_stack = latest_stack.getNextStack()
|
||||
|
||||
# Override "getExtruderValue" with "getDefaultExtruderValue" so we can get the default values
|
||||
user_changes = containers.pop(0)
|
||||
default_value_resolve_context = cura_formula_functions.createContextForDefaultValueEvaluation(stack)
|
||||
|
||||
for setting_key in user_changes.getAllKeys():
|
||||
original_value = None
|
||||
|
||||
# Find the category of the instance by moving up until we find a category.
|
||||
category = user_changes.getInstance(setting_key).definition
|
||||
while category.type != "category":
|
||||
category = category.parent
|
||||
|
||||
# Handle translation (and fallback if we weren't able to find any translation files.
|
||||
if self._i18n_catalog:
|
||||
category_label = self._i18n_catalog.i18nc(category.key + " label", category.label)
|
||||
else:
|
||||
category_label = category.label
|
||||
|
||||
if self._i18n_catalog:
|
||||
label = self._i18n_catalog.i18nc(setting_key + " label", stack.getProperty(setting_key, "label"))
|
||||
else:
|
||||
label = stack.getProperty(setting_key, "label")
|
||||
|
||||
for container in containers:
|
||||
if stack == global_stack:
|
||||
resolve = global_stack.getProperty(setting_key, "resolve", default_value_resolve_context)
|
||||
if resolve is not None:
|
||||
original_value = resolve
|
||||
break
|
||||
|
||||
original_value = container.getProperty(setting_key, "value", default_value_resolve_context)
|
||||
|
||||
# If a value is a function, ensure it's called with the stack it's in.
|
||||
if isinstance(original_value, SettingFunction):
|
||||
original_value = original_value(stack, default_value_resolve_context)
|
||||
|
||||
if original_value is not None:
|
||||
break
|
||||
|
||||
item_to_add = {"key": setting_key,
|
||||
"label": label,
|
||||
"user_value": str(user_changes.getProperty(setting_key, "value")),
|
||||
"original_value": str(original_value),
|
||||
"extruder": "",
|
||||
"category": category_label}
|
||||
|
||||
if stack != global_stack:
|
||||
item_to_add["extruder"] = stack.getName()
|
||||
|
||||
if category_label not in item_dict:
|
||||
item_dict[category_label] = []
|
||||
item_dict[category_label].append(item_to_add)
|
||||
for each_item_list in item_dict.values():
|
||||
item_list += each_item_list
|
||||
self.setItems(item_list)
|
0
cura/Machines/Models/__init__.py
Normal file
0
cura/Machines/Models/__init__.py
Normal file
Loading…
Add table
Add a link
Reference in a new issue