1# Copyright (c) 2018 Ultimaker B.V. 2# Cura is released under the terms of the LGPLv3 or higher. 3 4from typing import TYPE_CHECKING, Optional, List, Set, Dict 5 6from PyQt5.QtCore import QObject 7 8from UM.FlameProfiler import pyqtSlot 9from UM.Logger import Logger 10from UM.PluginRegistry import PluginRegistry # So MachineAction can be added as plugin type 11 12if TYPE_CHECKING: 13 from cura.CuraApplication import CuraApplication 14 from cura.Settings.GlobalStack import GlobalStack 15 from cura.MachineAction import MachineAction 16 17 18class UnknownMachineActionError(Exception): 19 """Raised when trying to add an unknown machine action as a required action""" 20 21 pass 22 23 24class NotUniqueMachineActionError(Exception): 25 """Raised when trying to add a machine action that does not have an unique key.""" 26 27 pass 28 29 30class MachineActionManager(QObject): 31 def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None: 32 super().__init__(parent = parent) 33 self._application = application 34 self._container_registry = self._application.getContainerRegistry() 35 36 # Keeps track of which machines have already been processed so we don't do that again. 37 self._definition_ids_with_default_actions_added = set() # type: Set[str] 38 39 # Dict of all known machine actions 40 self._machine_actions = {} # type: Dict[str, MachineAction] 41 # Dict of all required actions by definition ID 42 self._required_actions = {} # type: Dict[str, List[MachineAction]] 43 # Dict of all supported actions by definition ID 44 self._supported_actions = {} # type: Dict[str, List[MachineAction]] 45 # Dict of all actions that need to be done when first added by definition ID 46 self._first_start_actions = {} # type: Dict[str, List[MachineAction]] 47 48 def initialize(self) -> None: 49 # Add machine_action as plugin type 50 PluginRegistry.addType("machine_action", self.addMachineAction) 51 52 # Adds all default machine actions that are defined in the machine definition for the given machine. 53 def addDefaultMachineActions(self, global_stack: "GlobalStack") -> None: 54 definition_id = global_stack.definition.getId() 55 56 if definition_id in self._definition_ids_with_default_actions_added: 57 Logger.log("i", "Default machine actions have been added for machine definition [%s], do nothing.", 58 definition_id) 59 return 60 61 supported_actions = global_stack.getMetaDataEntry("supported_actions", []) 62 for action_key in supported_actions: 63 self.addSupportedAction(definition_id, action_key) 64 65 required_actions = global_stack.getMetaDataEntry("required_actions", []) 66 for action_key in required_actions: 67 self.addRequiredAction(definition_id, action_key) 68 69 first_start_actions = global_stack.getMetaDataEntry("first_start_actions", []) 70 for action_key in first_start_actions: 71 self.addFirstStartAction(definition_id, action_key) 72 73 self._definition_ids_with_default_actions_added.add(definition_id) 74 Logger.log("i", "Default machine actions added for machine definition [%s]", definition_id) 75 76 def addRequiredAction(self, definition_id: str, action_key: str) -> None: 77 """Add a required action to a machine 78 79 Raises an exception when the action is not recognised. 80 """ 81 if action_key in self._machine_actions: 82 if definition_id in self._required_actions: 83 if self._machine_actions[action_key] not in self._required_actions[definition_id]: 84 self._required_actions[definition_id].append(self._machine_actions[action_key]) 85 else: 86 self._required_actions[definition_id] = [self._machine_actions[action_key]] 87 else: 88 raise UnknownMachineActionError("Action %s, which is required for %s is not known." % (action_key, definition_id)) 89 90 def addSupportedAction(self, definition_id: str, action_key: str) -> None: 91 """Add a supported action to a machine.""" 92 93 if action_key in self._machine_actions: 94 if definition_id in self._supported_actions: 95 if self._machine_actions[action_key] not in self._supported_actions[definition_id]: 96 self._supported_actions[definition_id].append(self._machine_actions[action_key]) 97 else: 98 self._supported_actions[definition_id] = [self._machine_actions[action_key]] 99 else: 100 Logger.log("w", "Unable to add %s to %s, as the action is not recognised", action_key, definition_id) 101 102 def addFirstStartAction(self, definition_id: str, action_key: str) -> None: 103 """Add an action to the first start list of a machine.""" 104 105 if action_key in self._machine_actions: 106 if definition_id in self._first_start_actions: 107 self._first_start_actions[definition_id].append(self._machine_actions[action_key]) 108 else: 109 self._first_start_actions[definition_id] = [self._machine_actions[action_key]] 110 else: 111 Logger.log("w", "Unable to add %s to %s, as the action is not recognised", action_key, definition_id) 112 113 def addMachineAction(self, action: "MachineAction") -> None: 114 """Add a (unique) MachineAction 115 116 if the Key of the action is not unique, an exception is raised. 117 """ 118 if action.getKey() not in self._machine_actions: 119 self._machine_actions[action.getKey()] = action 120 else: 121 raise NotUniqueMachineActionError("MachineAction with key %s was already added. Actions must have unique keys.", action.getKey()) 122 123 @pyqtSlot(str, result = "QVariantList") 124 def getSupportedActions(self, definition_id: str) -> List["MachineAction"]: 125 """Get all actions supported by given machine 126 127 :param definition_id: The ID of the definition you want the supported actions of 128 :returns: set of supported actions. 129 """ 130 if definition_id in self._supported_actions: 131 return list(self._supported_actions[definition_id]) 132 else: 133 return list() 134 135 def getRequiredActions(self, definition_id: str) -> List["MachineAction"]: 136 """Get all actions required by given machine 137 138 :param definition_id: The ID of the definition you want the required actions of 139 :returns: set of required actions. 140 """ 141 if definition_id in self._required_actions: 142 return self._required_actions[definition_id] 143 else: 144 return list() 145 146 @pyqtSlot(str, result = "QVariantList") 147 def getFirstStartActions(self, definition_id: str) -> List["MachineAction"]: 148 """Get all actions that need to be performed upon first start of a given machine. 149 150 Note that contrary to required / supported actions a list is returned (as it could be required to run the same 151 action multiple times). 152 :param definition_id: The ID of the definition that you want to get the "on added" actions for. 153 :returns: List of actions. 154 """ 155 if definition_id in self._first_start_actions: 156 return self._first_start_actions[definition_id] 157 else: 158 return [] 159 160 def removeMachineAction(self, action: "MachineAction") -> None: 161 """Remove Machine action from manager 162 163 :param action: to remove 164 """ 165 try: 166 del self._machine_actions[action.getKey()] 167 except KeyError: 168 Logger.log("w", "Trying to remove MachineAction (%s) that was already removed", action.getKey()) 169 170 def getMachineAction(self, key: str) -> Optional["MachineAction"]: 171 """Get MachineAction by key 172 173 :param key: String of key to select 174 :return: Machine action if found, None otherwise 175 """ 176 if key in self._machine_actions: 177 return self._machine_actions[key] 178 else: 179 return None 180