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:
Jelle Spijker 2020-05-08 18:59:38 +02:00 committed by Jelle Spijker
parent d69bf84424
commit 120541a8db
No known key found for this signature in database
GPG key ID: 6662DC033BE6B99A
26 changed files with 577 additions and 441 deletions

View file

@ -9,47 +9,59 @@ from UM.Logger import Logger
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:
## Creates a new node for the container tree.
# \param container_id The ID of the container that this node should
# represent.
"""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.
"""
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 = None # type: Optional[InstanceContainer]
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]:
"""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]
## 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:
"""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)
if len(container_metadata) == 0:
return 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
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:
container_list = ContainerRegistry.getInstance().findInstanceContainers(id = self.container_id)
if len(container_list) == 0:

View file

@ -19,17 +19,16 @@ if TYPE_CHECKING:
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:
"""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"]
@classmethod
@ -43,13 +42,15 @@ class ContainerTree:
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.
## 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"]:
"""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()
if global_stack is None:
return {}
@ -58,14 +59,15 @@ class ContainerTree:
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
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"]:
"""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()
if global_stack is None:
return []
@ -74,31 +76,43 @@ class ContainerTree:
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
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:
"""Ran after completely starting up the application."""
currently_added = ContainerRegistry.getInstance().findContainerStacks() # Find all currently added global stacks.
JobQueue.getInstance().add(self._MachineNodeLoadJob(self, currently_added))
## Dictionary-like object that contains the machines.
#
# This handles the lazy loading of MachineNodes.
class _MachineNodeMap:
"""Dictionary-like object that contains the machines.
This handles the lazy loading of MachineNodes.
"""
def __init__(self) -> None:
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:
"""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
## 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:
"""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:
start_time = time.time()
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))
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]:
"""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:
return default
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:
"""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
## Pre-loads all currently added printers as a background task so that
# switching printers in the interface is faster.
class _MachineNodeLoadJob(Job):
## 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.
"""Pre-loads all currently added printers as a background task so that switching printers in the interface is
faster.
"""
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.container_stacks = container_stacks
super().__init__()
## Starts the background task.
#
# The ``JobQueue`` will schedule this on a different thread.
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.
if not isinstance(stack, GlobalStack):
continue

View file

@ -11,10 +11,12 @@ if TYPE_CHECKING:
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):
"""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:
super().__init__(container_id)
self.quality = quality

View file

@ -13,16 +13,16 @@ from UM.Settings.SettingDefinition import SettingDefinition
from UM.Settings.Validator import ValidatorState
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):
"""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:
super().__init__(parent)
@ -92,24 +92,37 @@ class MachineErrorChecker(QObject):
def needToWaitForResult(self) -> bool:
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:
"""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":
return
self.startErrorCheck()
# Starts the error check timer to schedule a new error check.
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:
self._need_to_check = True
self.needToWaitForResultChanged.emit()
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:
"""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:
self._need_to_check = True
self.needToWaitForResultChanged.emit()

View file

@ -17,10 +17,12 @@ from cura.Machines.VariantNode import VariantNode
import UM.FlameProfiler
## This class represents a machine in the container tree.
#
# The subnodes of these nodes are variants.
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:
super().__init__(container_id)
self.variants = {} # type: Dict[str, VariantNode] # Mapping variant names to their nodes.
@ -47,20 +49,21 @@ class MachineNode(ContainerNode):
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]:
"""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):
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 {}
@ -98,28 +101,26 @@ class MachineNode(ContainerNode):
quality_groups[quality_type].is_available = True
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]:
"""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.
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())
## 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":
"""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())))
## (Re)loads all variants under this printer.
@UM.FlameProfiler.profile
def _loadAll(self) -> None:
"""(Re)loads all variants under this printer."""
container_registry = ContainerRegistry.getInstance()
if not self.has_variants:
self.variants["empty"] = VariantNode("empty_variant", machine = self)

View file

