mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-12 22:08:59 +08:00
Add Cura.NetworkMJPGImage widget
This commit is contained in:
parent
5133b6300a
commit
890ddc015e
@ -114,6 +114,8 @@ from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions
|
|||||||
|
|
||||||
from cura.ObjectsModel import ObjectsModel
|
from cura.ObjectsModel import ObjectsModel
|
||||||
|
|
||||||
|
from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage
|
||||||
|
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
from UM.Decorators import override
|
from UM.Decorators import override
|
||||||
|
|
||||||
@ -947,6 +949,8 @@ class CuraApplication(QtApplication):
|
|||||||
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
|
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
|
||||||
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
||||||
|
|
||||||
|
qmlRegisterType(NetworkMJPGImage, "Cura", 1, 0, "NetworkMJPGImage")
|
||||||
|
|
||||||
qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 0, "ObjectsModel", self.getObjectsModel)
|
qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 0, "ObjectsModel", self.getObjectsModel)
|
||||||
qmlRegisterType(BuildPlateModel, "Cura", 1, 0, "BuildPlateModel")
|
qmlRegisterType(BuildPlateModel, "Cura", 1, 0, "BuildPlateModel")
|
||||||
qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel")
|
qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel")
|
||||||
|
153
cura/PrinterOutput/NetworkMJPGImage.py
Normal file
153
cura/PrinterOutput/NetworkMJPGImage.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
# Copyright (c) 2018 Aldo Hoeben / fieldOfView
|
||||||
|
# NetworkMJPGImage is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QUrl, pyqtProperty, pyqtSignal, pyqtSlot, QRect
|
||||||
|
from PyQt5.QtGui import QImage
|
||||||
|
from PyQt5.QtQuick import QQuickPaintedItem
|
||||||
|
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
|
||||||
|
|
||||||
|
from UM.Logger import Logger
|
||||||
|
|
||||||
|
#
|
||||||
|
# A QQuickPaintedItem that progressively downloads a network mjpeg stream,
|
||||||
|
# picks it apart in individual jpeg frames, and paints it.
|
||||||
|
#
|
||||||
|
class NetworkMJPGImage(QQuickPaintedItem):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self._stream_buffer = b""
|
||||||
|
self._stream_buffer_start_index = -1
|
||||||
|
self._network_manager = None
|
||||||
|
self._image_request = None
|
||||||
|
self._image_reply = None
|
||||||
|
self._image = QImage()
|
||||||
|
self._image_rect = QRect()
|
||||||
|
|
||||||
|
self._source_url = QUrl()
|
||||||
|
self._started = False
|
||||||
|
|
||||||
|
self._mirror = False
|
||||||
|
|
||||||
|
self.setAntialiasing(True)
|
||||||
|
|
||||||
|
## Ensure that close gets called when object is destroyed
|
||||||
|
def __del__(self) -> None:
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
|
||||||
|
def paint(self, painter: "QPainter") -> None:
|
||||||
|
if self._mirror:
|
||||||
|
painter.drawImage(self.contentsBoundingRect(), self._image.mirrored())
|
||||||
|
return
|
||||||
|
|
||||||
|
painter.drawImage(self.contentsBoundingRect(), self._image)
|
||||||
|
|
||||||
|
|
||||||
|
def setSourceURL(self, source_url: "QUrl") -> None:
|
||||||
|
self._source_url = source_url
|
||||||
|
self.sourceURLChanged.emit()
|
||||||
|
if self._started:
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def getSourceURL(self) -> "QUrl":
|
||||||
|
return self._source_url
|
||||||
|
|
||||||
|
sourceURLChanged = pyqtSignal()
|
||||||
|
source = pyqtProperty(QUrl, fget = getSourceURL, fset = setSourceURL, notify = sourceURLChanged)
|
||||||
|
|
||||||
|
def setMirror(self, mirror: bool) -> None:
|
||||||
|
if mirror == self._mirror:
|
||||||
|
return
|
||||||
|
self._mirror = mirror
|
||||||
|
self.mirrorChanged.emit()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def getMirror(self) -> bool:
|
||||||
|
return self._mirror
|
||||||
|
|
||||||
|
mirrorChanged = pyqtSignal()
|
||||||
|
mirror = pyqtProperty(bool, fget = getMirror, fset = setMirror, notify = mirrorChanged)
|
||||||
|
|
||||||
|
imageSizeChanged = pyqtSignal()
|
||||||
|
|
||||||
|
@pyqtProperty(int, notify = imageSizeChanged)
|
||||||
|
def imageWidth(self) -> int:
|
||||||
|
return self._image.width()
|
||||||
|
|
||||||
|
@pyqtProperty(int, notify = imageSizeChanged)
|
||||||
|
def imageHeight(self) -> int:
|
||||||
|
return self._image.height()
|
||||||
|
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def start(self) -> None:
|
||||||
|
self.stop() # Ensure that previous requests (if any) are stopped.
|
||||||
|
|
||||||
|
if not self._source_url:
|
||||||
|
Logger.log("w", "Unable to start camera stream without target!")
|
||||||
|
return
|
||||||
|
self._started = True
|
||||||
|
|
||||||
|
self._image_request = QNetworkRequest(self._source_url)
|
||||||
|
if self._network_manager is None:
|
||||||
|
self._network_manager = QNetworkAccessManager()
|
||||||
|
|
||||||
|
self._image_reply = self._network_manager.get(self._image_request)
|
||||||
|
self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def stop(self) -> None:
|
||||||
|
self._stream_buffer = b""
|
||||||
|
self._stream_buffer_start_index = -1
|
||||||
|
|
||||||
|
if self._image_reply:
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not self._image_reply.isFinished():
|
||||||
|
self._image_reply.close()
|
||||||
|
except Exception as e: # RuntimeError
|
||||||
|
pass # It can happen that the wrapped c++ object is already deleted.
|
||||||
|
|
||||||
|
self._image_reply = None
|
||||||
|
self._image_request = None
|
||||||
|
|
||||||
|
self._network_manager = None
|
||||||
|
|
||||||
|
self._started = False
|
||||||
|
|
||||||
|
|
||||||
|
def _onStreamDownloadProgress(self, bytes_received: int, bytes_total: int) -> None:
|
||||||
|
# An MJPG stream is (for our purpose) a stream of concatenated JPG images.
|
||||||
|
# JPG images start with the marker 0xFFD8, and end with 0xFFD9
|
||||||
|
if self._image_reply is None:
|
||||||
|
return
|
||||||
|
self._stream_buffer += self._image_reply.readAll()
|
||||||
|
|
||||||
|
if len(self._stream_buffer) > 2000000: # No single camera frame should be 2 Mb or larger
|
||||||
|
Logger.log("w", "MJPEG buffer exceeds reasonable size. Restarting stream...")
|
||||||
|
self.stop() # resets stream buffer and start index
|
||||||
|
self.start()
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._stream_buffer_start_index == -1:
|
||||||
|
self._stream_buffer_start_index = self._stream_buffer.indexOf(b'\xff\xd8')
|
||||||
|
stream_buffer_end_index = self._stream_buffer.lastIndexOf(b'\xff\xd9')
|
||||||
|
# If this happens to be more than a single frame, then so be it; the JPG decoder will
|
||||||
|
# ignore the extra data. We do it like this in order not to get a buildup of frames
|
||||||
|
|
||||||
|
if self._stream_buffer_start_index != -1 and stream_buffer_end_index != -1:
|
||||||
|
jpg_data = self._stream_buffer[self._stream_buffer_start_index:stream_buffer_end_index + 2]
|
||||||
|
self._stream_buffer = self._stream_buffer[stream_buffer_end_index + 2:]
|
||||||
|
self._stream_buffer_start_index = -1
|
||||||
|
self._image.loadFromData(jpg_data)
|
||||||
|
|
||||||
|
if self._image.rect() != self._image_rect:
|
||||||
|
self.imageSizeChanged.emit()
|
||||||
|
|
||||||
|
self.update()
|
Loading…
x
Reference in New Issue
Block a user