PrinterOutputModel.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot, QUrl
  4. from typing import List, Dict, Optional, TYPE_CHECKING
  5. from UM.Math.Vector import Vector
  6. from cura.PrinterOutput.Peripheral import Peripheral
  7. from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel
  8. from cura.PrinterOutput.Models.ExtruderOutputModel import ExtruderOutputModel
  9. from UM.Logger import Logger
  10. if TYPE_CHECKING:
  11. from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel
  12. from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
  13. class PrinterOutputModel(QObject):
  14. bedTemperatureChanged = pyqtSignal()
  15. targetBedTemperatureChanged = pyqtSignal()
  16. isPreheatingChanged = pyqtSignal()
  17. stateChanged = pyqtSignal()
  18. activePrintJobChanged = pyqtSignal()
  19. nameChanged = pyqtSignal()
  20. headPositionChanged = pyqtSignal()
  21. keyChanged = pyqtSignal()
  22. typeChanged = pyqtSignal()
  23. buildplateChanged = pyqtSignal()
  24. cameraUrlChanged = pyqtSignal()
  25. configurationChanged = pyqtSignal()
  26. canUpdateFirmwareChanged = pyqtSignal()
  27. def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = "") -> None:
  28. super().__init__(parent)
  29. self._bed_temperature = -1 # type: float # Use -1 for no heated bed.
  30. self._target_bed_temperature = 0 # type: float
  31. self._name = ""
  32. self._key = "" # Unique identifier
  33. self._controller = output_controller
  34. self._controller.canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged)
  35. self._extruders = [ExtruderOutputModel(printer = self, position = i) for i in range(number_of_extruders)]
  36. self._active_printer_configuration = PrinterConfigurationModel() # Indicates the current configuration setup in this printer
  37. self._head_position = Vector(0, 0, 0)
  38. self._active_print_job = None # type: Optional[PrintJobOutputModel]
  39. self._firmware_version = firmware_version
  40. self._printer_state = "unknown"
  41. self._is_preheating = False
  42. self._printer_type = ""
  43. self._buildplate = ""
  44. self._peripherals = [] # type: List[Peripheral]
  45. self._active_printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
  46. self._extruders]
  47. self._active_printer_configuration.configurationChanged.connect(self.configurationChanged)
  48. self._available_printer_configurations = [] # type: List[PrinterConfigurationModel]
  49. self._camera_url = QUrl() # type: QUrl
  50. @pyqtProperty(str, constant = True)
  51. def firmwareVersion(self) -> str:
  52. return self._firmware_version
  53. def setCameraUrl(self, camera_url: "QUrl") -> None:
  54. if self._camera_url != camera_url:
  55. self._camera_url = camera_url
  56. self.cameraUrlChanged.emit()
  57. @pyqtProperty(QUrl, fset = setCameraUrl, notify = cameraUrlChanged)
  58. def cameraUrl(self) -> "QUrl":
  59. return self._camera_url
  60. def updateIsPreheating(self, pre_heating: bool) -> None:
  61. if self._is_preheating != pre_heating:
  62. self._is_preheating = pre_heating
  63. self.isPreheatingChanged.emit()
  64. @pyqtProperty(bool, notify=isPreheatingChanged)
  65. def isPreheating(self) -> bool:
  66. return self._is_preheating
  67. @pyqtProperty(str, notify = typeChanged)
  68. def type(self) -> str:
  69. return self._printer_type
  70. def updateType(self, printer_type: str) -> None:
  71. if self._printer_type != printer_type:
  72. self._printer_type = printer_type
  73. self._active_printer_configuration.printerType = self._printer_type
  74. self.typeChanged.emit()
  75. self.configurationChanged.emit()
  76. @pyqtProperty(str, notify = buildplateChanged)
  77. def buildplate(self) -> str:
  78. return self._buildplate
  79. def updateBuildplate(self, buildplate: str) -> None:
  80. if self._buildplate != buildplate:
  81. self._buildplate = buildplate
  82. self._active_printer_configuration.buildplateConfiguration = self._buildplate
  83. self.buildplateChanged.emit()
  84. self.configurationChanged.emit()
  85. @pyqtProperty(str, notify=keyChanged)
  86. def key(self) -> str:
  87. return self._key
  88. def updateKey(self, key: str) -> None:
  89. if self._key != key:
  90. self._key = key
  91. self.keyChanged.emit()
  92. @pyqtSlot()
  93. def homeHead(self) -> None:
  94. self._controller.homeHead(self)
  95. @pyqtSlot()
  96. def homeBed(self) -> None:
  97. self._controller.homeBed(self)
  98. @pyqtSlot(str)
  99. def sendRawCommand(self, command: str) -> None:
  100. self._controller.sendRawCommand(self, command)
  101. @pyqtProperty("QVariantList", constant = True)
  102. def extruders(self) -> List["ExtruderOutputModel"]:
  103. return self._extruders
  104. @pyqtProperty(QVariant, notify = headPositionChanged)
  105. def headPosition(self) -> Dict[str, float]:
  106. return {"x": self._head_position.x, "y": self._head_position.y, "z": self.head_position.z}
  107. def updateHeadPosition(self, x: float, y: float, z: float) -> None:
  108. if self._head_position.x != x or self._head_position.y != y or self._head_position.z != z:
  109. self._head_position = Vector(x, y, z)
  110. self.headPositionChanged.emit()
  111. @pyqtProperty(float, float, float)
  112. @pyqtProperty(float, float, float, float)
  113. def setHeadPosition(self, x: float, y: float, z: float, speed: float = 3000) -> None:
  114. self.updateHeadPosition(x, y, z)
  115. self._controller.setHeadPosition(self, x, y, z, speed)
  116. @pyqtProperty(float)
  117. @pyqtProperty(float, float)
  118. def setHeadX(self, x: float, speed: float = 3000) -> None:
  119. self.updateHeadPosition(x, self._head_position.y, self._head_position.z)
  120. self._controller.setHeadPosition(self, x, self._head_position.y, self._head_position.z, speed)
  121. @pyqtProperty(float)
  122. @pyqtProperty(float, float)
  123. def setHeadY(self, y: float, speed: float = 3000) -> None:
  124. self.updateHeadPosition(self._head_position.x, y, self._head_position.z)
  125. self._controller.setHeadPosition(self, self._head_position.x, y, self._head_position.z, speed)
  126. @pyqtProperty(float)
  127. @pyqtProperty(float, float)
  128. def setHeadZ(self, z: float, speed:float = 3000) -> None:
  129. self.updateHeadPosition(self._head_position.x, self._head_position.y, z)
  130. self._controller.setHeadPosition(self, self._head_position.x, self._head_position.y, z, speed)
  131. @pyqtSlot(float, float, float)
  132. @pyqtSlot(float, float, float, float)
  133. def moveHead(self, x: float = 0, y: float = 0, z: float = 0, speed: float = 3000) -> None:
  134. self._controller.moveHead(self, x, y, z, speed)
  135. ## Pre-heats the heated bed of the printer.
  136. #
  137. # \param temperature The temperature to heat the bed to, in degrees
  138. # Celsius.
  139. # \param duration How long the bed should stay warm, in seconds.
  140. @pyqtSlot(float, float)
  141. def preheatBed(self, temperature: float, duration: float) -> None:
  142. self._controller.preheatBed(self, temperature, duration)
  143. @pyqtSlot()
  144. def cancelPreheatBed(self) -> None:
  145. self._controller.cancelPreheatBed(self)
  146. def getController(self) -> "PrinterOutputController":
  147. return self._controller
  148. @pyqtProperty(str, notify = nameChanged)
  149. def name(self) -> str:
  150. return self._name
  151. def setName(self, name: str) -> None:
  152. self.updateName(name)
  153. def updateName(self, name: str) -> None:
  154. if self._name != name:
  155. self._name = name
  156. self.nameChanged.emit()
  157. ## Update the bed temperature. This only changes it locally.
  158. def updateBedTemperature(self, temperature: float) -> None:
  159. if self._bed_temperature != temperature:
  160. self._bed_temperature = temperature
  161. self.bedTemperatureChanged.emit()
  162. def updateTargetBedTemperature(self, temperature: float) -> None:
  163. if self._target_bed_temperature != temperature:
  164. self._target_bed_temperature = temperature
  165. self.targetBedTemperatureChanged.emit()
  166. ## Set the target bed temperature. This ensures that it's actually sent to the remote.
  167. @pyqtSlot(float)
  168. def setTargetBedTemperature(self, temperature: float) -> None:
  169. self._controller.setTargetBedTemperature(self, temperature)
  170. self.updateTargetBedTemperature(temperature)
  171. def updateActivePrintJob(self, print_job: Optional["PrintJobOutputModel"]) -> None:
  172. if self._active_print_job != print_job:
  173. old_print_job = self._active_print_job
  174. if print_job is not None:
  175. print_job.updateAssignedPrinter(self)
  176. self._active_print_job = print_job
  177. if old_print_job is not None:
  178. old_print_job.updateAssignedPrinter(None)
  179. self.activePrintJobChanged.emit()
  180. def updateState(self, printer_state: str) -> None:
  181. if self._printer_state != printer_state:
  182. self._printer_state = printer_state
  183. self.stateChanged.emit()
  184. @pyqtProperty(QObject, notify = activePrintJobChanged)
  185. def activePrintJob(self) -> Optional["PrintJobOutputModel"]:
  186. return self._active_print_job
  187. @pyqtProperty(str, notify = stateChanged)
  188. def state(self) -> str:
  189. return self._printer_state
  190. @pyqtProperty(float, notify = bedTemperatureChanged)
  191. def bedTemperature(self) -> float:
  192. return self._bed_temperature
  193. @pyqtProperty(float, notify = targetBedTemperatureChanged)
  194. def targetBedTemperature(self) -> float:
  195. return self._target_bed_temperature
  196. # Does the printer support pre-heating the bed at all
  197. @pyqtProperty(bool, constant = True)
  198. def canPreHeatBed(self) -> bool:
  199. if self._controller:
  200. return self._controller.can_pre_heat_bed
  201. return False
  202. # Does the printer support pre-heating the bed at all
  203. @pyqtProperty(bool, constant = True)
  204. def canPreHeatHotends(self) -> bool:
  205. if self._controller:
  206. return self._controller.can_pre_heat_hotends
  207. return False
  208. # Does the printer support sending raw G-code at all
  209. @pyqtProperty(bool, constant = True)
  210. def canSendRawGcode(self) -> bool:
  211. if self._controller:
  212. return self._controller.can_send_raw_gcode
  213. return False
  214. # Does the printer support pause at all
  215. @pyqtProperty(bool, constant = True)
  216. def canPause(self) -> bool:
  217. if self._controller:
  218. return self._controller.can_pause
  219. return False
  220. # Does the printer support abort at all
  221. @pyqtProperty(bool, constant = True)
  222. def canAbort(self) -> bool:
  223. if self._controller:
  224. return self._controller.can_abort
  225. return False
  226. # Does the printer support manual control at all
  227. @pyqtProperty(bool, constant = True)
  228. def canControlManually(self) -> bool:
  229. if self._controller:
  230. return self._controller.can_control_manually
  231. return False
  232. # Does the printer support upgrading firmware
  233. @pyqtProperty(bool, notify = canUpdateFirmwareChanged)
  234. def canUpdateFirmware(self) -> bool:
  235. if self._controller:
  236. return self._controller.can_update_firmware
  237. return False
  238. # Stub to connect UM.Signal to pyqtSignal
  239. def _onControllerCanUpdateFirmwareChanged(self) -> None:
  240. self.canUpdateFirmwareChanged.emit()
  241. # Returns the active configuration (material, variant and buildplate) of the current printer
  242. @pyqtProperty(QObject, notify = configurationChanged)
  243. def printerConfiguration(self) -> Optional[PrinterConfigurationModel]:
  244. if self._active_printer_configuration.isValid():
  245. return self._active_printer_configuration
  246. return None
  247. peripheralsChanged = pyqtSignal()
  248. @pyqtProperty(str, notify = peripheralsChanged)
  249. def peripherals(self) -> str:
  250. return ", ".join([peripheral.name for peripheral in self._peripherals])
  251. def addPeripheral(self, peripheral: Peripheral) -> None:
  252. self._peripherals.append(peripheral)
  253. self.peripheralsChanged.emit()
  254. def removePeripheral(self, peripheral: Peripheral) -> None:
  255. self._peripherals.remove(peripheral)
  256. self.peripheralsChanged.emit()
  257. availableConfigurationsChanged = pyqtSignal()
  258. # The availableConfigurations are configuration options that a printer can switch to, but doesn't currently have
  259. # active (eg; Automatic tool changes, material loaders, etc).
  260. @pyqtProperty("QVariantList", notify = availableConfigurationsChanged)
  261. def availableConfigurations(self) -> List[PrinterConfigurationModel]:
  262. return self._available_printer_configurations
  263. def addAvailableConfiguration(self, new_configuration: PrinterConfigurationModel) -> None:
  264. if new_configuration not in self._available_printer_configurations:
  265. self._available_printer_configurations.append(new_configuration)
  266. self.availableConfigurationsChanged.emit()
  267. def removeAvailableConfiguration(self, config_to_remove: PrinterConfigurationModel) -> None:
  268. try:
  269. self._available_printer_configurations.remove(config_to_remove)
  270. except ValueError:
  271. Logger.log("w", "Unable to remove configuration that isn't in the list of available configurations")
  272. else:
  273. self.availableConfigurationsChanged.emit()
  274. def setAvailableConfigurations(self, new_configurations: List[PrinterConfigurationModel]) -> None:
  275. self._available_printer_configurations = new_configurations
  276. self.availableConfigurationsChanged.emit()