@ -7,18 +7,21 @@ if TYPE_CHECKING:
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:
"""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")
def __init__(self, name: str, root_material_node: "MaterialNode") -> None:

View file

@ -15,10 +15,12 @@ if TYPE_CHECKING:
from cura.Machines.VariantNode import VariantNode
## Represents a material in the container tree.
#
# Its subcontainers are quality profiles.
class MaterialNode(ContainerNode):
"""Represents a material in the container tree.
Its subcontainers are quality profiles.
"""
def __init__(self, container_id: str, variant: "VariantNode") -> None:
super().__init__(container_id)
self.variant = variant
@ -34,16 +36,16 @@ class MaterialNode(ContainerNode):
container_registry.containerRemoved.connect(self._onRemoved)
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:
"""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():
if self.variant.machine.preferred_quality_type == quality_node.quality_type:
return quality_node
@ -107,10 +109,13 @@ class MaterialNode(ContainerNode):
if not self.qualities:
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:
"""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:
# Remove myself from my parent.
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.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:
"""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:
return

View file

@ -13,11 +13,13 @@ from cura.Machines.ContainerTree import ContainerTree
from cura.Machines.MaterialNode import MaterialNode
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):
"""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()
enabledChanged = pyqtSignal()
@ -121,10 +123,13 @@ class BaseMaterialsModel(ListModel):
def enabled(self):
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:
"""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:
return
if material.variant.container_id != self._extruder_stack.variant.getId():
@ -136,14 +141,15 @@ class BaseMaterialsModel(ListModel):
return
self._onChanged()
## Triggered when the list of favorite materials is changed.
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:
self._onChanged()
## This is an abstract method that needs to be implemented by the specific
# models themselves.
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(";"))
# 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()
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):
"""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
if global_stack is None or not self._enabled:
return False
@ -177,9 +183,10 @@ class BaseMaterialsModel(ListModel):
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):
"""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)
if not metadata_list:
return None

View file

@ -14,9 +14,8 @@ if TYPE_CHECKING:
from UM.Settings.Interfaces import ContainerInterface
## This model is used for the custom profile items in the profile drop down
# menu.
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:
super().__init__(parent)

View file

@ -9,9 +9,9 @@ if TYPE_CHECKING:
class DiscoveredCloudPrintersModel(ListModel):
"""
Model used to inform the application about newly added cloud printers, which are discovered from the user's account
"""
"""Model used to inform the application about newly added cloud printers, which are discovered from the user's
account """
DeviceKeyRole = Qt.UserRole + 1
DeviceNameRole = Qt.UserRole + 2
DeviceTypeRole = Qt.UserRole + 3
@ -31,18 +31,24 @@ class DiscoveredCloudPrintersModel(ListModel):
self._application = application # type: CuraApplication
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",
"name": "NG 001",
"machine_type": "Ultimaker S5",
"firmware_version": "5.5.12.202001"
}
:param new_devices: List of dictionaries which contain information about added cloud printers.
:return: None
"""
self._discovered_cloud_printers_list.extend(new_devices)
self._update()
@ -51,21 +57,21 @@ class DiscoveredCloudPrintersModel(ListModel):
@pyqtSlot()
def clear(self) -> None:
"""
Clears the contents of the DiscoveredCloudPrintersModel.
"""Clears the contents of the DiscoveredCloudPrintersModel.
:return: None
"""
self._discovered_cloud_printers_list = []
self._update()
self.cloudPrintersDetectedChanged.emit(False)
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
"""
items = self._discovered_cloud_printers_list[:]
items.sort(key = lambda k: k["name"])
self.setItems(items)

View file

@ -115,12 +115,11 @@ class DiscoveredPrinter(QObject):
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):
"""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:
super().__init__(parent)
@ -254,8 +253,14 @@ class DiscoveredPrintersModel(QObject):
del self._discovered_printer_by_ip_dict[ip_address]
self.discoveredPrintersChanged.emit()
# A convenience function for QML to create a machine (GlobalStack) out of the given discovered printer.
# This function invokes the given discovered printer's "create_callback" to do this.
@pyqtSlot("QVariant")
def createMachineFromDiscoveredPrinter(self, discovered_printer: "DiscoveredPrinter") -> None:
"""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())

View file

@ -15,27 +15,27 @@ if TYPE_CHECKING:
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):
"""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.
IdRole = Qt.UserRole + 1
## Human-readable name of the extruder.
NameRole = Qt.UserRole + 2
"""Human-readable name of the extruder."""
## Colour of the material loaded in the extruder.
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
"""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.
DefinitionRole = Qt.UserRole + 5
@ -50,18 +50,18 @@ class ExtrudersModel(ListModel):
MaterialBrandRole = Qt.UserRole + 9
ColorNameRole = Qt.UserRole + 10
## Is the extruder enabled?
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"]
"""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):
"""Initialises the extruders model, defining the roles and listening for changes in the data.
:param parent: Parent QtObject of this list.
"""
super().__init__(parent)
self.addRoleName(self.IdRole, "id")
@ -101,14 +101,15 @@ class ExtrudersModel(ListModel):
def addOptionalExtruder(self):
return self._add_optional_extruder
## Links to the stack-changed signal of the new extruders when an extruder
# is swapped out or added in the current machine.
#
# \param machine_id The machine for which the extruders changed. This is
# filled by the ExtruderManager.extrudersChanged signal when coming from
# that signal. Application.globalContainerStackChanged doesn't fill this
# signal; it's assumed to be the current printer in that case.
def _extrudersChanged(self, machine_id = None):
"""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()
if machine_id is not None:
if machine_manager.activeMachine is None:
@ -146,11 +147,13 @@ class ExtrudersModel(ListModel):
def _updateExtruders(self):
self._update_extruder_timer.start()
## Update the list of extruders.
#
# This should be called whenever the list of extruders changes.
@UM.FlameProfiler.profile
def __updateExtruders(self):
"""Update the list of extruders.
This should be called whenever the list of extruders changes.
"""
extruders_changed = False
if self.count != 0:

View file

@ -4,16 +4,17 @@
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
import cura.CuraApplication # To listen to changes to the preferences.
## Model that shows the list of favorite materials.
class FavoriteMaterialsModel(BaseMaterialsModel):
"""Model that shows the list of favorite materials."""
def __init__(self, parent = None):
super().__init__(parent)
cura.CuraApplication.CuraApplication.getInstance().getPreferences().preferenceChanged.connect(self._onFavoritesChanged)
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:
"""Triggered when any preference changes, but only handles it when the list of favourites is changed. """
if preference_key != "cura/favorite_materials":
return
self._onChanged()

View file

@ -11,13 +11,13 @@ if TYPE_CHECKING:
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):
"""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
ContentRole = Qt.UserRole + 2
@ -73,9 +73,10 @@ class FirstStartMachineActionsModel(ListModel):
self._current_action_index += 1
self.currentActionIndexChanged.emit()
# Resets the current action index to 0 so the wizard panel can show actions from the beginning.
@pyqtSlot()
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.currentActionIndexChanged.emit()

View file

@ -42,8 +42,9 @@ class GlobalStacksModel(ListModel):
CuraContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
self._updateDelayed()
## Handler for container added/removed events from registry
def _onContainerChanged(self, container) -> None:
"""Handler for container added/removed events from registry"""
# We only need to update when the added / removed container GlobalStack
if isinstance(container, GlobalStack):
self._updateDelayed()

View file

