diff --git a/cura/PrinterOutput/Models/PrinterOutputModel.py b/cura/PrinterOutput/Models/PrinterOutputModel.py index 13fe85e674..b8bea999c7 100644 --- a/cura/PrinterOutput/Models/PrinterOutputModel.py +++ b/cura/PrinterOutput/Models/PrinterOutputModel.py @@ -7,6 +7,7 @@ from UM.Math.Vector import Vector from cura.PrinterOutput.Peripheral import Peripheral from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel from cura.PrinterOutput.Models.ExtruderOutputModel import ExtruderOutputModel +from UM.Logger import Logger if TYPE_CHECKING: from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel @@ -37,7 +38,7 @@ class PrinterOutputModel(QObject): self._controller = output_controller self._controller.canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged) self._extruders = [ExtruderOutputModel(printer = self, position = i) for i in range(number_of_extruders)] - self._printer_configuration = PrinterConfigurationModel() # Indicates the current configuration setup in this printer + self._active_printer_configuration = PrinterConfigurationModel() # Indicates the current configuration setup in this printer self._head_position = Vector(0, 0, 0) self._active_print_job = None # type: Optional[PrintJobOutputModel] self._firmware_version = firmware_version @@ -47,8 +48,10 @@ class PrinterOutputModel(QObject): self._buildplate = "" self._peripherals = [] # type: List[Peripheral] - self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in - self._extruders] + self._active_printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in + self._extruders] + + self._available_printer_configurations = [] # type: List[PrinterConfigurationModel] self._camera_url = QUrl() # type: QUrl @@ -81,7 +84,7 @@ class PrinterOutputModel(QObject): def updateType(self, printer_type: str) -> None: if self._printer_type != printer_type: self._printer_type = printer_type - self._printer_configuration.printerType = self._printer_type + self._active_printer_configuration.printerType = self._printer_type self.typeChanged.emit() self.configurationChanged.emit() @@ -92,7 +95,7 @@ class PrinterOutputModel(QObject): def updateBuildplate(self, buildplate: str) -> None: if self._buildplate != buildplate: self._buildplate = buildplate - self._printer_configuration.buildplateConfiguration = self._buildplate + self._active_printer_configuration.buildplateConfiguration = self._buildplate self.buildplateChanged.emit() self.configurationChanged.emit() @@ -290,18 +293,18 @@ class PrinterOutputModel(QObject): def _onControllerCanUpdateFirmwareChanged(self) -> None: self.canUpdateFirmwareChanged.emit() - # Returns the configuration (material, variant and buildplate) of the current printer + # Returns the active configuration (material, variant and buildplate) of the current printer @pyqtProperty(QObject, notify = configurationChanged) def printerConfiguration(self) -> Optional[PrinterConfigurationModel]: - if self._printer_configuration.isValid(): - return self._printer_configuration + if self._active_printer_configuration.isValid(): + return self._active_printer_configuration return None peripheralsChanged = pyqtSignal() @pyqtProperty(str, notify = peripheralsChanged) def peripherals(self) -> str: - return ", ".join(*[peripheral.name for peripheral in self._peripherals]) + return ", ".join([peripheral.name for peripheral in self._peripherals]) def addPeripheral(self, peripheral: Peripheral) -> None: self._peripherals.append(peripheral) @@ -309,4 +312,29 @@ class PrinterOutputModel(QObject): def removePeripheral(self, peripheral: Peripheral) -> None: self._peripherals.remove(peripheral) - self.peripheralsChanged.emit() \ No newline at end of file + self.peripheralsChanged.emit() + + availableConfigurationsChanged = pyqtSignal() + + # The availableConfigurations are configuration options that a printer can switch to, but doesn't currently have + # active (eg; Automatic tool changes, material loaders, etc). + @pyqtProperty("QVariantList", notify = availableConfigurationsChanged) + def availableConfigurations(self) -> List[PrinterConfigurationModel]: + return self._available_printer_configurations + + def addAvailableConfiguration(self, new_configuration: PrinterConfigurationModel) -> None: + if new_configuration not in self._available_printer_configurations: + self._available_printer_configurations.append(new_configuration) + self.availableConfigurationsChanged.emit() + + def removeAvailableConfiguration(self, config_to_remove: PrinterConfigurationModel) -> None: + try: + self._available_printer_configurations.remove(config_to_remove) + except ValueError: + Logger.log("w", "Unable to remove configuration that isn't in the list of available configurations") + else: + self.availableConfigurationsChanged.emit() + + def setAvailableConfigurations(self, new_configurations: List[PrinterConfigurationModel]) -> None: + self._available_printer_configurations = new_configurations + self.availableConfigurationsChanged.emit() diff --git a/cura/PrinterOutput/PrinterOutputDevice.py b/cura/PrinterOutput/PrinterOutputDevice.py index d4a37b3d68..bb4f9e79fb 100644 --- a/cura/PrinterOutput/PrinterOutputDevice.py +++ b/cura/PrinterOutput/PrinterOutputDevice.py @@ -220,11 +220,15 @@ class PrinterOutputDevice(QObject, OutputDevice): return self._unique_configurations def _updateUniqueConfigurations(self) -> None: - self._unique_configurations = sorted( - {printer.printerConfiguration for printer in self._printers if printer.printerConfiguration is not None}, - key=lambda config: config.printerType, - ) - self.uniqueConfigurationsChanged.emit() + all_configurations = set() + for printer in self._printers: + if printer.printerConfiguration is not None: + all_configurations.add(printer.printerConfiguration) + all_configurations.update(printer.availableConfigurations) + new_configurations = sorted(all_configurations, key = lambda config: config.printerType) + if new_configurations != self._unique_configurations: + self._unique_configurations = new_configurations + self.uniqueConfigurationsChanged.emit() # Returns the unique configurations of the printers within this output device @pyqtProperty("QStringList", notify = uniqueConfigurationsChanged) @@ -234,6 +238,7 @@ class PrinterOutputDevice(QObject, OutputDevice): def _onPrintersChanged(self) -> None: for printer in self._printers: printer.configurationChanged.connect(self._updateUniqueConfigurations) + printer.availableConfigurationsChanged.connect(self._updateUniqueConfigurations) # At this point there may be non-updated configurations self._updateUniqueConfigurations() diff --git a/tests/PrinterOutput/Models/TestPrinterOutputModel.py b/tests/PrinterOutput/Models/TestPrinterOutputModel.py index 3fdb61adbd..9848e0a5fa 100644 --- a/tests/PrinterOutput/Models/TestPrinterOutputModel.py +++ b/tests/PrinterOutput/Models/TestPrinterOutputModel.py @@ -5,11 +5,14 @@ from unittest.mock import MagicMock import pytest from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel +from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel +from cura.PrinterOutput.Peripheral import Peripheral test_validate_data_get_set = [ {"attribute": "name", "value": "YAY"}, {"attribute": "targetBedTemperature", "value": 192}, + {"attribute": "cameraUrl", "value": "YAY!"} ] test_validate_data_get_update = [ @@ -22,6 +25,7 @@ test_validate_data_get_update = [ {"attribute": "targetBedTemperature", "value": 9001}, {"attribute": "activePrintJob", "value": PrintJobOutputModel(MagicMock())}, {"attribute": "state", "value": "BEEPBOOP"}, + ] @@ -79,3 +83,67 @@ def test_getAndUpdate(data): getattr(model, "update" + attribute)(data["value"]) # The signal should not fire again assert signal.emit.call_count == 1 + + +def test_peripherals(): + model = PrinterOutputModel(MagicMock()) + model.peripheralsChanged = MagicMock() + + peripheral = MagicMock(spec=Peripheral) + peripheral.name = "test" + peripheral2 = MagicMock(spec=Peripheral) + peripheral2.name = "test2" + + model.addPeripheral(peripheral) + assert model.peripheralsChanged.emit.call_count == 1 + model.addPeripheral(peripheral2) + assert model.peripheralsChanged.emit.call_count == 2 + + assert model.peripherals == "test, test2" + + model.removePeripheral(peripheral) + assert model.peripheralsChanged.emit.call_count == 3 + assert model.peripherals == "test2" + + +def test_availableConfigurations_addConfiguration(): + model = PrinterOutputModel(MagicMock()) + + configuration = MagicMock(spec = PrinterConfigurationModel) + + model.addAvailableConfiguration(configuration) + assert model.availableConfigurations == [configuration] + + +def test_availableConfigurations_addConfigTwice(): + model = PrinterOutputModel(MagicMock()) + + configuration = MagicMock(spec=PrinterConfigurationModel) + + model.setAvailableConfigurations([configuration]) + assert model.availableConfigurations == [configuration] + + # Adding it again should not have any effect + model.addAvailableConfiguration(configuration) + assert model.availableConfigurations == [configuration] + + +def test_availableConfigurations_removeConfig(): + model = PrinterOutputModel(MagicMock()) + + configuration = MagicMock(spec=PrinterConfigurationModel) + + model.addAvailableConfiguration(configuration) + model.removeAvailableConfiguration(configuration) + assert model.availableConfigurations == [] + + +def test_removeAlreadyRemovedConfiguration(): + model = PrinterOutputModel(MagicMock()) + + configuration = MagicMock(spec=PrinterConfigurationModel) + model.availableConfigurationsChanged = MagicMock() + model.removeAvailableConfiguration(configuration) + assert model.availableConfigurationsChanged.emit.call_count == 0 + assert model.availableConfigurations == [] + diff --git a/tests/PrinterOutput/TestPrinterOutputDevice.py b/tests/PrinterOutput/TestPrinterOutputDevice.py index 4c12a34859..e0415295c1 100644 --- a/tests/PrinterOutput/TestPrinterOutputDevice.py +++ b/tests/PrinterOutput/TestPrinterOutputDevice.py @@ -1,6 +1,10 @@ from unittest.mock import MagicMock import pytest +from unittest.mock import patch + +from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel +from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice test_validate_data_get_set = [ @@ -8,10 +12,15 @@ test_validate_data_get_set = [ {"attribute": "connectionState", "value": 1}, ] +@pytest.fixture() +def printer_output_device(): + with patch("UM.Application.Application.getInstance"): + return PrinterOutputDevice("whatever") + @pytest.mark.parametrize("data", test_validate_data_get_set) -def test_getAndSet(data): - model = PrinterOutputDevice("whatever") +def test_getAndSet(data, printer_output_device): + model = printer_output_device # Convert the first letter into a capital attribute = list(data["attribute"]) @@ -35,3 +44,21 @@ def test_getAndSet(data): getattr(model, "set" + attribute)(data["value"]) # The signal should not fire again assert signal.emit.call_count == 1 + + +def test_uniqueConfigurations(printer_output_device): + printer = PrinterOutputModel(MagicMock()) + # Add a printer and fire the signal that ensures they get hooked up correctly. + printer_output_device._printers = [printer] + printer_output_device._onPrintersChanged() + + assert printer_output_device.uniqueConfigurations == [] + configuration = PrinterConfigurationModel() + printer.addAvailableConfiguration(configuration) + + assert printer_output_device.uniqueConfigurations == [configuration] + + # Once the type of printer is set, it's active configuration counts as being set. + # In that case, that should also be added to the list of available configurations + printer.updateType("blarg!") + assert printer_output_device.uniqueConfigurations == [configuration, printer.printerConfiguration] \ No newline at end of file