PrinterOutputModel.py 14 KB

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