mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-07 23:17:32 -06:00
Added videostream to cluster again
CL-541
This commit is contained in:
parent
aba8bd89c3
commit
77e3965fc7
5 changed files with 139 additions and 6 deletions
|
@ -12,7 +12,7 @@ class CameraImageProvider(QQuickImageProvider):
|
||||||
def requestImage(self, id, size):
|
def requestImage(self, id, size):
|
||||||
for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
|
for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
|
||||||
try:
|
try:
|
||||||
return output_device.getCameraImage(), QSize(15, 15)
|
return output_device.activePrinter.camera.getImage(), QSize(15, 15)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
return QImage(), QSize(15, 15)
|
return QImage(), QSize(15, 15)
|
113
cura/PrinterOutput/NetworkCamera.py
Normal file
113
cura/PrinterOutput/NetworkCamera.py
Normal file
|
@ -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()
|
|
@ -22,6 +22,7 @@ class PrinterOutputModel(QObject):
|
||||||
headPositionChanged = pyqtSignal()
|
headPositionChanged = pyqtSignal()
|
||||||
keyChanged = pyqtSignal()
|
keyChanged = pyqtSignal()
|
||||||
typeChanged = pyqtSignal()
|
typeChanged = pyqtSignal()
|
||||||
|
cameraChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None):
|
def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
@ -38,6 +39,17 @@ class PrinterOutputModel(QObject):
|
||||||
|
|
||||||
self._type = ""
|
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)
|
@pyqtProperty(str, notify = typeChanged)
|
||||||
def type(self):
|
def type(self):
|
||||||
return self._type
|
return self._type
|
||||||
|
|
|
@ -12,6 +12,7 @@ from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutp
|
||||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||||
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
|
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
|
||||||
|
from cura.PrinterOutput.NetworkCamera import NetworkCamera
|
||||||
|
|
||||||
from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController
|
from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController
|
||||||
|
|
||||||
|
@ -290,6 +291,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
if printer is None:
|
if printer is None:
|
||||||
printer = PrinterOutputModel(output_controller=ClusterUM3PrinterOutputController(self), number_of_extruders=self._number_of_extruders)
|
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)
|
self._printers.append(printer)
|
||||||
printer_list_changed = True
|
printer_list_changed = True
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ Item
|
||||||
width: 20 * screenScaleFactor
|
width: 20 * screenScaleFactor
|
||||||
height: 20 * screenScaleFactor
|
height: 20 * screenScaleFactor
|
||||||
|
|
||||||
onClicked: OutputDevice.selectAutomaticPrinter()
|
onClicked: OutputDevice.setActivePrinter(null)
|
||||||
|
|
||||||
style: ButtonStyle
|
style: ButtonStyle
|
||||||
{
|
{
|
||||||
|
@ -65,17 +65,23 @@ Item
|
||||||
{
|
{
|
||||||
if(visible)
|
if(visible)
|
||||||
{
|
{
|
||||||
OutputDevice.startCamera()
|
if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
|
||||||
|
{
|
||||||
|
OutputDevice.activePrinter.camera.start()
|
||||||
|
}
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
OutputDevice.stopCamera()
|
if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
|
||||||
|
{
|
||||||
|
OutputDevice.activePrinter.camera.stop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
source:
|
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 "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue