PrinterOutputDevice.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. from UM.i18n import i18nCatalog
  2. from UM.OutputDevice.OutputDevice import OutputDevice
  3. from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
  4. from PyQt5.QtWidgets import QMessageBox
  5. import UM.Settings.ContainerRegistry
  6. from enum import IntEnum # For the connection state tracking.
  7. from UM.Logger import Logger
  8. from UM.Application import Application
  9. from UM.Signal import signalemitter
  10. i18n_catalog = i18nCatalog("cura")
  11. ## Printer output device adds extra interface options on top of output device.
  12. #
  13. # The assumption is made the printer is a FDM printer.
  14. #
  15. # Note that a number of settings are marked as "final". This is because decorators
  16. # are not inherited by children. To fix this we use the private counter part of those
  17. # functions to actually have the implementation.
  18. #
  19. # For all other uses it should be used in the same way as a "regular" OutputDevice.
  20. @signalemitter
  21. class PrinterOutputDevice(QObject, OutputDevice):
  22. def __init__(self, device_id, parent = None):
  23. super().__init__(device_id = device_id, parent = parent)
  24. self._container_registry = UM.Settings.ContainerRegistry.getInstance()
  25. self._target_bed_temperature = 0
  26. self._bed_temperature = 0
  27. self._num_extruders = 1
  28. self._hotend_temperatures = [0] * self._num_extruders
  29. self._target_hotend_temperatures = [0] * self._num_extruders
  30. self._material_ids = [""] * self._num_extruders
  31. self._hotend_ids = [""] * self._num_extruders
  32. self._progress = 0
  33. self._head_x = 0
  34. self._head_y = 0
  35. self._head_z = 0
  36. self._connection_state = ConnectionState.closed
  37. self._connection_text = ""
  38. self._time_elapsed = 0
  39. self._time_total = 0
  40. self._job_state = ""
  41. self._job_name = ""
  42. self._error_text = ""
  43. self._accepts_commands = True
  44. self._printer_state = ""
  45. self._printer_type = "unknown"
  46. def requestWrite(self, node, file_name = None, filter_by_machine = False):
  47. raise NotImplementedError("requestWrite needs to be implemented")
  48. ## Signals
  49. # Signal to be emitted when bed temp is changed
  50. bedTemperatureChanged = pyqtSignal()
  51. # Signal to be emitted when target bed temp is changed
  52. targetBedTemperatureChanged = pyqtSignal()
  53. # Signal when the progress is changed (usually when this output device is printing / sending lots of data)
  54. progressChanged = pyqtSignal()
  55. # Signal to be emitted when hotend temp is changed
  56. hotendTemperaturesChanged = pyqtSignal()
  57. # Signal to be emitted when target hotend temp is changed
  58. targetHotendTemperaturesChanged = pyqtSignal()
  59. # Signal to be emitted when head position is changed (x,y,z)
  60. headPositionChanged = pyqtSignal()
  61. # Signal to be emitted when either of the material ids is changed
  62. materialIdChanged = pyqtSignal(int, str, arguments = ["index", "id"])
  63. # Signal to be emitted when either of the hotend ids is changed
  64. hotendIdChanged = pyqtSignal(int, str, arguments = ["index", "id"])
  65. # Signal that is emitted every time connection state is changed.
  66. # it also sends it's own device_id (for convenience sake)
  67. connectionStateChanged = pyqtSignal(str)
  68. connectionTextChanged = pyqtSignal()
  69. timeElapsedChanged = pyqtSignal()
  70. timeTotalChanged = pyqtSignal()
  71. jobStateChanged = pyqtSignal()
  72. jobNameChanged = pyqtSignal()
  73. errorTextChanged = pyqtSignal()
  74. acceptsCommandsChanged = pyqtSignal()
  75. printerStateChanged = pyqtSignal()
  76. printerTypeChanged = pyqtSignal()
  77. @pyqtProperty(str, notify=printerTypeChanged)
  78. def printerType(self):
  79. return self._printer_type
  80. @pyqtProperty(str, notify=printerStateChanged)
  81. def printerState(self):
  82. return self._printer_state
  83. @pyqtProperty(str, notify = jobStateChanged)
  84. def jobState(self):
  85. return self._job_state
  86. def _updatePrinterType(self, printer_type):
  87. if self._printer_type != printer_type:
  88. self._printer_type = printer_type
  89. self.printerTypeChanged.emit()
  90. def _updatePrinterState(self, printer_state):
  91. if self._printer_state != printer_state:
  92. self._printer_state = printer_state
  93. self.printerStateChanged.emit()
  94. def _updateJobState(self, job_state):
  95. if self._job_state != job_state:
  96. self._job_state = job_state
  97. self.jobStateChanged.emit()
  98. @pyqtSlot(str)
  99. def setJobState(self, job_state):
  100. self._setJobState(job_state)
  101. def _setJobState(self, job_state):
  102. Logger.log("w", "_setJobState is not implemented by this output device")
  103. @pyqtProperty(str, notify = jobNameChanged)
  104. def jobName(self):
  105. return self._job_name
  106. def setJobName(self, name):
  107. if self._job_name != name:
  108. self._job_name = name
  109. self.jobNameChanged.emit()
  110. @pyqtProperty(str, notify = errorTextChanged)
  111. def errorText(self):
  112. return self._error_text
  113. ## Set the error-text that is shown in the print monitor in case of an error
  114. def setErrorText(self, error_text):
  115. if self._error_text != error_text:
  116. self._error_text = error_text
  117. self.errorTextChanged.emit()
  118. @pyqtProperty(bool, notify = acceptsCommandsChanged)
  119. def acceptsCommands(self):
  120. return self._accepts_commands
  121. ## Set a flag to signal the UI that the printer is not (yet) ready to receive commands
  122. def setAcceptsCommands(self, accepts_commands):
  123. if self._accepts_commands != accepts_commands:
  124. self._accepts_commands = accepts_commands
  125. self.acceptsCommandsChanged.emit()
  126. ## Get the bed temperature of the bed (if any)
  127. # This function is "final" (do not re-implement)
  128. # /sa _getBedTemperature implementation function
  129. @pyqtProperty(float, notify = bedTemperatureChanged)
  130. def bedTemperature(self):
  131. return self._bed_temperature
  132. ## Set the (target) bed temperature
  133. # This function is "final" (do not re-implement)
  134. # /param temperature new target temperature of the bed (in deg C)
  135. # /sa _setTargetBedTemperature implementation function
  136. @pyqtSlot(int)
  137. def setTargetBedTemperature(self, temperature):
  138. self._setTargetBedTemperature(temperature)
  139. if self._target_bed_temperature != temperature:
  140. self._target_bed_temperature = temperature
  141. self.targetBedTemperatureChanged.emit()
  142. ## Time the print has been printing.
  143. # Note that timeTotal - timeElapsed should give time remaining.
  144. @pyqtProperty(float, notify = timeElapsedChanged)
  145. def timeElapsed(self):
  146. return self._time_elapsed
  147. ## Total time of the print
  148. # Note that timeTotal - timeElapsed should give time remaining.
  149. @pyqtProperty(float, notify=timeTotalChanged)
  150. def timeTotal(self):
  151. return self._time_total
  152. @pyqtSlot(float)
  153. def setTimeTotal(self, new_total):
  154. if self._time_total != new_total:
  155. self._time_total = new_total
  156. self.timeTotalChanged.emit()
  157. @pyqtSlot(float)
  158. def setTimeElapsed(self, time_elapsed):
  159. if self._time_elapsed != time_elapsed:
  160. self._time_elapsed = time_elapsed
  161. self.timeElapsedChanged.emit()
  162. ## Home the head of the connected printer
  163. # This function is "final" (do not re-implement)
  164. # /sa _homeHead implementation function
  165. @pyqtSlot()
  166. def homeHead(self):
  167. self._homeHead()
  168. ## Home the head of the connected printer
  169. # This is an implementation function and should be overriden by children.
  170. def _homeHead(self):
  171. Logger.log("w", "_homeHead is not implemented by this output device")
  172. ## Home the bed of the connected printer
  173. # This function is "final" (do not re-implement)
  174. # /sa _homeBed implementation function
  175. @pyqtSlot()
  176. def homeBed(self):
  177. self._homeBed()
  178. ## Home the bed of the connected printer
  179. # This is an implementation function and should be overriden by children.
  180. # /sa homeBed
  181. def _homeBed(self):
  182. Logger.log("w", "_homeBed is not implemented by this output device")
  183. ## Protected setter for the bed temperature of the connected printer (if any).
  184. # /parameter temperature Temperature bed needs to go to (in deg celsius)
  185. # /sa setTargetBedTemperature
  186. def _setTargetBedTemperature(self, temperature):
  187. Logger.log("w", "_setTargetBedTemperature is not implemented by this output device")
  188. ## Protected setter for the current bed temperature.
  189. # This simply sets the bed temperature, but ensures that a signal is emitted.
  190. # /param temperature temperature of the bed.
  191. def _setBedTemperature(self, temperature):
  192. if self._bed_temperature != temperature:
  193. self._bed_temperature = temperature
  194. self.bedTemperatureChanged.emit()
  195. ## Get the target bed temperature if connected printer (if any)
  196. @pyqtProperty(int, notify = targetBedTemperatureChanged)
  197. def targetBedTemperature(self):
  198. return self._target_bed_temperature
  199. ## Set the (target) hotend temperature
  200. # This function is "final" (do not re-implement)
  201. # /param index the index of the hotend that needs to change temperature
  202. # /param temperature The temperature it needs to change to (in deg celsius).
  203. # /sa _setTargetHotendTemperature implementation function
  204. @pyqtSlot(int, int)
  205. def setTargetHotendTemperature(self, index, temperature):
  206. self._setTargetHotendTemperature(index, temperature)
  207. if self._target_hotend_temperatures[index] != temperature:
  208. self._target_hotend_temperatures[index] = temperature
  209. self.targetHotendTemperaturesChanged.emit()
  210. ## Implementation function of setTargetHotendTemperature.
  211. # /param index Index of the hotend to set the temperature of
  212. # /param temperature Temperature to set the hotend to (in deg C)
  213. # /sa setTargetHotendTemperature
  214. def _setTargetHotendTemperature(self, index, temperature):
  215. Logger.log("w", "_setTargetHotendTemperature is not implemented by this output device")
  216. @pyqtProperty("QVariantList", notify = targetHotendTemperaturesChanged)
  217. def targetHotendTemperatures(self):
  218. return self._target_hotend_temperatures
  219. @pyqtProperty("QVariantList", notify = hotendTemperaturesChanged)
  220. def hotendTemperatures(self):
  221. return self._hotend_temperatures
  222. ## Protected setter for the current hotend temperature.
  223. # This simply sets the hotend temperature, but ensures that a signal is emitted.
  224. # /param index Index of the hotend
  225. # /param temperature temperature of the hotend (in deg C)
  226. def _setHotendTemperature(self, index, temperature):
  227. if self._hotend_temperatures[index] != temperature:
  228. self._hotend_temperatures[index] = temperature
  229. self.hotendTemperaturesChanged.emit()
  230. @pyqtProperty("QVariantList", notify = materialIdChanged)
  231. def materialIds(self):
  232. return self._material_ids
  233. @pyqtProperty("QVariantList", notify = materialIdChanged)
  234. def materialNames(self):
  235. result = []
  236. for material_id in self._material_ids:
  237. if material_id is None:
  238. result.append(i18n_catalog.i18nc("@item:material", "No material loaded"))
  239. continue
  240. containers = self._container_registry.findInstanceContainers(type = "material", GUID = material_id)
  241. if containers:
  242. result.append(containers[0].getName())
  243. else:
  244. result.append(i18n_catalog.i18nc("@item:material", "Unknown material"))
  245. return result
  246. ## Protected setter for the current material id.
  247. # /param index Index of the extruder
  248. # /param material_id id of the material
  249. def _setMaterialId(self, index, material_id):
  250. if material_id and material_id != "" and material_id != self._material_ids[index]:
  251. Logger.log("d", "Setting material id of hotend %d to %s" % (index, material_id))
  252. self._material_ids[index] = material_id
  253. self.materialIdChanged.emit(index, material_id)
  254. @pyqtProperty("QVariantList", notify = hotendIdChanged)
  255. def hotendIds(self):
  256. return self._hotend_ids
  257. ## Protected setter for the current hotend id.
  258. # /param index Index of the extruder
  259. # /param hotend_id id of the hotend
  260. def _setHotendId(self, index, hotend_id):
  261. if hotend_id and hotend_id != "" and hotend_id != self._hotend_ids[index]:
  262. Logger.log("d", "Setting hotend id of hotend %d to %s" % (index, hotend_id))
  263. self._hotend_ids[index] = hotend_id
  264. self.hotendIdChanged.emit(index, hotend_id)
  265. ## Let the user decide if the hotends and/or material should be synced with the printer
  266. # NB: the UX needs to be implemented by the plugin
  267. def materialHotendChangedMessage(self, callback):
  268. Logger.log("w", "materialHotendChangedMessage needs to be implemented, returning 'Yes'")
  269. callback(QMessageBox.Yes)
  270. ## Attempt to establish connection
  271. def connect(self):
  272. raise NotImplementedError("connect needs to be implemented")
  273. ## Attempt to close the connection
  274. def close(self):
  275. raise NotImplementedError("close needs to be implemented")
  276. @pyqtProperty(bool, notify = connectionStateChanged)
  277. def connectionState(self):
  278. return self._connection_state
  279. ## Set the connection state of this output device.
  280. # /param connection_state ConnectionState enum.
  281. def setConnectionState(self, connection_state):
  282. if self._connection_state != connection_state:
  283. self._connection_state = connection_state
  284. self.connectionStateChanged.emit(self._id)
  285. @pyqtProperty(str, notify = connectionTextChanged)
  286. def connectionText(self):
  287. return self._connection_text
  288. ## Set a text that is shown on top of the print monitor tab
  289. def setConnectionText(self, connection_text):
  290. if self._connection_text != connection_text:
  291. self._connection_text = connection_text
  292. self.connectionTextChanged.emit()
  293. ## Ensure that close gets called when object is destroyed
  294. def __del__(self):
  295. self.close()
  296. ## Get the x position of the head.
  297. # This function is "final" (do not re-implement)
  298. @pyqtProperty(float, notify = headPositionChanged)
  299. def headX(self):
  300. return self._head_x
  301. ## Get the y position of the head.
  302. # This function is "final" (do not re-implement)
  303. @pyqtProperty(float, notify = headPositionChanged)
  304. def headY(self):
  305. return self._head_y
  306. ## Get the z position of the head.
  307. # In some machines it's actually the bed that moves. For convenience sake we simply see it all as head movements.
  308. # This function is "final" (do not re-implement)
  309. @pyqtProperty(float, notify = headPositionChanged)
  310. def headZ(self):
  311. return self._head_z
  312. ## Update the saved position of the head
  313. # This function should be called when a new position for the head is received.
  314. def _updateHeadPosition(self, x, y ,z):
  315. position_changed = False
  316. if self._head_x != x:
  317. self._head_x = x
  318. position_changed = True
  319. if self._head_y != y:
  320. self._head_y = y
  321. position_changed = True
  322. if self._head_z != z:
  323. self._head_z = z
  324. position_changed = True
  325. if position_changed:
  326. self.headPositionChanged.emit()
  327. ## Set the position of the head.
  328. # In some machines it's actually the bed that moves. For convenience sake we simply see it all as head movements.
  329. # This function is "final" (do not re-implement)
  330. # /param x new x location of the head.
  331. # /param y new y location of the head.
  332. # /param z new z location of the head.
  333. # /param speed Speed by which it needs to move (in mm/minute)
  334. # /sa _setHeadPosition implementation function
  335. @pyqtSlot("long", "long", "long")
  336. @pyqtSlot("long", "long", "long", "long")
  337. def setHeadPosition(self, x, y, z, speed = 3000):
  338. self._setHeadPosition(x, y , z, speed)
  339. ## Set the X position of the head.
  340. # This function is "final" (do not re-implement)
  341. # /param x x position head needs to move to.
  342. # /param speed Speed by which it needs to move (in mm/minute)
  343. # /sa _setHeadx implementation function
  344. @pyqtSlot("long")
  345. @pyqtSlot("long", "long")
  346. def setHeadX(self, x, speed = 3000):
  347. self._setHeadX(x, speed)
  348. ## Set the Y position of the head.
  349. # This function is "final" (do not re-implement)
  350. # /param y y position head needs to move to.
  351. # /param speed Speed by which it needs to move (in mm/minute)
  352. # /sa _setHeadY implementation function
  353. @pyqtSlot("long")
  354. @pyqtSlot("long", "long")
  355. def setHeadY(self, y, speed = 3000):
  356. self._setHeadY(y, speed)
  357. ## Set the Z position of the head.
  358. # In some machines it's actually the bed that moves. For convenience sake we simply see it all as head movements.
  359. # This function is "final" (do not re-implement)
  360. # /param z z position head needs to move to.
  361. # /param speed Speed by which it needs to move (in mm/minute)
  362. # /sa _setHeadZ implementation function
  363. @pyqtSlot("long")
  364. @pyqtSlot("long", "long")
  365. def setHeadZ(self, z, speed = 3000):
  366. self._setHeadY(z, speed)
  367. ## Move the head of the printer.
  368. # Note that this is a relative move. If you want to move the head to a specific position you can use
  369. # setHeadPosition
  370. # This function is "final" (do not re-implement)
  371. # /param x distance in x to move
  372. # /param y distance in y to move
  373. # /param z distance in z to move
  374. # /param speed Speed by which it needs to move (in mm/minute)
  375. # /sa _moveHead implementation function
  376. @pyqtSlot("long", "long", "long")
  377. @pyqtSlot("long", "long", "long", "long")
  378. def moveHead(self, x = 0, y = 0, z = 0, speed = 3000):
  379. self._moveHead(x, y, z, speed)
  380. ## Implementation function of moveHead.
  381. # /param x distance in x to move
  382. # /param y distance in y to move
  383. # /param z distance in z to move
  384. # /param speed Speed by which it needs to move (in mm/minute)
  385. # /sa moveHead
  386. def _moveHead(self, x, y, z, speed):
  387. Logger.log("w", "_moveHead is not implemented by this output device")
  388. ## Implementation function of setHeadPosition.
  389. # /param x new x location of the head.
  390. # /param y new y location of the head.
  391. # /param z new z location of the head.
  392. # /param speed Speed by which it needs to move (in mm/minute)
  393. # /sa setHeadPosition
  394. def _setHeadPosition(self, x, y, z, speed):
  395. Logger.log("w", "_setHeadPosition is not implemented by this output device")
  396. ## Implementation function of setHeadX.
  397. # /param x new x location of the head.
  398. # /param speed Speed by which it needs to move (in mm/minute)
  399. # /sa setHeadX
  400. def _setHeadX(self, x, speed):
  401. Logger.log("w", "_setHeadX is not implemented by this output device")
  402. ## Implementation function of setHeadY.
  403. # /param y new y location of the head.
  404. # /param speed Speed by which it needs to move (in mm/minute)
  405. # /sa _setHeadY
  406. def _setHeadY(self, y, speed):
  407. Logger.log("w", "_setHeadY is not implemented by this output device")
  408. ## Implementation function of setHeadZ.
  409. # /param z new z location of the head.
  410. # /param speed Speed by which it needs to move (in mm/minute)
  411. # /sa _setHeadZ
  412. def _setHeadZ(self, z, speed):
  413. Logger.log("w", "_setHeadZ is not implemented by this output device")
  414. ## Get the progress of any currently active process.
  415. # This function is "final" (do not re-implement)
  416. # /sa _getProgress
  417. # /returns float progress of the process. -1 indicates that there is no process.
  418. @pyqtProperty(float, notify = progressChanged)
  419. def progress(self):
  420. return self._progress
  421. ## Set the progress of any currently active process
  422. # /param progress Progress of the process.
  423. def setProgress(self, progress):
  424. if self._progress != progress:
  425. self._progress = progress
  426. self.progressChanged.emit()
  427. ## The current processing state of the backend.
  428. class ConnectionState(IntEnum):
  429. closed = 0
  430. connecting = 1
  431. connected = 2
  432. busy = 3
  433. error = 4