Browse Source

Added videostream to cluster again

CL-541
Jaime van Kessel 7 years ago
parent
commit
77e3965fc7

+ 1 - 1
cura/CameraImageProvider.py

@@ -12,7 +12,7 @@ class CameraImageProvider(QQuickImageProvider):
     def requestImage(self, id, size):
         for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
             try:
-                return output_device.getCameraImage(), QSize(15, 15)
+                return output_device.activePrinter.camera.getImage(), QSize(15, 15)
             except AttributeError:
                 pass
         return QImage(), QSize(15, 15)

+ 113 - 0
cura/PrinterOutput/NetworkCamera.py

@@ -0,0 +1,113 @@
+from UM.Logger import Logger
+
+from PyQt5.QtCore import QUrl, pyqtProperty, pyqtSignal, QObject, pyqtSlot
+from PyQt5.QtGui import QImage
+from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
+
+
+class NetworkCamera(QObject):
+    newImage = pyqtSignal()
+
+    def __init__(self, target = None, parent = None):
+        super().__init__(parent)
+        self._stream_buffer = b""
+        self._stream_buffer_start_index = -1
+        self._manager = None
+        self._image_request = None
+        self._image_reply = None
+        self._image = QImage()
+        self._image_id = 0
+
+        self._target = target
+        self._started = False
+
+    @pyqtSlot(str)
+    def setTarget(self, target):
+        restart_required = False
+        if self._started:
+            self.stop()
+            restart_required = True
+
+        self._target = target
+
+        if restart_required:
+            self.start()
+
+    @pyqtProperty(QUrl, notify=newImage)
+    def latestImage(self):
+        self._image_id += 1
+        # There is an image provider that is called "camera". In order to ensure that the image qml object, that
+        # requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
+        # as new (instead of relying on cached version and thus forces an update.
+        temp = "image://camera/" + str(self._image_id)
+
+        return QUrl(temp, QUrl.TolerantMode)
+
+    @pyqtSlot()
+    def start(self):
+        if self._target is None:
+            Logger.log("w", "Unable to start camera stream without target!")
+            return
+        self._started = True
+        url = QUrl(self._target)
+        self._image_request = QNetworkRequest(url)
+        if self._manager is None:
+            self._manager = QNetworkAccessManager()
+
+        self._image_reply = self._manager.get(self._image_request)
+        self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)
+
+    @pyqtSlot()
+    def stop(self):
+        self._manager = None
+
+        self._stream_buffer = b""
+        self._stream_buffer_start_index = -1
+
+        if self._image_reply:
+            try:
+                # disconnect the signal
+                try:
+                    self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
+                except Exception:
+                    pass
+                # abort the request if it's not finished
+                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._started = False
+
+    def getImage(self):
+        return self._image
+
+    def _onStreamDownloadProgress(self, bytes_received, bytes_total):
+        # 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)
+
+        self.newImage.emit()

+ 12 - 0
cura/PrinterOutput/PrinterOutputModel.py

@@ -22,6 +22,7 @@ class PrinterOutputModel(QObject):
     headPositionChanged = pyqtSignal()
     keyChanged = pyqtSignal()
     typeChanged = pyqtSignal()
+    cameraChanged = pyqtSignal()
 
     def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None):
         super().__init__(parent)
@@ -38,6 +39,17 @@ class PrinterOutputModel(QObject):
 
         self._type = ""
 
+        self._camera = None
+
+    def setCamera(self, camera):
+        if self._camera is not camera:
+            self._camera = camera
+            self.cameraChanged.emit()
+
+    @pyqtProperty(QObject, notify=cameraChanged)
+    def camera(self):
+        return self._camera
+
     @pyqtProperty(str, notify = typeChanged)
     def type(self):
         return self._type

+ 2 - 0
plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py

@@ -12,6 +12,7 @@ from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutp
 from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
 from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
 from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
+from cura.PrinterOutput.NetworkCamera import NetworkCamera
 
 from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController
 
@@ -290,6 +291,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
 
                 if printer is None:
                     printer = PrinterOutputModel(output_controller=ClusterUM3PrinterOutputController(self), number_of_extruders=self._number_of_extruders)
+                    printer.setCamera(NetworkCamera("http://" + printer_data["ip_address"] + ":8080/?action=stream"))
                     self._printers.append(printer)
                     printer_list_changed = True
 

+ 11 - 5
plugins/UM3NetworkPrinting/PrinterVideoStream.qml

@@ -32,7 +32,7 @@ Item
         width: 20 * screenScaleFactor
         height: 20 * screenScaleFactor
 
-        onClicked: OutputDevice.selectAutomaticPrinter()
+        onClicked: OutputDevice.setActivePrinter(null)
 
         style: ButtonStyle
         {
@@ -65,17 +65,23 @@ Item
         {
             if(visible)
             {
-                OutputDevice.startCamera()
+                if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
+                {
+                    OutputDevice.activePrinter.camera.start()
+                }
             } else
             {
-                OutputDevice.stopCamera()
+                if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
+                {
+                    OutputDevice.activePrinter.camera.stop()
+                }
             }
         }
         source:
         {
-            if(OutputDevice.cameraImage)
+            if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null && OutputDevice.activePrinter.camera.latestImage)
             {
-                return OutputDevice.cameraImage;
+                return OutputDevice.activePrinter.camera.latestImage;
             }
             return "";
         }