diff --git a/CMakeLists.txt b/CMakeLists.txt index 98dca222b4..40d94135ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,12 @@ include(GNUInstallDirs) set(URANIUM_SCRIPTS_DIR "${CMAKE_SOURCE_DIR}/../uranium/scripts" CACHE DIRECTORY "The location of the scripts directory of the Uranium repository") +# Tests +# Note that we use exit 0 here to not mark the build as a failure on test failure +add_custom_target(tests) +add_custom_command(TARGET tests POST_BUILD COMMAND "PYTHONPATH=${CMAKE_SOURCE_DIR}/../Uranium/:${CMAKE_SOURCE_DIR}" ${PYTHON_EXECUTABLE} -m pytest -r a --junitxml=${CMAKE_BINARY_DIR}/junit.xml ${CMAKE_SOURCE_DIR} || exit 0) + + set(CURA_VERSION "master" CACHE STRING "Version name of Cura") set(CURA_BUILDTYPE "" CACHE STRING "Build type of Cura, eg. 'PPA'") configure_file(cura/CuraVersion.py.in CuraVersion.py @ONLY) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 12818e1e87..08c8513933 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -44,6 +44,7 @@ from . import ZOffsetDecorator from . import CuraSplashScreen from . import MachineManagerModel from . import ContainerSettingsModel +from . import MachineActionManager from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS from PyQt5.QtGui import QColor, QIcon @@ -99,6 +100,8 @@ class CuraApplication(QtApplication): SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default = True) SettingDefinition.addSettingType("extruder", int, str, UM.Settings.Validator) + self._machine_action_manager = MachineActionManager.MachineActionManager() + super().__init__(name = "cura", version = CuraVersion, buildtype = CuraBuildType) self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png"))) @@ -367,6 +370,7 @@ class CuraApplication(QtApplication): qmlRegisterSingletonType(MachineManagerModel.MachineManagerModel, "Cura", 1, 0, "MachineManager", MachineManagerModel.createMachineManagerModel) + qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager) self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml")) self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles)) self.initializeEngine() @@ -383,6 +387,12 @@ class CuraApplication(QtApplication): self.exec_() + ## Get the machine action manager + # We ignore any *args given to this, as we also register the machine manager as qml singleton. + # It wants to give this function an engine and script engine, but we don't care about that. + def getMachineActionManager(self, *args): + return self._machine_action_manager + ## Handle Qt events def event(self, event): if event.type() == QEvent.FileOpen: diff --git a/cura/MachineAction.py b/cura/MachineAction.py new file mode 100644 index 0000000000..6a4df0fce1 --- /dev/null +++ b/cura/MachineAction.py @@ -0,0 +1,78 @@ +# Copyright (c) 2016 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal, QUrl +from PyQt5.QtQml import QQmlComponent, QQmlContext + +from UM.PluginObject import PluginObject +from UM.PluginRegistry import PluginRegistry + +from UM.Application import Application + +import os + + +class MachineAction(QObject, PluginObject): + def __init__(self, key, label = ""): + super().__init__() + self._key = key + self._label = label + self._qml_url = "" + + self._component = None + self._context = None + self._view = None + self._finished = False + + labelChanged = pyqtSignal() + onFinished = pyqtSignal() + + def getKey(self): + return self._key + + @pyqtProperty(str, notify = labelChanged) + def label(self): + return self._label + + def setLabel(self, label): + if self._label != label: + self._label = label + self.labelChanged.emit() + + ## Reset the action to it's default state. + # This should not be re-implemented by child classes, instead re-implement _reset. + # /sa _reset + @pyqtSlot() + def reset(self): + self._finished = False + self._reset() + + ## Protected implementation of reset. + # /sa reset() + def _reset(self): + pass + + @pyqtSlot() + def setFinished(self): + self._finished = True + self._reset() + self.onFinished.emit() + + @pyqtProperty(bool, notify = onFinished) + def finished(self): + return self._finished + + def _createViewFromQML(self): + path = QUrl.fromLocalFile( + os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url)) + self._component = QQmlComponent(Application.getInstance()._engine, path) + self._context = QQmlContext(Application.getInstance()._engine.rootContext()) + self._context.setContextProperty("manager", self) + self._view = self._component.create(self._context) + + @pyqtProperty(QObject, constant = True) + def displayItem(self): + if not self._component: + self._createViewFromQML() + + return self._view \ No newline at end of file diff --git a/cura/MachineActionManager.py b/cura/MachineActionManager.py new file mode 100644 index 0000000000..b50bb95e7f --- /dev/null +++ b/cura/MachineActionManager.py @@ -0,0 +1,143 @@ +# Copyright (c) 2016 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. +from UM.Logger import Logger +from UM.PluginRegistry import PluginRegistry # So MachineAction can be added as plugin type + +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.DefinitionContainer import DefinitionContainer + +from PyQt5.QtCore import QObject, pyqtSlot + +## Raised when trying to add an unknown machine action as a required action +class UnknownMachineActionError(Exception): + pass + + +## Raised when trying to add a machine action that does not have an unique key. +class NotUniqueMachineActionError(Exception): + pass + + +class MachineActionManager(QObject): + def __init__(self, parent = None): + super().__init__(parent) + + 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 + + # 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 ContainerRegistry.getInstance().findDefinitionContainers(): + self._onContainerAdded(container) + + ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) + + 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) + + required_actions = container.getMetaDataEntry("required_actions", []) + for action in required_actions: + self.addRequiredAction(container.getId(), action) + + first_start_actions = container.getMetaDataEntry("first_start_actions", []) + for action in first_start_actions: + self.addFirstStartAction(container.getId(), action) + + ## Add a required action to a machine + # Raises an exception when the action is not recognised. + def addRequiredAction(self, definition_id, action_key): + if action_key in self._machine_actions: + if definition_id in self._required_actions: + self._required_actions[definition_id] |= {self._machine_actions[action_key]} + else: + self._required_actions[definition_id] = {self._machine_actions[action_key]} + else: + 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): + if action_key in self._machine_actions: + if definition_id in self._supported_actions: + self._supported_actions[definition_id] |= {self._machine_actions[action_key]} + else: + self._supported_actions[definition_id] = {self._machine_actions[action_key]} + else: + 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): + 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]) + else: + self._first_start_actions[definition_id] = [self._machine_actions[action_key]] + else: + Logger.log("w", "Unable to add %s to %s, as the action is not recognised", action_key, definition_id) + + ## Add a (unique) MachineAction + # if the Key of the action is not unique, an exception is raised. + def addMachineAction(self, action): + if action.getKey() not in self._machine_actions: + self._machine_actions[action.getKey()] = action + else: + raise NotUniqueMachineActionError("MachineAction with key %s was already added. Actions must have unique keys.", action.getKey()) + + ## Get all actions supported by given machine + # \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): + if definition_id in self._supported_actions: + return list(self._supported_actions[definition_id]) + else: + return set() + + ## 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): + if definition_id in self._required_actions: + return self._required_actions[definition_id] + else: + return set() + + ## 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 + # action multiple times). + # \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): + if definition_id in self._first_start_actions: + return self._first_start_actions[definition_id] + else: + return [] + + ## Remove Machine action from manager + # \param action to remove + def removeMachineAction(self, action): + try: + del self._machine_actions[action.getKey()] + except KeyError: + Logger.log("w", "Trying to remove MachineAction (%s) that was already removed", action.getKey()) + + ## Get MachineAction by key + # \param key String of key to select + # \return Machine action if found, None otherwise + def getMachineAction(self, key): + if key in self._machine_actions: + return self._machine_actions[key] + else: + return None diff --git a/plugins/UltimakerMachineActions/BedLevelMachineAction.py b/plugins/UltimakerMachineActions/BedLevelMachineAction.py new file mode 100644 index 0000000000..b6b52c552e --- /dev/null +++ b/plugins/UltimakerMachineActions/BedLevelMachineAction.py @@ -0,0 +1,53 @@ +from cura.MachineAction import MachineAction + +from PyQt5.QtCore import pyqtSlot + +from UM.Application import Application + +from cura.PrinterOutputDevice import PrinterOutputDevice + +class BedLevelMachineAction(MachineAction): + def __init__(self): + super().__init__("BedLevel", "Level bed") + self._qml_url = "BedLevelMachineAction.qml" + self._bed_level_position = 0 + + def _execute(self): + pass + + def _reset(self): + self._bed_level_position = 0 + printer_output_devices = self._getPrinterOutputDevices() + if printer_output_devices: + printer_output_devices[0].homeBed() + printer_output_devices[0].moveHead(0, 0, 3) + printer_output_devices[0].homeHead() + + def _getPrinterOutputDevices(self): + return [printer_output_device for printer_output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices() if isinstance(printer_output_device, PrinterOutputDevice)] + + @pyqtSlot() + def moveToNextLevelPosition(self): + output_devices = self._getPrinterOutputDevices() + if output_devices: # We found at least one output device + output_device = output_devices[0] + + if self._bed_level_position == 0: + output_device.moveHead(0, 0, 3) + output_device.homeHead() + output_device.moveHead(0, 0, 3) + output_device.moveHead(Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") - 10, 0, 0) + output_device.moveHead(0, 0, -3) + self._bed_level_position += 1 + elif self._bed_level_position == 1: + output_device.moveHead(0, 0, 3) + output_device.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value" ) / 2, Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") - 10, 0) + output_device.moveHead(0, 0, -3) + self._bed_level_position += 1 + elif self._bed_level_position == 2: + output_device.moveHead(0, 0, 3) + output_device.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") / 2 + 10, -(Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") + 10), 0) + output_device.moveHead(0, 0, -3) + self._bed_level_position += 1 + elif self._bed_level_position >= 3: + self.setFinished() \ No newline at end of file diff --git a/plugins/UltimakerMachineActions/BedLevelMachineAction.qml b/plugins/UltimakerMachineActions/BedLevelMachineAction.qml new file mode 100644 index 0000000000..d043c20df5 --- /dev/null +++ b/plugins/UltimakerMachineActions/BedLevelMachineAction.qml @@ -0,0 +1,85 @@ +// Copyright (c) 2016 Ultimaker B.V. +// Cura is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 + +import UM 1.2 as UM +import Cura 1.0 as Cura + + +Cura.MachineAction +{ + anchors.fill: parent; + Item + { + id: bedLevelMachineAction + anchors.fill: parent; + + UM.I18nCatalog { id: catalog; name: "cura"; } + + Label + { + id: pageTitle + width: parent.width + text: catalog.i18nc("@title", "Bed Leveling") + wrapMode: Text.WordWrap + font.pointSize: 18; + } + Label + { + id: pageDescription + anchors.top: pageTitle.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + width: parent.width + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "To make sure your prints will come out great, you can now adjust your buildplate. When you click 'Move to Next Position' the nozzle will move to the different positions that can be adjusted.") + } + Label + { + id: bedlevelingText + anchors.top: pageDescription.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + width: parent.width + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "For every position; insert a piece of paper under the nozzle and adjust the print bed height. The print bed height is right when the paper is slightly gripped by the tip of the nozzle.") + } + + Item + { + id: bedlevelingWrapper + anchors.top: bedlevelingText.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + anchors.horizontalCenter: parent.horizontalCenter + height: skipBedlevelingButton.height + width: bedlevelingButton.width + skipBedlevelingButton.width + UM.Theme.getSize("default_margin").height < bedLevelMachineAction.width ? bedlevelingButton.width + skipBedlevelingButton.width + UM.Theme.getSize("default_margin").height : bedLevelMachineAction.width + Button + { + id: bedlevelingButton + anchors.top: parent.top + anchors.left: parent.left + text: catalog.i18nc("@action:button","Move to Next Position"); + onClicked: + { + manager.moveToNextLevelPosition() + } + } + + Button + { + id: skipBedlevelingButton + anchors.top: parent.width < bedLevelMachineAction.width ? parent.top : bedlevelingButton.bottom + anchors.topMargin: parent.width < bedLevelMachineAction.width ? 0 : UM.Theme.getSize("default_margin").height/2 + anchors.left: parent.width < bedLevelMachineAction.width ? bedlevelingButton.right : parent.left + anchors.leftMargin: parent.width < bedLevelMachineAction.width ? UM.Theme.getSize("default_margin").width : 0 + text: catalog.i18nc("@action:button","Skip bed leveling"); + onClicked: + { + manager.setFinished() + } + } + } + } +} diff --git a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py new file mode 100644 index 0000000000..392f89682f --- /dev/null +++ b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py @@ -0,0 +1,145 @@ +from cura.MachineAction import MachineAction +from cura.PrinterOutputDevice import PrinterOutputDevice +from UM.Application import Application +from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty + +class UMOCheckupMachineAction(MachineAction): + def __init__(self): + super().__init__("UMOCheckup", "Checkup") + self._qml_url = "UMOCheckupMachineAction.qml" + self._hotend_target_temp = 180 + self._bed_target_temp = 60 + self._output_device = None + self._bed_test_completed = False + self._hotend_test_completed = False + + # Endstop tests + self._x_min_endstop_test_completed = False + self._y_min_endstop_test_completed = False + self._z_min_endstop_test_completed = False + + onBedTestCompleted = pyqtSignal() + onHotendTestCompleted = pyqtSignal() + + onXMinEndstopTestCompleted = pyqtSignal() + onYMinEndstopTestCompleted = pyqtSignal() + onZMinEndstopTestCompleted = pyqtSignal() + + bedTemperatureChanged = pyqtSignal() + hotendTemperatureChanged = pyqtSignal() + + def _getPrinterOutputDevices(self): + return [printer_output_device for printer_output_device in + Application.getInstance().getOutputDeviceManager().getOutputDevices() if + isinstance(printer_output_device, PrinterOutputDevice)] + + def _reset(self): + if self._output_device: + self._output_device.bedTemperatureChanged.disconnect(self.bedTemperatureChanged) + self._output_device.hotendTemperaturesChanged.disconnect(self.hotendTemperatureChanged) + self._output_device.bedTemperatureChanged.disconnect(self._onBedTemperatureChanged) + self._output_device.hotendTemperaturesChanged.disconnect(self._onHotendTemperatureChanged) + self._output_device.endstopStateChanged.disconnect(self._onEndstopStateChanged) + try: + self._output_device.stopPollEndstop() + except AttributeError: # Connection is probably not a USB connection. Something went pretty wrong if this happens. + pass + self._output_device = None + + # Ensure everything is reset (and right signals are emitted again) + self._bed_test_completed = False + self.onBedTestCompleted.emit() + self._hotend_test_completed = False + self.onHotendTestCompleted.emit() + + self._x_min_endstop_test_completed = False + self.onXMinEndstopTestCompleted.emit() + self._y_min_endstop_test_completed = False + self.onYMinEndstopTestCompleted.emit() + self._z_min_endstop_test_completed = False + self.onZMinEndstopTestCompleted.emit() + + @pyqtProperty(bool, notify = onBedTestCompleted) + def bedTestCompleted(self): + return self._bed_test_completed + + @pyqtProperty(bool, notify = onHotendTestCompleted) + def hotendTestCompleted(self): + return self._hotend_test_completed + + @pyqtProperty(bool, notify = onXMinEndstopTestCompleted) + def xMinEndstopTestCompleted(self): + return self._x_min_endstop_test_completed + + @pyqtProperty(bool, notify=onYMinEndstopTestCompleted) + def yMinEndstopTestCompleted(self): + return self._y_min_endstop_test_completed + + @pyqtProperty(bool, notify=onZMinEndstopTestCompleted) + def zMinEndstopTestCompleted(self): + return self._z_min_endstop_test_completed + + @pyqtProperty(float, notify = bedTemperatureChanged) + def bedTemperature(self): + if not self._output_device: + return 0 + return self._output_device.bedTemperature + + @pyqtProperty(float, notify=hotendTemperatureChanged) + def hotendTemperature(self): + if not self._output_device: + return 0 + return self._output_device.hotendTemperatures[0] + + def _onHotendTemperatureChanged(self): + if not self._output_device: + return + if not self._hotend_test_completed: + if self._output_device.hotendTemperatures[0] + 10 > self._hotend_target_temp and self._output_device.hotendTemperatures[0] - 10 < self._hotend_target_temp: + self._hotend_test_completed = True + self.onHotendTestCompleted.emit() + + def _onBedTemperatureChanged(self): + if not self._output_device: + return + if not self._bed_test_completed: + if self._output_device.bedTemperature + 5 > self._bed_target_temp and self._output_device.bedTemperature - 5 < self._bed_target_temp: + self._bed_test_completed = True + self.onBedTestCompleted.emit() + + def _onEndstopStateChanged(self, switch_type, state): + if state: + if switch_type == "x_min": + self._x_min_endstop_test_completed = True + self.onXMinEndstopTestCompleted.emit() + elif switch_type == "y_min": + self._y_min_endstop_test_completed = True + self.onYMinEndstopTestCompleted.emit() + elif switch_type == "z_min": + self._z_min_endstop_test_completed = True + self.onZMinEndstopTestCompleted.emit() + + @pyqtSlot() + def startCheck(self): + output_devices = self._getPrinterOutputDevices() + if output_devices: + self._output_device = output_devices[0] + try: + self._output_device.startPollEndstop() + self._output_device.bedTemperatureChanged.connect(self.bedTemperatureChanged) + self._output_device.hotendTemperaturesChanged.connect(self.hotendTemperatureChanged) + self._output_device.bedTemperatureChanged.connect(self._onBedTemperatureChanged) + self._output_device.hotendTemperaturesChanged.connect(self._onHotendTemperatureChanged) + self._output_device.endstopStateChanged.connect(self._onEndstopStateChanged) + except AttributeError: # Connection is probably not a USB connection. Something went pretty wrong if this happens. + pass + + @pyqtSlot() + def heatupHotend(self): + if self._output_device is not None: + self._output_device.setTargetHotendTemperature(0, self._hotend_target_temp) + + @pyqtSlot() + def heatupBed(self): + if self._output_device is not None: + self._output_device.setTargetBedTemperature(self._bed_target_temp) \ No newline at end of file diff --git a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml new file mode 100644 index 0000000000..1c3ac84010 --- /dev/null +++ b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml @@ -0,0 +1,271 @@ +import UM 1.2 as UM +import Cura 1.0 as Cura + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 + +Cura.MachineAction +{ + anchors.fill: parent; + Item + { + id: checkupMachineAction + anchors.fill: parent; + property int leftRow: checkupMachineAction.width * 0.40 + property int rightRow: checkupMachineAction.width * 0.60 + UM.I18nCatalog { id: catalog; name:"cura"} + Label + { + id: pageTitle + width: parent.width + text: catalog.i18nc("@title", "Check Printer") + wrapMode: Text.WordWrap + font.pointSize: 18; + } + + Label + { + id: pageDescription + anchors.top: pageTitle.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + width: parent.width + wrapMode: Text.WordWrap + text: catalog.i18nc("@label","It's a good idea to do a few sanity checks on your Ultimaker. You can skip this step if you know your machine is functional"); + } + + Item + { + id: startStopButtons + anchors.top: pageDescription.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + anchors.horizontalCenter: parent.horizontalCenter + height: childrenRect.height + width: startCheckButton.width + skipCheckButton.width + UM.Theme.getSize("default_margin").height < checkupMachineAction.width ? startCheckButton.width + skipCheckButton.width + UM.Theme.getSize("default_margin").height : checkupMachineAction.width + Button + { + id: startCheckButton + anchors.top: parent.top + anchors.left: parent.left + text: catalog.i18nc("@action:button","Start Printer Check"); + onClicked: + { + checkupContent.visible = true + startCheckButton.enabled = false + manager.startCheck() + } + } + + Button + { + id: skipCheckButton + anchors.top: parent.width < checkupMachineAction.width ? parent.top : startCheckButton.bottom + anchors.topMargin: parent.width < checkupMachineAction.width ? 0 : UM.Theme.getSize("default_margin").height/2 + anchors.left: parent.width < checkupMachineAction.width ? startCheckButton.right : parent.left + anchors.leftMargin: parent.width < checkupMachineAction.width ? UM.Theme.getSize("default_margin").width : 0 + text: catalog.i18nc("@action:button", "Skip Printer Check"); + onClicked: manager.setFinished() + } + } + + Item + { + id: checkupContent + anchors.top: startStopButtons.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + visible: false + width: parent.width + height: 250 + ////////////////////////////////////////////////////////// + Label + { + id: connectionLabel + width: checkupMachineAction.leftRow + anchors.left: parent.left + anchors.top: parent.top + wrapMode: Text.WordWrap + text: catalog.i18nc("@label","Connection: ") + } + Label + { + id: connectionStatus + width: checkupMachineAction.rightRow + anchors.left: connectionLabel.right + anchors.top: parent.top + wrapMode: Text.WordWrap + text: Cura.USBPrinterManager.connectedPrinterList.rowCount() > 0 || base.addOriginalProgress.checkUp[0] ? catalog.i18nc("@info:status","Done"):catalog.i18nc("@info:status","Incomplete") + } + ////////////////////////////////////////////////////////// + Label + { + id: endstopXLabel + width: checkupMachineAction.leftRow + anchors.left: parent.left + anchors.top: connectionLabel.bottom + wrapMode: Text.WordWrap + text: catalog.i18nc("@label","Min endstop X: ") + } + Label + { + id: endstopXStatus + width: checkupMachineAction.rightRow + anchors.left: endstopXLabel.right + anchors.top: connectionLabel.bottom + wrapMode: Text.WordWrap + text: manager.xMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked") + } + ////////////////////////////////////////////////////////////// + Label + { + id: endstopYLabel + width: checkupMachineAction.leftRow + anchors.left: parent.left + anchors.top: endstopXLabel.bottom + wrapMode: Text.WordWrap + text: catalog.i18nc("@label","Min endstop Y: ") + } + Label + { + id: endstopYStatus + width: checkupMachineAction.rightRow + anchors.left: endstopYLabel.right + anchors.top: endstopXLabel.bottom + wrapMode: Text.WordWrap + text: manager.yMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked") + } + ///////////////////////////////////////////////////////////////////// + Label + { + id: endstopZLabel + width: checkupMachineAction.leftRow + anchors.left: parent.left + anchors.top: endstopYLabel.bottom + wrapMode: Text.WordWrap + text: catalog.i18nc("@label","Min endstop Z: ") + } + Label + { + id: endstopZStatus + width: checkupMachineAction.rightRow + anchors.left: endstopZLabel.right + anchors.top: endstopYLabel.bottom + wrapMode: Text.WordWrap + text: manager.zMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked") + } + //////////////////////////////////////////////////////////// + Label + { + id: nozzleTempLabel + width: checkupMachineAction.leftRow + anchors.left: parent.left + anchors.top: endstopZLabel.bottom + wrapMode: Text.WordWrap + text: catalog.i18nc("@label","Nozzle temperature check: ") + } + Label + { + id: nozzleTempStatus + width: checkupMachineAction.rightRow * 0.4 + anchors.top: nozzleTempLabel.top + anchors.left: nozzleTempLabel.right + wrapMode: Text.WordWrap + text: catalog.i18nc("@info:status","Not checked") + } + Item + { + id: nozzleTempButton + width: checkupMachineAction.rightRow * 0.3 + height: nozzleTemp.height + anchors.top: nozzleTempLabel.top + anchors.left: bedTempStatus.right + anchors.leftMargin: UM.Theme.getSize("default_margin").width/2 + Button + { + height: nozzleTemp.height - 2 + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + text: catalog.i18nc("@action:button","Start Heating") + onClicked: + { + manager.heatupHotend() + nozzleTempStatus.text = catalog.i18nc("@info:progress","Checking") + } + } + } + Label + { + id: nozzleTemp + anchors.top: nozzleTempLabel.top + anchors.left: nozzleTempButton.right + anchors.leftMargin: UM.Theme.getSize("default_margin").width + width: checkupMachineAction.rightRow * 0.2 + wrapMode: Text.WordWrap + text: manager.hotendTemperature + "°C" + font.bold: true + } + ///////////////////////////////////////////////////////////////////////////// + Label + { + id: bedTempLabel + width: checkupMachineAction.leftRow + anchors.left: parent.left + anchors.top: nozzleTempLabel.bottom + wrapMode: Text.WordWrap + text: catalog.i18nc("@label","bed temperature check:") + } + + Label + { + id: bedTempStatus + width: checkupMachineAction.rightRow * 0.4 + anchors.top: bedTempLabel.top + anchors.left: bedTempLabel.right + wrapMode: Text.WordWrap + text: manager.bedTestCompleted ? catalog.i18nc("@info:status","Not checked"): catalog.i18nc("@info:status","Checked") + } + Item + { + id: bedTempButton + width: checkupMachineAction.rightRow * 0.3 + height: bedTemp.height + anchors.top: bedTempLabel.top + anchors.left: bedTempStatus.right + anchors.leftMargin: UM.Theme.getSize("default_margin").width/2 + Button + { + height: bedTemp.height - 2 + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + text: catalog.i18nc("@action:button","Start Heating") + onClicked: + { + manager.heatupBed() + } + } + } + Label + { + id: bedTemp + width: checkupMachineAction.rightRow * 0.2 + anchors.top: bedTempLabel.top + anchors.left: bedTempButton.right + anchors.leftMargin: UM.Theme.getSize("default_margin").width + wrapMode: Text.WordWrap + text: manager.bedTemperature + "°C" + font.bold: true + } + Label + { + id: resultText + visible: false + anchors.top: bedTemp.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + anchors.left: parent.left + width: parent.width + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "Everything is in order! You're done with your CheckUp.") + } + } + } +} \ No newline at end of file diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py new file mode 100644 index 0000000000..7d696a871e --- /dev/null +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py @@ -0,0 +1,6 @@ +from cura.MachineAction import MachineAction + +class UpgradeFirmwareMachineAction(MachineAction): + def __init__(self): + super().__init__("UpgradeFirmware", "Upgrade Firmware") + self._qml_url = "UpgradeFirmwareMachineAction.qml" \ No newline at end of file diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml new file mode 100644 index 0000000000..37e4eae2d3 --- /dev/null +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml @@ -0,0 +1,85 @@ +// Copyright (c) 2016 Ultimaker B.V. +// Cura is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 + +import UM 1.2 as UM +import Cura 1.0 as Cura + + +Cura.MachineAction +{ + anchors.fill: parent; + Item + { + id: upgradeFirmwareMachineAction + anchors.fill: parent; + UM.I18nCatalog { id: catalog; name:"cura"} + + Label + { + id: pageTitle + width: parent.width + text: catalog.i18nc("@title", "Upgrade Firmware") + wrapMode: Text.WordWrap + font.pointSize: 18 + } + Label + { + id: pageDescription + anchors.top: pageTitle.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + width: parent.width + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work.") + } + + Label + { + id: upgradeText1 + anchors.top: pageDescription.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + width: parent.width + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "The firmware shipping with new Ultimakers works, but upgrades have been made to make better prints, and make calibration easier."); + } + + Label + { + id: upgradeText2 + anchors.top: upgradeText1.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + width: parent.width + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "Cura requires these new features and thus your firmware will most likely need to be upgraded. You can do so now."); + } + Item + { + anchors.top: upgradeText2.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + anchors.horizontalCenter: parent.horizontalCenter + width: upgradeButton.width + skipUpgradeButton.width + UM.Theme.getSize("default_margin").height < upgradeFirmwareMachineAction.width ? upgradeButton.width + skipUpgradeButton.width + UM.Theme.getSize("default_margin").height : upgradeFirmwareMachineAction.width + Button + { + id: upgradeButton + anchors.top: parent.top + anchors.left: parent.left + text: catalog.i18nc("@action:button","Upgrade to Marlin Firmware"); + onClicked: Cura.USBPrinterManager.updateAllFirmware() + } + Button + { + id: skipUpgradeButton + anchors.top: parent.width < upgradeFirmwareMachineAction.width ? parent.top : upgradeButton.bottom + anchors.topMargin: parent.width < upgradeFirmwareMachineAction.width ? 0 : UM.Theme.getSize("default_margin").height / 2 + anchors.left: parent.width < upgradeFirmwareMachineAction.width ? upgradeButton.right : parent.left + anchors.leftMargin: parent.width < upgradeFirmwareMachineAction.width ? UM.Theme.getSize("default_margin").width : 0 + text: catalog.i18nc("@action:button", "Skip Upgrade"); + onClicked: manager.setFinished() + } + } + } +} \ No newline at end of file diff --git a/plugins/UltimakerMachineActions/__init__.py b/plugins/UltimakerMachineActions/__init__.py new file mode 100644 index 0000000000..39734d00e3 --- /dev/null +++ b/plugins/UltimakerMachineActions/__init__.py @@ -0,0 +1,23 @@ +# Copyright (c) 2016 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from . import BedLevelMachineAction +from . import UpgradeFirmwareMachineAction +from . import UMOCheckupMachineAction + +from UM.i18n import i18nCatalog +catalog = i18nCatalog("cura") + +def getMetaData(): + return { + "plugin": { + "name": catalog.i18nc("@label", "Ultimaker machine actions"), + "author": "Ultimaker", + "version": "1.0", + "description": catalog.i18nc("@info:whatsthis", "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc)"), + "api": 3 + } + } + +def register(app): + return { "machine_action": [BedLevelMachineAction.BedLevelMachineAction(), UpgradeFirmwareMachineAction.UpgradeFirmwareMachineAction(), UMOCheckupMachineAction.UMOCheckupMachineAction()]} diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000000..de6e8797fb --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +testpaths = tests +python_files = Test*.py +python_classes = Test diff --git a/resources/definitions/ultimaker2.def.json b/resources/definitions/ultimaker2.def.json index 7b2222e5b3..ec1c97fd2c 100644 --- a/resources/definitions/ultimaker2.def.json +++ b/resources/definitions/ultimaker2.def.json @@ -12,7 +12,8 @@ "icon": "icon_ultimaker2.png", "platform": "ultimaker2_platform.obj", "platform_texture": "Ultimaker2backplate.png", - "platform_offset": [9, 0, 0] + "platform_offset": [9, 0, 0], + "supported_actions":["UpgradeFirmware"] }, "overrides": { "machine_start_gcode" : { diff --git a/resources/definitions/ultimaker2_extended.def.json b/resources/definitions/ultimaker2_extended.def.json index e3c7d1fd01..cead008643 100644 --- a/resources/definitions/ultimaker2_extended.def.json +++ b/resources/definitions/ultimaker2_extended.def.json @@ -10,7 +10,8 @@ "file_formats": "text/x-gcode", "icon": "icon_ultimaker2.png", "platform": "ultimaker2_platform.obj", - "platform_texture": "Ultimaker2Extendedbackplate.png" + "platform_texture": "Ultimaker2Extendedbackplate.png", + "supported_actions": ["UpgradeFirmware"] }, "overrides": { diff --git a/resources/definitions/ultimaker2_extended_plus.def.json b/resources/definitions/ultimaker2_extended_plus.def.json index 50f0e73b9f..23b308461d 100644 --- a/resources/definitions/ultimaker2_extended_plus.def.json +++ b/resources/definitions/ultimaker2_extended_plus.def.json @@ -9,7 +9,8 @@ "category": "Ultimaker", "file_formats": "text/x-gcode", "platform": "ultimaker2_platform.obj", - "platform_texture": "Ultimaker2ExtendedPlusbackplate.png" + "platform_texture": "Ultimaker2ExtendedPlusbackplate.png", + "supported_actions":["UpgradeFirmware"] }, "overrides": { diff --git a/resources/definitions/ultimaker2_go.def.json b/resources/definitions/ultimaker2_go.def.json index d3ef53d633..27b179eef9 100644 --- a/resources/definitions/ultimaker2_go.def.json +++ b/resources/definitions/ultimaker2_go.def.json @@ -11,7 +11,8 @@ "icon": "icon_ultimaker2.png", "platform": "ultimaker2go_platform.obj", "platform_texture": "Ultimaker2Gobackplate.png", - "platform_offset": [0, 0, 0] + "platform_offset": [0, 0, 0], + "supported_actions":["UpgradeFirmware"] }, "overrides": { diff --git a/resources/definitions/ultimaker2_plus.def.json b/resources/definitions/ultimaker2_plus.def.json index 4432fab170..d5a7c9f4f1 100644 --- a/resources/definitions/ultimaker2_plus.def.json +++ b/resources/definitions/ultimaker2_plus.def.json @@ -16,7 +16,8 @@ "has_variants": true, "has_materials": true, "has_machine_materials": true, - "has_machine_quality": true + "has_machine_quality": true, + "supported_actions":["UpgradeFirmware"] }, "overrides": { diff --git a/resources/definitions/ultimaker_original.def.json b/resources/definitions/ultimaker_original.def.json index 55668946a0..e95431c99e 100644 --- a/resources/definitions/ultimaker_original.def.json +++ b/resources/definitions/ultimaker_original.def.json @@ -14,12 +14,7 @@ "has_materials": true, "preferred_material": "*pla*", "preferred_quality": "*normal*", - "pages": [ - "SelectUpgradedParts", - "UpgradeFirmware", - "UltimakerCheckup", - "BedLeveling" - ] + "supported_actions":["UMOCheckup", "UpgradeFirmware", "BedLevel"] }, "overrides": { diff --git a/resources/definitions/ultimaker_original_plus.def.json b/resources/definitions/ultimaker_original_plus.def.json index 830050beb0..0e7bf3bddd 100644 --- a/resources/definitions/ultimaker_original_plus.def.json +++ b/resources/definitions/ultimaker_original_plus.def.json @@ -11,11 +11,7 @@ "icon": "icon_ultimaker.png", "platform": "ultimaker2_platform.obj", "platform_texture": "UltimakerPlusbackplate.png", - "pages": [ - "UpgradeFirmware", - "UltimakerCheckup", - "BedLeveling" - ] + "supported_actions":["UMOCheckup", "UpgradeFirmware", "BedLevel"] }, "overrides": { diff --git a/resources/qml/AddMachineDialog.qml b/resources/qml/AddMachineDialog.qml index 9d2d1a24ff..38221030ea 100644 --- a/resources/qml/AddMachineDialog.qml +++ b/resources/qml/AddMachineDialog.qml @@ -18,6 +18,7 @@ UM.Dialog title: catalog.i18nc("@title:window", "Add Printer") property string activeManufacturer: "Ultimaker"; + signal machineAdded(string id) function getMachineName() { var name = machineList.model.getItem(machineList.currentIndex).name @@ -162,6 +163,7 @@ UM.Dialog base.visible = false var item = machineList.model.getItem(machineList.currentIndex); Cura.MachineManager.addMachine(machineName.text, item.id) + base.machineAdded(item.id) // Emit signal that the user added a machine. } } diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 6dd57e17c8..a27c232e5e 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -771,6 +771,38 @@ UM.MainWindow AddMachineDialog { id: addMachineDialog + onMachineAdded: + { + machineActionsWizard.start(id) + } + } + + // Dialog to handle first run machine actions + UM.Wizard + { + id: machineActionsWizard; + + title: catalog.i18nc("@title:window", "Add Printer") + property var machine; + + function start(id) + { + var actions = Cura.MachineActionManager.getFirstStartActions(id) + resetPages() // Remove previous pages + + for (var i = 0; i < actions.length; i++) + { + actions[i].displayItem.reset() + machineActionsWizard.appendPage(actions[i].displayItem, catalog.i18nc("@title", actions[i].label)); + } + + //Only start if there are actions to perform. + if (actions.length > 0) + { + machineActionsWizard.currentPage = 0; + show() + } + } } Connections diff --git a/resources/qml/MachineAction.qml b/resources/qml/MachineAction.qml new file mode 100644 index 0000000000..59fb3946a3 --- /dev/null +++ b/resources/qml/MachineAction.qml @@ -0,0 +1,15 @@ +import QtQuick 2.2 + +Item +{ + id: contentItem + // Connect the finished property change to completed signal. + property var finished: manager.finished + onFinishedChanged: if(manager.finished) {completed()} + signal completed() + + function reset() + { + manager.reset() + } +} \ No newline at end of file diff --git a/resources/qml/Preferences/MachinesPage.qml b/resources/qml/Preferences/MachinesPage.qml index faef019deb..8a0a7dc096 100644 --- a/resources/qml/Preferences/MachinesPage.qml +++ b/resources/qml/Preferences/MachinesPage.qml @@ -41,6 +41,37 @@ UM.ManagementPage anchors.fill: parent; spacing: UM.Theme.getSize("default_margin").height; + Row + { + Repeater + { + id: machineActionRepeater + model: Cura.MachineActionManager.getSupportedActions(Cura.MachineManager.activeDefinitionId) + + Button + { + text: machineActionRepeater.model[index].label; + onClicked: + { + actionDialog.content = machineActionRepeater.model[index].displayItem + machineActionRepeater.model[index].displayItem.reset() + actionDialog.show() + } + } + } + } + + UM.Dialog + { + id: actionDialog + property var content + onContentChanged: + { + contents = content; + content.onCompleted.connect(hide) + } + } + Label { text: base.currentItem && base.currentItem.name ? base.currentItem.name : "" diff --git a/resources/qml/WizardPages/AddMachine.qml b/resources/qml/WizardPages/AddMachine.qml deleted file mode 100644 index e4d40d7723..0000000000 --- a/resources/qml/WizardPages/AddMachine.qml +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) 2015 Ultimaker B.V. -// Cura is released under the terms of the AGPLv3 or higher. - -import QtQuick 2.2 -import QtQuick.Controls 1.1 -import QtQuick.Window 2.1 -import QtQuick.Controls.Styles 1.1 - -import UM 1.1 as UM - -Item -{ - id: base - - property string activeManufacturer: "Ultimaker"; - - property variant wizard: null; - - property bool visibility: base.wizard.visible - onVisibilityChanged: - { - machineName.text = getMachineName() - } - - function getMachineName() - { - var name = machineList.model.getItem(machineList.currentIndex).name - - return name - } - - Connections - { - target: base.wizard - onNextClicked: //You can add functions here that get triggered when the final button is clicked in the wizard-element - { - base.wizard.resetPages() - saveMachine() - } - onBackClicked: base.wizard.resetPages() - } - - Label - { - id: title - anchors.left: parent.left - anchors.top: parent.top - text: catalog.i18nc("@title", "Add Printer") - font.pointSize: 18; - } - - Label - { - id: subTitle - anchors.left: parent.left - anchors.top: title.bottom - text: catalog.i18nc("@label", "Please select the type of printer:"); - } - - ScrollView - { - id: machinesHolder - - anchors - { - left: parent.left; - top: subTitle.bottom; - right: parent.right; - bottom: machineNameHolder.top; - } - - ListView - { - id: machineList - - model: UM.MachineDefinitionsModel { id: machineDefinitionsModel; showVariants: false; } - focus: true - - section.property: "manufacturer" - section.delegate: Button { - text: section - style: ButtonStyle { - background: Rectangle { - border.width: 0 - color: "transparent"; - height: UM.Theme.getSize("standard_list_lineheight").height - width: machineList.width - } - label: Label { - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("standard_arrow").width + UM.Theme.getSize("default_margin").width - text: control.text - color: palette.windowText - font.bold: true - UM.RecolorImage { - id: downArrow - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.left - anchors.rightMargin: UM.Theme.getSize("default_margin").width - width: UM.Theme.getSize("standard_arrow").width - height: UM.Theme.getSize("standard_arrow").height - sourceSize.width: width - sourceSize.height: width - color: palette.windowText - source: base.activeManufacturer == section ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_right") - } - } - } - - onClicked: { - base.activeManufacturer = section; - machineList.currentIndex = machineList.model.find("manufacturer", section) - machineName.text = getMachineName() - } - } - - delegate: RadioButton { - id: machineButton - - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("standard_list_lineheight").width - - opacity: 1; - height: UM.Theme.getSize("standard_list_lineheight").height; - - checked: ListView.isCurrentItem; - - exclusiveGroup: printerGroup; - - text: model.name - - onClicked: { - ListView.view.currentIndex = index; - machineName.text = getMachineName() - } - - states: State { - name: "collapsed"; - when: base.activeManufacturer != model.manufacturer; - - PropertyChanges { target: machineButton; opacity: 0; height: 0; } - } - - transitions: [ - Transition { - to: "collapsed"; - SequentialAnimation { - NumberAnimation { property: "opacity"; duration: 75; } - NumberAnimation { property: "height"; duration: 75; } - } - }, - Transition { - from: "collapsed"; - SequentialAnimation { - NumberAnimation { property: "height"; duration: 75; } - NumberAnimation { property: "opacity"; duration: 75; } - } - } - ] - } - } - } - - - - Column - { - id: machineNameHolder - anchors.bottom: parent.bottom; - - Item - { - height: errorMessage.lineHeight - anchors.bottom: insertNameLabel.top - anchors.bottomMargin: insertNameLabel.height * errorMessage.lineCount - Label - { - id: errorMessage - property bool show: false - width: base.width - height: errorMessage.show ? errorMessage.lineHeight : 0 - visible: errorMessage.show - text: catalog.i18nc("@label", "This printer name has already been used. Please choose a different printer name."); - wrapMode: Text.WordWrap - Behavior on height {NumberAnimation {duration: 75; }} - color: UM.Theme.getColor("error") - } - } - - Label - { - id: insertNameLabel - text: catalog.i18nc("@label:textbox", "Printer Name:"); - } - TextField - { - id: machineName; - text: getMachineName() - implicitWidth: UM.Theme.getSize("standard_list_input").width - maximumLength: 40 - } - } - - function saveMachine() - { - if(machineList.currentIndex != -1) - { - var item = machineList.model.getItem(machineList.currentIndex); - machineList.model.createInstance(machineName.text, item.id) - - var pages = machineList.model.getItem(machineList.currentIndex).pages - - // Insert new pages (if any) - for(var i = 0; i < pages.length; i++) - { - switch(pages[i]) { - case "SelectUpgradedParts": - base.wizard.appendPage(Qt.resolvedUrl("SelectUpgradedParts.qml"), catalog.i18nc("@title", "Select Upgraded Parts")); - break; - case "SelectUpgradedPartsUM2": - base.wizard.appendPage(Qt.resolvedUrl("SelectUpgradedPartsUM2.qml"), catalog.i18nc("@title", "Select Upgraded Parts")); - break; - case "UpgradeFirmware": - base.wizard.appendPage(Qt.resolvedUrl("UpgradeFirmware.qml"), catalog.i18nc("@title", "Upgrade Firmware")); - break; - case "UltimakerCheckup": - base.wizard.appendPage(Qt.resolvedUrl("UltimakerCheckup.qml"), catalog.i18nc("@title", "Check Printer")); - break; - case "BedLeveling": - base.wizard.appendPage(Qt.resolvedUrl("Bedleveling.qml"), catalog.i18nc("@title", "Bed Levelling")); - break; - default: - base.wizard.appendPage(Qt.resolvedUrl("%1.qml".arg(pages[i])), pages[i]) - break; - } - } - } - } - - ExclusiveGroup { id: printerGroup; } - UM.I18nCatalog { id: catalog; name: "cura"; } - SystemPalette { id: palette } -} diff --git a/resources/qml/WizardPages/Bedleveling.qml b/resources/qml/WizardPages/Bedleveling.qml deleted file mode 100644 index 1a7b85a07b..0000000000 --- a/resources/qml/WizardPages/Bedleveling.qml +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) 2015 Ultimaker B.V. -// Cura is released under the terms of the AGPLv3 or higher. - -import QtQuick 2.2 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import QtQuick.Window 2.1 - -import UM 1.1 as UM -import Cura 1.0 as Cura -import ".." - -Item -{ - id: wizardPage - property int leveling_state: 0 - property bool three_point_leveling: true - property int platform_width: UM.MachineManager.getSettingValue("machine_width") - property int platform_height: UM.MachineManager.getSettingValue("machine_depth") - anchors.fill: parent; - property variant printer_connection: Cura.USBPrinterManager.connectedPrinterList.getItem(0).printer - Component.onCompleted: - { - printer_connection.homeBed() - printer_connection.moveHead(0, 0, 3) - printer_connection.homeHead() - } - UM.I18nCatalog { id: catalog; name:"cura"} - property variant wizard: null; - - Label - { - id: pageTitle - width: parent.width - text: catalog.i18nc("@title", "Bed Leveling") - wrapMode: Text.WordWrap - font.pointSize: 18; - } - - Label - { - id: pageDescription - anchors.top: pageTitle.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width - wrapMode: Text.WordWrap - text: catalog.i18nc("@label","To make sure your prints will come out great, you can now adjust your buildplate. When you click 'Move to Next Position' the nozzle will move to the different positions that can be adjusted.") - } - Label - { - id: bedlevelingText - anchors.top: pageDescription.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width - wrapMode: Text.WordWrap - text: catalog.i18nc("@label", "For every postition; insert a piece of paper under the nozzle and adjust the print bed height. The print bed height is right when the paper is slightly gripped by the tip of the nozzle.") - } - - Item{ - id: bedlevelingWrapper - anchors.top: bedlevelingText.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - anchors.horizontalCenter: parent.horizontalCenter - height: skipBedlevelingButton.height - width: bedlevelingButton.width + skipBedlevelingButton.width + UM.Theme.getSize("default_margin").height < wizardPage.width ? bedlevelingButton.width + skipBedlevelingButton.width + UM.Theme.getSize("default_margin").height : wizardPage.width - Button - { - id: bedlevelingButton - anchors.top: parent.top - anchors.left: parent.left - text: catalog.i18nc("@action:button","Move to Next Position"); - onClicked: - { - if(wizardPage.leveling_state == 0) - { - printer_connection.moveHead(0, 0, 3) - printer_connection.homeHead() - printer_connection.moveHead(0, 0, 3) - printer_connection.moveHead(platform_width - 10, 0, 0) - printer_connection.moveHead(0, 0, -3) - } - if(wizardPage.leveling_state == 1) - { - printer_connection.moveHead(0, 0, 3) - printer_connection.moveHead(-platform_width/2, platform_height - 10, 0) - printer_connection.moveHead(0, 0, -3) - } - if(wizardPage.leveling_state == 2) - { - printer_connection.moveHead(0, 0, 3) - printer_connection.moveHead(-platform_width/2 + 10, -(platform_height + 10), 0) - printer_connection.moveHead(0, 0, -3) - } - wizardPage.leveling_state++ - if (wizardPage.leveling_state >= 3){ - resultText.visible = true - skipBedlevelingButton.enabled = false - bedlevelingButton.enabled = false - wizardPage.leveling_state = 0 - } - } - } - - Button - { - id: skipBedlevelingButton - anchors.top: parent.width < wizardPage.width ? parent.top : bedlevelingButton.bottom - anchors.topMargin: parent.width < wizardPage.width ? 0 : UM.Theme.getSize("default_margin").height/2 - anchors.left: parent.width < wizardPage.width ? bedlevelingButton.right : parent.left - anchors.leftMargin: parent.width < wizardPage.width ? UM.Theme.getSize("default_margin").width : 0 - text: catalog.i18nc("@action:button","Skip Bedleveling"); - onClicked: base.nextPage() - } - } - - Label - { - id: resultText - visible: false - anchors.top: bedlevelingWrapper.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - anchors.left: parent.left - width: parent.width - wrapMode: Text.WordWrap - text: catalog.i18nc("@label", "Everything is in order! You're done with bedleveling.") - } - - function threePointLeveling(width, height) - { - - } -} diff --git a/resources/qml/WizardPages/SelectUpgradedParts.qml b/resources/qml/WizardPages/SelectUpgradedParts.qml deleted file mode 100644 index a49401ada9..0000000000 --- a/resources/qml/WizardPages/SelectUpgradedParts.qml +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2015 Ultimaker B.V. -// Cura is released under the terms of the AGPLv3 or higher. - -import QtQuick 2.2 -import QtQuick.Controls 1.1 -import QtQuick.Window 2.1 - -import UM 1.1 as UM - -Item -{ - id: wizardPage - property string title - - SystemPalette{id: palette} - UM.I18nCatalog { id: catalog; name:"cura"} - - Component.onDestruction: - { - if (heatedBedCheckBox1.checked == true || heatedBedCheckBox2.checked == true){ - UM.MachineManager.setMachineSettingValue("machine_heated_bed", true) - } - } - Label - { - id: pageTitle - width: parent.width - text: catalog.i18nc("@title", "Select Upgraded Parts") - wrapMode: Text.WordWrap - font.pointSize: 18 - } - Label - { - id: pageDescription - anchors.top: pageTitle.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width - wrapMode: Text.WordWrap - text: catalog.i18nc("@label","To assist you in having better default settings for your Ultimaker. Cura would like to know which upgrades you have in your machine:") - } - - Item - { - id: pageCheckboxes - height: childrenRect.height - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("default_margin").width - anchors.top: pageDescription.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width - UM.Theme.getSize("default_margin").width - CheckBox - { - id: heatedBedCheckBox1 - text: catalog.i18nc("@option:check","Heated printer bed") - y: extruderCheckBox.height * 1 - checked: false - onClicked: { - if (heatedBedCheckBox2.checked == true) - heatedBedCheckBox2.checked = false - } - } - CheckBox - { - id: heatedBedCheckBox2 - text: catalog.i18nc("@option:check","Heated printer bed (self built)") - y: extruderCheckBox.height * 2 - checked: false - onClicked: { - if (heatedBedCheckBox1.checked == true) - heatedBedCheckBox1.checked = false - } - } - } - - Label - { - width: parent.width - anchors.top: pageCheckboxes.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - wrapMode: Text.WordWrap - text: catalog.i18nc("@label","If you bought your Ultimaker after october 2012 you will have the Extruder drive upgrade. If you do not have this upgrade, it is highly recommended to improve reliability. This upgrade can be bought from the Ultimaker webshop or found on thingiverse as thing:26094"); - } - - ExclusiveGroup { id: printerGroup; } -} diff --git a/resources/qml/WizardPages/UltimakerCheckup.qml b/resources/qml/WizardPages/UltimakerCheckup.qml deleted file mode 100644 index a57174a53d..0000000000 --- a/resources/qml/WizardPages/UltimakerCheckup.qml +++ /dev/null @@ -1,378 +0,0 @@ -// Copyright (c) 2015 Ultimaker B.V. -// Cura is released under the terms of the AGPLv3 or higher. - -import QtQuick 2.2 -import QtQuick.Controls 1.1 -import QtQuick.Window 2.1 - -import UM 1.1 as UM - -Item -{ - id: wizardPage - property string title - property int leftRow: wizardPage.width*0.40 - property int rightRow: wizardPage.width*0.60 - anchors.fill: parent; - property bool x_min_pressed: false - property bool y_min_pressed: false - property bool z_min_pressed: false - property bool heater_works: false - property int extruder_target_temp: 0 - property int bed_target_temp: 0 - UM.I18nCatalog { id: catalog; name:"cura"} - property var checkupProgress: { - "connection": false, - "endstopX": wizardPage.x_min_pressed, - "endstopY": wizardPage.y_min_pressed, - "endstopZ": wizardPage.z_min_pressed, - "nozzleTemp": false, - "bedTemp": false - } - - property variant printer_connection: { - if (Cura.USBPrinterManager.connectedPrinterList.rowCount() != 0){ - wizardPage.checkupProgress.connection = true - return Cura.USBPrinterManager.connectedPrinterList.getItem(0).printer - } - else { - return null - } - } - - function checkTotalCheckUp(){ - var allDone = true - for(var property in checkupProgress){ - if (checkupProgress[property] == false){ - allDone = false - } - } - if (allDone == true){ - skipCheckButton.enabled = false - resultText.visible = true - } - } - - Component.onCompleted: - { - if (printer_connection != null){ - printer_connection.startPollEndstop() - } - } - Component.onDestruction: - { - if (printer_connection != null){ - printer_connection.stopPollEndstop() - } - } - Label - { - id: pageTitle - width: parent.width - text: catalog.i18nc("@title", "Check Printer") - wrapMode: Text.WordWrap - font.pointSize: 18; - } - - Label - { - id: pageDescription - anchors.top: pageTitle.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width - wrapMode: Text.WordWrap - text: catalog.i18nc("@label","It's a good idea to do a few sanity checks on your Ultimaker. You can skip this step if you know your machine is functional"); - } - - Item{ - id: startStopButtons - anchors.top: pageDescription.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - anchors.horizontalCenter: parent.horizontalCenter - height: childrenRect.height - width: startCheckButton.width + skipCheckButton.width + UM.Theme.getSize("default_margin").height < wizardPage.width ? startCheckButton.width + skipCheckButton.width + UM.Theme.getSize("default_margin").height : wizardPage.width - Button - { - id: startCheckButton - anchors.top: parent.top - anchors.left: parent.left - //enabled: !alreadyTested - text: catalog.i18nc("@action:button","Start Printer Check"); - onClicked: { - checkupContent.visible = true - startCheckButton.enabled = false - printer_connection.homeHead() - } - } - - Button - { - id: skipCheckButton - anchors.top: parent.width < wizardPage.width ? parent.top : startCheckButton.bottom - anchors.topMargin: parent.width < wizardPage.width ? 0 : UM.Theme.getSize("default_margin").height/2 - anchors.left: parent.width < wizardPage.width ? startCheckButton.right : parent.left - anchors.leftMargin: parent.width < wizardPage.width ? UM.Theme.getSize("default_margin").width : 0 - //enabled: !alreadyTested - text: catalog.i18nc("@action:button","Skip Printer Check"); - onClicked: base.nextPage() - } - } - - Item{ - id: checkupContent - anchors.top: startStopButtons.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - visible: false - ////////////////////////////////////////////////////////// - Label - { - id: connectionLabel - width: wizardPage.leftRow - anchors.left: parent.left - anchors.top: parent.top - wrapMode: Text.WordWrap - text: catalog.i18nc("@label","Connection: ") - } - Label - { - id: connectionStatus - width: wizardPage.rightRow - anchors.left: connectionLabel.right - anchors.top: parent.top - wrapMode: Text.WordWrap - text: Cura.USBPrinterManager.connectedPrinterList.rowCount() > 0 || base.addOriginalProgress.checkUp[0] ? catalog.i18nc("@info:status","Done"):catalog.i18nc("@info:status","Incomplete") - } - ////////////////////////////////////////////////////////// - Label - { - id: endstopXLabel - width: wizardPage.leftRow - anchors.left: parent.left - anchors.top: connectionLabel.bottom - wrapMode: Text.WordWrap - text: catalog.i18nc("@label","Min endstop X: ") - } - Label - { - id: endstopXStatus - width: wizardPage.rightRow - anchors.left: endstopXLabel.right - anchors.top: connectionLabel.bottom - wrapMode: Text.WordWrap - text: x_min_pressed ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked") - } - ////////////////////////////////////////////////////////////// - Label - { - id: endstopYLabel - width: wizardPage.leftRow - anchors.left: parent.left - anchors.top: endstopXLabel.bottom - wrapMode: Text.WordWrap - text: catalog.i18nc("@label","Min endstop Y: ") - } - Label - { - id: endstopYStatus - width: wizardPage.rightRow - anchors.left: endstopYLabel.right - anchors.top: endstopXLabel.bottom - wrapMode: Text.WordWrap - text: y_min_pressed ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked") - } - ///////////////////////////////////////////////////////////////////// - Label - { - id: endstopZLabel - width: wizardPage.leftRow - anchors.left: parent.left - anchors.top: endstopYLabel.bottom - wrapMode: Text.WordWrap - text: catalog.i18nc("@label","Min endstop Z: ") - } - Label - { - id: endstopZStatus - width: wizardPage.rightRow - anchors.left: endstopZLabel.right - anchors.top: endstopYLabel.bottom - wrapMode: Text.WordWrap - text: z_min_pressed ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked") - } - //////////////////////////////////////////////////////////// - Label - { - id: nozzleTempLabel - width: wizardPage.leftRow - anchors.left: parent.left - anchors.top: endstopZLabel.bottom - wrapMode: Text.WordWrap - text: catalog.i18nc("@label","Nozzle temperature check: ") - } - Label - { - id: nozzleTempStatus - width: wizardPage.rightRow * 0.4 - anchors.top: nozzleTempLabel.top - anchors.left: nozzleTempLabel.right - wrapMode: Text.WordWrap - text: catalog.i18nc("@info:status","Not checked") - } - Item - { - id: nozzleTempButton - width: wizardPage.rightRow * 0.3 - height: nozzleTemp.height - anchors.top: nozzleTempLabel.top - anchors.left: bedTempStatus.right - anchors.leftMargin: UM.Theme.getSize("default_margin").width/2 - Button - { - height: nozzleTemp.height - 2 - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - text: catalog.i18nc("@action:button","Start Heating") - onClicked: - { - if(printer_connection != null) - { - nozzleTempStatus.text = catalog.i18nc("@info:progress","Checking") - printer_connection.setTargetHotendTemperature(0, 190) - wizardPage.extruder_target_temp = 190 - } - } - } - } - Label - { - id: nozzleTemp - anchors.top: nozzleTempLabel.top - anchors.left: nozzleTempButton.right - anchors.leftMargin: UM.Theme.getSize("default_margin").width - width: wizardPage.rightRow * 0.2 - wrapMode: Text.WordWrap - text: printer_connection != null ? printer_connection.hotendTemperatures[0] + "°C" : "0°C" - font.bold: true - } - ///////////////////////////////////////////////////////////////////////////// - Label - { - id: bedTempLabel - width: wizardPage.leftRow - anchors.left: parent.left - anchors.top: nozzleTempLabel.bottom - wrapMode: Text.WordWrap - text: catalog.i18nc("@label","bed temperature check:") - } - - Label - { - id: bedTempStatus - width: wizardPage.rightRow * 0.4 - anchors.top: bedTempLabel.top - anchors.left: bedTempLabel.right - wrapMode: Text.WordWrap - text: catalog.i18nc("@info:status","Not checked") - } - Item - { - id: bedTempButton - width: wizardPage.rightRow * 0.3 - height: bedTemp.height - anchors.top: bedTempLabel.top - anchors.left: bedTempStatus.right - anchors.leftMargin: UM.Theme.getSize("default_margin").width/2 - Button - { - height: bedTemp.height - 2 - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - text: catalog.i18nc("@action:button","Start Heating") - onClicked: - { - if(printer_connection != null) - { - bedTempStatus.text = catalog.i18nc("@info:progress","Checking") - printer_connection.setTargetBedTemperature(60) - wizardPage.bed_target_temp = 60 - } - } - } - } - Label - { - id: bedTemp - width: wizardPage.rightRow * 0.2 - anchors.top: bedTempLabel.top - anchors.left: bedTempButton.right - anchors.leftMargin: UM.Theme.getSize("default_margin").width - wrapMode: Text.WordWrap - text: printer_connection != null ? printer_connection.bedTemperature + "°C": "0°C" - font.bold: true - } - Label - { - id: resultText - visible: false - anchors.top: bedTemp.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - anchors.left: parent.left - width: parent.width - wrapMode: Text.WordWrap - text: catalog.i18nc("@label", "Everything is in order! You're done with your CheckUp.") - } - } - - - Connections - { - target: printer_connection - onEndstopStateChanged: - { - if(key == "x_min") - { - x_min_pressed = true - checkTotalCheckUp() - } - if(key == "y_min") - { - y_min_pressed = true - checkTotalCheckUp() - } - if(key == "z_min") - { - z_min_pressed = true - checkTotalCheckUp() - } - } - - onHotendTemperaturesChanged: - { - if(printer_connection.hotendTemperatures[0] > wizardPage.extruder_target_temp - 10 && printer_connection.hotendTemperatures[0] < wizardPage.extruder_target_temp + 10) - { - if(printer_connection != null) - { - nozzleTempStatus.text = catalog.i18nc("@info:status","Works") - wizardPage.checkupProgress.nozzleTemp = true - checkTotalCheckUp() - printer_connection.setTargetHotendTemperature(0, 0) - } - } - } - onBedTemperatureChanged: - { - if(printer_connection.bedTemperature > wizardPage.bed_target_temp - 5 && printer_connection.bedTemperature < wizardPage.bed_target_temp + 5) - { - bedTempStatus.text = catalog.i18nc("@info:status","Works") - wizardPage.checkupProgress.bedTemp = true - checkTotalCheckUp() - printer_connection.setTargetBedTemperature(0) - } - } - } - - ExclusiveGroup - { - id: printerGroup; - } -} \ No newline at end of file diff --git a/resources/qml/WizardPages/UpgradeFirmware.qml b/resources/qml/WizardPages/UpgradeFirmware.qml deleted file mode 100644 index 18bad132c6..0000000000 --- a/resources/qml/WizardPages/UpgradeFirmware.qml +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2015 Ultimaker B.V. -// Cura is released under the terms of the AGPLv3 or higher. - -import QtQuick 2.2 -import QtQuick.Controls 1.1 -import QtQuick.Window 2.1 - -import UM 1.1 as UM - -Item -{ - id: wizardPage - property string title - - SystemPalette{id: palette} - UM.I18nCatalog { id: catalog; name:"cura"} - property variant printer_connection: Cura.USBPrinterManager.connectedPrinterList.rowCount() != 0 ? Cura.USBPrinterManager.connectedPrinterList.getItem(0).printer : null - Label - { - id: pageTitle - width: parent.width - text: catalog.i18nc("@title", "Upgrade Firmware") - wrapMode: Text.WordWrap - font.pointSize: 18 - } - Label - { - id: pageDescription - anchors.top: pageTitle.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width - wrapMode: Text.WordWrap - text: catalog.i18nc("@label","Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work.") - } - - Label - { - id: upgradeText1 - anchors.top: pageDescription.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width - wrapMode: Text.WordWrap - text: catalog.i18nc("@label","The firmware shipping with new Ultimakers works, but upgrades have been made to make better prints, and make calibration easier."); - } - - Label - { - id: upgradeText2 - anchors.top: upgradeText1.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width - wrapMode: Text.WordWrap - text: catalog.i18nc("@label","Cura requires these new features and thus your firmware will most likely need to be upgraded. You can do so now."); - } - Item{ - anchors.top: upgradeText2.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - anchors.horizontalCenter: parent.horizontalCenter - width: upgradeButton.width + skipUpgradeButton.width + UM.Theme.getSize("default_margin").height < wizardPage.width ? upgradeButton.width + skipUpgradeButton.width + UM.Theme.getSize("default_margin").height : wizardPage.width - Button { - id: upgradeButton - anchors.top: parent.top - anchors.left: parent.left - text: catalog.i18nc("@action:button","Upgrade to Marlin Firmware"); - onClicked: Cura.USBPrinterManager.updateAllFirmware() - } - Button { - id: skipUpgradeButton - anchors.top: parent.width < wizardPage.width ? parent.top : upgradeButton.bottom - anchors.topMargin: parent.width < wizardPage.width ? 0 : UM.Theme.getSize("default_margin").height/2 - anchors.left: parent.width < wizardPage.width ? upgradeButton.right : parent.left - anchors.leftMargin: parent.width < wizardPage.width ? UM.Theme.getSize("default_margin").width : 0 - text: catalog.i18nc("@action:button","Skip Upgrade"); - onClicked: base.nextPage() - } - } - ExclusiveGroup { id: printerGroup; } -} \ No newline at end of file diff --git a/tests/TestMachineAction.py b/tests/TestMachineAction.py new file mode 100644 index 0000000000..1d593e92a1 --- /dev/null +++ b/tests/TestMachineAction.py @@ -0,0 +1,77 @@ +#Todo: Write tests + +import pytest + +from cura.MachineAction import MachineAction +from cura.MachineActionManager import MachineActionManager, NotUniqueMachineActionError, UnknownMachineActionError + +class Machine: + def __init__(self, key = ""): + self._key = key + + def getKey(self): + return self._key + +def test_addMachineAction(): + + machine_manager = MachineActionManager() + + test_action = MachineAction(key = "test_action") + test_action_2 = MachineAction(key = "test_action_2") + test_machine = Machine("test_machine") + machine_manager.addMachineAction(test_action) + machine_manager.addMachineAction(test_action_2) + + assert machine_manager.getMachineAction("test_action") == test_action + assert machine_manager.getMachineAction("key_that_doesnt_exist") is None + + # Adding the same machine action is not allowed. + with pytest.raises(NotUniqueMachineActionError): + machine_manager.addMachineAction(test_action) + + # Check that the machine has no supported actions yet. + assert machine_manager.getSupportedActions(test_machine) == set() + + # Check if adding a supported action works. + machine_manager.addSupportedAction(test_machine, "test_action") + assert machine_manager.getSupportedActions(test_machine) == {test_action} + + # Check that adding a unknown action doesn't change anything. + machine_manager.addSupportedAction(test_machine, "key_that_doesnt_exist") + assert machine_manager.getSupportedActions(test_machine) == {test_action} + + # Check if adding multiple supported actions works. + machine_manager.addSupportedAction(test_machine, "test_action_2") + assert machine_manager.getSupportedActions(test_machine) == {test_action, test_action_2} + + # Check that the machine has no required actions yet. + assert machine_manager.getRequiredActions(test_machine) == set() + + ## Ensure that only known actions can be added. + with pytest.raises(UnknownMachineActionError): + machine_manager.addRequiredAction(test_machine, "key_that_doesnt_exist") + + ## Check if adding single required action works + machine_manager.addRequiredAction(test_machine, "test_action") + assert machine_manager.getRequiredActions(test_machine) == {test_action} + + # Check if adding multiple required actions works. + machine_manager.addRequiredAction(test_machine, "test_action_2") + assert machine_manager.getRequiredActions(test_machine) == {test_action, test_action_2} + + # Ensure that firstStart actions are empty by default. + assert machine_manager.getFirstStartActions(test_machine) == [] + + # Check if adding multiple (the same) actions to first start actions work. + machine_manager.addFirstStartAction(test_machine, "test_action") + machine_manager.addFirstStartAction(test_machine, "test_action") + assert machine_manager.getFirstStartActions(test_machine) == [test_action, test_action] + + # Check if inserting an action works + machine_manager.addFirstStartAction(test_machine, "test_action_2", index = 1) + assert machine_manager.getFirstStartActions(test_machine) == [test_action, test_action_2, test_action] + + # Check that adding a unknown action doesn't change anything. + machine_manager.addFirstStartAction(test_machine, "key_that_doesnt_exist", index = 1) + assert machine_manager.getFirstStartActions(test_machine) == [test_action, test_action_2, test_action] +