Linux PyQt tray application to control OctoPrint instances
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CamWindow.py 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. #!/usr/bin/env python3
  2. # OctoTray Linux Qt System Tray OctoPrint client
  3. #
  4. # CamWindow.py
  5. #
  6. # see also:
  7. # https://doc.qt.io/qt-5/qtwidgets-widgets-imageviewer-example.html
  8. # https://stackoverflow.com/a/22618496
  9. import time
  10. from PyQt5 import QtNetwork
  11. from PyQt5.QtWidgets import QWidget, QLabel, QVBoxLayout, QHBoxLayout, QSlider, QPushButton
  12. from PyQt5.QtGui import QPixmap, QImageReader
  13. from PyQt5.QtCore import QUrl, QTimer, Qt
  14. from AspectRatioPixmapLabel import AspectRatioPixmapLabel
  15. class CamWindow(QWidget):
  16. reloadDelayDefault = 1000 # in ms
  17. statusDelayFactor = 2
  18. reloadOn = True
  19. sliderFactor = 100
  20. def __init__(self, parent, printer, *args, **kwargs):
  21. super(CamWindow, self).__init__(*args, **kwargs)
  22. self.app = parent.app
  23. self.manager = parent.manager
  24. self.manager.finished.connect(self.handleResponse)
  25. self.parent = parent
  26. self.printer = printer
  27. self.url = self.printer.api.getWebcamURL()
  28. print("Webcam: " + self.url)
  29. self.setWindowTitle(parent.name + " Webcam Stream")
  30. self.setWindowIcon(parent.icon)
  31. box = QVBoxLayout()
  32. self.setLayout(box)
  33. label = QLabel(self.url)
  34. box.addWidget(label, 0)
  35. box.setAlignment(label, Qt.AlignHCenter)
  36. slide = QHBoxLayout()
  37. box.addLayout(slide, 0)
  38. self.slideStaticLabel = QLabel("Refresh")
  39. slide.addWidget(self.slideStaticLabel, 0)
  40. self.slider = QSlider(Qt.Horizontal)
  41. self.slider.setMinimum(int(100 / self.sliderFactor))
  42. self.slider.setMaximum(int(2000 / self.sliderFactor))
  43. self.slider.setTickInterval(int(100 / self.sliderFactor))
  44. self.slider.setPageStep(int(100 / self.sliderFactor))
  45. self.slider.setSingleStep(int(100 / self.sliderFactor))
  46. self.slider.setTickPosition(QSlider.TicksBelow)
  47. self.slider.setValue(int(self.reloadDelayDefault / self.sliderFactor))
  48. self.slider.valueChanged.connect(self.sliderChanged)
  49. slide.addWidget(self.slider, 1)
  50. self.slideLabel = QLabel(str(self.reloadDelayDefault) + "ms")
  51. slide.addWidget(self.slideLabel, 0)
  52. self.img = AspectRatioPixmapLabel()
  53. self.img.setPixmap(QPixmap(640, 480))
  54. box.addWidget(self.img, 1)
  55. self.statusLabel = QLabel("Status: unavailable")
  56. box.addWidget(self.statusLabel, 0)
  57. box.setAlignment(self.statusLabel, Qt.AlignHCenter)
  58. self.method = self.printer.api.getMethod()
  59. if self.method != "unknown":
  60. controls_power = QHBoxLayout()
  61. box.addLayout(controls_power, 0)
  62. self.turnOnButton = QPushButton("Turn O&n")
  63. self.turnOnButton.clicked.connect(self.turnOn)
  64. controls_power.addWidget(self.turnOnButton)
  65. self.turnOffButton = QPushButton("Turn O&ff")
  66. self.turnOffButton.clicked.connect(self.turnOff)
  67. controls_power.addWidget(self.turnOffButton)
  68. controls_temp = QHBoxLayout()
  69. box.addLayout(controls_temp, 0)
  70. self.cooldownButton = QPushButton("&Cooldown")
  71. self.cooldownButton.clicked.connect(self.cooldown)
  72. controls_temp.addWidget(self.cooldownButton)
  73. self.preheatToolButton = QPushButton("Preheat &Tool")
  74. self.preheatToolButton.clicked.connect(self.preheatTool)
  75. controls_temp.addWidget(self.preheatToolButton)
  76. self.preheatBedButton = QPushButton("Preheat &Bed")
  77. self.preheatBedButton.clicked.connect(self.preheatBed)
  78. controls_temp.addWidget(self.preheatBedButton)
  79. controls_home = QHBoxLayout()
  80. box.addLayout(controls_home, 0)
  81. self.homeAllButton = QPushButton("Home &All")
  82. self.homeAllButton.clicked.connect(self.homeAll)
  83. controls_home.addWidget(self.homeAllButton, 1)
  84. self.homeXButton = QPushButton("Home &X")
  85. self.homeXButton.clicked.connect(self.homeX)
  86. controls_home.addWidget(self.homeXButton, 0)
  87. self.homeYButton = QPushButton("Home &Y")
  88. self.homeYButton.clicked.connect(self.homeY)
  89. controls_home.addWidget(self.homeYButton, 0)
  90. self.homeZButton = QPushButton("Home &Z")
  91. self.homeZButton.clicked.connect(self.homeZ)
  92. controls_home.addWidget(self.homeZButton, 0)
  93. controls_move = QHBoxLayout()
  94. box.addLayout(controls_move, 0)
  95. self.XPButton = QPushButton("X+")
  96. self.XPButton.clicked.connect(self.moveXP)
  97. controls_move.addWidget(self.XPButton)
  98. self.XMButton = QPushButton("X-")
  99. self.XMButton.clicked.connect(self.moveXM)
  100. controls_move.addWidget(self.XMButton)
  101. self.YPButton = QPushButton("Y+")
  102. self.YPButton.clicked.connect(self.moveYP)
  103. controls_move.addWidget(self.YPButton)
  104. self.YMButton = QPushButton("Y-")
  105. self.YMButton.clicked.connect(self.moveYM)
  106. controls_move.addWidget(self.YMButton)
  107. self.ZPButton = QPushButton("Z+")
  108. self.ZPButton.clicked.connect(self.moveZP)
  109. controls_move.addWidget(self.ZPButton)
  110. self.ZMButton = QPushButton("Z-")
  111. self.ZMButton.clicked.connect(self.moveZM)
  112. controls_move.addWidget(self.ZMButton)
  113. controls_job = QHBoxLayout()
  114. box.addLayout(controls_job, 0)
  115. self.PauseButton = QPushButton("Pause/Resume")
  116. self.PauseButton.clicked.connect(self.pauseResume)
  117. controls_job.addWidget(self.PauseButton)
  118. self.CancelButton = QPushButton("Cancel Job")
  119. self.CancelButton.clicked.connect(self.cancelJob)
  120. controls_job.addWidget(self.CancelButton)
  121. self.loadImage()
  122. self.loadStatus()
  123. def pauseResume(self):
  124. self.printer.api.callPauseResume()
  125. def cancelJob(self):
  126. self.printer.api.callJobCancel()
  127. def moveXP(self):
  128. self.printer.api.callMove("x", int(self.printer.jogLength), int(self.printer.jogSpeed), True)
  129. def moveXM(self):
  130. self.printer.api.callMove("x", -1 * int(self.printer.jogLength), int(self.printer.jogSpeed), True)
  131. def moveYP(self):
  132. self.printer.api.callMove("y", int(self.printer.jogLength), int(self.printer.jogSpeed), True)
  133. def moveYM(self):
  134. self.printer.api.callMove("y", -1 * int(self.printer.jogLength), int(self.printer.jogSpeed), True)
  135. def moveZP(self):
  136. self.printer.api.callMove("z", int(self.printer.jogLength), int(self.printer.jogSpeed), True)
  137. def moveZM(self):
  138. self.printer.api.callMove("z", -1 * int(self.printer.jogLength), int(self.printer.jogSpeed), True)
  139. def homeX(self):
  140. self.printer.api.callHoming("x")
  141. def homeY(self):
  142. self.printer.api.callHoming("y")
  143. def homeZ(self):
  144. self.printer.api.callHoming("z")
  145. def homeAll(self):
  146. self.printer.api.callHoming("xyz")
  147. def turnOn(self):
  148. self.printer.api.turnOn()
  149. def turnOff(self):
  150. self.printer.api.turnOff()
  151. def cooldown(self):
  152. self.printer.api.printerCooldown()
  153. def preheatTool(self):
  154. self.printer.api.printerHeatTool(self.printer.tempTool)
  155. def preheatBed(self):
  156. self.printer.api.printerHeatBed(self.printer.tempBed)
  157. def getHost(self):
  158. return self.printer.host
  159. def sliderChanged(self):
  160. self.slideLabel.setText(str(self.slider.value() * self.sliderFactor) + "ms")
  161. def closeEvent(self, event):
  162. self.reloadOn = False
  163. self.url = ""
  164. self.parent.removeWebcamWindow(self)
  165. def scheduleLoadImage(self):
  166. if self.reloadOn:
  167. QTimer.singleShot(self.slider.value() * self.sliderFactor, self.loadImage)
  168. def scheduleLoadStatus(self):
  169. if self.reloadOn:
  170. QTimer.singleShot(self.slider.value() * self.sliderFactor * self.statusDelayFactor, self.loadStatus)
  171. def loadImage(self):
  172. url = QUrl(self.url)
  173. request = QtNetwork.QNetworkRequest(url)
  174. self.manager.get(request)
  175. def loadStatus(self):
  176. s = "Status: "
  177. t = self.printer.api.getTemperatureString()
  178. if len(t) > 0:
  179. s += t
  180. else:
  181. s += "Unknown"
  182. s += " - "
  183. p = self.printer.api.getProgressString()
  184. if len(p) > 0:
  185. s += p
  186. else:
  187. s += "Unknown"
  188. self.statusLabel.setText(s)
  189. self.scheduleLoadStatus()
  190. def handleResponse(self, reply):
  191. if reply.url().url() != self.url:
  192. print("Reponse for unknown resource: " + reply.url().url())
  193. return
  194. if reply.error() != QtNetwork.QNetworkReply.NoError:
  195. print("Error loading image: " + reply.errorString())
  196. return
  197. reader = QImageReader(reply)
  198. reader.setAutoTransform(True)
  199. image = reader.read()
  200. if image == None:
  201. print("Error decoding image: " + reader.errorString())
  202. return
  203. if image.colorSpace().isValid():
  204. image.convertToColorSpace(QColorSpace.SRgb)
  205. self.img.setPixmap(QPixmap.fromImage(image))
  206. self.scheduleLoadImage()