DiscoveredPrintersModel.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import Callable, Dict, List, Optional, TYPE_CHECKING
  4. from PyQt5.QtCore import pyqtSlot, pyqtProperty, pyqtSignal, QObject, QTimer
  5. from UM.i18n import i18nCatalog
  6. from UM.Logger import Logger
  7. from UM.Util import parseBool
  8. from UM.OutputDevice.OutputDeviceManager import ManualDeviceAdditionAttempt
  9. if TYPE_CHECKING:
  10. from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
  11. from cura.CuraApplication import CuraApplication
  12. from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice
  13. catalog = i18nCatalog("cura")
  14. class DiscoveredPrinter(QObject):
  15. def __init__(self, ip_address: str, key: str, name: str, create_callback: Callable[[str], None], machine_type: str,
  16. device: "NetworkedPrinterOutputDevice", parent: Optional["QObject"] = None) -> None:
  17. super().__init__(parent)
  18. self._ip_address = ip_address
  19. self._key = key
  20. self._name = name
  21. self.create_callback = create_callback
  22. self._machine_type = machine_type
  23. self._device = device
  24. nameChanged = pyqtSignal()
  25. def getKey(self) -> str:
  26. return self._key
  27. @pyqtProperty(str, notify = nameChanged)
  28. def name(self) -> str:
  29. return self._name
  30. def setName(self, name: str) -> None:
  31. if self._name != name:
  32. self._name = name
  33. self.nameChanged.emit()
  34. @pyqtProperty(str, constant = True)
  35. def address(self) -> str:
  36. return self._ip_address
  37. machineTypeChanged = pyqtSignal()
  38. @pyqtProperty(str, notify = machineTypeChanged)
  39. def machineType(self) -> str:
  40. return self._machine_type
  41. def setMachineType(self, machine_type: str) -> None:
  42. if self._machine_type != machine_type:
  43. self._machine_type = machine_type
  44. self.machineTypeChanged.emit()
  45. # Checks if the given machine type name in the available machine list.
  46. # The machine type is a code name such as "ultimaker_3", while the machine type name is the human-readable name of
  47. # the machine type, which is "Ultimaker 3" for "ultimaker_3".
  48. def _hasHumanReadableMachineTypeName(self, machine_type_name: str) -> bool:
  49. from cura.CuraApplication import CuraApplication
  50. results = CuraApplication.getInstance().getContainerRegistry().findDefinitionContainersMetadata(name = machine_type_name)
  51. return len(results) > 0
  52. # Human readable machine type string
  53. @pyqtProperty(str, notify = machineTypeChanged)
  54. def readableMachineType(self) -> str:
  55. from cura.CuraApplication import CuraApplication
  56. machine_manager = CuraApplication.getInstance().getMachineManager()
  57. # In NetworkOutputDevice, when it updates a printer information, it updates the machine type using the field
  58. # "machine_variant", and for some reason, it's not the machine type ID/codename/... but a human-readable string
  59. # like "Ultimaker 3". The code below handles this case.
  60. if self._hasHumanReadableMachineTypeName(self._machine_type):
  61. readable_type = self._machine_type
  62. else:
  63. readable_type = self._getMachineTypeNameFromId(self._machine_type)
  64. if not readable_type:
  65. readable_type = catalog.i18nc("@label", "Unknown")
  66. return readable_type
  67. @pyqtProperty(bool, notify = machineTypeChanged)
  68. def isUnknownMachineType(self) -> bool:
  69. if self._hasHumanReadableMachineTypeName(self._machine_type):
  70. readable_type = self._machine_type
  71. else:
  72. readable_type = self._getMachineTypeNameFromId(self._machine_type)
  73. return not readable_type
  74. def _getMachineTypeNameFromId(self, machine_type_id: str) -> str:
  75. machine_type_name = ""
  76. from cura.CuraApplication import CuraApplication
  77. results = CuraApplication.getInstance().getContainerRegistry().findDefinitionContainersMetadata(id = machine_type_id)
  78. if results:
  79. machine_type_name = results[0]["name"]
  80. return machine_type_name
  81. @pyqtProperty(QObject, constant = True)
  82. def device(self) -> "NetworkedPrinterOutputDevice":
  83. return self._device
  84. @pyqtProperty(bool, constant = True)
  85. def isHostOfGroup(self) -> bool:
  86. return getattr(self._device, "clusterSize", 1) > 0
  87. @pyqtProperty(str, constant = True)
  88. def sectionName(self) -> str:
  89. if self.isUnknownMachineType or not self.isHostOfGroup:
  90. return catalog.i18nc("@label", "The printer(s) below cannot be connected because they are part of a group")
  91. else:
  92. return catalog.i18nc("@label", "Available networked printers")
  93. #
  94. # Discovered printers are all the printers that were found on the network, which provide a more convenient way
  95. # to add networked printers (Plugin finds a bunch of printers, user can select one from the list, plugin can then
  96. # add that printer to Cura as the active one).
  97. #
  98. class DiscoveredPrintersModel(QObject):
  99. def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
  100. super().__init__(parent)
  101. self._application = application
  102. self._discovered_printer_by_ip_dict = dict() # type: Dict[str, DiscoveredPrinter]
  103. self._plugin_for_manual_device = None # type: Optional[OutputDevicePlugin]
  104. self._manual_device_address = ""
  105. self._manual_device_request_timeout_in_seconds = 5 # timeout for adding a manual device in seconds
  106. self._manual_device_request_timer = QTimer()
  107. self._manual_device_request_timer.setInterval(self._manual_device_request_timeout_in_seconds * 1000)
  108. self._manual_device_request_timer.setSingleShot(True)
  109. self._manual_device_request_timer.timeout.connect(self._onManualRequestTimeout)
  110. discoveredPrintersChanged = pyqtSignal()
  111. @pyqtSlot(str)
  112. def checkManualDevice(self, address: str) -> None:
  113. if self.hasManualDeviceRequestInProgress:
  114. Logger.log("i", "A manual device request for address [%s] is still in progress, do nothing",
  115. self._manual_device_address)
  116. return
  117. priority_order = [
  118. ManualDeviceAdditionAttempt.PRIORITY,
  119. ManualDeviceAdditionAttempt.POSSIBLE,
  120. ] # type: List[ManualDeviceAdditionAttempt]
  121. all_plugins_dict = self._application.getOutputDeviceManager().getAllOutputDevicePlugins()
  122. can_add_manual_plugins = [item for item in filter(
  123. lambda plugin_item: plugin_item.canAddManualDevice(address) in priority_order,
  124. all_plugins_dict.values())]
  125. if not can_add_manual_plugins:
  126. Logger.log("d", "Could not find a plugin to accept adding %s manually via address.", address)
  127. return
  128. plugin = max(can_add_manual_plugins, key = lambda p: priority_order.index(p.canAddManualDevice(address)))
  129. self._plugin_for_manual_device = plugin
  130. self._plugin_for_manual_device.addManualDevice(address, callback = self._onManualDeviceRequestFinished)
  131. self._manual_device_address = address
  132. self._manual_device_request_timer.start()
  133. self.hasManualDeviceRequestInProgressChanged.emit()
  134. @pyqtSlot()
  135. def cancelCurrentManualDeviceRequest(self) -> None:
  136. self._manual_device_request_timer.stop()
  137. if self._manual_device_address:
  138. if self._plugin_for_manual_device is not None:
  139. self._plugin_for_manual_device.removeManualDevice(self._manual_device_address, address = self._manual_device_address)
  140. self._manual_device_address = ""
  141. self._plugin_for_manual_device = None
  142. self.hasManualDeviceRequestInProgressChanged.emit()
  143. self.manualDeviceRequestFinished.emit(False)
  144. def _onManualRequestTimeout(self) -> None:
  145. Logger.log("w", "Manual printer [%s] request timed out. Cancel the current request.", self._manual_device_address)
  146. self.cancelCurrentManualDeviceRequest()
  147. hasManualDeviceRequestInProgressChanged = pyqtSignal()
  148. @pyqtProperty(bool, notify = hasManualDeviceRequestInProgressChanged)
  149. def hasManualDeviceRequestInProgress(self) -> bool:
  150. return self._manual_device_address != ""
  151. manualDeviceRequestFinished = pyqtSignal(bool, arguments = ["success"])
  152. def _onManualDeviceRequestFinished(self, success: bool, address: str) -> None:
  153. self._manual_device_request_timer.stop()
  154. if address == self._manual_device_address:
  155. self._manual_device_address = ""
  156. self.hasManualDeviceRequestInProgressChanged.emit()
  157. self.manualDeviceRequestFinished.emit(success)
  158. @pyqtProperty("QVariantMap", notify = discoveredPrintersChanged)
  159. def discoveredPrintersByAddress(self) -> Dict[str, DiscoveredPrinter]:
  160. return self._discovered_printer_by_ip_dict
  161. @pyqtProperty("QVariantList", notify = discoveredPrintersChanged)
  162. def discoveredPrinters(self) -> List["DiscoveredPrinter"]:
  163. item_list = list(
  164. x for x in self._discovered_printer_by_ip_dict.values() if not parseBool(x.device.getProperty("temporary")))
  165. # Split the printers into 2 lists and sort them ascending based on names.
  166. available_list = []
  167. not_available_list = []
  168. for item in item_list:
  169. if item.isUnknownMachineType or getattr(item.device, "clusterSize", 1) < 1:
  170. not_available_list.append(item)
  171. else:
  172. available_list.append(item)
  173. available_list.sort(key = lambda x: x.device.name)
  174. not_available_list.sort(key = lambda x: x.device.name)
  175. return available_list + not_available_list
  176. def addDiscoveredPrinter(self, ip_address: str, key: str, name: str, create_callback: Callable[[str], None],
  177. machine_type: str, device: "NetworkedPrinterOutputDevice") -> None:
  178. if ip_address in self._discovered_printer_by_ip_dict:
  179. Logger.log("e", "Printer with ip [%s] has already been added", ip_address)
  180. return
  181. discovered_printer = DiscoveredPrinter(ip_address, key, name, create_callback, machine_type, device, parent = self)
  182. self._discovered_printer_by_ip_dict[ip_address] = discovered_printer
  183. self.discoveredPrintersChanged.emit()
  184. def updateDiscoveredPrinter(self, ip_address: str,
  185. name: Optional[str] = None,
  186. machine_type: Optional[str] = None) -> None:
  187. if ip_address not in self._discovered_printer_by_ip_dict:
  188. Logger.log("w", "Printer with ip [%s] is not known", ip_address)
  189. return
  190. item = self._discovered_printer_by_ip_dict[ip_address]
  191. if name is not None:
  192. item.setName(name)
  193. if machine_type is not None:
  194. item.setMachineType(machine_type)
  195. def removeDiscoveredPrinter(self, ip_address: str) -> None:
  196. if ip_address not in self._discovered_printer_by_ip_dict:
  197. Logger.log("w", "Key [%s] does not exist in the discovered printers list.", ip_address)
  198. return
  199. del self._discovered_printer_by_ip_dict[ip_address]
  200. self.discoveredPrintersChanged.emit()
  201. # A convenience function for QML to create a machine (GlobalStack) out of the given discovered printer.
  202. # This function invokes the given discovered printer's "create_callback" to do this.
  203. @pyqtSlot("QVariant")
  204. def createMachineFromDiscoveredPrinter(self, discovered_printer: "DiscoveredPrinter") -> None:
  205. discovered_printer.create_callback(discovered_printer.getKey())