From e5efd1e41f839cc6be173948e94806a966e15221 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 Oct 2018 09:32:40 +0200 Subject: [PATCH 01/10] Move constant definition into constructor CURA-5812 --- cura/CuraApplication.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index f8fc081d5c..c91514c37a 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -166,6 +166,8 @@ class CuraApplication(QtApplication): self.default_theme = "cura-light" + self.change_log_url = "https://ultimaker.com/ultimaker-cura-latest-features" + self._boot_loading_time = time.time() self._on_exit_callback_manager = OnExitCallbackManager(self) @@ -302,8 +304,6 @@ class CuraApplication(QtApplication): self._machine_action_manager = MachineActionManager.MachineActionManager(self) self._machine_action_manager.initialize() - self.change_log_url = "https://ultimaker.com/ultimaker-cura-latest-features" - def __sendCommandToSingleInstance(self): self._single_instance = SingleInstance(self, self._files_to_open) From 9b94db8957fcd3c6f6bdddab6708406a276e1ee9 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 Oct 2018 09:37:20 +0200 Subject: [PATCH 02/10] Directly use empty containers in MachineManager CURA-5812 Instead of looking up for the empty containers via ContainerRegistry, import and use them directly. --- cura/CuraApplication.py | 2 +- cura/Settings/MachineManager.py | 87 ++++++++++++++++----------------- 2 files changed, 43 insertions(+), 46 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index c91514c37a..9f309c9e7b 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -691,7 +691,7 @@ class CuraApplication(QtApplication): self._quality_manager.initialize() Logger.log("i", "Initializing machine manager") - self._machine_manager = MachineManager(self) + self._machine_manager = MachineManager(self, parent = self) Logger.log("i", "Initializing container manager") self._container_manager = ContainerManager(self) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 063f894d23..e5902106e3 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -20,7 +20,6 @@ from UM.Message import Message from UM.Settings.SettingFunction import SettingFunction from UM.Signal import postponeSignals, CompressTechnique -import cura.CuraApplication from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch from cura.PrinterOutputDevice import PrinterOutputDevice from cura.PrinterOutput.ConfigurationModel import ConfigurationModel @@ -29,6 +28,9 @@ from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel from cura.Settings.CuraContainerRegistry import CuraContainerRegistry from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderStack import ExtruderStack +from cura.Settings.cura_empty_instance_containers import (empty_definition_changes_container, empty_variant_container, + empty_material_container, empty_quality_container, + empty_quality_changes_container) from .CuraStackBuilder import CuraStackBuilder @@ -36,6 +38,7 @@ from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") if TYPE_CHECKING: + from cura.CuraApplication import CuraApplication from cura.Settings.CuraContainerStack import CuraContainerStack from cura.Settings.GlobalStack import GlobalStack from cura.Machines.MaterialManager import MaterialManager @@ -47,7 +50,7 @@ if TYPE_CHECKING: class MachineManager(QObject): - def __init__(self, parent: QObject = None) -> None: + def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None: super().__init__(parent) self._active_container_stack = None # type: Optional[ExtruderStack] @@ -66,9 +69,10 @@ class MachineManager(QObject): self._instance_container_timer.setSingleShot(True) self._instance_container_timer.timeout.connect(self.__emitChangedSignals) - self._application = cura.CuraApplication.CuraApplication.getInstance() #type: cura.CuraApplication.CuraApplication + self._application = application + self._container_registry = self._application.getContainerRegistry() self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged) - self._application.getContainerRegistry().containerLoadComplete.connect(self._onContainersChanged) + self._container_registry.containerLoadComplete.connect(self._onContainersChanged) ## When the global container is changed, active material probably needs to be updated. self.globalContainerChanged.connect(self.activeMaterialChanged) @@ -80,13 +84,6 @@ class MachineManager(QObject): self._stacks_have_errors = None # type: Optional[bool] - self._empty_container = CuraContainerRegistry.getInstance().getEmptyInstanceContainer() #type: InstanceContainer - self._empty_definition_changes_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_definition_changes")[0] #type: InstanceContainer - self._empty_variant_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_variant")[0] #type: InstanceContainer - self._empty_material_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_material")[0] #type: InstanceContainer - self._empty_quality_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_quality")[0] #type: InstanceContainer - self._empty_quality_changes_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_quality_changes")[0] #type: InstanceContainer - self._onGlobalContainerChanged() ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged) @@ -192,19 +189,19 @@ class MachineManager(QObject): for extruder in self._global_container_stack.extruders.values(): extruder_configuration = ExtruderConfigurationModel() # For compare just the GUID is needed at this moment - mat_type = extruder.material.getMetaDataEntry("material") if extruder.material != self._empty_material_container else None - mat_guid = extruder.material.getMetaDataEntry("GUID") if extruder.material != self._empty_material_container else None - mat_color = extruder.material.getMetaDataEntry("color_name") if extruder.material != self._empty_material_container else None - mat_brand = extruder.material.getMetaDataEntry("brand") if extruder.material != self._empty_material_container else None - mat_name = extruder.material.getMetaDataEntry("name") if extruder.material != self._empty_material_container else None + mat_type = extruder.material.getMetaDataEntry("material") if extruder.material != empty_material_container else None + mat_guid = extruder.material.getMetaDataEntry("GUID") if extruder.material != empty_material_container else None + mat_color = extruder.material.getMetaDataEntry("color_name") if extruder.material != empty_material_container else None + mat_brand = extruder.material.getMetaDataEntry("brand") if extruder.material != empty_material_container else None + mat_name = extruder.material.getMetaDataEntry("name") if extruder.material != empty_material_container else None material_model = MaterialOutputModel(mat_guid, mat_type, mat_color, mat_brand, mat_name) extruder_configuration.position = int(extruder.getMetaDataEntry("position")) extruder_configuration.material = material_model - extruder_configuration.hotendID = extruder.variant.getName() if extruder.variant != self._empty_variant_container else None + extruder_configuration.hotendID = extruder.variant.getName() if extruder.variant != empty_variant_container else None self._current_printer_configuration.extruderConfigurations.append(extruder_configuration) - self._current_printer_configuration.buildplateConfiguration = self._global_container_stack.getProperty("machine_buildplate_type", "value") if self._global_container_stack.variant != self._empty_variant_container else None + self._current_printer_configuration.buildplateConfiguration = self._global_container_stack.getProperty("machine_buildplate_type", "value") if self._global_container_stack.variant != empty_variant_container else None self.currentConfigurationChanged.emit() @pyqtSlot(QObject, result = bool) @@ -258,14 +255,14 @@ class MachineManager(QObject): # Global stack can have only a variant if it is a buildplate global_variant = self._global_container_stack.variant - if global_variant != self._empty_variant_container: + if global_variant != empty_variant_container: if global_variant.getMetaDataEntry("hardware_type") != "buildplate": - self._global_container_stack.setVariant(self._empty_variant_container) + self._global_container_stack.setVariant(empty_variant_container) # set the global material to empty as we now use the extruder stack at all times - CURA-4482 global_material = self._global_container_stack.material - if global_material != self._empty_material_container: - self._global_container_stack.setMaterial(self._empty_material_container) + if global_material != empty_material_container: + self._global_container_stack.setMaterial(empty_material_container) # Listen for changes on all extruder stacks for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): @@ -593,7 +590,7 @@ class MachineManager(QObject): def globalVariantName(self) -> str: if self._global_container_stack: variant = self._global_container_stack.variant - if variant and not isinstance(variant, type(self._empty_variant_container)): + if variant and not isinstance(variant, type(empty_variant_container)): return variant.getName() return "" @@ -781,7 +778,7 @@ class MachineManager(QObject): if not stack.isEnabled: continue material_container = stack.material - if material_container == self._empty_material_container: + if material_container == empty_material_container: continue if material_container.getMetaDataEntry("buildplate_compatible"): buildplate_compatible = buildplate_compatible and material_container.getMetaDataEntry("buildplate_compatible")[self.activeVariantBuildplateName] @@ -803,7 +800,7 @@ class MachineManager(QObject): extruder_stacks = self._global_container_stack.extruders.values() for stack in extruder_stacks: material_container = stack.material - if material_container == self._empty_material_container: + if material_container == empty_material_container: continue buildplate_compatible = material_container.getMetaDataEntry("buildplate_compatible")[self.activeVariantBuildplateName] if material_container.getMetaDataEntry("buildplate_compatible") else True buildplate_usable = material_container.getMetaDataEntry("buildplate_recommended")[self.activeVariantBuildplateName] if material_container.getMetaDataEntry("buildplate_recommended") else True @@ -873,7 +870,7 @@ class MachineManager(QObject): extruder_manager = self._application.getExtruderManager() definition_changes_container = self._global_container_stack.definitionChanges - if not self._global_container_stack or definition_changes_container == self._empty_definition_changes_container: + if not self._global_container_stack or definition_changes_container == empty_definition_changes_container: return previous_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value") @@ -1072,7 +1069,7 @@ class MachineManager(QObject): for stack in active_stacks: variant_container = stack.variant position = stack.getMetaDataEntry("position") - if variant_container and variant_container != self._empty_variant_container: + if variant_container and variant_container != empty_variant_container: result[position] = variant_container.getName() return result @@ -1086,11 +1083,11 @@ class MachineManager(QObject): return self._current_quality_group = None self._current_quality_changes_group = None - self._global_container_stack.quality = self._empty_quality_container - self._global_container_stack.qualityChanges = self._empty_quality_changes_container + self._global_container_stack.quality = empty_quality_container + self._global_container_stack.qualityChanges = empty_quality_changes_container for extruder in self._global_container_stack.extruders.values(): - extruder.quality = self._empty_quality_container - extruder.qualityChanges = self._empty_quality_changes_container + extruder.quality = empty_quality_container + extruder.qualityChanges = empty_quality_changes_container self.activeQualityGroupChanged.emit() self.activeQualityChangesGroupChanged.emit() @@ -1115,13 +1112,13 @@ class MachineManager(QObject): # Set quality and quality_changes for the GlobalStack self._global_container_stack.quality = quality_group.node_for_global.getContainer() if empty_quality_changes: - self._global_container_stack.qualityChanges = self._empty_quality_changes_container + self._global_container_stack.qualityChanges = empty_quality_changes_container # Set quality and quality_changes for each ExtruderStack for position, node in quality_group.nodes_for_extruders.items(): self._global_container_stack.extruders[str(position)].quality = node.getContainer() if empty_quality_changes: - self._global_container_stack.extruders[str(position)].qualityChanges = self._empty_quality_changes_container + self._global_container_stack.extruders[str(position)].qualityChanges = empty_quality_changes_container self.activeQualityGroupChanged.emit() self.activeQualityChangesGroupChanged.emit() @@ -1147,8 +1144,8 @@ class MachineManager(QObject): if quality_group is None: self._fixQualityChangesGroupToNotSupported(quality_changes_group) - quality_changes_container = self._empty_quality_changes_container - quality_container = self._empty_quality_container # type: Optional[InstanceContainer] + quality_changes_container = empty_quality_changes_container + quality_container = empty_quality_container # type: Optional[InstanceContainer] if quality_changes_group.node_for_global and quality_changes_group.node_for_global.getContainer(): quality_changes_container = cast(InstanceContainer, quality_changes_group.node_for_global.getContainer()) if quality_group is not None and quality_group.node_for_global and quality_group.node_for_global.getContainer(): @@ -1163,8 +1160,8 @@ class MachineManager(QObject): if quality_group is not None: quality_node = quality_group.nodes_for_extruders.get(position) - quality_changes_container = self._empty_quality_changes_container - quality_container = self._empty_quality_container + quality_changes_container = empty_quality_changes_container + quality_container = empty_quality_container if quality_changes_node and quality_changes_node.getContainer(): quality_changes_container = cast(InstanceContainer, quality_changes_node.getContainer()) if quality_node and quality_node.getContainer(): @@ -1198,7 +1195,7 @@ class MachineManager(QObject): self._global_container_stack.extruders[position].material = container_node.getContainer() root_material_id = container_node.getMetaDataEntry("base_file", None) else: - self._global_container_stack.extruders[position].material = self._empty_material_container + self._global_container_stack.extruders[position].material = empty_material_container root_material_id = None # The _current_root_material_id is used in the MaterialMenu to see which material is selected if root_material_id != self._current_root_material_id[position]: @@ -1273,7 +1270,7 @@ class MachineManager(QObject): current_material_base_name = extruder.material.getMetaDataEntry("base_file") current_nozzle_name = None - if extruder.variant.getId() != self._empty_variant_container.getId(): + if extruder.variant.getId() != empty_variant_container.getId(): current_nozzle_name = extruder.variant.getMetaDataEntry("name") from UM.Settings.Interfaces import PropertyEvaluationContext @@ -1348,12 +1345,12 @@ class MachineManager(QObject): if variant_container_node: self._setVariantNode(position, variant_container_node) else: - self._global_container_stack.extruders[position].variant = self._empty_variant_container + self._global_container_stack.extruders[position].variant = empty_variant_container if material_container_node: self._setMaterial(position, material_container_node) else: - self._global_container_stack.extruders[position].material = self._empty_material_container + self._global_container_stack.extruders[position].material = empty_material_container self.updateMaterialWithVariant(position) if configuration.buildplateConfiguration is not None: @@ -1361,9 +1358,9 @@ class MachineManager(QObject): if global_variant_container_node: self._setGlobalVariant(global_variant_container_node) else: - self._global_container_stack.variant = self._empty_variant_container + self._global_container_stack.variant = empty_variant_container else: - self._global_container_stack.variant = self._empty_variant_container + self._global_container_stack.variant = empty_variant_container self._updateQualityWithMaterial() # See if we need to show the Discard or Keep changes screen @@ -1481,7 +1478,7 @@ class MachineManager(QObject): # This is not changing the quality for the active machine !!!!!!!! global_stack.quality = quality_group.node_for_global.getContainer() for extruder_nr, extruder_stack in global_stack.extruders.items(): - quality_container = self._empty_quality_container + quality_container = empty_quality_container if extruder_nr in quality_group.nodes_for_extruders: container = quality_group.nodes_for_extruders[extruder_nr].getContainer() quality_container = container if container is not None else quality_container @@ -1525,7 +1522,7 @@ class MachineManager(QObject): @pyqtProperty(str, notify = activeQualityGroupChanged) def activeQualityOrQualityChangesName(self) -> str: - name = self._empty_quality_container.getName() + name = empty_quality_container.getName() if self._current_quality_changes_group: name = self._current_quality_changes_group.name elif self._current_quality_group: From c1b9d527bb1d72e95b76c8ebe18eea6b8e702446 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 Oct 2018 09:38:39 +0200 Subject: [PATCH 03/10] Add typing for MachineAction CURA-5812 --- cura/MachineAction.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/cura/MachineAction.py b/cura/MachineAction.py index 969fef0edf..94b096f9c1 100644 --- a/cura/MachineAction.py +++ b/cura/MachineAction.py @@ -1,13 +1,13 @@ # Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import os + from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal +from UM.Logger import Logger from UM.PluginObject import PluginObject from UM.PluginRegistry import PluginRegistry -from UM.Application import Application - -import os ## Machine actions are actions that are added to a specific machine type. Examples of such actions are @@ -19,7 +19,7 @@ class MachineAction(QObject, PluginObject): ## Create a new Machine action. # \param key unique key of the machine action # \param label Human readable label used to identify the machine action. - def __init__(self, key, label = ""): + def __init__(self, key: str, label: str = "") -> None: super().__init__() self._key = key self._label = label @@ -30,14 +30,14 @@ class MachineAction(QObject, PluginObject): labelChanged = pyqtSignal() onFinished = pyqtSignal() - def getKey(self): + def getKey(self) -> str: return self._key @pyqtProperty(str, notify = labelChanged) - def label(self): + def label(self) -> str: return self._label - def setLabel(self, label): + def setLabel(self, label: str) -> None: if self._label != label: self._label = label self.labelChanged.emit() @@ -46,29 +46,35 @@ class MachineAction(QObject, PluginObject): # This should not be re-implemented by child classes, instead re-implement _reset. # /sa _reset @pyqtSlot() - def reset(self): + def reset(self) -> None: self._finished = False self._reset() ## Protected implementation of reset. # /sa reset() - def _reset(self): + def _reset(self) -> None: pass @pyqtSlot() - def setFinished(self): + def setFinished(self) -> None: self._finished = True self._reset() self.onFinished.emit() @pyqtProperty(bool, notify = onFinished) - def finished(self): + def finished(self) -> bool: return self._finished ## Protected helper to create a view object based on provided QML. - def _createViewFromQML(self): - path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url) - self._view = Application.getInstance().createQmlComponent(path, {"manager": self}) + def _createViewFromQML(self) -> None: + plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) + if plugin_path is None: + Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId()) + return + path = os.path.join(plugin_path, self._qml_url) + + from cura.CuraApplication import CuraApplication + self._view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) @pyqtProperty(QObject, constant = True) def displayItem(self): From 6dc01d4c0811d6d368ed1a76a825fc9b6a5cbe43 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 Oct 2018 09:42:08 +0200 Subject: [PATCH 04/10] Add typing for MachineActionsManager CURA-5812 --- cura/MachineActionManager.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/cura/MachineActionManager.py b/cura/MachineActionManager.py index 65eb33b54c..1c99b45c9d 100644 --- a/cura/MachineActionManager.py +++ b/cura/MachineActionManager.py @@ -1,12 +1,18 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from typing import TYPE_CHECKING, Optional, List, Set + from PyQt5.QtCore import QObject from UM.FlameProfiler import pyqtSlot from UM.Logger import Logger from UM.PluginRegistry import PluginRegistry # So MachineAction can be added as plugin type -from UM.Settings.DefinitionContainer import DefinitionContainer + +if TYPE_CHECKING: + from cura.CuraApplication import CuraApplication + from cura.Settings.GlobalStack import GlobalStack + from .MachineAction import MachineAction ## Raised when trying to add an unknown machine action as a required action @@ -20,9 +26,10 @@ class NotUniqueMachineActionError(Exception): class MachineActionManager(QObject): - def __init__(self, application, parent = None): - super().__init__(parent) + def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None): + super().__init__(parent = parent) self._application = application + self._container_registry = self._application.getContainerRegistry() self._machine_actions = {} # Dict of all known machine actions self._required_actions = {} # Dict of all required actions by definition ID @@ -30,8 +37,6 @@ class MachineActionManager(QObject): self._first_start_actions = {} # Dict of all actions that need to be done when first added by definition ID def initialize(self): - container_registry = self._application.getContainerRegistry() - # Add machine_action as plugin type PluginRegistry.addType("machine_action", self.addMachineAction) @@ -59,7 +64,7 @@ class MachineActionManager(QObject): ## Add a required action to a machine # Raises an exception when the action is not recognised. - def addRequiredAction(self, definition_id, action_key): + def addRequiredAction(self, definition_id: str, action_key: str) -> None: if action_key in self._machine_actions: if definition_id in self._required_actions: if self._machine_actions[action_key] not in self._required_actions[definition_id]: @@ -70,7 +75,7 @@ class MachineActionManager(QObject): raise UnknownMachineActionError("Action %s, which is required for %s is not known." % (action_key, definition_id)) ## Add a supported action to a machine. - def addSupportedAction(self, definition_id, action_key): + def addSupportedAction(self, definition_id: str, action_key: str) -> None: if action_key in self._machine_actions: if definition_id in self._supported_actions: if self._machine_actions[action_key] not in self._supported_actions[definition_id]: @@ -95,7 +100,7 @@ class MachineActionManager(QObject): ## Add a (unique) MachineAction # if the Key of the action is not unique, an exception is raised. - def addMachineAction(self, action): + def addMachineAction(self, action: "MachineAction") -> None: if action.getKey() not in self._machine_actions: self._machine_actions[action.getKey()] = action else: @@ -105,7 +110,7 @@ class MachineActionManager(QObject): # \param definition_id The ID of the definition you want the supported actions of # \returns set of supported actions. @pyqtSlot(str, result = "QVariantList") - def getSupportedActions(self, definition_id): + def getSupportedActions(self, definition_id: str) -> List["MachineAction"]: if definition_id in self._supported_actions: return list(self._supported_actions[definition_id]) else: @@ -114,7 +119,7 @@ class MachineActionManager(QObject): ## Get all actions required by given machine # \param definition_id The ID of the definition you want the required actions of # \returns set of required actions. - def getRequiredActions(self, definition_id): + def getRequiredActions(self, definition_id: str) -> Set["MachineAction"]: if definition_id in self._required_actions: return self._required_actions[definition_id] else: @@ -126,7 +131,7 @@ class MachineActionManager(QObject): # \param definition_id The ID of the definition that you want to get the "on added" actions for. # \returns List of actions. @pyqtSlot(str, result="QVariantList") - def getFirstStartActions(self, definition_id): + def getFirstStartActions(self, definition_id: str) -> List["MachineAction"]: if definition_id in self._first_start_actions: return self._first_start_actions[definition_id] else: @@ -134,7 +139,7 @@ class MachineActionManager(QObject): ## Remove Machine action from manager # \param action to remove - def removeMachineAction(self, action): + def removeMachineAction(self, action: "MachineAction") -> None: try: del self._machine_actions[action.getKey()] except KeyError: @@ -143,7 +148,7 @@ class MachineActionManager(QObject): ## Get MachineAction by key # \param key String of key to select # \return Machine action if found, None otherwise - def getMachineAction(self, key): + def getMachineAction(self, key: str) -> Optional["MachineAction"]: if key in self._machine_actions: return self._machine_actions[key] else: From c67abb61a8daf8d55f8ac177c585d6a239922d25 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 Oct 2018 09:43:48 +0200 Subject: [PATCH 05/10] Remove unused argument "index" in addFirstStartAction() CURA-5812 --- cura/MachineActionManager.py | 7 ++----- tests/TestMachineAction.py | 9 --------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/cura/MachineActionManager.py b/cura/MachineActionManager.py index 1c99b45c9d..cfa40e9e4b 100644 --- a/cura/MachineActionManager.py +++ b/cura/MachineActionManager.py @@ -86,13 +86,10 @@ class MachineActionManager(QObject): Logger.log("w", "Unable to add %s to %s, as the action is not recognised", action_key, definition_id) ## Add an action to the first start list of a machine. - def addFirstStartAction(self, definition_id, action_key, index = None): + def addFirstStartAction(self, definition_id: str, action_key: str) -> None: if action_key in self._machine_actions: if definition_id in self._first_start_actions: - if index is not None: - self._first_start_actions[definition_id].insert(index, self._machine_actions[action_key]) - else: - self._first_start_actions[definition_id].append(self._machine_actions[action_key]) + self._first_start_actions[definition_id].append(self._machine_actions[action_key]) else: self._first_start_actions[definition_id] = [self._machine_actions[action_key]] else: diff --git a/tests/TestMachineAction.py b/tests/TestMachineAction.py index 7121fcc218..f23d15adcc 100755 --- a/tests/TestMachineAction.py +++ b/tests/TestMachineAction.py @@ -65,12 +65,3 @@ def test_addMachineAction(machine_action_manager): machine_action_manager.addFirstStartAction(test_machine, "test_action") machine_action_manager.addFirstStartAction(test_machine, "test_action") assert machine_action_manager.getFirstStartActions(test_machine) == [test_action, test_action] - - # Check if inserting an action works - machine_action_manager.addFirstStartAction(test_machine, "test_action_2", index = 1) - assert machine_action_manager.getFirstStartActions(test_machine) == [test_action, test_action_2, test_action] - - # Check that adding a unknown action doesn't change anything. - machine_action_manager.addFirstStartAction(test_machine, "key_that_doesnt_exist", index = 1) - assert machine_action_manager.getFirstStartActions(test_machine) == [test_action, test_action_2, test_action] - From 59704e4c0eeefb0a8139b2993d555511caf39ca5 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 Oct 2018 09:44:45 +0200 Subject: [PATCH 06/10] Make sure that a machine's default actions are added before it gets activated CURA-5812 --- cura/MachineActionManager.py | 39 +++++++++++++++++++-------------- cura/Settings/MachineManager.py | 4 ++++ 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/cura/MachineActionManager.py b/cura/MachineActionManager.py index cfa40e9e4b..f436db82f7 100644 --- a/cura/MachineActionManager.py +++ b/cura/MachineActionManager.py @@ -31,6 +31,9 @@ class MachineActionManager(QObject): self._application = application self._container_registry = self._application.getContainerRegistry() + # Keeps track of which machines have already been processed so we don't do that again. + self._definition_ids_with_default_actions_added = set() # type: Set[str] + self._machine_actions = {} # Dict of all known machine actions self._required_actions = {} # Dict of all required actions by definition ID self._supported_actions = {} # Dict of all supported actions by definition ID @@ -40,27 +43,29 @@ class MachineActionManager(QObject): # Add machine_action as plugin type PluginRegistry.addType("machine_action", self.addMachineAction) - # Ensure that all containers that were registered before creation of this registry are also handled. - # This should not have any effect, but it makes it safer if we ever refactor the order of things. - for container in container_registry.findDefinitionContainers(): - self._onContainerAdded(container) + # Adds all default machine actions that are defined in the machine definition for the given machine. + def addDefaultMachineActions(self, global_stack: "GlobalStack") -> None: + definition_id = global_stack.definition.getId() - container_registry.containerAdded.connect(self._onContainerAdded) + if definition_id in self._definition_ids_with_default_actions_added: + Logger.log("i", "Default machine actions have been added for machine definition [%s], do nothing.", + definition_id) + return - def _onContainerAdded(self, container): - ## Ensure that the actions are added to this manager - if isinstance(container, DefinitionContainer): - supported_actions = container.getMetaDataEntry("supported_actions", []) - for action in supported_actions: - self.addSupportedAction(container.getId(), action) + supported_actions = global_stack.getMetaDataEntry("supported_actions", []) + for action in supported_actions: + self.addSupportedAction(definition_id, action) - required_actions = container.getMetaDataEntry("required_actions", []) - for action in required_actions: - self.addRequiredAction(container.getId(), action) + required_actions = global_stack.getMetaDataEntry("required_actions", []) + for action in required_actions: + self.addRequiredAction(definition_id, action) - first_start_actions = container.getMetaDataEntry("first_start_actions", []) - for action in first_start_actions: - self.addFirstStartAction(container.getId(), action) + first_start_actions = global_stack.getMetaDataEntry("first_start_actions", []) + for action in first_start_actions: + self.addFirstStartAction(definition_id, action) + + self._definition_ids_with_default_actions_added.add(definition_id) + Logger.log("i", "Default machine actions added for machine definition [%s]", definition_id) ## Add a required action to a machine # Raises an exception when the action is not recognised. diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index e5902106e3..b6a08bb4cc 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -364,6 +364,10 @@ class MachineManager(QObject): return global_stack = containers[0] + + # Make sure that the default machine actions for this machine have been added + self._application.getMachineActionManager().addDefaultMachineActions(global_stack) + ExtruderManager.getInstance()._fixSingleExtrusionMachineExtruderDefinition(global_stack) if not global_stack.isValid(): # Mark global stack as invalid From 0e772beb14e1841076d468e7a7f025de4ea9f623 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 Oct 2018 09:55:53 +0200 Subject: [PATCH 07/10] Fix typing in MachineActionManager CURA-5812 --- cura/MachineActionManager.py | 32 ++++++++++++++++++-------------- tests/TestMachineAction.py | 2 +- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/cura/MachineActionManager.py b/cura/MachineActionManager.py index f436db82f7..db0f7bfbff 100644 --- a/cura/MachineActionManager.py +++ b/cura/MachineActionManager.py @@ -1,7 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import TYPE_CHECKING, Optional, List, Set +from typing import TYPE_CHECKING, Optional, List, Set, Dict from PyQt5.QtCore import QObject @@ -26,7 +26,7 @@ class NotUniqueMachineActionError(Exception): class MachineActionManager(QObject): - def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None): + def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None: super().__init__(parent = parent) self._application = application self._container_registry = self._application.getContainerRegistry() @@ -34,10 +34,14 @@ class MachineActionManager(QObject): # Keeps track of which machines have already been processed so we don't do that again. self._definition_ids_with_default_actions_added = set() # type: Set[str] - self._machine_actions = {} # Dict of all known machine actions - self._required_actions = {} # Dict of all required actions by definition ID - self._supported_actions = {} # Dict of all supported actions by definition ID - self._first_start_actions = {} # Dict of all actions that need to be done when first added by definition ID + # Dict of all known machine actions + self._machine_actions = {} # type: Dict[str, MachineAction] + # Dict of all required actions by definition ID + self._required_actions = {} # type: Dict[str, List[MachineAction]] + # Dict of all supported actions by definition ID + self._supported_actions = {} # type: Dict[str, List[MachineAction]] + # Dict of all actions that need to be done when first added by definition ID + self._first_start_actions = {} # type: Dict[str, List[MachineAction]] def initialize(self): # Add machine_action as plugin type @@ -53,16 +57,16 @@ class MachineActionManager(QObject): return supported_actions = global_stack.getMetaDataEntry("supported_actions", []) - for action in supported_actions: - self.addSupportedAction(definition_id, action) + for action_key in supported_actions: + self.addSupportedAction(definition_id, action_key) required_actions = global_stack.getMetaDataEntry("required_actions", []) - for action in required_actions: - self.addRequiredAction(definition_id, action) + for action_key in required_actions: + self.addRequiredAction(definition_id, action_key) first_start_actions = global_stack.getMetaDataEntry("first_start_actions", []) - for action in first_start_actions: - self.addFirstStartAction(definition_id, action) + for action_key in first_start_actions: + self.addFirstStartAction(definition_id, action_key) self._definition_ids_with_default_actions_added.add(definition_id) Logger.log("i", "Default machine actions added for machine definition [%s]", definition_id) @@ -121,11 +125,11 @@ class MachineActionManager(QObject): ## Get all actions required by given machine # \param definition_id The ID of the definition you want the required actions of # \returns set of required actions. - def getRequiredActions(self, definition_id: str) -> Set["MachineAction"]: + def getRequiredActions(self, definition_id: str) -> List["MachineAction"]: if definition_id in self._required_actions: return self._required_actions[definition_id] else: - return set() + return list() ## Get all actions that need to be performed upon first start of a given machine. # Note that contrary to required / supported actions a list is returned (as it could be required to run the same diff --git a/tests/TestMachineAction.py b/tests/TestMachineAction.py index f23d15adcc..0d819b9120 100755 --- a/tests/TestMachineAction.py +++ b/tests/TestMachineAction.py @@ -44,7 +44,7 @@ def test_addMachineAction(machine_action_manager): assert machine_action_manager.getSupportedActions(test_machine) == [test_action, test_action_2] # Check that the machine has no required actions yet. - assert machine_action_manager.getRequiredActions(test_machine) == set() + assert machine_action_manager.getRequiredActions(test_machine) == list() ## Ensure that only known actions can be added. with pytest.raises(UnknownMachineActionError): From 537108032e0beb4c1caba7b9dc8f2296e7f39ee4 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 Oct 2018 09:57:34 +0200 Subject: [PATCH 08/10] Fix typing in PrinterOutputModel CURA-5812 --- cura/PrinterOutput/PrinterOutputModel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index 5870414c26..c1c5586f9f 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -50,7 +50,7 @@ class PrinterOutputModel(QObject): self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in self._extruders] - self._camera = None + self._camera = None # type: Optional[NetworkCamera] @pyqtProperty(str, constant = True) def firmwareVersion(self) -> str: From 89cb67017fe808c615f1d11604597223c992e923 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 23 Oct 2018 08:49:20 +0200 Subject: [PATCH 09/10] Add unit test for addDefaultMachineActions() CURA-5812 --- cura/Settings/CuraContainerStack.py | 8 +++---- tests/TestMachineAction.py | 36 +++++++++++++++++++++++++++++ tests/conftest.py | 1 - 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index 0ec95e2e41..042b065226 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.py @@ -145,13 +145,11 @@ class CuraContainerStack(ContainerStack): def setDefinition(self, new_definition: DefinitionContainerInterface) -> None: self.replaceContainer(_ContainerIndexes.Definition, new_definition) - ## Get the definition container. - # - # \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer. - @pyqtProperty(QObject, fset = setDefinition, notify = pyqtContainersChanged) - def definition(self) -> DefinitionContainer: + def getDefinition(self) -> "DefinitionContainer": return cast(DefinitionContainer, self._containers[_ContainerIndexes.Definition]) + definition = pyqtProperty(QObject, fget = getDefinition, fset = setDefinition, notify = pyqtContainersChanged) + @override(ContainerStack) def getBottom(self) -> "DefinitionContainer": return self.definition diff --git a/tests/TestMachineAction.py b/tests/TestMachineAction.py index 0d819b9120..9601d68bce 100755 --- a/tests/TestMachineAction.py +++ b/tests/TestMachineAction.py @@ -1,10 +1,24 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from unittest import mock + import pytest from cura.MachineAction import MachineAction from cura.MachineActionManager import NotUniqueMachineActionError, UnknownMachineActionError +from cura.Settings.GlobalStack import GlobalStack + + +@pytest.fixture() +def global_stack(): + gs = GlobalStack("test_global_stack") + gs._metadata = {"supported_actions": ["supported_action_1", "supported_action_2"], + "required_actions": ["required_action_1", "required_action_2"], + "first_start_actions": ["first_start_actions_1", "first_start_actions_2"] + } + return gs + class Machine: def __init__(self, key = ""): @@ -13,6 +27,28 @@ class Machine: def getKey(self): return self._key + +def test_addDefaultMachineActions(machine_action_manager, global_stack): + all_actions = [] + for action_key_list in global_stack._metadata.values(): + for key in action_key_list: + all_actions.append(MachineAction(key = key)) + for action in all_actions: + machine_action_manager.addMachineAction(action) + + machine_action_manager.addDefaultMachineActions(global_stack) + definition_id = global_stack.getDefinition().getId() + + support_action_keys = [a.getKey() for a in machine_action_manager.getSupportedActions(definition_id)] + assert support_action_keys == global_stack.getMetaDataEntry("supported_actions") + + required_action_keys = [a.getKey() for a in machine_action_manager.getRequiredActions(definition_id)] + assert required_action_keys == global_stack.getMetaDataEntry("required_actions") + + first_start_action_keys = [a.getKey() for a in machine_action_manager.getFirstStartActions(definition_id)] + assert first_start_action_keys == global_stack.getMetaDataEntry("first_start_actions") + + def test_addMachineAction(machine_action_manager): test_action = MachineAction(key = "test_action") diff --git a/tests/conftest.py b/tests/conftest.py index ad0bc609ee..b21b32b028 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,7 +13,6 @@ from cura.CuraApplication import CuraApplication from cura.MachineActionManager import MachineActionManager - # Create a CuraApplication object that will be shared among all tests. It needs to be initialized. # Since we need to use it more that once, we create the application the first time and use its instance afterwards. @pytest.fixture() From e061fc42636ab1c2199ed672f17f0dbe6b7e5250 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 23 Oct 2018 10:59:36 +0200 Subject: [PATCH 10/10] Add comments to the unit test to better know how it works. Contributes to CURA-5808. --- tests/TestMachineAction.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/TestMachineAction.py b/tests/TestMachineAction.py index 9601d68bce..5d1805b707 100755 --- a/tests/TestMachineAction.py +++ b/tests/TestMachineAction.py @@ -1,8 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from unittest import mock - import pytest from cura.MachineAction import MachineAction @@ -13,10 +11,11 @@ from cura.Settings.GlobalStack import GlobalStack @pytest.fixture() def global_stack(): gs = GlobalStack("test_global_stack") - gs._metadata = {"supported_actions": ["supported_action_1", "supported_action_2"], - "required_actions": ["required_action_1", "required_action_2"], - "first_start_actions": ["first_start_actions_1", "first_start_actions_2"] - } + gs._metadata = { + "supported_actions": ["supported_action_1", "supported_action_2"], + "required_actions": ["required_action_1", "required_action_2"], + "first_start_actions": ["first_start_actions_1", "first_start_actions_2"] + } return gs @@ -29,6 +28,8 @@ class Machine: def test_addDefaultMachineActions(machine_action_manager, global_stack): + # The actions need to be registered first as "available actions" in the manager, + # same as the "machine_action" type does when registering a plugin. all_actions = [] for action_key_list in global_stack._metadata.values(): for key in action_key_list: @@ -36,6 +37,8 @@ def test_addDefaultMachineActions(machine_action_manager, global_stack): for action in all_actions: machine_action_manager.addMachineAction(action) + # Only the actions in the definition that were registered first will be added to the machine. + # For the sake of this test, all the actions were previouly added. machine_action_manager.addDefaultMachineActions(global_stack) definition_id = global_stack.getDefinition().getId()