mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-12 09:17:50 -06:00
Updated comments in Models
Converted doxygen style comments to reStructuredText style in the files found in Cura/cura/Model directory recursively using the script dox_2_rst.py (provided in the Uranium repo). Comments were manually checked and changed if needed. Note: dox_2rst.py struggles with decorated functions.
This commit is contained in:
parent
d69bf84424
commit
120541a8db
26 changed files with 577 additions and 441 deletions
|
@ -9,47 +9,59 @@ from UM.Logger import Logger
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
|
||||||
|
|
||||||
## A node in the container tree. It represents one container.
|
|
||||||
#
|
|
||||||
# The container it represents is referenced by its container_id. During normal
|
|
||||||
# use of the tree, this container is not constructed. Only when parts of the
|
|
||||||
# tree need to get loaded in the container stack should it get constructed.
|
|
||||||
class ContainerNode:
|
class ContainerNode:
|
||||||
## Creates a new node for the container tree.
|
"""A node in the container tree. It represents one container.
|
||||||
# \param container_id The ID of the container that this node should
|
|
||||||
# represent.
|
The container it represents is referenced by its container_id. During normal use of the tree, this container is
|
||||||
|
not constructed. Only when parts of the tree need to get loaded in the container stack should it get constructed.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, container_id: str) -> None:
|
def __init__(self, container_id: str) -> None:
|
||||||
|
"""Creates a new node for the container tree.
|
||||||
|
|
||||||
|
:param container_id: The ID of the container that this node should represent.
|
||||||
|
"""
|
||||||
|
|
||||||
self.container_id = container_id
|
self.container_id = container_id
|
||||||
self._container = None # type: Optional[InstanceContainer]
|
self._container = None # type: Optional[InstanceContainer]
|
||||||
self.children_map = {} # type: Dict[str, ContainerNode] # Mapping from container ID to container node.
|
self.children_map = {} # type: Dict[str, ContainerNode] # Mapping from container ID to container node.
|
||||||
|
|
||||||
## Gets the metadata of the container that this node represents.
|
|
||||||
# Getting the metadata from the container directly is about 10x as fast.
|
|
||||||
# \return The metadata of the container in this node.
|
|
||||||
def getMetadata(self) -> Dict[str, Any]:
|
def getMetadata(self) -> Dict[str, Any]:
|
||||||
|
"""Gets the metadata of the container that this node represents.
|
||||||
|
|
||||||
|
Getting the metadata from the container directly is about 10x as fast.
|
||||||
|
|
||||||
|
:return: The metadata of the container in this node.
|
||||||
|
"""
|
||||||
|
|
||||||
return ContainerRegistry.getInstance().findContainersMetadata(id = self.container_id)[0]
|
return ContainerRegistry.getInstance().findContainersMetadata(id = self.container_id)[0]
|
||||||
|
|
||||||
## Get an entry from the metadata of the container that this node contains.
|
|
||||||
#
|
|
||||||
# This is just a convenience function.
|
|
||||||
# \param entry The metadata entry key to return.
|
|
||||||
# \param default If the metadata is not present or the container is not
|
|
||||||
# found, the value of this default is returned.
|
|
||||||
# \return The value of the metadata entry, or the default if it was not
|
|
||||||
# present.
|
|
||||||
def getMetaDataEntry(self, entry: str, default: Any = None) -> Any:
|
def getMetaDataEntry(self, entry: str, default: Any = None) -> Any:
|
||||||
|
"""Get an entry from the metadata of the container that this node contains.
|
||||||
|
|
||||||
|
This is just a convenience function.
|
||||||
|
|
||||||
|
:param entry: The metadata entry key to return.
|
||||||
|
:param default: If the metadata is not present or the container is not found, the value of this default is
|
||||||
|
returned.
|
||||||
|
|
||||||
|
:return: The value of the metadata entry, or the default if it was not present.
|
||||||
|
"""
|
||||||
|
|
||||||
container_metadata = ContainerRegistry.getInstance().findContainersMetadata(id = self.container_id)
|
container_metadata = ContainerRegistry.getInstance().findContainersMetadata(id = self.container_id)
|
||||||
if len(container_metadata) == 0:
|
if len(container_metadata) == 0:
|
||||||
return default
|
return default
|
||||||
return container_metadata[0].get(entry, default)
|
return container_metadata[0].get(entry, default)
|
||||||
|
|
||||||
## The container that this node's container ID refers to.
|
|
||||||
#
|
|
||||||
# This can be used to finally instantiate the container in order to put it
|
|
||||||
# in the container stack.
|
|
||||||
# \return A container.
|
|
||||||
@property
|
@property
|
||||||
def container(self) -> Optional[InstanceContainer]:
|
def container(self) -> Optional[InstanceContainer]:
|
||||||
|
"""The container that this node's container ID refers to.
|
||||||
|
|
||||||
|
This can be used to finally instantiate the container in order to put it in the container stack.
|
||||||
|
|
||||||
|
:return: A container.
|
||||||
|
"""
|
||||||
|
|
||||||
if not self._container:
|
if not self._container:
|
||||||
container_list = ContainerRegistry.getInstance().findInstanceContainers(id = self.container_id)
|
container_list = ContainerRegistry.getInstance().findInstanceContainers(id = self.container_id)
|
||||||
if len(container_list) == 0:
|
if len(container_list) == 0:
|
||||||
|
|
|
@ -19,17 +19,16 @@ if TYPE_CHECKING:
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
|
|
||||||
|
|
||||||
## This class contains a look-up tree for which containers are available at
|
|
||||||
# which stages of configuration.
|
|
||||||
#
|
|
||||||
# The tree starts at the machine definitions. For every distinct definition
|
|
||||||
# there will be one machine node here.
|
|
||||||
#
|
|
||||||
# All of the fallbacks for material choices, quality choices, etc. should be
|
|
||||||
# encoded in this tree. There must always be at least one child node (for
|
|
||||||
# nodes that have children) but that child node may be a node representing the
|
|
||||||
# empty instance container.
|
|
||||||
class ContainerTree:
|
class ContainerTree:
|
||||||
|
"""This class contains a look-up tree for which containers are available at which stages of configuration.
|
||||||
|
|
||||||
|
The tree starts at the machine definitions. For every distinct definition there will be one machine node here.
|
||||||
|
|
||||||
|
All of the fallbacks for material choices, quality choices, etc. should be encoded in this tree. There must
|
||||||
|
always be at least one child node (for nodes that have children) but that child node may be a node representing
|
||||||
|
the empty instance container.
|
||||||
|
"""
|
||||||
|
|
||||||
__instance = None # type: Optional["ContainerTree"]
|
__instance = None # type: Optional["ContainerTree"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -43,13 +42,15 @@ class ContainerTree:
|
||||||
self.materialsChanged = Signal() # Emitted when any of the material nodes in the tree got changed.
|
self.materialsChanged = Signal() # Emitted when any of the material nodes in the tree got changed.
|
||||||
cura.CuraApplication.CuraApplication.getInstance().initializationFinished.connect(self._onStartupFinished) # Start the background task to load more machine nodes after start-up is completed.
|
cura.CuraApplication.CuraApplication.getInstance().initializationFinished.connect(self._onStartupFinished) # Start the background task to load more machine nodes after start-up is completed.
|
||||||
|
|
||||||
## Get the quality groups available for the currently activated printer.
|
|
||||||
#
|
|
||||||
# This contains all quality groups, enabled or disabled. To check whether
|
|
||||||
# the quality group can be activated, test for the
|
|
||||||
# ``QualityGroup.is_available`` property.
|
|
||||||
# \return For every quality type, one quality group.
|
|
||||||
def getCurrentQualityGroups(self) -> Dict[str, "QualityGroup"]:
|
def getCurrentQualityGroups(self) -> Dict[str, "QualityGroup"]:
|
||||||
|
"""Get the quality groups available for the currently activated printer.
|
||||||
|
|
||||||
|
This contains all quality groups, enabled or disabled. To check whether the quality group can be activated,
|
||||||
|
test for the ``QualityGroup.is_available`` property.
|
||||||
|
|
||||||
|
:return: For every quality type, one quality group.
|
||||||
|
"""
|
||||||
|
|
||||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if global_stack is None:
|
if global_stack is None:
|
||||||
return {}
|
return {}
|
||||||
|
@ -58,14 +59,15 @@ class ContainerTree:
|
||||||
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
|
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
|
||||||
return self.machines[global_stack.definition.getId()].getQualityGroups(variant_names, material_bases, extruder_enabled)
|
return self.machines[global_stack.definition.getId()].getQualityGroups(variant_names, material_bases, extruder_enabled)
|
||||||
|
|
||||||
## Get the quality changes groups available for the currently activated
|
|
||||||
# printer.
|
|
||||||
#
|
|
||||||
# This contains all quality changes groups, enabled or disabled. To check
|
|
||||||
# whether the quality changes group can be activated, test for the
|
|
||||||
# ``QualityChangesGroup.is_available`` property.
|
|
||||||
# \return A list of all quality changes groups.
|
|
||||||
def getCurrentQualityChangesGroups(self) -> List["QualityChangesGroup"]:
|
def getCurrentQualityChangesGroups(self) -> List["QualityChangesGroup"]:
|
||||||
|
"""Get the quality changes groups available for the currently activated printer.
|
||||||
|
|
||||||
|
This contains all quality changes groups, enabled or disabled. To check whether the quality changes group can
|
||||||
|
be activated, test for the ``QualityChangesGroup.is_available`` property.
|
||||||
|
|
||||||
|
:return: A list of all quality changes groups.
|
||||||
|
"""
|
||||||
|
|
||||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if global_stack is None:
|
if global_stack is None:
|
||||||
return []
|
return []
|
||||||
|
@ -74,31 +76,43 @@ class ContainerTree:
|
||||||
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
|
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
|
||||||
return self.machines[global_stack.definition.getId()].getQualityChangesGroups(variant_names, material_bases, extruder_enabled)
|
return self.machines[global_stack.definition.getId()].getQualityChangesGroups(variant_names, material_bases, extruder_enabled)
|
||||||
|
|
||||||
## Ran after completely starting up the application.
|
|
||||||
def _onStartupFinished(self) -> None:
|
def _onStartupFinished(self) -> None:
|
||||||
|
"""Ran after completely starting up the application."""
|
||||||
|
|
||||||
currently_added = ContainerRegistry.getInstance().findContainerStacks() # Find all currently added global stacks.
|
currently_added = ContainerRegistry.getInstance().findContainerStacks() # Find all currently added global stacks.
|
||||||
JobQueue.getInstance().add(self._MachineNodeLoadJob(self, currently_added))
|
JobQueue.getInstance().add(self._MachineNodeLoadJob(self, currently_added))
|
||||||
|
|
||||||
## Dictionary-like object that contains the machines.
|
|
||||||
#
|
|
||||||
# This handles the lazy loading of MachineNodes.
|
|
||||||
class _MachineNodeMap:
|
class _MachineNodeMap:
|
||||||
|
"""Dictionary-like object that contains the machines.
|
||||||
|
|
||||||
|
This handles the lazy loading of MachineNodes.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._machines = {} # type: Dict[str, MachineNode]
|
self._machines = {} # type: Dict[str, MachineNode]
|
||||||
|
|
||||||
## Returns whether a printer with a certain definition ID exists. This
|
|
||||||
# is regardless of whether or not the printer is loaded yet.
|
|
||||||
# \param definition_id The definition to look for.
|
|
||||||
# \return Whether or not a printer definition exists with that name.
|
|
||||||
def __contains__(self, definition_id: str) -> bool:
|
def __contains__(self, definition_id: str) -> bool:
|
||||||
|
"""Returns whether a printer with a certain definition ID exists.
|
||||||
|
|
||||||
|
This is regardless of whether or not the printer is loaded yet.
|
||||||
|
|
||||||
|
:param definition_id: The definition to look for.
|
||||||
|
|
||||||
|
:return: Whether or not a printer definition exists with that name.
|
||||||
|
"""
|
||||||
|
|
||||||
return len(ContainerRegistry.getInstance().findContainersMetadata(id = definition_id)) > 0
|
return len(ContainerRegistry.getInstance().findContainersMetadata(id = definition_id)) > 0
|
||||||
|
|
||||||
## Returns a machine node for the specified definition ID.
|
|
||||||
#
|
|
||||||
# If the machine node wasn't loaded yet, this will load it lazily.
|
|
||||||
# \param definition_id The definition to look for.
|
|
||||||
# \return A machine node for that definition.
|
|
||||||
def __getitem__(self, definition_id: str) -> MachineNode:
|
def __getitem__(self, definition_id: str) -> MachineNode:
|
||||||
|
"""Returns a machine node for the specified definition ID.
|
||||||
|
|
||||||
|
If the machine node wasn't loaded yet, this will load it lazily.
|
||||||
|
|
||||||
|
:param definition_id: The definition to look for.
|
||||||
|
|
||||||
|
:return: A machine node for that definition.
|
||||||
|
"""
|
||||||
|
|
||||||
if definition_id not in self._machines:
|
if definition_id not in self._machines:
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
self._machines[definition_id] = MachineNode(definition_id)
|
self._machines[definition_id] = MachineNode(definition_id)
|
||||||
|
@ -106,46 +120,58 @@ class ContainerTree:
|
||||||
Logger.log("d", "Adding container tree for {definition_id} took {duration} seconds.".format(definition_id = definition_id, duration = time.time() - start_time))
|
Logger.log("d", "Adding container tree for {definition_id} took {duration} seconds.".format(definition_id = definition_id, duration = time.time() - start_time))
|
||||||
return self._machines[definition_id]
|
return self._machines[definition_id]
|
||||||
|
|
||||||
## Gets a machine node for the specified definition ID, with default.
|
|
||||||
#
|
|
||||||
# The default is returned if there is no definition with the specified
|
|
||||||
# ID. If the machine node wasn't loaded yet, this will load it lazily.
|
|
||||||
# \param definition_id The definition to look for.
|
|
||||||
# \param default The machine node to return if there is no machine
|
|
||||||
# with that definition (can be ``None`` optionally or if not
|
|
||||||
# provided).
|
|
||||||
# \return A machine node for that definition, or the default if there
|
|
||||||
# is no definition with the provided definition_id.
|
|
||||||
def get(self, definition_id: str, default: Optional[MachineNode] = None) -> Optional[MachineNode]:
|
def get(self, definition_id: str, default: Optional[MachineNode] = None) -> Optional[MachineNode]:
|
||||||
|
"""Gets a machine node for the specified definition ID, with default.
|
||||||
|
|
||||||
|
The default is returned if there is no definition with the specified ID. If the machine node wasn't
|
||||||
|
loaded yet, this will load it lazily.
|
||||||
|
|
||||||
|
:param definition_id: The definition to look for.
|
||||||
|
:param default: The machine node to return if there is no machine with that definition (can be ``None``
|
||||||
|
optionally or if not provided).
|
||||||
|
|
||||||
|
:return: A machine node for that definition, or the default if there is no definition with the provided
|
||||||
|
definition_id.
|
||||||
|
"""
|
||||||
|
|
||||||
if definition_id not in self:
|
if definition_id not in self:
|
||||||
return default
|
return default
|
||||||
return self[definition_id]
|
return self[definition_id]
|
||||||
|
|
||||||
## Returns whether we've already cached this definition's node.
|
|
||||||
# \param definition_id The definition that we may have cached.
|
|
||||||
# \return ``True`` if it's cached.
|
|
||||||
def is_loaded(self, definition_id: str) -> bool:
|
def is_loaded(self, definition_id: str) -> bool:
|
||||||
|
"""Returns whether we've already cached this definition's node.
|
||||||
|
|
||||||
|
:param definition_id: The definition that we may have cached.
|
||||||
|
|
||||||
|
:return: ``True`` if it's cached.
|
||||||
|
"""
|
||||||
|
|
||||||
return definition_id in self._machines
|
return definition_id in self._machines
|
||||||
|
|
||||||
## Pre-loads all currently added printers as a background task so that
|
|
||||||
# switching printers in the interface is faster.
|
|
||||||
class _MachineNodeLoadJob(Job):
|
class _MachineNodeLoadJob(Job):
|
||||||
## Creates a new background task.
|
"""Pre-loads all currently added printers as a background task so that switching printers in the interface is
|
||||||
# \param tree_root The container tree instance. This cannot be
|
faster.
|
||||||
# obtained through the singleton static function since the instance
|
"""
|
||||||
# may not yet be constructed completely.
|
|
||||||
# \param container_stacks All of the stacks to pre-load the container
|
|
||||||
# trees for. This needs to be provided from here because the stacks
|
|
||||||
# need to be constructed on the main thread because they are QObject.
|
|
||||||
def __init__(self, tree_root: "ContainerTree", container_stacks: List["ContainerStack"]) -> None:
|
def __init__(self, tree_root: "ContainerTree", container_stacks: List["ContainerStack"]) -> None:
|
||||||
|
"""Creates a new background task.
|
||||||
|
|
||||||
|
:param tree_root: The container tree instance. This cannot be obtained through the singleton static
|
||||||
|
function since the instance may not yet be constructed completely.
|
||||||
|
:param container_stacks: All of the stacks to pre-load the container trees for. This needs to be provided
|
||||||
|
from here because the stacks need to be constructed on the main thread because they are QObject.
|
||||||
|
"""
|
||||||
|
|
||||||
self.tree_root = tree_root
|
self.tree_root = tree_root
|
||||||
self.container_stacks = container_stacks
|
self.container_stacks = container_stacks
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
## Starts the background task.
|
|
||||||
#
|
|
||||||
# The ``JobQueue`` will schedule this on a different thread.
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
|
"""Starts the background task.
|
||||||
|
|
||||||
|
The ``JobQueue`` will schedule this on a different thread.
|
||||||
|
"""
|
||||||
|
|
||||||
for stack in self.container_stacks: # Load all currently-added containers.
|
for stack in self.container_stacks: # Load all currently-added containers.
|
||||||
if not isinstance(stack, GlobalStack):
|
if not isinstance(stack, GlobalStack):
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -11,10 +11,12 @@ if TYPE_CHECKING:
|
||||||
from cura.Machines.QualityNode import QualityNode
|
from cura.Machines.QualityNode import QualityNode
|
||||||
|
|
||||||
|
|
||||||
## This class represents an intent profile in the container tree.
|
|
||||||
#
|
|
||||||
# This class has no more subnodes.
|
|
||||||
class IntentNode(ContainerNode):
|
class IntentNode(ContainerNode):
|
||||||
|
"""This class represents an intent profile in the container tree.
|
||||||
|
|
||||||
|
This class has no more subnodes.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, container_id: str, quality: "QualityNode") -> None:
|
def __init__(self, container_id: str, quality: "QualityNode") -> None:
|
||||||
super().__init__(container_id)
|
super().__init__(container_id)
|
||||||
self.quality = quality
|
self.quality = quality
|
||||||
|
|
|
@ -13,16 +13,16 @@ from UM.Settings.SettingDefinition import SettingDefinition
|
||||||
from UM.Settings.Validator import ValidatorState
|
from UM.Settings.Validator import ValidatorState
|
||||||
|
|
||||||
import cura.CuraApplication
|
import cura.CuraApplication
|
||||||
#
|
|
||||||
# This class performs setting error checks for the currently active machine.
|
|
||||||
#
|
|
||||||
# The whole error checking process is pretty heavy which can take ~0.5 secs, so it can cause GUI to lag.
|
|
||||||
# The idea here is to split the whole error check into small tasks, each of which only checks a single setting key
|
|
||||||
# in a stack. According to my profiling results, the maximal runtime for such a sub-task is <0.03 secs, which should
|
|
||||||
# be good enough. Moreover, if any changes happened to the machine, we can cancel the check in progress without wait
|
|
||||||
# for it to finish the complete work.
|
|
||||||
#
|
|
||||||
class MachineErrorChecker(QObject):
|
class MachineErrorChecker(QObject):
|
||||||
|
"""This class performs setting error checks for the currently active machine.
|
||||||
|
|
||||||
|
The whole error checking process is pretty heavy which can take ~0.5 secs, so it can cause GUI to lag. The idea
|
||||||
|
here is to split the whole error check into small tasks, each of which only checks a single setting key in a
|
||||||
|
stack. According to my profiling results, the maximal runtime for such a sub-task is <0.03 secs, which should be
|
||||||
|
good enough. Moreover, if any changes happened to the machine, we can cancel the check in progress without wait
|
||||||
|
for it to finish the complete work.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, parent: Optional[QObject] = None) -> None:
|
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
@ -92,24 +92,37 @@ class MachineErrorChecker(QObject):
|
||||||
def needToWaitForResult(self) -> bool:
|
def needToWaitForResult(self) -> bool:
|
||||||
return self._need_to_check or self._check_in_progress
|
return self._need_to_check or self._check_in_progress
|
||||||
|
|
||||||
# Start the error check for property changed
|
|
||||||
# this is seperate from the startErrorCheck because it ignores a number property types
|
|
||||||
def startErrorCheckPropertyChanged(self, key: str, property_name: str) -> None:
|
def startErrorCheckPropertyChanged(self, key: str, property_name: str) -> None:
|
||||||
|
"""Start the error check for property changed
|
||||||
|
|
||||||
|
this is seperate from the startErrorCheck because it ignores a number property types
|
||||||
|
|
||||||
|
:param key:
|
||||||
|
:param property_name:
|
||||||
|
"""
|
||||||
|
|
||||||
if property_name != "value":
|
if property_name != "value":
|
||||||
return
|
return
|
||||||
self.startErrorCheck()
|
self.startErrorCheck()
|
||||||
|
|
||||||
# Starts the error check timer to schedule a new error check.
|
|
||||||
def startErrorCheck(self, *args: Any) -> None:
|
def startErrorCheck(self, *args: Any) -> None:
|
||||||
|
"""Starts the error check timer to schedule a new error check.
|
||||||
|
|
||||||
|
:param args:
|
||||||
|
"""
|
||||||
|
|
||||||
if not self._check_in_progress:
|
if not self._check_in_progress:
|
||||||
self._need_to_check = True
|
self._need_to_check = True
|
||||||
self.needToWaitForResultChanged.emit()
|
self.needToWaitForResultChanged.emit()
|
||||||
self._error_check_timer.start()
|
self._error_check_timer.start()
|
||||||
|
|
||||||
# This function is called by the timer to reschedule a new error check.
|
|
||||||
# If there is no check in progress, it will start a new one. If there is any, it sets the "_need_to_check" flag
|
|
||||||
# to notify the current check to stop and start a new one.
|
|
||||||
def _rescheduleCheck(self) -> None:
|
def _rescheduleCheck(self) -> None:
|
||||||
|
"""This function is called by the timer to reschedule a new error check.
|
||||||
|
|
||||||
|
If there is no check in progress, it will start a new one. If there is any, it sets the "_need_to_check" flag
|
||||||
|
to notify the current check to stop and start a new one.
|
||||||
|
"""
|
||||||
|
|
||||||
if self._check_in_progress and not self._need_to_check:
|
if self._check_in_progress and not self._need_to_check:
|
||||||
self._need_to_check = True
|
self._need_to_check = True
|
||||||
self.needToWaitForResultChanged.emit()
|
self.needToWaitForResultChanged.emit()
|
||||||
|
|
|
@ -17,10 +17,12 @@ from cura.Machines.VariantNode import VariantNode
|
||||||
import UM.FlameProfiler
|
import UM.FlameProfiler
|
||||||
|
|
||||||
|
|
||||||
## This class represents a machine in the container tree.
|
|
||||||
#
|
|
||||||
# The subnodes of these nodes are variants.
|
|
||||||
class MachineNode(ContainerNode):
|
class MachineNode(ContainerNode):
|
||||||
|
"""This class represents a machine in the container tree.
|
||||||
|
|
||||||
|
The subnodes of these nodes are variants.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, container_id: str) -> None:
|
def __init__(self, container_id: str) -> None:
|
||||||
super().__init__(container_id)
|
super().__init__(container_id)
|
||||||
self.variants = {} # type: Dict[str, VariantNode] # Mapping variant names to their nodes.
|
self.variants = {} # type: Dict[str, VariantNode] # Mapping variant names to their nodes.
|
||||||
|
@ -47,20 +49,21 @@ class MachineNode(ContainerNode):
|
||||||
|
|
||||||
self._loadAll()
|
self._loadAll()
|
||||||
|
|
||||||
## Get the available quality groups for this machine.
|
|
||||||
#
|
|
||||||
# This returns all quality groups, regardless of whether they are
|
|
||||||
# available to the combination of extruders or not. On the resulting
|
|
||||||
# quality groups, the is_available property is set to indicate whether the
|
|
||||||
# quality group can be selected according to the combination of extruders
|
|
||||||
# in the parameters.
|
|
||||||
# \param variant_names The names of the variants loaded in each extruder.
|
|
||||||
# \param material_bases The base file names of the materials loaded in
|
|
||||||
# each extruder.
|
|
||||||
# \param extruder_enabled Whether or not the extruders are enabled. This
|
|
||||||
# allows the function to set the is_available properly.
|
|
||||||
# \return For each available quality type, a QualityGroup instance.
|
|
||||||
def getQualityGroups(self, variant_names: List[str], material_bases: List[str], extruder_enabled: List[bool]) -> Dict[str, QualityGroup]:
|
def getQualityGroups(self, variant_names: List[str], material_bases: List[str], extruder_enabled: List[bool]) -> Dict[str, QualityGroup]:
|
||||||
|
"""Get the available quality groups for this machine.
|
||||||
|
|
||||||
|
This returns all quality groups, regardless of whether they are available to the combination of extruders or
|
||||||
|
not. On the resulting quality groups, the is_available property is set to indicate whether the quality group
|
||||||
|
can be selected according to the combination of extruders in the parameters.
|
||||||
|
|
||||||
|
:param variant_names: The names of the variants loaded in each extruder.
|
||||||
|
:param material_bases: The base file names of the materials loaded in each extruder.
|
||||||
|
:param extruder_enabled: Whether or not the extruders are enabled. This allows the function to set the
|
||||||
|
is_available properly.
|
||||||
|
|
||||||
|
:return: For each available quality type, a QualityGroup instance.
|
||||||
|
"""
|
||||||
|
|
||||||
if len(variant_names) != len(material_bases) or len(variant_names) != len(extruder_enabled):
|
if len(variant_names) != len(material_bases) or len(variant_names) != len(extruder_enabled):
|
||||||
Logger.log("e", "The number of extruders in the list of variants (" + str(len(variant_names)) + ") is not equal to the number of extruders in the list of materials (" + str(len(material_bases)) + ") or the list of enabled extruders (" + str(len(extruder_enabled)) + ").")
|
Logger.log("e", "The number of extruders in the list of variants (" + str(len(variant_names)) + ") is not equal to the number of extruders in the list of materials (" + str(len(material_bases)) + ") or the list of enabled extruders (" + str(len(extruder_enabled)) + ").")
|
||||||
return {}
|
return {}
|
||||||
|
@ -98,28 +101,26 @@ class MachineNode(ContainerNode):
|
||||||
quality_groups[quality_type].is_available = True
|
quality_groups[quality_type].is_available = True
|
||||||
return quality_groups
|
return quality_groups
|
||||||
|
|
||||||
## Returns all of the quality changes groups available to this printer.
|
|
||||||
#
|
|
||||||
# The quality changes groups store which quality type and intent category
|
|
||||||
# they were made for, but not which material and nozzle. Instead for the
|
|
||||||
# quality type and intent category, the quality changes will always be
|
|
||||||
# available but change the quality type and intent category when
|
|
||||||
# activated.
|
|
||||||
#
|
|
||||||
# The quality changes group does depend on the printer: Which quality
|
|
||||||
# definition is used.
|
|
||||||
#
|
|
||||||
# The quality changes groups that are available do depend on the quality
|
|
||||||
# types that are available, so it must still be known which extruders are
|
|
||||||
# enabled and which materials and variants are loaded in them. This allows
|
|
||||||
# setting the correct is_available flag.
|
|
||||||
# \param variant_names The names of the variants loaded in each extruder.
|
|
||||||
# \param material_bases The base file names of the materials loaded in
|
|
||||||
# each extruder.
|
|
||||||
# \param extruder_enabled For each extruder whether or not they are
|
|
||||||
# enabled.
|
|
||||||
# \return List of all quality changes groups for the printer.
|
|
||||||
def getQualityChangesGroups(self, variant_names: List[str], material_bases: List[str], extruder_enabled: List[bool]) -> List[QualityChangesGroup]:
|
def getQualityChangesGroups(self, variant_names: List[str], material_bases: List[str], extruder_enabled: List[bool]) -> List[QualityChangesGroup]:
|
||||||
|
"""Returns all of the quality changes groups available to this printer.
|
||||||
|
|
||||||
|
The quality changes groups store which quality type and intent category they were made for, but not which
|
||||||
|
material and nozzle. Instead for the quality type and intent category, the quality changes will always be
|
||||||
|
available but change the quality type and intent category when activated.
|
||||||
|
|
||||||
|
The quality changes group does depend on the printer: Which quality definition is used.
|
||||||
|
|
||||||
|
The quality changes groups that are available do depend on the quality types that are available, so it must
|
||||||
|
still be known which extruders are enabled and which materials and variants are loaded in them. This allows
|
||||||
|
setting the correct is_available flag.
|
||||||
|
|
||||||
|
:param variant_names: The names of the variants loaded in each extruder.
|
||||||
|
:param material_bases: The base file names of the materials loaded in each extruder.
|
||||||
|
:param extruder_enabled: For each extruder whether or not they are enabled.
|
||||||
|
|
||||||
|
:return: List of all quality changes groups for the printer.
|
||||||
|
"""
|
||||||
|
|
||||||
machine_quality_changes = ContainerRegistry.getInstance().findContainersMetadata(type = "quality_changes", definition = self.quality_definition) # All quality changes for each extruder.
|
machine_quality_changes = ContainerRegistry.getInstance().findContainersMetadata(type = "quality_changes", definition = self.quality_definition) # All quality changes for each extruder.
|
||||||
|
|
||||||
groups_by_name = {} #type: Dict[str, QualityChangesGroup] # Group quality changes profiles by their display name. The display name must be unique for quality changes. This finds profiles that belong together in a group.
|
groups_by_name = {} #type: Dict[str, QualityChangesGroup] # Group quality changes profiles by their display name. The display name must be unique for quality changes. This finds profiles that belong together in a group.
|
||||||
|
@ -147,18 +148,19 @@ class MachineNode(ContainerNode):
|
||||||
|
|
||||||
return list(groups_by_name.values())
|
return list(groups_by_name.values())
|
||||||
|
|
||||||
## Gets the preferred global quality node, going by the preferred quality
|
|
||||||
# type.
|
|
||||||
#
|
|
||||||
# If the preferred global quality is not in there, an arbitrary global
|
|
||||||
# quality is taken.
|
|
||||||
# If there are no global qualities, an empty quality is returned.
|
|
||||||
def preferredGlobalQuality(self) -> "QualityNode":
|
def preferredGlobalQuality(self) -> "QualityNode":
|
||||||
|
"""Gets the preferred global quality node, going by the preferred quality type.
|
||||||
|
|
||||||
|
If the preferred global quality is not in there, an arbitrary global quality is taken. If there are no global
|
||||||
|
qualities, an empty quality is returned.
|
||||||
|
"""
|
||||||
|
|
||||||
return self.global_qualities.get(self.preferred_quality_type, next(iter(self.global_qualities.values())))
|
return self.global_qualities.get(self.preferred_quality_type, next(iter(self.global_qualities.values())))
|
||||||
|
|
||||||
## (Re)loads all variants under this printer.
|
|
||||||
@UM.FlameProfiler.profile
|
@UM.FlameProfiler.profile
|
||||||
def _loadAll(self) -> None:
|
def _loadAll(self) -> None:
|
||||||
|
"""(Re)loads all variants under this printer."""
|
||||||
|
|
||||||
container_registry = ContainerRegistry.getInstance()
|
container_registry = ContainerRegistry.getInstance()
|
||||||
if not self.has_variants:
|
if not self.has_variants:
|
||||||
self.variants["empty"] = VariantNode("empty_variant", machine = self)
|
self.variants["empty"] = VariantNode("empty_variant", machine = self)
|
||||||
|
|
|
@ -7,18 +7,21 @@ if TYPE_CHECKING:
|
||||||
from cura.Machines.MaterialNode import MaterialNode
|
from cura.Machines.MaterialNode import MaterialNode
|
||||||
|
|
||||||
|
|
||||||
## A MaterialGroup represents a group of material InstanceContainers that are derived from a single material profile.
|
|
||||||
# The main InstanceContainer which has the ID of the material profile file name is called the "root_material". For
|
|
||||||
# example: "generic_abs" is the root material (ID) of "generic_abs_ultimaker3" and "generic_abs_ultimaker3_AA_0.4",
|
|
||||||
# and "generic_abs_ultimaker3" and "generic_abs_ultimaker3_AA_0.4" are derived materials of "generic_abs".
|
|
||||||
#
|
|
||||||
# Using "generic_abs" as an example, the MaterialGroup for "generic_abs" will contain the following information:
|
|
||||||
# - name: "generic_abs", root_material_id
|
|
||||||
# - root_material_node: MaterialNode of "generic_abs"
|
|
||||||
# - derived_material_node_list: A list of MaterialNodes that are derived from "generic_abs",
|
|
||||||
# so "generic_abs_ultimaker3", "generic_abs_ultimaker3_AA_0.4", etc.
|
|
||||||
#
|
|
||||||
class MaterialGroup:
|
class MaterialGroup:
|
||||||
|
"""A MaterialGroup represents a group of material InstanceContainers that are derived from a single material profile.
|
||||||
|
|
||||||
|
The main InstanceContainer which has the ID of the material profile file name is called the "root_material". For
|
||||||
|
example: "generic_abs" is the root material (ID) of "generic_abs_ultimaker3" and "generic_abs_ultimaker3_AA_0.4",
|
||||||
|
and "generic_abs_ultimaker3" and "generic_abs_ultimaker3_AA_0.4" are derived materials of "generic_abs".
|
||||||
|
|
||||||
|
Using "generic_abs" as an example, the MaterialGroup for "generic_abs" will contain the following information:
|
||||||
|
- name: "generic_abs", root_material_id
|
||||||
|
- root_material_node: MaterialNode of "generic_abs"
|
||||||
|
- derived_material_node_list: A list of MaterialNodes that are derived from "generic_abs", so
|
||||||
|
"generic_abs_ultimaker3", "generic_abs_ultimaker3_AA_0.4", etc.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
__slots__ = ("name", "is_read_only", "root_material_node", "derived_material_node_list")
|
__slots__ = ("name", "is_read_only", "root_material_node", "derived_material_node_list")
|
||||||
|
|
||||||
def __init__(self, name: str, root_material_node: "MaterialNode") -> None:
|
def __init__(self, name: str, root_material_node: "MaterialNode") -> None:
|
||||||
|
|
|
@ -15,10 +15,12 @@ if TYPE_CHECKING:
|
||||||
from cura.Machines.VariantNode import VariantNode
|
from cura.Machines.VariantNode import VariantNode
|
||||||
|
|
||||||
|
|
||||||
## Represents a material in the container tree.
|
|
||||||
#
|
|
||||||
# Its subcontainers are quality profiles.
|
|
||||||
class MaterialNode(ContainerNode):
|
class MaterialNode(ContainerNode):
|
||||||
|
"""Represents a material in the container tree.
|
||||||
|
|
||||||
|
Its subcontainers are quality profiles.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, container_id: str, variant: "VariantNode") -> None:
|
def __init__(self, container_id: str, variant: "VariantNode") -> None:
|
||||||
super().__init__(container_id)
|
super().__init__(container_id)
|
||||||
self.variant = variant
|
self.variant = variant
|
||||||
|
@ -34,16 +36,16 @@ class MaterialNode(ContainerNode):
|
||||||
container_registry.containerRemoved.connect(self._onRemoved)
|
container_registry.containerRemoved.connect(self._onRemoved)
|
||||||
container_registry.containerMetaDataChanged.connect(self._onMetadataChanged)
|
container_registry.containerMetaDataChanged.connect(self._onMetadataChanged)
|
||||||
|
|
||||||
## Finds the preferred quality for this printer with this material and this
|
|
||||||
# variant loaded.
|
|
||||||
#
|
|
||||||
# If the preferred quality is not available, an arbitrary quality is
|
|
||||||
# returned. If there is a configuration mistake (like a typo in the
|
|
||||||
# preferred quality) this returns a random available quality. If there are
|
|
||||||
# no available qualities, this will return the empty quality node.
|
|
||||||
# \return The node for the preferred quality, or any arbitrary quality if
|
|
||||||
# there is no match.
|
|
||||||
def preferredQuality(self) -> QualityNode:
|
def preferredQuality(self) -> QualityNode:
|
||||||
|
"""Finds the preferred quality for this printer with this material and this variant loaded.
|
||||||
|
|
||||||
|
If the preferred quality is not available, an arbitrary quality is returned. If there is a configuration
|
||||||
|
mistake (like a typo in the preferred quality) this returns a random available quality. If there are no
|
||||||
|
available qualities, this will return the empty quality node.
|
||||||
|
|
||||||
|
:return: The node for the preferred quality, or any arbitrary quality if there is no match.
|
||||||
|
"""
|
||||||
|
|
||||||
for quality_id, quality_node in self.qualities.items():
|
for quality_id, quality_node in self.qualities.items():
|
||||||
if self.variant.machine.preferred_quality_type == quality_node.quality_type:
|
if self.variant.machine.preferred_quality_type == quality_node.quality_type:
|
||||||
return quality_node
|
return quality_node
|
||||||
|
@ -107,10 +109,13 @@ class MaterialNode(ContainerNode):
|
||||||
if not self.qualities:
|
if not self.qualities:
|
||||||
self.qualities["empty_quality"] = QualityNode("empty_quality", parent = self)
|
self.qualities["empty_quality"] = QualityNode("empty_quality", parent = self)
|
||||||
|
|
||||||
## Triggered when any container is removed, but only handles it when the
|
|
||||||
# container is removed that this node represents.
|
|
||||||
# \param container The container that was allegedly removed.
|
|
||||||
def _onRemoved(self, container: ContainerInterface) -> None:
|
def _onRemoved(self, container: ContainerInterface) -> None:
|
||||||
|
"""Triggered when any container is removed, but only handles it when the container is removed that this node
|
||||||
|
represents.
|
||||||
|
|
||||||
|
:param container: The container that was allegedly removed.
|
||||||
|
"""
|
||||||
|
|
||||||
if container.getId() == self.container_id:
|
if container.getId() == self.container_id:
|
||||||
# Remove myself from my parent.
|
# Remove myself from my parent.
|
||||||
if self.base_file in self.variant.materials:
|
if self.base_file in self.variant.materials:
|
||||||
|
@ -119,13 +124,15 @@ class MaterialNode(ContainerNode):
|
||||||
self.variant.materials["empty_material"] = MaterialNode("empty_material", variant = self.variant)
|
self.variant.materials["empty_material"] = MaterialNode("empty_material", variant = self.variant)
|
||||||
self.materialChanged.emit(self)
|
self.materialChanged.emit(self)
|
||||||
|
|
||||||
## Triggered when any metadata changed in any container, but only handles
|
|
||||||
# it when the metadata of this node is changed.
|
|
||||||
# \param container The container whose metadata changed.
|
|
||||||
# \param kwargs Key-word arguments provided when changing the metadata.
|
|
||||||
# These are ignored. As far as I know they are never provided to this
|
|
||||||
# call.
|
|
||||||
def _onMetadataChanged(self, container: ContainerInterface, **kwargs: Any) -> None:
|
def _onMetadataChanged(self, container: ContainerInterface, **kwargs: Any) -> None:
|
||||||
|
"""Triggered when any metadata changed in any container, but only handles it when the metadata of this node is
|
||||||
|
changed.
|
||||||
|
|
||||||
|
:param container: The container whose metadata changed.
|
||||||
|
:param kwargs: Key-word arguments provided when changing the metadata. These are ignored. As far as I know they
|
||||||
|
are never provided to this call.
|
||||||
|
"""
|
||||||
|
|
||||||
if container.getId() != self.container_id:
|
if container.getId() != self.container_id:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -13,11 +13,13 @@ from cura.Machines.ContainerTree import ContainerTree
|
||||||
from cura.Machines.MaterialNode import MaterialNode
|
from cura.Machines.MaterialNode import MaterialNode
|
||||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||||
|
|
||||||
## This is the base model class for GenericMaterialsModel and MaterialBrandsModel.
|
|
||||||
# Those 2 models are used by the material drop down menu to show generic materials and branded materials separately.
|
|
||||||
# The extruder position defined here is being used to bound a menu to the correct extruder. This is used in the top
|
|
||||||
# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu
|
|
||||||
class BaseMaterialsModel(ListModel):
|
class BaseMaterialsModel(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
|
||||||
|
"""
|
||||||
|
|
||||||
extruderPositionChanged = pyqtSignal()
|
extruderPositionChanged = pyqtSignal()
|
||||||
enabledChanged = pyqtSignal()
|
enabledChanged = pyqtSignal()
|
||||||
|
@ -121,10 +123,13 @@ class BaseMaterialsModel(ListModel):
|
||||||
def enabled(self):
|
def enabled(self):
|
||||||
return self._enabled
|
return self._enabled
|
||||||
|
|
||||||
## Triggered when a list of materials changed somewhere in the container
|
|
||||||
# tree. This change may trigger an _update() call when the materials
|
|
||||||
# changed for the configuration that this model is looking for.
|
|
||||||
def _materialsListChanged(self, material: MaterialNode) -> None:
|
def _materialsListChanged(self, material: MaterialNode) -> None:
|
||||||
|
"""Triggered when a list of materials changed somewhere in the container
|
||||||
|
|
||||||
|
tree. This change may trigger an _update() call when the materials changed for the configuration that this
|
||||||
|
model is looking for.
|
||||||
|
"""
|
||||||
|
|
||||||
if self._extruder_stack is None:
|
if self._extruder_stack is None:
|
||||||
return
|
return
|
||||||
if material.variant.container_id != self._extruder_stack.variant.getId():
|
if material.variant.container_id != self._extruder_stack.variant.getId():
|
||||||
|
@ -136,14 +141,15 @@ class BaseMaterialsModel(ListModel):
|
||||||
return
|
return
|
||||||
self._onChanged()
|
self._onChanged()
|
||||||
|
|
||||||
## Triggered when the list of favorite materials is changed.
|
|
||||||
def _favoritesChanged(self, material_base_file: str) -> None:
|
def _favoritesChanged(self, material_base_file: str) -> None:
|
||||||
|
"""Triggered when the list of favorite materials is changed."""
|
||||||
|
|
||||||
if material_base_file in self._available_materials:
|
if material_base_file in self._available_materials:
|
||||||
self._onChanged()
|
self._onChanged()
|
||||||
|
|
||||||
## This is an abstract method that needs to be implemented by the specific
|
|
||||||
# models themselves.
|
|
||||||
def _update(self):
|
def _update(self):
|
||||||
|
"""This is an abstract method that needs to be implemented by the specific models themselves. """
|
||||||
|
|
||||||
self._favorite_ids = set(cura.CuraApplication.CuraApplication.getInstance().getPreferences().getValue("cura/favorite_materials").split(";"))
|
self._favorite_ids = set(cura.CuraApplication.CuraApplication.getInstance().getPreferences().getValue("cura/favorite_materials").split(";"))
|
||||||
|
|
||||||
# Update the available materials (ContainerNode) for the current active machine and extruder setup.
|
# Update the available materials (ContainerNode) for the current active machine and extruder setup.
|
||||||
|
@ -163,10 +169,10 @@ class BaseMaterialsModel(ListModel):
|
||||||
approximate_material_diameter = extruder_stack.getApproximateMaterialDiameter()
|
approximate_material_diameter = extruder_stack.getApproximateMaterialDiameter()
|
||||||
self._available_materials = {key: material for key, material in materials.items() if float(material.getMetaDataEntry("approximate_diameter", -1)) == approximate_material_diameter}
|
self._available_materials = {key: material for key, material in materials.items() if float(material.getMetaDataEntry("approximate_diameter", -1)) == approximate_material_diameter}
|
||||||
|
|
||||||
## 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):
|
def _canUpdate(self):
|
||||||
|
"""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. """
|
||||||
|
|
||||||
global_stack = self._machine_manager.activeMachine
|
global_stack = self._machine_manager.activeMachine
|
||||||
if global_stack is None or not self._enabled:
|
if global_stack is None or not self._enabled:
|
||||||
return False
|
return False
|
||||||
|
@ -177,9 +183,10 @@ class BaseMaterialsModel(ListModel):
|
||||||
|
|
||||||
return True
|
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):
|
def _createMaterialItem(self, root_material_id, container_node):
|
||||||
|
"""This is another convenience function which is shared by all material models so it's put here to avoid having
|
||||||
|
so much duplicated code. """
|
||||||
|
|
||||||
metadata_list = CuraContainerRegistry.getInstance().findContainersMetadata(id = container_node.container_id)
|
metadata_list = CuraContainerRegistry.getInstance().findContainersMetadata(id = container_node.container_id)
|
||||||
if not metadata_list:
|
if not metadata_list:
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -14,9 +14,8 @@ if TYPE_CHECKING:
|
||||||
from UM.Settings.Interfaces import ContainerInterface
|
from UM.Settings.Interfaces import ContainerInterface
|
||||||
|
|
||||||
|
|
||||||
## This model is used for the custom profile items in the profile drop down
|
|
||||||
# menu.
|
|
||||||
class CustomQualityProfilesDropDownMenuModel(QualityProfilesDropDownMenuModel):
|
class CustomQualityProfilesDropDownMenuModel(QualityProfilesDropDownMenuModel):
|
||||||
|
"""This model is used for the custom profile items in the profile drop down menu."""
|
||||||
|
|
||||||
def __init__(self, parent: Optional["QObject"] = None) -> None:
|
def __init__(self, parent: Optional["QObject"] = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
|
@ -9,9 +9,9 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class DiscoveredCloudPrintersModel(ListModel):
|
class DiscoveredCloudPrintersModel(ListModel):
|
||||||
"""
|
"""Model used to inform the application about newly added cloud printers, which are discovered from the user's
|
||||||
Model used to inform the application about newly added cloud printers, which are discovered from the user's account
|
account """
|
||||||
"""
|
|
||||||
DeviceKeyRole = Qt.UserRole + 1
|
DeviceKeyRole = Qt.UserRole + 1
|
||||||
DeviceNameRole = Qt.UserRole + 2
|
DeviceNameRole = Qt.UserRole + 2
|
||||||
DeviceTypeRole = Qt.UserRole + 3
|
DeviceTypeRole = Qt.UserRole + 3
|
||||||
|
@ -31,18 +31,24 @@ class DiscoveredCloudPrintersModel(ListModel):
|
||||||
self._application = application # type: CuraApplication
|
self._application = application # type: CuraApplication
|
||||||
|
|
||||||
def addDiscoveredCloudPrinters(self, new_devices: List[Dict[str, str]]) -> None:
|
def addDiscoveredCloudPrinters(self, new_devices: List[Dict[str, str]]) -> None:
|
||||||
"""
|
"""Adds all the newly discovered cloud printers into the DiscoveredCloudPrintersModel.
|
||||||
Adds all the newly discovered cloud printers into the DiscoveredCloudPrintersModel.
|
|
||||||
|
Example new_devices entry:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
:param new_devices: List of dictionaries which contain information about added cloud printers. Example:
|
|
||||||
{
|
{
|
||||||
"key": "YjW8pwGYcaUvaa0YgVyWeFkX3z",
|
"key": "YjW8pwGYcaUvaa0YgVyWeFkX3z",
|
||||||
"name": "NG 001",
|
"name": "NG 001",
|
||||||
"machine_type": "Ultimaker S5",
|
"machine_type": "Ultimaker S5",
|
||||||
"firmware_version": "5.5.12.202001"
|
"firmware_version": "5.5.12.202001"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:param new_devices: List of dictionaries which contain information about added cloud printers.
|
||||||
|
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._discovered_cloud_printers_list.extend(new_devices)
|
self._discovered_cloud_printers_list.extend(new_devices)
|
||||||
self._update()
|
self._update()
|
||||||
|
|
||||||
|
@ -51,21 +57,21 @@ class DiscoveredCloudPrintersModel(ListModel):
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
"""
|
"""Clears the contents of the DiscoveredCloudPrintersModel.
|
||||||
Clears the contents of the DiscoveredCloudPrintersModel.
|
|
||||||
|
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._discovered_cloud_printers_list = []
|
self._discovered_cloud_printers_list = []
|
||||||
self._update()
|
self._update()
|
||||||
self.cloudPrintersDetectedChanged.emit(False)
|
self.cloudPrintersDetectedChanged.emit(False)
|
||||||
|
|
||||||
def _update(self) -> None:
|
def _update(self) -> None:
|
||||||
"""
|
"""Sorts the newly discovered cloud printers by name and then updates the ListModel.
|
||||||
Sorts the newly discovered cloud printers by name and then updates the ListModel.
|
|
||||||
|
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
items = self._discovered_cloud_printers_list[:]
|
items = self._discovered_cloud_printers_list[:]
|
||||||
items.sort(key = lambda k: k["name"])
|
items.sort(key = lambda k: k["name"])
|
||||||
self.setItems(items)
|
self.setItems(items)
|
||||||
|
|
|
@ -115,12 +115,11 @@ class DiscoveredPrinter(QObject):
|
||||||
return catalog.i18nc("@label", "Available networked printers")
|
return catalog.i18nc("@label", "Available networked printers")
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# 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):
|
class DiscoveredPrintersModel(QObject):
|
||||||
|
"""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).
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
|
def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
@ -254,8 +253,14 @@ class DiscoveredPrintersModel(QObject):
|
||||||
del self._discovered_printer_by_ip_dict[ip_address]
|
del self._discovered_printer_by_ip_dict[ip_address]
|
||||||
self.discoveredPrintersChanged.emit()
|
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")
|
@pyqtSlot("QVariant")
|
||||||
def createMachineFromDiscoveredPrinter(self, discovered_printer: "DiscoveredPrinter") -> None:
|
def createMachineFromDiscoveredPrinter(self, discovered_printer: "DiscoveredPrinter") -> None:
|
||||||
|
"""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
|
||||||
|
|
||||||
|
:param discovered_printer:
|
||||||
|
"""
|
||||||
|
|
||||||
discovered_printer.create_callback(discovered_printer.getKey())
|
discovered_printer.create_callback(discovered_printer.getKey())
|
||||||
|
|
|
@ -15,27 +15,27 @@ if TYPE_CHECKING:
|
||||||
catalog = i18nCatalog("cura")
|
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):
|
class ExtrudersModel(ListModel):
|
||||||
|
"""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.
|
||||||
|
"""
|
||||||
|
|
||||||
# The ID of the container stack for the extruder.
|
# The ID of the container stack for the extruder.
|
||||||
IdRole = Qt.UserRole + 1
|
IdRole = Qt.UserRole + 1
|
||||||
|
|
||||||
## Human-readable name of the extruder.
|
|
||||||
NameRole = Qt.UserRole + 2
|
NameRole = Qt.UserRole + 2
|
||||||
|
"""Human-readable name of the extruder."""
|
||||||
|
|
||||||
## Colour of the material loaded in the extruder.
|
|
||||||
ColorRole = Qt.UserRole + 3
|
ColorRole = Qt.UserRole + 3
|
||||||
|
"""Colour of the material loaded in the extruder."""
|
||||||
|
|
||||||
## 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
|
IndexRole = Qt.UserRole + 4
|
||||||
|
"""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. """
|
||||||
|
|
||||||
# The ID of the definition of the extruder.
|
# The ID of the definition of the extruder.
|
||||||
DefinitionRole = Qt.UserRole + 5
|
DefinitionRole = Qt.UserRole + 5
|
||||||
|
@ -50,18 +50,18 @@ class ExtrudersModel(ListModel):
|
||||||
MaterialBrandRole = Qt.UserRole + 9
|
MaterialBrandRole = Qt.UserRole + 9
|
||||||
ColorNameRole = Qt.UserRole + 10
|
ColorNameRole = Qt.UserRole + 10
|
||||||
|
|
||||||
## Is the extruder enabled?
|
|
||||||
EnabledRole = Qt.UserRole + 11
|
EnabledRole = Qt.UserRole + 11
|
||||||
|
"""Is the extruder enabled?"""
|
||||||
|
|
||||||
## List of colours to display if there is no material or the material has no known
|
|
||||||
# colour.
|
|
||||||
defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
|
defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
|
||||||
|
"""List of colours to display if there is no material or the material has no known colour. """
|
||||||
|
|
||||||
## 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):
|
def __init__(self, parent = None):
|
||||||
|
"""Initialises the extruders model, defining the roles and listening for changes in the data.
|
||||||
|
|
||||||
|
:param parent: Parent QtObject of this list.
|
||||||
|
"""
|
||||||
|
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self.addRoleName(self.IdRole, "id")
|
self.addRoleName(self.IdRole, "id")
|
||||||
|
@ -101,14 +101,15 @@ class ExtrudersModel(ListModel):
|
||||||
def addOptionalExtruder(self):
|
def addOptionalExtruder(self):
|
||||||
return self._add_optional_extruder
|
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):
|
def _extrudersChanged(self, machine_id = None):
|
||||||
|
"""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.
|
||||||
|
"""
|
||||||
|
|
||||||
machine_manager = Application.getInstance().getMachineManager()
|
machine_manager = Application.getInstance().getMachineManager()
|
||||||
if machine_id is not None:
|
if machine_id is not None:
|
||||||
if machine_manager.activeMachine is None:
|
if machine_manager.activeMachine is None:
|
||||||
|
@ -146,11 +147,13 @@ class ExtrudersModel(ListModel):
|
||||||
def _updateExtruders(self):
|
def _updateExtruders(self):
|
||||||
self._update_extruder_timer.start()
|
self._update_extruder_timer.start()
|
||||||
|
|
||||||
## Update the list of extruders.
|
|
||||||
#
|
|
||||||
# This should be called whenever the list of extruders changes.
|
|
||||||
@UM.FlameProfiler.profile
|
@UM.FlameProfiler.profile
|
||||||
def __updateExtruders(self):
|
def __updateExtruders(self):
|
||||||
|
"""Update the list of extruders.
|
||||||
|
|
||||||
|
This should be called whenever the list of extruders changes.
|
||||||
|
"""
|
||||||
|
|
||||||
extruders_changed = False
|
extruders_changed = False
|
||||||
|
|
||||||
if self.count != 0:
|
if self.count != 0:
|
||||||
|
|
|
@ -4,16 +4,17 @@
|
||||||
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
||||||
import cura.CuraApplication # To listen to changes to the preferences.
|
import cura.CuraApplication # To listen to changes to the preferences.
|
||||||
|
|
||||||
## Model that shows the list of favorite materials.
|
|
||||||
class FavoriteMaterialsModel(BaseMaterialsModel):
|
class FavoriteMaterialsModel(BaseMaterialsModel):
|
||||||
|
"""Model that shows the list of favorite materials."""
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
cura.CuraApplication.CuraApplication.getInstance().getPreferences().preferenceChanged.connect(self._onFavoritesChanged)
|
cura.CuraApplication.CuraApplication.getInstance().getPreferences().preferenceChanged.connect(self._onFavoritesChanged)
|
||||||
self._onChanged()
|
self._onChanged()
|
||||||
|
|
||||||
## Triggered when any preference changes, but only handles it when the list
|
|
||||||
# of favourites is changed.
|
|
||||||
def _onFavoritesChanged(self, preference_key: str) -> None:
|
def _onFavoritesChanged(self, preference_key: str) -> None:
|
||||||
|
"""Triggered when any preference changes, but only handles it when the list of favourites is changed. """
|
||||||
|
|
||||||
if preference_key != "cura/favorite_materials":
|
if preference_key != "cura/favorite_materials":
|
||||||
return
|
return
|
||||||
self._onChanged()
|
self._onChanged()
|
||||||
|
|
|
@ -11,13 +11,13 @@ if TYPE_CHECKING:
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# This model holds all first-start machine actions for the currently active machine. It has 2 roles:
|
|
||||||
# - title : the title/name of the action
|
|
||||||
# - content : the QObject of the QML content of the action
|
|
||||||
# - action : the MachineAction object itself
|
|
||||||
#
|
|
||||||
class FirstStartMachineActionsModel(ListModel):
|
class FirstStartMachineActionsModel(ListModel):
|
||||||
|
"""This model holds all first-start machine actions for the currently active machine. It has 2 roles:
|
||||||
|
|
||||||
|
- title : the title/name of the action
|
||||||
|
- content : the QObject of the QML content of the action
|
||||||
|
- action : the MachineAction object itself
|
||||||
|
"""
|
||||||
|
|
||||||
TitleRole = Qt.UserRole + 1
|
TitleRole = Qt.UserRole + 1
|
||||||
ContentRole = Qt.UserRole + 2
|
ContentRole = Qt.UserRole + 2
|
||||||
|
@ -73,9 +73,10 @@ class FirstStartMachineActionsModel(ListModel):
|
||||||
self._current_action_index += 1
|
self._current_action_index += 1
|
||||||
self.currentActionIndexChanged.emit()
|
self.currentActionIndexChanged.emit()
|
||||||
|
|
||||||
# Resets the current action index to 0 so the wizard panel can show actions from the beginning.
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
|
"""Resets the current action index to 0 so the wizard panel can show actions from the beginning."""
|
||||||
|
|
||||||
self._current_action_index = 0
|
self._current_action_index = 0
|
||||||
self.currentActionIndexChanged.emit()
|
self.currentActionIndexChanged.emit()
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,9 @@ class GlobalStacksModel(ListModel):
|
||||||
CuraContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
|
CuraContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
|
||||||
self._updateDelayed()
|
self._updateDelayed()
|
||||||
|
|
||||||
## Handler for container added/removed events from registry
|
|
||||||
def _onContainerChanged(self, container) -> None:
|
def _onContainerChanged(self, container) -> None:
|
||||||
|
"""Handler for container added/removed events from registry"""
|
||||||
|
|
||||||
# We only need to update when the added / removed container GlobalStack
|
# We only need to update when the added / removed container GlobalStack
|
||||||
if isinstance(container, GlobalStack):
|
if isinstance(container, GlobalStack):
|
||||||
self._updateDelayed()
|
self._updateDelayed()
|
||||||
|
|
|
@ -18,9 +18,9 @@ from UM.i18n import i18nCatalog
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
## Lists the intent categories that are available for the current printer
|
|
||||||
# configuration.
|
|
||||||
class IntentCategoryModel(ListModel):
|
class IntentCategoryModel(ListModel):
|
||||||
|
"""Lists the intent categories that are available for the current printer configuration. """
|
||||||
|
|
||||||
NameRole = Qt.UserRole + 1
|
NameRole = Qt.UserRole + 1
|
||||||
IntentCategoryRole = Qt.UserRole + 2
|
IntentCategoryRole = Qt.UserRole + 2
|
||||||
WeightRole = Qt.UserRole + 3
|
WeightRole = Qt.UserRole + 3
|
||||||
|
@ -31,10 +31,12 @@ class IntentCategoryModel(ListModel):
|
||||||
|
|
||||||
_translations = collections.OrderedDict() # type: "collections.OrderedDict[str,Dict[str,Optional[str]]]"
|
_translations = collections.OrderedDict() # type: "collections.OrderedDict[str,Dict[str,Optional[str]]]"
|
||||||
|
|
||||||
# Translations to user-visible string. Ordered by weight.
|
|
||||||
# TODO: Create a solution for this name and weight to be used dynamically.
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_translations(cls):
|
def _get_translations(cls):
|
||||||
|
"""Translations to user-visible string. Ordered by weight.
|
||||||
|
|
||||||
|
TODO: Create a solution for this name and weight to be used dynamically.
|
||||||
|
"""
|
||||||
if len(cls._translations) == 0:
|
if len(cls._translations) == 0:
|
||||||
cls._translations["default"] = {
|
cls._translations["default"] = {
|
||||||
"name": catalog.i18nc("@label", "Default")
|
"name": catalog.i18nc("@label", "Default")
|
||||||
|
@ -53,9 +55,12 @@ class IntentCategoryModel(ListModel):
|
||||||
}
|
}
|
||||||
return cls._translations
|
return cls._translations
|
||||||
|
|
||||||
## Creates a new model for a certain intent category.
|
|
||||||
# \param The category to list the intent profiles for.
|
|
||||||
def __init__(self, intent_category: str) -> None:
|
def __init__(self, intent_category: str) -> None:
|
||||||
|
"""Creates a new model for a certain intent category.
|
||||||
|
|
||||||
|
:param intent_category: category to list the intent profiles for.
|
||||||
|
"""
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._intent_category = intent_category
|
self._intent_category = intent_category
|
||||||
|
|
||||||
|
@ -84,16 +89,18 @@ class IntentCategoryModel(ListModel):
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
## Updates the list of intents if an intent profile was added or removed.
|
|
||||||
def _onContainerChange(self, container: "ContainerInterface") -> None:
|
def _onContainerChange(self, container: "ContainerInterface") -> None:
|
||||||
|
"""Updates the list of intents if an intent profile was added or removed."""
|
||||||
|
|
||||||
if container.getMetaDataEntry("type") == "intent":
|
if container.getMetaDataEntry("type") == "intent":
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
self._update_timer.start()
|
self._update_timer.start()
|
||||||
|
|
||||||
## Updates the list of intents.
|
|
||||||
def _update(self) -> None:
|
def _update(self) -> None:
|
||||||
|
"""Updates the list of intents."""
|
||||||
|
|
||||||
available_categories = IntentManager.getInstance().currentAvailableIntentCategories()
|
available_categories = IntentManager.getInstance().currentAvailableIntentCategories()
|
||||||
result = []
|
result = []
|
||||||
for category in available_categories:
|
for category in available_categories:
|
||||||
|
@ -109,9 +116,9 @@ class IntentCategoryModel(ListModel):
|
||||||
result.sort(key = lambda k: k["weight"])
|
result.sort(key = lambda k: k["weight"])
|
||||||
self.setItems(result)
|
self.setItems(result)
|
||||||
|
|
||||||
## Get a display value for a category.
|
|
||||||
## for categories and keys
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def translation(category: str, key: str, default: Optional[str] = None):
|
def translation(category: str, key: str, default: Optional[str] = None):
|
||||||
|
"""Get a display value for a category.for categories and keys"""
|
||||||
|
|
||||||
display_strings = IntentCategoryModel._get_translations().get(category, {})
|
display_strings = IntentCategoryModel._get_translations().get(category, {})
|
||||||
return display_strings.get(key, default)
|
return display_strings.get(key, default)
|
||||||
|
|
|
@ -98,8 +98,9 @@ class IntentModel(ListModel):
|
||||||
new_items = sorted(new_items, key = lambda x: x["layer_height"])
|
new_items = sorted(new_items, key = lambda x: x["layer_height"])
|
||||||
self.setItems(new_items)
|
self.setItems(new_items)
|
||||||
|
|
||||||
## Get the active materials for all extruders. No duplicates will be returned
|
|
||||||
def _getActiveMaterials(self) -> Set["MaterialNode"]:
|
def _getActiveMaterials(self) -> Set["MaterialNode"]:
|
||||||
|
"""Get the active materials for all extruders. No duplicates will be returned"""
|
||||||
|
|
||||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if global_stack is None:
|
if global_stack is None:
|
||||||
return set()
|
return set()
|
||||||
|
|
|
@ -19,28 +19,31 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
## Proxy class to the materials page in the preferences.
|
|
||||||
#
|
|
||||||
# This class handles the actions in that page, such as creating new materials,
|
|
||||||
# renaming them, etc.
|
|
||||||
class MaterialManagementModel(QObject):
|
class MaterialManagementModel(QObject):
|
||||||
## Triggered when a favorite is added or removed.
|
"""Proxy class to the materials page in the preferences.
|
||||||
# \param The base file of the material is provided as parameter when this
|
|
||||||
# emits.
|
This class handles the actions in that page, such as creating new materials, renaming them, etc.
|
||||||
favoritesChanged = pyqtSignal(str)
|
"""
|
||||||
|
|
||||||
|
favoritesChanged = pyqtSignal(str)
|
||||||
|
"""Triggered when a favorite is added or removed.
|
||||||
|
|
||||||
|
:param The base file of the material is provided as parameter when this emits
|
||||||
|
"""
|
||||||
|
|
||||||
## Can a certain material be deleted, or is it still in use in one of the
|
|
||||||
# container stacks anywhere?
|
|
||||||
#
|
|
||||||
# We forbid the user from deleting a material if it's in use in any stack.
|
|
||||||
# Deleting it while it's in use can lead to corrupted stacks. In the
|
|
||||||
# future we might enable this functionality again (deleting the material
|
|
||||||
# from those stacks) but for now it is easier to prevent the user from
|
|
||||||
# doing this.
|
|
||||||
# \param material_node The ContainerTree node of the material to check.
|
|
||||||
# \return Whether or not the material can be removed.
|
|
||||||
@pyqtSlot("QVariant", result = bool)
|
@pyqtSlot("QVariant", result = bool)
|
||||||
def canMaterialBeRemoved(self, material_node: "MaterialNode") -> bool:
|
def canMaterialBeRemoved(self, material_node: "MaterialNode") -> bool:
|
||||||
|
"""Can a certain material be deleted, or is it still in use in one of the container stacks anywhere?
|
||||||
|
|
||||||
|
We forbid the user from deleting a material if it's in use in any stack. Deleting it while it's in use can
|
||||||
|
lead to corrupted stacks. In the future we might enable this functionality again (deleting the material from
|
||||||
|
those stacks) but for now it is easier to prevent the user from doing this.
|
||||||
|
|
||||||
|
:param material_node: The ContainerTree node of the material to check.
|
||||||
|
|
||||||
|
:return: Whether or not the material can be removed.
|
||||||
|
"""
|
||||||
|
|
||||||
container_registry = CuraContainerRegistry.getInstance()
|
container_registry = CuraContainerRegistry.getInstance()
|
||||||
ids_to_remove = {metadata.get("id", "") for metadata in container_registry.findInstanceContainersMetadata(base_file = material_node.base_file)}
|
ids_to_remove = {metadata.get("id", "") for metadata in container_registry.findInstanceContainersMetadata(base_file = material_node.base_file)}
|
||||||
for extruder_stack in container_registry.findContainerStacks(type = "extruder_train"):
|
for extruder_stack in container_registry.findContainerStacks(type = "extruder_train"):
|
||||||
|
@ -48,11 +51,14 @@ class MaterialManagementModel(QObject):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
## Change the user-visible name of a material.
|
|
||||||
# \param material_node The ContainerTree node of the material to rename.
|
|
||||||
# \param name The new name for the material.
|
|
||||||
@pyqtSlot("QVariant", str)
|
@pyqtSlot("QVariant", str)
|
||||||
def setMaterialName(self, material_node: "MaterialNode", name: str) -> None:
|
def setMaterialName(self, material_node: "MaterialNode", name: str) -> None:
|
||||||
|
"""Change the user-visible name of a material.
|
||||||
|
|
||||||
|
:param material_node: The ContainerTree node of the material to rename.
|
||||||
|
:param name: The new name for the material.
|
||||||
|
"""
|
||||||
|
|
||||||
container_registry = CuraContainerRegistry.getInstance()
|
container_registry = CuraContainerRegistry.getInstance()
|
||||||
root_material_id = material_node.base_file
|
root_material_id = material_node.base_file
|
||||||
if container_registry.isReadOnly(root_material_id):
|
if container_registry.isReadOnly(root_material_id):
|
||||||
|
@ -60,18 +66,20 @@ class MaterialManagementModel(QObject):
|
||||||
return
|
return
|
||||||
return container_registry.findContainers(id = root_material_id)[0].setName(name)
|
return container_registry.findContainers(id = root_material_id)[0].setName(name)
|
||||||
|
|
||||||
## Deletes a material from Cura.
|
|
||||||
#
|
|
||||||
# This function does not do any safety checking any more. Please call this
|
|
||||||
# function only if:
|
|
||||||
# - The material is not read-only.
|
|
||||||
# - The material is not used in any stacks.
|
|
||||||
# If the material was not lazy-loaded yet, this will fully load the
|
|
||||||
# container. When removing this material node, all other materials with
|
|
||||||
# the same base fill will also be removed.
|
|
||||||
# \param material_node The material to remove.
|
|
||||||
@pyqtSlot("QVariant")
|
@pyqtSlot("QVariant")
|
||||||
def removeMaterial(self, material_node: "MaterialNode") -> None:
|
def removeMaterial(self, material_node: "MaterialNode") -> None:
|
||||||
|
"""Deletes a material from Cura.
|
||||||
|
|
||||||
|
This function does not do any safety checking any more. Please call this function only if:
|
||||||
|
- The material is not read-only.
|
||||||
|
- The material is not used in any stacks.
|
||||||
|
|
||||||
|
If the material was not lazy-loaded yet, this will fully load the container. When removing this material
|
||||||
|
node, all other materials with the same base fill will also be removed.
|
||||||
|
|
||||||
|
:param material_node: The material to remove.
|
||||||
|
"""
|
||||||
|
|
||||||
container_registry = CuraContainerRegistry.getInstance()
|
container_registry = CuraContainerRegistry.getInstance()
|
||||||
materials_this_base_file = container_registry.findContainersMetadata(base_file = material_node.base_file)
|
materials_this_base_file = container_registry.findContainersMetadata(base_file = material_node.base_file)
|
||||||
|
|
||||||
|
@ -89,17 +97,19 @@ class MaterialManagementModel(QObject):
|
||||||
for material_metadata in materials_this_base_file:
|
for material_metadata in materials_this_base_file:
|
||||||
container_registry.removeContainer(material_metadata["id"])
|
container_registry.removeContainer(material_metadata["id"])
|
||||||
|
|
||||||
## Creates a duplicate of a material with the same GUID and base_file
|
|
||||||
# metadata.
|
|
||||||
# \param base_file: The base file of the material to duplicate.
|
|
||||||
# \param new_base_id A new material ID for the base material. The IDs of
|
|
||||||
# the submaterials will be based off this one. If not provided, a material
|
|
||||||
# ID will be generated automatically.
|
|
||||||
# \param new_metadata Metadata for the new material. If not provided, this
|
|
||||||
# will be duplicated from the original material.
|
|
||||||
# \return The root material ID of the duplicate material.
|
|
||||||
def duplicateMaterialByBaseFile(self, base_file: str, new_base_id: Optional[str] = None,
|
def duplicateMaterialByBaseFile(self, base_file: str, new_base_id: Optional[str] = None,
|
||||||
new_metadata: Optional[Dict[str, Any]] = None) -> Optional[str]:
|
new_metadata: Optional[Dict[str, Any]] = None) -> Optional[str]:
|
||||||
|
"""Creates a duplicate of a material with the same GUID and base_file metadata
|
||||||
|
|
||||||
|
:param base_file: The base file of the material to duplicate.
|
||||||
|
:param new_base_id: A new material ID for the base material. The IDs of the submaterials will be based off this
|
||||||
|
one. If not provided, a material ID will be generated automatically.
|
||||||
|
:param new_metadata: Metadata for the new material. If not provided, this will be duplicated from the original
|
||||||
|
material.
|
||||||
|
|
||||||
|
:return: The root material ID of the duplicate material.
|
||||||
|
"""
|
||||||
|
|
||||||
container_registry = CuraContainerRegistry.getInstance()
|
container_registry = CuraContainerRegistry.getInstance()
|
||||||
|
|
||||||
root_materials = container_registry.findContainers(id = base_file)
|
root_materials = container_registry.findContainers(id = base_file)
|
||||||
|
@ -171,29 +181,32 @@ class MaterialManagementModel(QObject):
|
||||||
|
|
||||||
return new_base_id
|
return new_base_id
|
||||||
|
|
||||||
## Creates a duplicate of a material with the same GUID and base_file
|
|
||||||
# metadata.
|
|
||||||
# \param material_node The node representing the material to duplicate.
|
|
||||||
# \param new_base_id A new material ID for the base material. The IDs of
|
|
||||||
# the submaterials will be based off this one. If not provided, a material
|
|
||||||
# ID will be generated automatically.
|
|
||||||
# \param new_metadata Metadata for the new material. If not provided, this
|
|
||||||
# will be duplicated from the original material.
|
|
||||||
# \return The root material ID of the duplicate material.
|
|
||||||
@pyqtSlot("QVariant", result = str)
|
@pyqtSlot("QVariant", result = str)
|
||||||
def duplicateMaterial(self, material_node: "MaterialNode", new_base_id: Optional[str] = None,
|
def duplicateMaterial(self, material_node: "MaterialNode", new_base_id: Optional[str] = None,
|
||||||
new_metadata: Optional[Dict[str, Any]] = None) -> Optional[str]:
|
new_metadata: Optional[Dict[str, Any]] = None) -> Optional[str]:
|
||||||
|
"""Creates a duplicate of a material with the same GUID and base_file metadata
|
||||||
|
|
||||||
|
:param material_node: The node representing the material to duplicate.
|
||||||
|
:param new_base_id: A new material ID for the base material. The IDs of the submaterials will be based off this
|
||||||
|
one. If not provided, a material ID will be generated automatically.
|
||||||
|
:param new_metadata: Metadata for the new material. If not provided, this will be duplicated from the original
|
||||||
|
material.
|
||||||
|
|
||||||
|
:return: The root material ID of the duplicate material.
|
||||||
|
"""
|
||||||
return self.duplicateMaterialByBaseFile(material_node.base_file, new_base_id, new_metadata)
|
return self.duplicateMaterialByBaseFile(material_node.base_file, new_base_id, new_metadata)
|
||||||
|
|
||||||
## Create a new material by cloning the preferred material for the current
|
|
||||||
# material diameter and generate a new GUID.
|
|
||||||
#
|
|
||||||
# The material type is explicitly left to be the one from the preferred
|
|
||||||
# material, since this allows the user to still have SOME profiles to work
|
|
||||||
# with.
|
|
||||||
# \return The ID of the newly created material.
|
|
||||||
@pyqtSlot(result = str)
|
@pyqtSlot(result = str)
|
||||||
def createMaterial(self) -> str:
|
def createMaterial(self) -> str:
|
||||||
|
"""Create a new material by cloning the preferred material for the current material diameter and generate a new
|
||||||
|
GUID.
|
||||||
|
|
||||||
|
The material type is explicitly left to be the one from the preferred material, since this allows the user to
|
||||||
|
still have SOME profiles to work with.
|
||||||
|
|
||||||
|
:return: The ID of the newly created material.
|
||||||
|
"""
|
||||||
|
|
||||||
# Ensure all settings are saved.
|
# Ensure all settings are saved.
|
||||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||||
application.saveSettings()
|
application.saveSettings()
|
||||||
|
@ -218,10 +231,13 @@ class MaterialManagementModel(QObject):
|
||||||
self.duplicateMaterial(preferred_material_node, new_base_id = new_id, new_metadata = new_metadata)
|
self.duplicateMaterial(preferred_material_node, new_base_id = new_id, new_metadata = new_metadata)
|
||||||
return new_id
|
return new_id
|
||||||
|
|
||||||
## Adds a certain material to the favorite materials.
|
|
||||||
# \param material_base_file The base file of the material to add.
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def addFavorite(self, material_base_file: str) -> None:
|
def addFavorite(self, material_base_file: str) -> None:
|
||||||
|
"""Adds a certain material to the favorite materials.
|
||||||
|
|
||||||
|
:param material_base_file: The base file of the material to add.
|
||||||
|
"""
|
||||||
|
|
||||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||||
favorites = application.getPreferences().getValue("cura/favorite_materials").split(";")
|
favorites = application.getPreferences().getValue("cura/favorite_materials").split(";")
|
||||||
if material_base_file not in favorites:
|
if material_base_file not in favorites:
|
||||||
|
@ -230,11 +246,13 @@ class MaterialManagementModel(QObject):
|
||||||
application.saveSettings()
|
application.saveSettings()
|
||||||
self.favoritesChanged.emit(material_base_file)
|
self.favoritesChanged.emit(material_base_file)
|
||||||
|
|
||||||
## Removes a certain material from the favorite materials.
|
|
||||||
#
|
|
||||||
# If the material was not in the favorite materials, nothing happens.
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def removeFavorite(self, material_base_file: str) -> None:
|
def removeFavorite(self, material_base_file: str) -> None:
|
||||||
|
"""Removes a certain material from the favorite materials.
|
||||||
|
|
||||||
|
If the material was not in the favorite materials, nothing happens.
|
||||||
|
"""
|
||||||
|
|
||||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||||
favorites = application.getPreferences().getValue("cura/favorite_materials").split(";")
|
favorites = application.getPreferences().getValue("cura/favorite_materials").split(";")
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -9,11 +9,11 @@ from UM.Scene.Selection import Selection
|
||||||
from UM.Qt.ListModel import ListModel
|
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):
|
class MultiBuildPlateModel(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.
|
||||||
|
"""
|
||||||
|
|
||||||
maxBuildPlateChanged = pyqtSignal()
|
maxBuildPlateChanged = pyqtSignal()
|
||||||
activeBuildPlateChanged = pyqtSignal()
|
activeBuildPlateChanged = pyqtSignal()
|
||||||
|
@ -39,9 +39,10 @@ class MultiBuildPlateModel(ListModel):
|
||||||
self._max_build_plate = max_build_plate
|
self._max_build_plate = max_build_plate
|
||||||
self.maxBuildPlateChanged.emit()
|
self.maxBuildPlateChanged.emit()
|
||||||
|
|
||||||
## Return the highest build plate number
|
|
||||||
@pyqtProperty(int, notify = maxBuildPlateChanged)
|
@pyqtProperty(int, notify = maxBuildPlateChanged)
|
||||||
def maxBuildPlate(self):
|
def maxBuildPlate(self):
|
||||||
|
"""Return the highest build plate number"""
|
||||||
|
|
||||||
return self._max_build_plate
|
return self._max_build_plate
|
||||||
|
|
||||||
def setActiveBuildPlate(self, nr):
|
def setActiveBuildPlate(self, nr):
|
||||||
|
|
|
@ -26,10 +26,9 @@ if TYPE_CHECKING:
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# This the QML model for the quality management page.
|
|
||||||
#
|
|
||||||
class QualityManagementModel(ListModel):
|
class QualityManagementModel(ListModel):
|
||||||
|
"""This the QML model for the quality management page."""
|
||||||
|
|
||||||
NameRole = Qt.UserRole + 1
|
NameRole = Qt.UserRole + 1
|
||||||
IsReadOnlyRole = Qt.UserRole + 2
|
IsReadOnlyRole = Qt.UserRole + 2
|
||||||
QualityGroupRole = Qt.UserRole + 3
|
QualityGroupRole = Qt.UserRole + 3
|
||||||
|
@ -74,11 +73,13 @@ class QualityManagementModel(ListModel):
|
||||||
def _onChange(self) -> None:
|
def _onChange(self) -> None:
|
||||||
self._update_timer.start()
|
self._update_timer.start()
|
||||||
|
|
||||||
## Deletes a custom profile. It will be gone forever.
|
|
||||||
# \param quality_changes_group The quality changes group representing the
|
|
||||||
# profile to delete.
|
|
||||||
@pyqtSlot(QObject)
|
@pyqtSlot(QObject)
|
||||||
def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None:
|
def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None:
|
||||||
|
"""Deletes a custom profile. It will be gone forever.
|
||||||
|
|
||||||
|
:param quality_changes_group: The quality changes group representing the profile to delete.
|
||||||
|
"""
|
||||||
|
|
||||||
Logger.log("i", "Removing quality changes group {group_name}".format(group_name = quality_changes_group.name))
|
Logger.log("i", "Removing quality changes group {group_name}".format(group_name = quality_changes_group.name))
|
||||||
removed_quality_changes_ids = set()
|
removed_quality_changes_ids = set()
|
||||||
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
||||||
|
@ -95,16 +96,19 @@ class QualityManagementModel(ListModel):
|
||||||
if extruder_stack.qualityChanges.getId() in removed_quality_changes_ids:
|
if extruder_stack.qualityChanges.getId() in removed_quality_changes_ids:
|
||||||
extruder_stack.qualityChanges = empty_quality_changes_container
|
extruder_stack.qualityChanges = empty_quality_changes_container
|
||||||
|
|
||||||
## Rename a custom profile.
|
|
||||||
#
|
|
||||||
# Because the names must be unique, the new name may not actually become
|
|
||||||
# the name that was given. The actual name is returned by this function.
|
|
||||||
# \param quality_changes_group The custom profile that must be renamed.
|
|
||||||
# \param new_name The desired name for the profile.
|
|
||||||
# \return The actual new name of the profile, after making the name
|
|
||||||
# unique.
|
|
||||||
@pyqtSlot(QObject, str, result = str)
|
@pyqtSlot(QObject, str, result = str)
|
||||||
def renameQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", new_name: str) -> str:
|
def renameQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", new_name: str) -> str:
|
||||||
|
"""Rename a custom profile.
|
||||||
|
|
||||||
|
Because the names must be unique, the new name may not actually become the name that was given. The actual
|
||||||
|
name is returned by this function.
|
||||||
|
|
||||||
|
:param quality_changes_group: The custom profile that must be renamed.
|
||||||
|
:param new_name: The desired name for the profile.
|
||||||
|
|
||||||
|
:return: The actual new name of the profile, after making the name unique.
|
||||||
|
"""
|
||||||
|
|
||||||
Logger.log("i", "Renaming QualityChangesGroup {old_name} to {new_name}.".format(old_name = quality_changes_group.name, new_name = new_name))
|
Logger.log("i", "Renaming QualityChangesGroup {old_name} to {new_name}.".format(old_name = quality_changes_group.name, new_name = new_name))
|
||||||
if new_name == quality_changes_group.name:
|
if new_name == quality_changes_group.name:
|
||||||
Logger.log("i", "QualityChangesGroup name {name} unchanged.".format(name = quality_changes_group.name))
|
Logger.log("i", "QualityChangesGroup name {name} unchanged.".format(name = quality_changes_group.name))
|
||||||
|
@ -138,13 +142,16 @@ class QualityManagementModel(ListModel):
|
||||||
|
|
||||||
return new_name
|
return new_name
|
||||||
|
|
||||||
## Duplicates a given quality profile OR quality changes profile.
|
|
||||||
# \param new_name The desired name of the new profile. This will be made
|
|
||||||
# unique, so it might end up with a different name.
|
|
||||||
# \param quality_model_item The item of this model to duplicate, as
|
|
||||||
# dictionary. See the descriptions of the roles of this list model.
|
|
||||||
@pyqtSlot(str, "QVariantMap")
|
@pyqtSlot(str, "QVariantMap")
|
||||||
def duplicateQualityChanges(self, new_name: str, quality_model_item: Dict[str, Any]) -> None:
|
def duplicateQualityChanges(self, new_name: str, quality_model_item: Dict[str, Any]) -> None:
|
||||||
|
"""Duplicates a given quality profile OR quality changes profile.
|
||||||
|
|
||||||
|
:param new_name: The desired name of the new profile. This will be made unique, so it might end up with a
|
||||||
|
different name.
|
||||||
|
:param quality_model_item: The item of this model to duplicate, as dictionary. See the descriptions of the
|
||||||
|
roles of this list model.
|
||||||
|
"""
|
||||||
|
|
||||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if not global_stack:
|
if not global_stack:
|
||||||
Logger.log("i", "No active global stack, cannot duplicate quality (changes) profile.")
|
Logger.log("i", "No active global stack, cannot duplicate quality (changes) profile.")
|
||||||
|
@ -170,18 +177,18 @@ class QualityManagementModel(ListModel):
|
||||||
new_id = container_registry.uniqueName(container.getId())
|
new_id = container_registry.uniqueName(container.getId())
|
||||||
container_registry.addContainer(container.duplicate(new_id, new_name))
|
container_registry.addContainer(container.duplicate(new_id, new_name))
|
||||||
|
|
||||||
## Create quality changes containers from the user containers in the active
|
|
||||||
# stacks.
|
|
||||||
#
|
|
||||||
# This will go through the global and extruder stacks and create
|
|
||||||
# quality_changes containers from the user containers in each stack. These
|
|
||||||
# then replace the quality_changes containers in the stack and clear the
|
|
||||||
# user settings.
|
|
||||||
# \param base_name The new name for the quality changes profile. The final
|
|
||||||
# name of the profile might be different from this, because it needs to be
|
|
||||||
# made unique.
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def createQualityChanges(self, base_name: str) -> None:
|
def createQualityChanges(self, base_name: str) -> None:
|
||||||
|
"""Create quality changes containers from the user containers in the active stacks.
|
||||||
|
|
||||||
|
This will go through the global and extruder stacks and create quality_changes containers from the user
|
||||||
|
containers in each stack. These then replace the quality_changes containers in the stack and clear the user
|
||||||
|
settings.
|
||||||
|
|
||||||
|
:param base_name: The new name for the quality changes profile. The final name of the profile might be
|
||||||
|
different from this, because it needs to be made unique.
|
||||||
|
"""
|
||||||
|
|
||||||
machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager()
|
machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager()
|
||||||
|
|
||||||
global_stack = machine_manager.activeMachine
|
global_stack = machine_manager.activeMachine
|
||||||
|
@ -220,14 +227,16 @@ class QualityManagementModel(ListModel):
|
||||||
|
|
||||||
container_registry.addContainer(new_changes)
|
container_registry.addContainer(new_changes)
|
||||||
|
|
||||||
## Create a quality changes container with the given set-up.
|
|
||||||
# \param quality_type The quality type of the new container.
|
|
||||||
# \param intent_category The intent category of the new container.
|
|
||||||
# \param new_name The name of the container. This name must be unique.
|
|
||||||
# \param machine The global stack to create the profile for.
|
|
||||||
# \param extruder_stack The extruder stack to create the profile for. If
|
|
||||||
# not provided, only a global container will be created.
|
|
||||||
def _createQualityChanges(self, quality_type: str, intent_category: Optional[str], new_name: str, machine: "GlobalStack", extruder_stack: Optional["ExtruderStack"]) -> "InstanceContainer":
|
def _createQualityChanges(self, quality_type: str, intent_category: Optional[str], new_name: str, machine: "GlobalStack", extruder_stack: Optional["ExtruderStack"]) -> "InstanceContainer":
|
||||||
|
"""Create a quality changes container with the given set-up.
|
||||||
|
|
||||||
|
:param quality_type: The quality type of the new container.
|
||||||
|
:param intent_category: The intent category of the new container.
|
||||||
|
:param new_name: The name of the container. This name must be unique.
|
||||||
|
:param machine: The global stack to create the profile for.
|
||||||
|
:param extruder_stack: The extruder stack to create the profile for. If not provided, only a global container will be created.
|
||||||
|
"""
|
||||||
|
|
||||||
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
||||||
base_id = machine.definition.getId() if extruder_stack is None else extruder_stack.getId()
|
base_id = machine.definition.getId() if extruder_stack is None else extruder_stack.getId()
|
||||||
new_id = base_id + "_" + new_name
|
new_id = base_id + "_" + new_name
|
||||||
|
@ -253,11 +262,13 @@ class QualityManagementModel(ListModel):
|
||||||
quality_changes.setMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.getInstance().SettingVersion)
|
quality_changes.setMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.getInstance().SettingVersion)
|
||||||
return quality_changes
|
return quality_changes
|
||||||
|
|
||||||
## Triggered when any container changed.
|
|
||||||
#
|
|
||||||
# This filters the updates to the container manager: When it applies to
|
|
||||||
# the list of quality changes, we need to update our list.
|
|
||||||
def _qualityChangesListChanged(self, container: "ContainerInterface") -> None:
|
def _qualityChangesListChanged(self, container: "ContainerInterface") -> None:
|
||||||
|
"""Triggered when any container changed.
|
||||||
|
|
||||||
|
This filters the updates to the container manager: When it applies to the list of quality changes, we need to
|
||||||
|
update our list.
|
||||||
|
"""
|
||||||
|
|
||||||
if container.getMetaDataEntry("type") == "quality_changes":
|
if container.getMetaDataEntry("type") == "quality_changes":
|
||||||
self._update()
|
self._update()
|
||||||
|
|
||||||
|
@ -366,18 +377,19 @@ class QualityManagementModel(ListModel):
|
||||||
|
|
||||||
self.setItems(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")
|
@pyqtSlot(str, result = "QVariantList")
|
||||||
def getFileNameFilters(self, io_type):
|
def getFileNameFilters(self, io_type):
|
||||||
|
"""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: name of the needed IO type
|
||||||
|
:return: A list of strings indicating file name filters for a file dialog.
|
||||||
|
|
||||||
|
TODO: Duplicated code here from InstanceContainersModel. Refactor and remove this later.
|
||||||
|
"""
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
catalog = i18nCatalog("uranium")
|
catalog = i18nCatalog("uranium")
|
||||||
#TODO: This function should be in UM.Resources!
|
#TODO: This function should be in UM.Resources!
|
||||||
|
@ -394,9 +406,11 @@ class QualityManagementModel(ListModel):
|
||||||
filters.append(catalog.i18nc("@item:inlistbox", "All Files (*)")) # Also allow arbitrary files, if the user so prefers.
|
filters.append(catalog.i18nc("@item:inlistbox", "All Files (*)")) # Also allow arbitrary files, if the user so prefers.
|
||||||
return filters
|
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):
|
def _getIOPlugins(self, io_type):
|
||||||
|
"""Gets a list of profile reader or writer plugins
|
||||||
|
|
||||||
|
:return: List of tuples of (plugin_id, meta_data).
|
||||||
|
"""
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
pr = PluginRegistry.getInstance()
|
pr = PluginRegistry.getInstance()
|
||||||
active_plugin_ids = pr.getActivePlugins()
|
active_plugin_ids = pr.getActivePlugins()
|
||||||
|
|
|
@ -10,10 +10,9 @@ from cura.Machines.ContainerTree import ContainerTree
|
||||||
from cura.Machines.Models.MachineModelUtils import fetchLayerHeight
|
from cura.Machines.Models.MachineModelUtils import fetchLayerHeight
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# QML Model for all built-in quality profiles. This model is used for the drop-down quality menu.
|
|
||||||
#
|
|
||||||
class QualityProfilesDropDownMenuModel(ListModel):
|
class QualityProfilesDropDownMenuModel(ListModel):
|
||||||
|
"""QML Model for all built-in quality profiles. This model is used for the drop-down quality menu."""
|
||||||
|
|
||||||
NameRole = Qt.UserRole + 1
|
NameRole = Qt.UserRole + 1
|
||||||
QualityTypeRole = Qt.UserRole + 2
|
QualityTypeRole = Qt.UserRole + 2
|
||||||
LayerHeightRole = Qt.UserRole + 3
|
LayerHeightRole = Qt.UserRole + 3
|
||||||
|
|
|
@ -10,10 +10,9 @@ from UM.Qt.ListModel import ListModel
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
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):
|
class QualitySettingsModel(ListModel):
|
||||||
|
"""This model is used to show details settings of the selected quality in the quality management page."""
|
||||||
|
|
||||||
KeyRole = Qt.UserRole + 1
|
KeyRole = Qt.UserRole + 1
|
||||||
LabelRole = Qt.UserRole + 2
|
LabelRole = Qt.UserRole + 2
|
||||||
UnitRole = Qt.UserRole + 3
|
UnitRole = Qt.UserRole + 3
|
||||||
|
|
|
@ -6,12 +6,12 @@ from typing import Any, Dict, Optional
|
||||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
|
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
|
||||||
|
|
||||||
|
|
||||||
## Data struct to group several quality changes instance containers together.
|
|
||||||
#
|
|
||||||
# Each group represents one "custom profile" as the user sees it, which
|
|
||||||
# contains an instance container for the global stack and one instance
|
|
||||||
# container per extruder.
|
|
||||||
class QualityChangesGroup(QObject):
|
class QualityChangesGroup(QObject):
|
||||||
|
"""Data struct to group several quality changes instance containers together.
|
||||||
|
|
||||||
|
Each group represents one "custom profile" as the user sees it, which contains an instance container for the
|
||||||
|
global stack and one instance container per extruder.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, name: str, quality_type: str, intent_category: str, parent: Optional["QObject"] = None) -> None:
|
def __init__(self, name: str, quality_type: str, intent_category: str, parent: Optional["QObject"] = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
|
@ -9,28 +9,34 @@ from UM.Util import parseBool
|
||||||
from cura.Machines.ContainerNode import ContainerNode
|
from cura.Machines.ContainerNode import ContainerNode
|
||||||
|
|
||||||
|
|
||||||
## A QualityGroup represents a group of quality containers that must be applied
|
|
||||||
# to each ContainerStack when it's used.
|
|
||||||
#
|
|
||||||
# A concrete example: When there are two extruders and the user selects the
|
|
||||||
# quality type "normal", this quality type must be applied to all stacks in a
|
|
||||||
# machine, although each stack can have different containers. So one global
|
|
||||||
# profile gets put on the global stack and one extruder profile gets put on
|
|
||||||
# each extruder stack. This quality group then contains the following
|
|
||||||
# profiles (for instance):
|
|
||||||
# GlobalStack ExtruderStack 1 ExtruderStack 2
|
|
||||||
# quality container: um3_global_normal um3_aa04_pla_normal um3_aa04_abs_normal
|
|
||||||
#
|
|
||||||
# The purpose of these quality groups is to group the containers that can be
|
|
||||||
# applied to a configuration, so that when a quality level is selected, the
|
|
||||||
# container can directly be applied to each stack instead of looking them up
|
|
||||||
# again.
|
|
||||||
class QualityGroup:
|
class QualityGroup:
|
||||||
## Constructs a new group.
|
"""A QualityGroup represents a group of quality containers that must be applied to each ContainerStack when it's
|
||||||
# \param name The user-visible name for the group.
|
used.
|
||||||
# \param quality_type The quality level that each profile in this group
|
|
||||||
# has.
|
A concrete example: When there are two extruders and the user selects the quality type "normal", this quality
|
||||||
|
type must be applied to all stacks in a machine, although each stack can have different containers. So one global
|
||||||
|
profile gets put on the global stack and one extruder profile gets put on each extruder stack. This quality group
|
||||||
|
then contains the following profiles (for instance):
|
||||||
|
- GlobalStack
|
||||||
|
- ExtruderStack 1
|
||||||
|
- ExtruderStack 2
|
||||||
|
quality container:
|
||||||
|
- um3_global_normal
|
||||||
|
- um3_aa04_pla_normal
|
||||||
|
- um3_aa04_abs_normal
|
||||||
|
|
||||||
|
The purpose of these quality groups is to group the containers that can be applied to a configuration,
|
||||||
|
so that when a quality level is selected, the container can directly be applied to each stack instead of looking
|
||||||
|
them up again.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, name: str, quality_type: str) -> None:
|
def __init__(self, name: str, quality_type: str) -> None:
|
||||||
|
"""Constructs a new group.
|
||||||
|
|
||||||
|
:param name: The user-visible name for the group.
|
||||||
|
:param quality_type: The quality level that each profile in this group has.
|
||||||
|
"""
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.node_for_global = None # type: Optional[ContainerNode]
|
self.node_for_global = None # type: Optional[ContainerNode]
|
||||||
self.nodes_for_extruders = {} # type: Dict[int, ContainerNode]
|
self.nodes_for_extruders = {} # type: Dict[int, ContainerNode]
|
||||||
|
|
|
@ -13,12 +13,14 @@ if TYPE_CHECKING:
|
||||||
from cura.Machines.MachineNode import MachineNode
|
from cura.Machines.MachineNode import MachineNode
|
||||||
|
|
||||||
|
|
||||||
## Represents a quality profile in the container tree.
|
|
||||||
#
|
|
||||||
# This may either be a normal quality profile or a global quality profile.
|
|
||||||
#
|
|
||||||
# Its subcontainers are intent profiles.
|
|
||||||
class QualityNode(ContainerNode):
|
class QualityNode(ContainerNode):
|
||||||
|
"""Represents a quality profile in the container tree.
|
||||||
|
|
||||||
|
This may either be a normal quality profile or a global quality profile.
|
||||||
|
|
||||||
|
Its subcontainers are intent profiles.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, container_id: str, parent: Union["MaterialNode", "MachineNode"]) -> None:
|
def __init__(self, container_id: str, parent: Union["MaterialNode", "MachineNode"]) -> None:
|
||||||
super().__init__(container_id)
|
super().__init__(container_id)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
|
|
@ -17,16 +17,16 @@ if TYPE_CHECKING:
|
||||||
from cura.Machines.MachineNode import MachineNode
|
from cura.Machines.MachineNode import MachineNode
|
||||||
|
|
||||||
|
|
||||||
## This class represents an extruder variant in the container tree.
|
|
||||||
#
|
|
||||||
# The subnodes of these nodes are materials.
|
|
||||||
#
|
|
||||||
# This node contains materials with ALL filament diameters underneath it. The
|
|
||||||
# tree of this variant is not specific to one global stack, so because the
|
|
||||||
# list of materials can be different per stack depending on the compatible
|
|
||||||
# material diameter setting, we cannot filter them here. Filtering must be
|
|
||||||
# done in the model.
|
|
||||||
class VariantNode(ContainerNode):
|
class VariantNode(ContainerNode):
|
||||||
|
"""This class represents an extruder variant in the container tree.
|
||||||
|
|
||||||
|
The subnodes of these nodes are materials.
|
||||||
|
|
||||||
|
This node contains materials with ALL filament diameters underneath it. The tree of this variant is not specific
|
||||||
|
to one global stack, so because the list of materials can be different per stack depending on the compatible
|
||||||
|
material diameter setting, we cannot filter them here. Filtering must be done in the model.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, container_id: str, machine: "MachineNode") -> None:
|
def __init__(self, container_id: str, machine: "MachineNode") -> None:
|
||||||
super().__init__(container_id)
|
super().__init__(container_id)
|
||||||
self.machine = machine
|
self.machine = machine
|
||||||
|
@ -39,9 +39,10 @@ class VariantNode(ContainerNode):
|
||||||
container_registry.containerRemoved.connect(self._materialRemoved)
|
container_registry.containerRemoved.connect(self._materialRemoved)
|
||||||
self._loadAll()
|
self._loadAll()
|
||||||
|
|
||||||
## (Re)loads all materials under this variant.
|
|
||||||
@UM.FlameProfiler.profile
|
@UM.FlameProfiler.profile
|
||||||
def _loadAll(self) -> None:
|
def _loadAll(self) -> None:
|
||||||
|
"""(Re)loads all materials under this variant."""
|
||||||
|
|
||||||
container_registry = ContainerRegistry.getInstance()
|
container_registry = ContainerRegistry.getInstance()
|
||||||
|
|
||||||
if not self.machine.has_materials:
|
if not self.machine.has_materials:
|
||||||
|
@ -69,18 +70,18 @@ class VariantNode(ContainerNode):
|
||||||
if not self.materials:
|
if not self.materials:
|
||||||
self.materials["empty_material"] = MaterialNode("empty_material", variant = self)
|
self.materials["empty_material"] = MaterialNode("empty_material", variant = self)
|
||||||
|
|
||||||
## Finds the preferred material for this printer with this nozzle in one of
|
|
||||||
# the extruders.
|
|
||||||
#
|
|
||||||
# If the preferred material is not available, an arbitrary material is
|
|
||||||
# returned. If there is a configuration mistake (like a typo in the
|
|
||||||
# preferred material) this returns a random available material. If there
|
|
||||||
# are no available materials, this will return the empty material node.
|
|
||||||
# \param approximate_diameter The desired approximate diameter of the
|
|
||||||
# material.
|
|
||||||
# \return The node for the preferred material, or any arbitrary material
|
|
||||||
# if there is no match.
|
|
||||||
def preferredMaterial(self, approximate_diameter: int) -> MaterialNode:
|
def preferredMaterial(self, approximate_diameter: int) -> MaterialNode:
|
||||||
|
"""Finds the preferred material for this printer with this nozzle in one of the extruders.
|
||||||
|
|
||||||
|
If the preferred material is not available, an arbitrary material is returned. If there is a configuration
|
||||||
|
mistake (like a typo in the preferred material) this returns a random available material. If there are no
|
||||||
|
available materials, this will return the empty material node.
|
||||||
|
|
||||||
|
:param approximate_diameter: The desired approximate diameter of the material.
|
||||||
|
|
||||||
|
:return: The node for the preferred material, or any arbitrary material if there is no match.
|
||||||
|
"""
|
||||||
|
|
||||||
for base_material, material_node in self.materials.items():
|
for base_material, material_node in self.materials.items():
|
||||||
if self.machine.preferred_material == base_material and approximate_diameter == int(material_node.getMetaDataEntry("approximate_diameter")):
|
if self.machine.preferred_material == base_material and approximate_diameter == int(material_node.getMetaDataEntry("approximate_diameter")):
|
||||||
return material_node
|
return material_node
|
||||||
|
@ -107,10 +108,10 @@ class VariantNode(ContainerNode):
|
||||||
))
|
))
|
||||||
return fallback
|
return fallback
|
||||||
|
|
||||||
## When a material gets added to the set of profiles, we need to update our
|
|
||||||
# tree here.
|
|
||||||
@UM.FlameProfiler.profile
|
@UM.FlameProfiler.profile
|
||||||
def _materialAdded(self, container: ContainerInterface) -> None:
|
def _materialAdded(self, container: ContainerInterface) -> None:
|
||||||
|
"""When a material gets added to the set of profiles, we need to update our tree here."""
|
||||||
|
|
||||||
if container.getMetaDataEntry("type") != "material":
|
if container.getMetaDataEntry("type") != "material":
|
||||||
return # Not interested.
|
return # Not interested.
|
||||||
if not ContainerRegistry.getInstance().findContainersMetadata(id = container.getId()):
|
if not ContainerRegistry.getInstance().findContainersMetadata(id = container.getId()):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue