diff --git a/cura/Machines/ContainerNode.py b/cura/Machines/ContainerNode.py index 8a9ddcc39b..1fb02b099f 100644 --- a/cura/Machines/ContainerNode.py +++ b/cura/Machines/ContainerNode.py @@ -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: diff --git a/cura/Machines/ContainerTree.py b/cura/Machines/ContainerTree.py index a7bb0610bd..7902f8163c 100644 --- a/cura/Machines/ContainerTree.py +++ b/cura/Machines/ContainerTree.py @@ -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 diff --git a/cura/Machines/IntentNode.py b/cura/Machines/IntentNode.py index 2b3a596f81..f25a171d55 100644 --- a/cura/Machines/IntentNode.py +++ b/cura/Machines/IntentNode.py @@ -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 diff --git a/cura/Machines/MachineErrorChecker.py b/cura/Machines/MachineErrorChecker.py index 7a5291dac5..bc9ef723d4 100644 --- a/cura/Machines/MachineErrorChecker.py +++ b/cura/Machines/MachineErrorChecker.py @@ -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() diff --git a/cura/Machines/MachineNode.py b/cura/Machines/MachineNode.py index 6a415b01c4..c546f4d68d 100644 --- a/cura/Machines/MachineNode.py +++ b/cura/Machines/MachineNode.py @@ -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) diff --git a/cura/Machines/MaterialGroup.py b/cura/Machines/MaterialGroup.py index e05647e674..bce7b410c3 100644 --- a/cura/Machines/MaterialGroup.py +++ b/cura/Machines/MaterialGroup.py @@ -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: diff --git a/cura/Machines/MaterialNode.py b/cura/Machines/MaterialNode.py index dcd4adcfdb..333452cc01 100644 --- a/cura/Machines/MaterialNode.py +++ b/cura/Machines/MaterialNode.py @@ -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 diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py index 5e672faa12..3bc7803e9a 100644 --- a/cura/Machines/Models/BaseMaterialsModel.py +++ b/cura/Machines/Models/BaseMaterialsModel.py @@ -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 diff --git a/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py b/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py index 1ab7e21700..ce4b87da2b 100644 --- a/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py +++ b/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py @@ -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) diff --git a/cura/Machines/Models/DiscoveredCloudPrintersModel.py b/cura/Machines/Models/DiscoveredCloudPrintersModel.py index 23dcba6de7..692ed49593 100644 --- a/cura/Machines/Models/DiscoveredCloudPrintersModel.py +++ b/cura/Machines/Models/DiscoveredCloudPrintersModel.py @@ -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) diff --git a/cura/Machines/Models/DiscoveredPrintersModel.py b/cura/Machines/Models/DiscoveredPrintersModel.py index 6d1bbdb698..176baf47e4 100644 --- a/cura/Machines/Models/DiscoveredPrintersModel.py +++ b/cura/Machines/Models/DiscoveredPrintersModel.py @@ -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()) diff --git a/cura/Machines/Models/ExtrudersModel.py b/cura/Machines/Models/ExtrudersModel.py index 9eee7f5f9e..942799cf43 100644 --- a/cura/Machines/Models/ExtrudersModel.py +++ b/cura/Machines/Models/ExtrudersModel.py @@ -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: diff --git a/cura/Machines/Models/FavoriteMaterialsModel.py b/cura/Machines/Models/FavoriteMaterialsModel.py index 6b8f0e8e56..203888d6fb 100644 --- a/cura/Machines/Models/FavoriteMaterialsModel.py +++ b/cura/Machines/Models/FavoriteMaterialsModel.py @@ -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() diff --git a/cura/Machines/Models/FirstStartMachineActionsModel.py b/cura/Machines/Models/FirstStartMachineActionsModel.py index 92caed7b12..7d83f0bff2 100644 --- a/cura/Machines/Models/FirstStartMachineActionsModel.py +++ b/cura/Machines/Models/FirstStartMachineActionsModel.py @@ -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() diff --git a/cura/Machines/Models/GlobalStacksModel.py b/cura/Machines/Models/GlobalStacksModel.py index 9db4ffe6db..73d58f22c0 100644 --- a/cura/Machines/Models/GlobalStacksModel.py +++ b/cura/Machines/Models/GlobalStacksModel.py @@ -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() diff --git a/cura/Machines/Models/IntentCategoryModel.py b/cura/Machines/Models/IntentCategoryModel.py index 427e60ec0c..d8502a7574 100644 --- a/cura/Machines/Models/IntentCategoryModel.py +++ b/cura/Machines/Models/IntentCategoryModel.py @@ -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) diff --git a/cura/Machines/Models/IntentModel.py b/cura/Machines/Models/IntentModel.py index 951be7ab2d..0ec7e268f0 100644 --- a/cura/Machines/Models/IntentModel.py +++ b/cura/Machines/Models/IntentModel.py @@ -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() diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py index f00b81e987..d18b040936 100644 --- a/cura/Machines/Models/MaterialManagementModel.py +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -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: diff --git a/cura/Machines/Models/MultiBuildPlateModel.py b/cura/Machines/Models/MultiBuildPlateModel.py index add960a545..8e2f086e3b 100644 --- a/cura/Machines/Models/MultiBuildPlateModel.py +++ b/cura/Machines/Models/MultiBuildPlateModel.py @@ -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): diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index 74dc8649d0..6dfed60f7b 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -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() diff --git a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py index 3a79ceeaf1..7aa30c6f82 100644 --- a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py +++ b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py @@ -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 diff --git a/cura/Machines/Models/QualitySettingsModel.py b/cura/Machines/Models/QualitySettingsModel.py index 8a956263e7..04af5aff7b 100644 --- a/cura/Machines/Models/QualitySettingsModel.py +++ b/cura/Machines/Models/QualitySettingsModel.py @@ -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 diff --git a/cura/Machines/QualityChangesGroup.py b/cura/Machines/QualityChangesGroup.py index 655060070b..ecaaa67b7a 100644 --- a/cura/Machines/QualityChangesGroup.py +++ b/cura/Machines/QualityChangesGroup.py @@ -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) diff --git a/cura/Machines/QualityGroup.py b/cura/Machines/QualityGroup.py index 97b5e28b41..8e470e0102 100644 --- a/cura/Machines/QualityGroup.py +++ b/cura/Machines/QualityGroup.py @@ -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] diff --git a/cura/Machines/QualityNode.py b/cura/Machines/QualityNode.py index 7696dfb117..85e55c7a2d 100644 --- a/cura/Machines/QualityNode.py +++ b/cura/Machines/QualityNode.py @@ -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 diff --git a/cura/Machines/VariantNode.py b/cura/Machines/VariantNode.py index 0f30782a91..72372b5a45 100644 --- a/cura/Machines/VariantNode.py +++ b/cura/Machines/VariantNode.py @@ -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()):