@ -18,9 +18,9 @@ from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
## Lists the intent categories that are available for the current printer
# configuration.
class IntentCategoryModel(ListModel):
"""Lists the intent categories that are available for the current printer configuration. """
NameRole = Qt.UserRole + 1
IntentCategoryRole = Qt.UserRole + 2
WeightRole = Qt.UserRole + 3
@ -31,10 +31,12 @@ class IntentCategoryModel(ListModel):
_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
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:
cls._translations["default"] = {
"name": catalog.i18nc("@label", "Default")
@ -53,9 +55,12 @@ class IntentCategoryModel(ListModel):
}
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:
"""Creates a new model for a certain intent category.
:param intent_category: category to list the intent profiles for.
"""
super().__init__()
self._intent_category = intent_category
@ -84,16 +89,18 @@ class IntentCategoryModel(ListModel):
self.update()
## Updates the list of intents if an intent profile was added or removed.
def _onContainerChange(self, container: "ContainerInterface") -> None:
"""Updates the list of intents if an intent profile was added or removed."""
if container.getMetaDataEntry("type") == "intent":
self.update()
def update(self):
self._update_timer.start()
## Updates the list of intents.
def _update(self) -> None:
"""Updates the list of intents."""
available_categories = IntentManager.getInstance().currentAvailableIntentCategories()
result = []
for category in available_categories:
@ -109,9 +116,9 @@ class IntentCategoryModel(ListModel):
result.sort(key = lambda k: k["weight"])
self.setItems(result)
## Get a display value for a category.
## for categories and keys
@staticmethod
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, {})
return display_strings.get(key, default)

View file

@ -98,8 +98,9 @@ class IntentModel(ListModel):
new_items = sorted(new_items, key = lambda x: x["layer_height"])
self.setItems(new_items)
## Get the active materials for all extruders. No duplicates will be returned
def _getActiveMaterials(self) -> Set["MaterialNode"]:
"""Get the active materials for all extruders. No duplicates will be returned"""
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
if global_stack is None:
return set()

View file

@ -19,28 +19,31 @@ if TYPE_CHECKING:
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):
## Triggered when a favorite is added or removed.
# \param The base file of the material is provided as parameter when this
# emits.
favoritesChanged = pyqtSignal(str)
"""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.
"""
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)
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()
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"):
@ -48,11 +51,14 @@ class MaterialManagementModel(QObject):
return False
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)
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()
root_material_id = material_node.base_file
if container_registry.isReadOnly(root_material_id):
@ -60,18 +66,20 @@ class MaterialManagementModel(QObject):
return
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")
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()
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:
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,
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()
root_materials = container_registry.findContainers(id = base_file)
@ -171,29 +181,32 @@ class MaterialManagementModel(QObject):
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)
def duplicateMaterial(self, material_node: "MaterialNode", new_base_id: Optional[str] = None,
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)
## 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)
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.
application = cura.CuraApplication.CuraApplication.getInstance()
application.saveSettings()
@ -218,10 +231,13 @@ class MaterialManagementModel(QObject):
self.duplicateMaterial(preferred_material_node, new_base_id = new_id, new_metadata = new_metadata)
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)
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()
favorites = application.getPreferences().getValue("cura/favorite_materials").split(";")
if material_base_file not in favorites:
@ -230,11 +246,13 @@ class MaterialManagementModel(QObject):
application.saveSettings()
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)
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()
favorites = application.getPreferences().getValue("cura/favorite_materials").split(";")
try:

View file

