diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 5d85763fea..f8b081bdcc 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -749,6 +749,7 @@ class CuraApplication(QtApplication): # Initialize Cura API self._cura_API.initialize() + self._output_device_manager.start() self._welcome_pages_model.initialize() # Detect in which mode to run and execute that mode diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 3416f0a321..d19932a7a0 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -4,7 +4,7 @@ import time import re import unicodedata -from typing import Any, List, Dict, TYPE_CHECKING, Optional, cast +from typing import Any, List, Dict, TYPE_CHECKING, Optional, cast, NamedTuple, Callable from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator @@ -49,6 +49,8 @@ if TYPE_CHECKING: from cura.Machines.QualityChangesGroup import QualityChangesGroup from cura.Machines.QualityGroup import QualityGroup +DiscoveredPrinter = NamedTuple("DiscoveredPrinter", [("key", str), ("name", str), ("create_callback", Callable[[str], None]), ("machine_type", "str")]) + class MachineManager(QObject): def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None: @@ -134,6 +136,9 @@ class MachineManager(QObject): self.globalContainerChanged.connect(self.printerConnectedStatusChanged) self.outputDevicesChanged.connect(self.printerConnectedStatusChanged) + # This will contain all discovered network printers + self._discovered_printers = {} # type: Dict[str, DiscoveredPrinter] + activeQualityGroupChanged = pyqtSignal() activeQualityChangesGroupChanged = pyqtSignal() @@ -157,6 +162,7 @@ class MachineManager(QObject): printerConnectedStatusChanged = pyqtSignal() # Emitted every time the active machine change or the outputdevices change rootMaterialChanged = pyqtSignal() + discoveredPrintersChanged = pyqtSignal() def setInitialActiveMachine(self) -> None: active_machine_id = self._application.getPreferences().getValue("cura/active_machine") @@ -171,7 +177,30 @@ class MachineManager(QObject): self._printer_output_devices.append(printer_output_device) self.outputDevicesChanged.emit() - self.printerConnectedStatusChanged.emit() + + # Discovered printers are all the printers that were found on the network, which provide a more convenient way + # to add networked printers (Plugin finds a bunch of printers, user can select one from the list, plugin can then + # add that printer to Cura as the active one). + def addDiscoveredPrinter(self, key: str, name: str, create_callback: Callable[[str], None], machine_type: str) -> None: + if key not in self._discovered_printers: + self._discovered_printers[key] = DiscoveredPrinter(key, name, create_callback, machine_type) + self.discoveredPrintersChanged.emit() + else: + Logger.log("e", "Printer with the key %s was already in the discovered printer list", key) + + def removeDiscoveredPrinter(self, key: str) -> None: + if key in self._discovered_printers: + del self._discovered_printers[key] + self.discoveredPrintersChanged.emit() + + @pyqtProperty("QVariantList", notify = discoveredPrintersChanged) + def discoveredPrinters(self): + return list(self._discovered_printers.values()) + + @pyqtSlot(str) + def addMachineFromDiscoveredPrinter(self, key: str) -> None: + if key in self._discovered_printers: + self._discovered_printers[key].create_callback(key) @pyqtProperty(QObject, notify = currentConfigurationChanged) def currentConfiguration(self) -> ConfigurationModel: @@ -386,9 +415,17 @@ class MachineManager(QObject): return machine return None + @pyqtSlot(str) @pyqtSlot(str, str) - def addMachine(self, name: str, definition_id: str) -> None: - new_stack = CuraStackBuilder.createMachine(name, definition_id) + def addMachine(self, definition_id: str, name: Optional[str] = None) -> None: + if name is None: + definitions = CuraContainerRegistry.getInstance().findDefinitionContainers(id = definition_id) + if definitions: + name = definitions[0].getName() + else: + name = definition_id + + new_stack = CuraStackBuilder.createMachine(cast(str, name), definition_id) if new_stack: # Instead of setting the global container stack here, we set the active machine and so the signals are emitted self.setActiveMachine(new_stack.getId()) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 723bcf2b7c..4529b31c45 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -213,6 +213,11 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._checkManualDevice(address) + def _createMachineFromDiscoveredPrinter(self, key: str) -> None: + # TODO: This needs to be implemented. It's supposed to create a machine given a unique key as already discovered + # by this plugin. + pass + def _checkManualDevice(self, address): # Check if a UM3 family device exists at this address. # If a printer responds, it will replace the preliminary printer created above @@ -293,7 +298,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): except TypeError: # Disconnect already happened. pass - + self._application.getMachineManager().removeDiscoveredPrinter(device.getId()) self.discoveredDevicesChanged.emit() def _onAddDevice(self, name, address, properties): @@ -318,7 +323,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): device = ClusterUM3OutputDevice.ClusterUM3OutputDevice(name, address, properties) else: device = LegacyUM3OutputDevice.LegacyUM3OutputDevice(name, address, properties) - + self._application.getMachineManager().addDiscoveredPrinter(device.getId(), name, self._createMachineFromDiscoveredPrinter, properties[b"printer_type"].decode("utf-8")) self._discovered_devices[device.getId()] = device self.discoveredDevicesChanged.emit() diff --git a/resources/qml/Dialogs/AddMachineDialog.qml b/resources/qml/Dialogs/AddMachineDialog.qml index f00359869c..dafe12693f 100644 --- a/resources/qml/Dialogs/AddMachineDialog.qml +++ b/resources/qml/Dialogs/AddMachineDialog.qml @@ -303,7 +303,7 @@ UM.Dialog { base.visible = false var item = machineList.model.getItem(machineList.currentIndex); - Cura.MachineManager.addMachine(machineName.text, item.id) + Cura.MachineManager.addMachine(item.id, machineName.text) base.machineAdded(item.id) // Emit signal that the user added a machine. } diff --git a/tests/TestMachineManager.py b/tests/TestMachineManager.py index b989a6ee79..34a0bbc35c 100644 --- a/tests/TestMachineManager.py +++ b/tests/TestMachineManager.py @@ -11,13 +11,18 @@ from cura.Settings.MachineManager import MachineManager def container_registry() -> ContainerRegistry: return MagicMock() + @pytest.fixture() def extruder_manager(application, container_registry) -> ExtruderManager: with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value=application)): with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)): + manager = ExtruderManager.getInstance() + if manager is None: manager = ExtruderManager() + return manager + @pytest.fixture() def machine_manager(application, extruder_manager, container_registry) -> MachineManager: application.getExtruderManager = MagicMock(return_value = extruder_manager) @@ -41,3 +46,19 @@ def test_setActiveMachine(machine_manager): # Although we mocked the application away, we still want to know if it was notified about the attempted change. machine_manager._application.setGlobalContainerStack.assert_called_with(mocked_global_stack) + +def test_discoveredMachine(machine_manager): + mocked_callback = MagicMock() + machine_manager.addDiscoveredPrinter("test", "zomg", mocked_callback, "derp") + machine_manager.addMachineFromDiscoveredPrinter("test") + mocked_callback.assert_called_with("test") + + assert len(machine_manager.discoveredPrinters) == 1 + + # Test if removing it works + machine_manager.removeDiscoveredPrinter("test") + assert len(machine_manager.discoveredPrinters) == 0 + + # Just in case, nothing should happen. + machine_manager.addMachineFromDiscoveredPrinter("test") + assert mocked_callback.call_count == 1 \ No newline at end of file