PrinterOutputDevice.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. # Copyright (c) 2017 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from UM.i18n import i18nCatalog
  4. from UM.OutputDevice.OutputDevice import OutputDevice
  5. from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal
  6. from PyQt5.QtWidgets import QMessageBox
  7. from UM.Logger import Logger
  8. from UM.Signal import signalemitter
  9. from UM.Application import Application
  10. from enum import IntEnum # For the connection state tracking.
  11. from typing import List, Optional
  12. MYPY = False
  13. if MYPY:
  14. from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
  15. i18n_catalog = i18nCatalog("cura")
  16. ## Printer output device adds extra interface options on top of output device.
  17. #
  18. # The assumption is made the printer is a FDM printer.
  19. #
  20. # Note that a number of settings are marked as "final". This is because decorators
  21. # are not inherited by children. To fix this we use the private counter part of those
  22. # functions to actually have the implementation.
  23. #
  24. # For all other uses it should be used in the same way as a "regular" OutputDevice.
  25. @signalemitter
  26. class PrinterOutputDevice(QObject, OutputDevice):
  27. printersChanged = pyqtSignal()
  28. connectionStateChanged = pyqtSignal(str)
  29. acceptsCommandsChanged = pyqtSignal()
  30. # Signal to indicate that the material of the active printer on the remote changed.
  31. materialIdChanged = pyqtSignal()
  32. # # Signal to indicate that the hotend of the active printer on the remote changed.
  33. hotendIdChanged = pyqtSignal()
  34. def __init__(self, device_id, parent = None):
  35. super().__init__(device_id = device_id, parent = parent)
  36. self._printers = [] # type: List[PrinterOutputModel]
  37. self._monitor_view_qml_path = ""
  38. self._monitor_component = None
  39. self._monitor_item = None
  40. self._control_view_qml_path = ""
  41. self._control_component = None
  42. self._control_item = None
  43. self._qml_context = None
  44. self._accepts_commands = False
  45. self._update_timer = QTimer()
  46. self._update_timer.setInterval(2000) # TODO; Add preference for update interval
  47. self._update_timer.setSingleShot(False)
  48. self._update_timer.timeout.connect(self._update)
  49. self._connection_state = ConnectionState.closed
  50. self._address = ""
  51. @pyqtProperty(str, constant = True)
  52. def address(self):
  53. return self._address
  54. def materialHotendChangedMessage(self, callback):
  55. Logger.log("w", "materialHotendChangedMessage needs to be implemented, returning 'Yes'")
  56. callback(QMessageBox.Yes)
  57. def isConnected(self):
  58. return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error
  59. def setConnectionState(self, connection_state):
  60. if self._connection_state != connection_state:
  61. self._connection_state = connection_state
  62. self.connectionStateChanged.emit(self._id)
  63. @pyqtProperty(str, notify = connectionStateChanged)
  64. def connectionState(self):
  65. return self._connection_state
  66. def _update(self):
  67. pass
  68. def _getPrinterByKey(self, key) -> Optional["PrinterOutputModel"]:
  69. for printer in self._printers:
  70. if printer.key == key:
  71. return printer
  72. return None
  73. def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
  74. raise NotImplementedError("requestWrite needs to be implemented")
  75. @pyqtProperty(QObject, notify = printersChanged)
  76. def activePrinter(self) -> Optional["PrinterOutputModel"]:
  77. if len(self._printers):
  78. return self._printers[0]
  79. return None
  80. @pyqtProperty("QVariantList", notify = printersChanged)
  81. def printers(self):
  82. return self._printers
  83. @pyqtProperty(QObject, constant=True)
  84. def monitorItem(self):
  85. # Note that we specifically only check if the monitor component is created.
  86. # It could be that it failed to actually create the qml item! If we check if the item was created, it will try to
  87. # create the item (and fail) every time.
  88. if not self._monitor_component:
  89. self._createMonitorViewFromQML()
  90. return self._monitor_item
  91. @pyqtProperty(QObject, constant=True)
  92. def controlItem(self):
  93. if not self._control_component:
  94. self._createControlViewFromQML()
  95. return self._control_item
  96. def _createControlViewFromQML(self):
  97. if not self._control_view_qml_path:
  98. return
  99. if self._control_item is None:
  100. self._control_item = Application.getInstance().createQmlComponent(self._control_view_qml_path, {"OutputDevice": self})
  101. def _createMonitorViewFromQML(self):
  102. if not self._monitor_view_qml_path:
  103. return
  104. if self._monitor_item is None:
  105. self._monitor_item = Application.getInstance().createQmlComponent(self._monitor_view_qml_path, {"OutputDevice": self})
  106. ## Attempt to establish connection
  107. def connect(self):
  108. self.setConnectionState(ConnectionState.connecting)
  109. self._update_timer.start()
  110. ## Attempt to close the connection
  111. def close(self):
  112. self._update_timer.stop()
  113. self.setConnectionState(ConnectionState.closed)
  114. ## Ensure that close gets called when object is destroyed
  115. def __del__(self):
  116. self.close()
  117. @pyqtProperty(bool, notify=acceptsCommandsChanged)
  118. def acceptsCommands(self):
  119. return self._accepts_commands
  120. ## Set a flag to signal the UI that the printer is not (yet) ready to receive commands
  121. def _setAcceptsCommands(self, accepts_commands):
  122. if self._accepts_commands != accepts_commands:
  123. self._accepts_commands = accepts_commands
  124. self.acceptsCommandsChanged.emit()
  125. ## The current processing state of the backend.
  126. class ConnectionState(IntEnum):
  127. closed = 0
  128. connecting = 1
  129. connected = 2
  130. busy = 3
  131. error = 4