@ -9,11 +9,11 @@ from UM.Scene.Selection import Selection
from UM.Qt.ListModel import ListModel
#
# This is the model for multi build plate feature.
# This has nothing to do with the build plate types you can choose on the sidebar for a machine.
#
class MultiBuildPlateModel(ListModel):
"""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()
activeBuildPlateChanged = pyqtSignal()
@ -39,9 +39,10 @@ class MultiBuildPlateModel(ListModel):
self._max_build_plate = max_build_plate
self.maxBuildPlateChanged.emit()
## Return the highest build plate number
@pyqtProperty(int, notify = maxBuildPlateChanged)
def maxBuildPlate(self):
"""Return the highest build plate number"""
return self._max_build_plate
def setActiveBuildPlate(self, nr):

View file

@ -26,10 +26,9 @@ if TYPE_CHECKING:
from cura.Settings.GlobalStack import GlobalStack
#
# This the QML model for the quality management page.
#
class QualityManagementModel(ListModel):
"""This the QML model for the quality management page."""
NameRole = Qt.UserRole + 1
IsReadOnlyRole = Qt.UserRole + 2
QualityGroupRole = Qt.UserRole + 3
@ -74,11 +73,13 @@ class QualityManagementModel(ListModel):
def _onChange(self) -> None:
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)
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))
removed_quality_changes_ids = set()
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
@ -95,16 +96,19 @@ class QualityManagementModel(ListModel):
if extruder_stack.qualityChanges.getId() in removed_quality_changes_ids:
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)
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))
if new_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
## 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")
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()
if not global_stack:
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())
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)
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()
global_stack = machine_manager.activeMachine
@ -220,14 +227,16 @@ class QualityManagementModel(ListModel):
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":
"""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()
base_id = machine.definition.getId() if extruder_stack is None else extruder_stack.getId()
new_id = base_id + "_" + new_name
@ -253,11 +262,13 @@ class QualityManagementModel(ListModel):
quality_changes.setMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.getInstance().SettingVersion)
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:
"""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":
self._update()
@ -366,18 +377,19 @@ class QualityManagementModel(ListModel):
self.setItems(item_list)
# TODO: Duplicated code here from InstanceContainersModel. Refactor and remove this later.
#
## Gets a list of the possible file filters that the plugins have
# registered they can read or write. The convenience meta-filters
# "All Supported Types" and "All Files" are added when listing
# readers, but not when listing writers.
#
# \param io_type \type{str} name of the needed IO type
# \return A list of strings indicating file name filters for a file
# dialog.
@pyqtSlot(str, result = "QVariantList")
def getFileNameFilters(self, io_type):
"""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
catalog = i18nCatalog("uranium")
#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.
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):
"""Gets a list of profile reader or writer plugins
:return: List of tuples of (plugin_id, meta_data).
"""
from UM.PluginRegistry import PluginRegistry
pr = PluginRegistry.getInstance()
active_plugin_ids = pr.getActivePlugins()

View file

@ -10,10 +10,9 @@ from cura.Machines.ContainerTree import ContainerTree
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):
"""QML Model for all built-in quality profiles. This model is used for the drop-down quality menu."""
NameRole = Qt.UserRole + 1
QualityTypeRole = Qt.UserRole + 2
LayerHeightRole = Qt.UserRole + 3

View file

@ -10,10 +10,9 @@ from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerRegistry import ContainerRegistry
#
# This model is used to show details settings of the selected quality in the quality management page.
#
class QualitySettingsModel(ListModel):
"""This model is used to show details settings of the selected quality in the quality management page."""
KeyRole = Qt.UserRole + 1
LabelRole = Qt.UserRole + 2
UnitRole = Qt.UserRole + 3

View file

@ -6,12 +6,12 @@ from typing import Any, Dict, Optional
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):
"""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:
super().__init__(parent)

View file

@ -9,28 +9,34 @@ from UM.Util import parseBool
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:
## 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.
"""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.
"""
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.node_for_global = None # type: Optional[ContainerNode]
self.nodes_for_extruders = {} # type: Dict[int, ContainerNode]

View file

@ -13,12 +13,14 @@ if TYPE_CHECKING:
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):
"""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:
super().__init__(container_id)
self.parent = parent

View file

@ -17,16 +17,16 @@ if TYPE_CHECKING:
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):
"""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:
super().__init__(container_id)
self.machine = machine
@ -39,9 +39,10 @@ class VariantNode(ContainerNode):
container_registry.containerRemoved.connect(self._materialRemoved)
self._loadAll()
## (Re)loads all materials under this variant.
@UM.FlameProfiler.profile
def _loadAll(self) -> None:
"""(Re)loads all materials under this variant."""
container_registry = ContainerRegistry.getInstance()
if not self.machine.has_materials:
@ -69,18 +70,18 @@ class VariantNode(ContainerNode):
if not self.materials:
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:
"""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():
if self.machine.preferred_material == base_material and approximate_diameter == int(material_node.getMetaDataEntry("approximate_diameter")):
return material_node
@ -107,10 +108,10 @@ class VariantNode(ContainerNode):
))
return fallback
## When a material gets added to the set of profiles, we need to update our
# tree here.
@UM.FlameProfiler.profile
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":
return # Not interested.
if not ContainerRegistry.getInstance().findContainersMetadata(id = container.getId()):