1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
|
# Copyright (c) 2019 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.
from enum import Enum
from typing import Dict, Optional, TYPE_CHECKING, ValuesView, KeysView
from UM.Signal import Signal, signalemitter
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry
if TYPE_CHECKING:
from UM.OutputDevice.OutputDevice import OutputDevice
from UM.OutputDevice.ProjectOutputDevice import ProjectOutputDevice
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
class ManualDeviceAdditionAttempt(Enum):
"""
Used internally to determine plugins capable of 'manual' addition of devices, see also [add|remove]ManualDevice below.
"""
NO = 0, # The plugin can't add a device 'manually' (or at least not with the given parameters).
POSSIBLE = 1, # The plugin will try to add the (specified) device 'manually', unless another plugin has priority.
PRIORITY = 2 # The plugin has determined by the specified parameters that it's responsible for adding this device
# and thus has priority. If this fails, the plugins that replied 'POSSIBLE' will be tried.
# NOTE: This last value should be used with great care!
@signalemitter
class OutputDeviceManager:
"""Manages all available output devices and the plugin objects used to create them.
This class is intended as the main entry point for anything relating to file saving.
For the most basic usage, call getActiveDevice() to get an output device, then
call OutputDevice::requestWrite() on the returned object.
Active Device
-------------
The active device by default is determined based on the priority of individual
OutputDevice instances when there is more than one OutputDevice available. When
adding a device, the active device will be updated with the highest priority device.
Should there be two devices with the same priority the active device will be the first
device encountered with that priority.
Calling setActiveDevice() will override this behaviour and instead force the active
device to the specified device. This active device will not change when a new device
is added or removed, but it will revert back to the default behaviour if the active
device is removed. Call resetActiveDevice() to reset the active device to the default
behaviour based on priority.
OutputDevicePlugin and OutputDevice creation/removal
----------------------------------------------------
Each instance of an OutputDevicePlugin is meant as an OutputDevice creation object.
Subclasses of OutputDevicePlugin are meant to perform device lookup and listening
for events like device hot-plugging. When a new device has been detected, the plugin
class should create an instance of an OutputDevice subclass and add it to this
manager class using addOutputDevice(). Similarly, if a device has been removed the
OutputDevicePlugin is expected to call removeOutputDevice() to remove the proper
device.
"""
def __init__(self) -> None:
super().__init__()
self._output_devices = {} # type: Dict[str, OutputDevice]
self._project_output_devices = {} # type: Dict[str, ProjectOutputDevice]
self._plugins = {} # type: Dict[str, OutputDevicePlugin]
self._active_device = None # type: Optional[OutputDevice]
self._active_device_override = False
self._write_in_progress = False
PluginRegistry.addType("output_device", self.addOutputDevicePlugin)
self._is_running = False
writeStarted = Signal()
"""Emitted whenever a registered device emits writeStarted.
:sa OutputDevice::writeStarted
"""
writeProgress = Signal()
"""Emitted whenever a registered device emits writeProgress.
:sa OutputDevice::writeProgress
"""
writeFinished = Signal()
"""Emitted whenever a registered device emits writeFinished.
:sa OutputDevice::writeFinished
"""
writeError = Signal()
"""Emitted whenever a registered device emits writeError.
:sa OutputDevice::writeError
"""
writeSuccess = Signal()
"""Emitted whenever a registered device emits writeSuccess.
:sa OutputDevice::writeSuccess
"""
manualDeviceAdded = Signal()
"""Emitted whenever a device has been added manually."""
manualDeviceRemoved = Signal()
"""Emitted whenever a device has been removed manually."""
outputDevicesChanged = Signal()
"""Emitted whenever an output device is added or removed."""
projectOutputDevicesChanged = Signal()
"""Emitted whenever an output device that can handle project files is added or removed."""
activeDeviceChanged = Signal()
"""Emitted whenever the active device changes."""
def getOutputDevices(self) -> ValuesView["OutputDevice"]:
"""Get a list of all registered output devices.
:return: :type{list} A list of all registered output devices.
"""
return self._output_devices.values()
def getProjectOutputDevices(self) -> ValuesView["ProjectOutputDevice"]:
"""Get a list of all registered output devices.
:return: :type{list} A list of all registered output devices.
"""
return self._project_output_devices.values()
def getOutputDeviceIds(self) -> KeysView[str]:
"""Get a list of all IDs of registered output devices.
:return: :type{list} A list of all registered output device ids.
"""
return self._output_devices.keys()
def getOutputDevice(self, device_id: str) -> Optional["OutputDevice"]:
"""Get an output device by ID.
:param device_id: The ID of the device to retrieve.
:return: :type{OutputDevice} The output device corresponding to the ID or None if not found.
"""
return self._output_devices.get(device_id, self._project_output_devices.get(device_id, None))
def start(self) -> None:
for plugin_id, plugin in self._plugins.items():
try:
plugin.start()
except Exception:
Logger.logException("e", "Exception starting OutputDevicePlugin %s", plugin.getPluginId())
def stop(self) -> None:
for plugin_id, plugin in self._plugins.items():
try:
plugin.stop()
except Exception:
Logger.logException("e", "Exception starting OutputDevicePlugin %s", plugin.getPluginId())
def startDiscovery(self) -> None:
for plugin_id, plugin in self._plugins.items():
try:
plugin.startDiscovery()
except Exception:
Logger.logException("e", "Exception startDiscovery OutputDevicePlugin %s", plugin.getPluginId())
def refreshConnections(self) -> None:
for plugin_id, plugin in self._plugins.items():
try:
plugin.refreshConnections()
except Exception:
Logger.logException("e", "Exception refreshConnections OutputDevicePlugin %s", plugin.getPluginId())
def connectWriteSignalsToDevice(self, device: "OutputDevice") -> None:
"""
Connects all the necessary write signals of the device to the write signals of the manager
:param device: The output device
"""
device.writeStarted.connect(self.writeStarted)
device.writeProgress.connect(self.writeProgress)
device.writeFinished.connect(self.writeFinished)
device.writeError.connect(self.writeError)
device.writeSuccess.connect(self.writeSuccess)
def disconnectWriteSignalsFromDevice(self, device: "OutputDevice") -> None:
"""
Disconnects all the necessary write signals of the device from the write signals of the manager
:param device: The output device
"""
device.writeStarted.disconnect(self.writeStarted)
device.writeProgress.disconnect(self.writeProgress)
device.writeFinished.disconnect(self.writeFinished)
device.writeError.disconnect(self.writeError)
device.writeSuccess.disconnect(self.writeSuccess)
def addOutputDevice(self, device: "OutputDevice") -> None:
"""Add and register an output device.
:param :type{OutputDevice} The output device to add.
:note Does nothing if a device with the same ID as the passed device was already added.
"""
if device.getId() in self._output_devices:
Logger.log("i", "Output Device %s already added", device.getId())
return
self._output_devices[device.getId()] = device
self.connectWriteSignalsToDevice(device)
self.outputDevicesChanged.emit()
if not self._active_device or not self._active_device_override:
self._active_device = self._findHighestPriorityDevice()
self.activeDeviceChanged.emit()
def removeOutputDevice(self, device_id: str) -> bool:
"""Remove a registered device by ID
:param device_id: The ID of the device to remove.
:note This does nothing if the device_id does not correspond to a registered device.
:return: Whether the device was successfully removed or not.
"""
if device_id not in self._output_devices:
Logger.log("w", "Could not find output device with id %s to remove", device_id)
return False
device = self._output_devices[device_id]
del self._output_devices[device_id]
self.disconnectWriteSignalsFromDevice(device)
self.outputDevicesChanged.emit()
if self._active_device is not None and self._active_device.getId() == device_id:
self._write_in_progress = False
self.resetActiveDevice()
return True
def addProjectOutputDevice(self, device: "ProjectOutputDevice") -> None:
"""Add and register a project output device.
:param device: The output device to add.
:note Does nothing if a device with the same ID as the passed device was already added.
"""
if device.getId() in self._project_output_devices:
Logger.log("i", "Project Output Device %s already added", device.getId())
return
self._project_output_devices[device.getId()] = device
device.enabledChanged.connect(self.projectOutputDevicesChanged.emit)
if device.enabled:
if device.add_to_output_devices:
self.addOutputDevice(device)
else:
# Call the connectWriteSignalsToDevice(..) only if addOutputDevice(..) function hasn't been called already
# to avoid connecting the signals twice
self.connectWriteSignalsToDevice(device)
self.projectOutputDevicesChanged.emit()
def removeProjectOutputDevice(self, device_id: str) -> bool:
"""Remove a registered project output device by ID
:param device_id: The ID of the device to remove.
:note This does nothing if the device_id does not correspond to a registered device.
:return: Whether the device was successfully removed or not.
"""
if device_id not in self._project_output_devices and device_id not in self._output_devices:
Logger.log("w", "Could not find output device with id %s to remove", device_id)
return False
device = self._project_output_devices[device_id]
del self._project_output_devices[device_id]
self.disconnectWriteSignalsFromDevice(device)
self.projectOutputDevicesChanged.emit()
if device_id in self._output_devices:
del self._output_devices[device_id]
self.outputDevicesChanged.emit()
if self._active_device is not None and self._active_device.getId() == device_id:
self._write_in_progress = False
self.resetActiveDevice()
return True
def getActiveDevice(self):
"""Get the active device."""
return self._active_device
def setActiveDevice(self, device_id: str) -> None:
"""Set the active device.
:param device_id: The ID of the device to set as active device.
:note This does nothing if the device_id does not correspond to a registered device.
:note This will override the default active device selection behaviour.
"""
if device_id not in self._output_devices:
return
if not self._active_device or self._active_device.getId() != device_id:
self._active_device = self.getOutputDevice(device_id)
self._write_in_progress = False
self._active_device_override = True
self.activeDeviceChanged.emit()
def resetActiveDevice(self) -> None:
"""Reset the active device to the default device."""
self._active_device = self._findHighestPriorityDevice()
self._active_device_override = False
self._write_in_progress = False
self.activeDeviceChanged.emit()
def addOutputDevicePlugin(self, plugin: "OutputDevicePlugin") -> None:
"""Add an OutputDevicePlugin instance.
:param :type{OutputDevicePlugin} The plugin to add.
:note This does nothing if the plugin was already added.
"""
if plugin.getPluginId() in self._plugins:
Logger.log("i", "Output Device Plugin %s was already added.", plugin.getPluginId())
return
self._plugins[plugin.getPluginId()] = plugin
if self._is_running:
try:
plugin.start()
except Exception:
Logger.logException("e", "Exception starting OutputDevicePlugin %s", plugin.getPluginId())
def removeOutputDevicePlugin(self, plugin_id: str) -> None:
"""Remove an OutputDevicePlugin by ID.
:param plugin_id: The ID of the plugin to remove.
:note This does nothing if the specified plugin_id was not found.
"""
Logger.log("d", "Remove output device plugin %s", plugin_id)
if plugin_id not in self._plugins:
return
try:
self._plugins[plugin_id].stop()
except Exception as e:
Logger.log("e", "Exception stopping plugin %s: %s", plugin_id, repr(e))
del self._plugins[plugin_id]
def getAllOutputDevicePlugins(self) -> Dict[str, "OutputDevicePlugin"]:
return self._plugins
def getOutputDevicePlugin(self, plugin_id: str) -> Optional["OutputDevicePlugin"]:
"""Get an OutputDevicePlugin by plugin ID
:param plugin_id: The ID of the plugin to retrieve
:return: The plugin corresponding to the specified ID or None if it was not found.
"""
return self._plugins.get(plugin_id, None)
def _findHighestPriorityDevice(self) -> Optional["OutputDevice"]:
"""private:
"""
device = None
for key, value in self._output_devices.items():
if not device or value.getPriority() > device.getPriority():
device = value
return device
|