From 890ddc015e54b79ccada91ec4e056edcab67212a Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 25 Oct 2018 21:36:23 +0200 Subject: [PATCH 1/7] Add Cura.NetworkMJPGImage widget --- cura/CuraApplication.py | 4 + cura/PrinterOutput/NetworkMJPGImage.py | 153 +++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 cura/PrinterOutput/NetworkMJPGImage.py diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 41dadc2d84..2b3677bcb1 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -114,6 +114,8 @@ from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions from cura.ObjectsModel import ObjectsModel +from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage + from UM.FlameProfiler import pyqtSlot from UM.Decorators import override @@ -947,6 +949,8 @@ class CuraApplication(QtApplication): qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager) qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager) + qmlRegisterType(NetworkMJPGImage, "Cura", 1, 0, "NetworkMJPGImage") + qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 0, "ObjectsModel", self.getObjectsModel) qmlRegisterType(BuildPlateModel, "Cura", 1, 0, "BuildPlateModel") qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel") diff --git a/cura/PrinterOutput/NetworkMJPGImage.py b/cura/PrinterOutput/NetworkMJPGImage.py new file mode 100644 index 0000000000..1efc21d2ea --- /dev/null +++ b/cura/PrinterOutput/NetworkMJPGImage.py @@ -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() From c187b6a25c6b5dd781af6ef2392f1e202ad16294 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 25 Oct 2018 21:58:21 +0200 Subject: [PATCH 2/7] Replace NetworkCamera with NetworkMJPGImage --- cura/PrinterOutput/PrinterOutputModel.py | 18 +++++------ .../resources/qml/MonitorItem.qml | 31 +++++++------------ .../resources/qml/PrinterVideoStream.qml | 22 ++++--------- .../src/ClusterUM3OutputDevice.py | 2 +- .../src/LegacyUM3OutputDevice.py | 4 +-- 5 files changed, 30 insertions(+), 47 deletions(-) diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index c1c5586f9f..59b98364e5 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -1,7 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot +from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot, QUrl from typing import List, Dict, Optional from UM.Math.Vector import Vector from cura.PrinterOutput.ConfigurationModel import ConfigurationModel @@ -50,16 +50,16 @@ class PrinterOutputModel(QObject): self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in self._extruders] - self._camera = None # type: Optional[NetworkCamera] + self._camera`_url = None # type: Optional[QUrl] @pyqtProperty(str, constant = True) def firmwareVersion(self) -> str: return self._firmware_version - def setCamera(self, camera: Optional["NetworkCamera"]) -> None: - if self._camera is not camera: - self._camera = camera - self.cameraChanged.emit() + def setCameraUrl(self, camera_url: Optional["QUrl"]) -> None: + if self._camera_url is not camera_url: + self._camera_url = camera_url + self.cameraUrlChanged.emit() def updateIsPreheating(self, pre_heating: bool) -> None: if self._is_preheating != pre_heating: @@ -70,9 +70,9 @@ class PrinterOutputModel(QObject): def isPreheating(self) -> bool: return self._is_preheating - @pyqtProperty(QObject, notify=cameraChanged) - def camera(self) -> Optional["NetworkCamera"]: - return self._camera + @pyqtProperty(QUrl, notify=cameraUrlChanged) + def cameraUrl(self) -> Optional["QUrl"]: + return self._camera_url @pyqtProperty(str, notify = printerTypeChanged) def type(self) -> str: diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorItem.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorItem.qml index 7aff32e424..41b3a93a7b 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorItem.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorItem.qml @@ -10,43 +10,36 @@ Component { height: maximumHeight; width: maximumWidth; - Cura.CameraView { + Cura.NetworkMJPGImage { id: cameraImage; anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter; } Component.onCompleted: { - if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null) { - OutputDevice.activePrinter.camera.start(); + if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) { + cameraImage.start(); } } height: Math.floor((imageHeight === 0 ? 600 * screenScaleFactor : imageHeight) * width / imageWidth); onVisibleChanged: { if (visible) { - if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null) { - OutputDevice.activePrinter.camera.start(); + if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) { + cameraImage.start(); } } else { - if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null) { - OutputDevice.activePrinter.camera.stop(); + if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) { + cameraImage.stop(); } } } + source: { + if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) { + return OutputDevice.activePrinter.cameraUrl; + } + } width: Math.min(imageWidth === 0 ? 800 * screenScaleFactor : imageWidth, maximumWidth); z: 1; - - Connections - { - target: OutputDevice.activePrinter.camera; - onNewImage: - { - if (cameraImage.visible) { - cameraImage.image = OutputDevice.activePrinter.camera.latestImage; - cameraImage.update(); - } - } - } } } } \ No newline at end of file diff --git a/plugins/UM3NetworkPrinting/resources/qml/PrinterVideoStream.qml b/plugins/UM3NetworkPrinting/resources/qml/PrinterVideoStream.qml index 71104872a1..b247034c70 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/PrinterVideoStream.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/PrinterVideoStream.qml @@ -34,33 +34,23 @@ Item { z: 999; } - Cura.CameraView { + Cura.NetworkMJPGImage { id: cameraImage anchors.horizontalCenter: parent.horizontalCenter; anchors.verticalCenter: parent.verticalCenter; height: Math.round((imageHeight === 0 ? 600 * screenScaleFactor : imageHeight) * width / imageWidth); onVisibleChanged: { if (visible) { - if (camera != null) { - camera.start(); + if (cameraUrl != null) { + start(); } } else { - if (camera != null) { - camera.stop(); - } - } - } - - Connections - { - target: camera - onNewImage: { - if (cameraImage.visible) { - cameraImage.image = camera.latestImage; - cameraImage.update(); + if (cameraUrl != null) { + stop(); } } } + source: cameraUrl width: Math.min(imageWidth === 0 ? 800 * screenScaleFactor : imageWidth, maximumWidth); z: 1 } diff --git a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py index 4c7b93c145..5089b61a2a 100644 --- a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py @@ -548,7 +548,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): def _createPrinterModel(self, data: Dict[str, Any]) -> PrinterOutputModel: printer = PrinterOutputModel(output_controller = ClusterUM3PrinterOutputController(self), number_of_extruders = self._number_of_extruders) - printer.setCamera(NetworkCamera("http://" + data["ip_address"] + ":8080/?action=stream")) + printer.setCameraUrl(QUrl("http://" + data["ip_address"] + ":8080/?action=stream")) self._printers.append(printer) return printer diff --git a/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py index e786840803..a49ec1f6a9 100644 --- a/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py @@ -18,7 +18,7 @@ from UM.i18n import i18nCatalog from UM.Message import Message from PyQt5.QtNetwork import QNetworkRequest -from PyQt5.QtCore import QTimer +from PyQt5.QtCore import QTimer, QUrl from PyQt5.QtWidgets import QMessageBox from .LegacyUM3PrinterOutputController import LegacyUM3PrinterOutputController @@ -568,7 +568,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): # Quickest way to get the firmware version is to grab it from the zeroconf. firmware_version = self._properties.get(b"firmware_version", b"").decode("utf-8") self._printers = [PrinterOutputModel(output_controller=self._output_controller, number_of_extruders=self._number_of_extruders, firmware_version=firmware_version)] - self._printers[0].setCamera(NetworkCamera("http://" + self._address + ":8080/?action=stream")) + self._printers[0].setCameraUrl(QUrl("http://" + self._address + ":8080/?action=stream")) for extruder in self._printers[0].extruders: extruder.activeMaterialChanged.connect(self.materialIdChanged) extruder.hotendIDChanged.connect(self.hotendIdChanged) From 484b1e5ec683660001e93d7019da3caaf1de5816 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 25 Oct 2018 22:00:47 +0200 Subject: [PATCH 3/7] Remove CameraImageProvider and NetworkCamera --- cura/CameraImageProvider.py | 40 ---------- cura/CuraApplication.py | 2 - cura/PrinterOutput/NetworkCamera.py | 112 ---------------------------- 3 files changed, 154 deletions(-) delete mode 100644 cura/CameraImageProvider.py delete mode 100644 cura/PrinterOutput/NetworkCamera.py diff --git a/cura/CameraImageProvider.py b/cura/CameraImageProvider.py deleted file mode 100644 index edb0f205c7..0000000000 --- a/cura/CameraImageProvider.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (c) 2018 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -from PyQt5.QtGui import QImage -from PyQt5.QtQuick import QQuickImageProvider -from PyQt5.QtCore import QSize - -from UM.Application import Application - -## Creates screenshots of the current scene. -class CameraImageProvider(QQuickImageProvider): - def __init__(self): - super().__init__(QQuickImageProvider.Image) - - ## Request a new image. - # - # The image will be taken using the current camera position. - # Only the actual objects in the scene will get rendered. Not the build - # plate and such! - # \param id The ID for the image to create. This is the requested image - # source, with the "image:" scheme and provider identifier removed. It's - # a Qt thing, they'll provide this parameter. - # \param size The dimensions of the image to scale to. - def requestImage(self, id, size): - for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices(): - try: - image = output_device.activePrinter.camera.getImage() - if image.isNull(): - image = QImage() - - return image, QSize(15, 15) - except AttributeError: - try: - image = output_device.activeCamera.getImage() - - return image, QSize(15, 15) - except AttributeError: - pass - - return QImage(), QSize(15, 15) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 2b3677bcb1..ba49f2b824 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -96,7 +96,6 @@ from . import PrintInformation from . import CuraActions from cura.Scene import ZOffsetDecorator from . import CuraSplashScreen -from . import CameraImageProvider from . import PrintJobPreviewImageProvider from . import MachineActionManager @@ -525,7 +524,6 @@ class CuraApplication(QtApplication): CuraApplication.Created = True def _onEngineCreated(self): - self._qml_engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider()) self._qml_engine.addImageProvider("print_job_preview", PrintJobPreviewImageProvider.PrintJobPreviewImageProvider()) @pyqtProperty(bool) diff --git a/cura/PrinterOutput/NetworkCamera.py b/cura/PrinterOutput/NetworkCamera.py deleted file mode 100644 index 1fff9945c8..0000000000 --- a/cura/PrinterOutput/NetworkCamera.py +++ /dev/null @@ -1,112 +0,0 @@ -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._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(QImage, notify=newImage) - def latestImage(self): - return self._image - - @pyqtSlot() - def start(self): - # Ensure that previous requests (if any) are stopped. - self.stop() - 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._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._manager = None - - self._started = False - - def getImage(self): - return self._image - - ## Ensure that close gets called when object is destroyed - def __del__(self): - self.stop() - - 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() From 736bf040a85fed2af4242eb56f9313fab0d0043c Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 25 Oct 2018 22:13:20 +0200 Subject: [PATCH 4/7] Fix typing and typos --- cura/PrinterOutput/NetworkMJPGImage.py | 14 +++++++------- cura/PrinterOutput/PrinterOutputModel.py | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cura/PrinterOutput/NetworkMJPGImage.py b/cura/PrinterOutput/NetworkMJPGImage.py index 1efc21d2ea..522d684085 100644 --- a/cura/PrinterOutput/NetworkMJPGImage.py +++ b/cura/PrinterOutput/NetworkMJPGImage.py @@ -1,8 +1,8 @@ # 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.QtCore import QUrl, pyqtProperty, pyqtSignal, pyqtSlot, QRect, QByteArray +from PyQt5.QtGui import QImage, QPainter from PyQt5.QtQuick import QQuickPaintedItem from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager @@ -17,11 +17,11 @@ class NetworkMJPGImage(QQuickPaintedItem): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - self._stream_buffer = b"" + self._stream_buffer = QByteArray() self._stream_buffer_start_index = -1 - self._network_manager = None - self._image_request = None - self._image_reply = None + self._network_manager = None # type: QNetworkAccessManager + self._image_request = None # type: QNetworkRequest + self._image_reply = None # type: QNetworkReply self._image = QImage() self._image_rect = QRect() @@ -99,7 +99,7 @@ class NetworkMJPGImage(QQuickPaintedItem): @pyqtSlot() def stop(self) -> None: - self._stream_buffer = b"" + self._stream_buffer = QByteArray() self._stream_buffer_start_index = -1 if self._image_reply: diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index 59b98364e5..b40b07e16b 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -25,7 +25,7 @@ class PrinterOutputModel(QObject): keyChanged = pyqtSignal() printerTypeChanged = pyqtSignal() buildplateChanged = pyqtSignal() - cameraChanged = pyqtSignal() + cameraUrlChanged = pyqtSignal() configurationChanged = pyqtSignal() canUpdateFirmwareChanged = pyqtSignal() @@ -50,7 +50,7 @@ class PrinterOutputModel(QObject): self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in self._extruders] - self._camera`_url = None # type: Optional[QUrl] + self._camera_url = None # type: Optional[QUrl] @pyqtProperty(str, constant = True) def firmwareVersion(self) -> str: From e0d6bac37d5ed161a655393c83e8f5d1152054d3 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 26 Oct 2018 15:23:51 +0200 Subject: [PATCH 5/7] Fix remaining references to NetworkCamera and OutputDevice.activeCamera --- cura/PrinterOutput/PrintJobOutputModel.py | 2 +- cura/PrinterOutput/PrinterOutputModel.py | 1 - .../resources/qml/CameraButton.qml | 6 ++-- .../resources/qml/ClusterMonitorItem.qml | 6 ++-- .../resources/qml/PrinterVideoStream.qml | 6 ++-- .../src/ClusterUM3OutputDevice.py | 29 +++++++++---------- .../src/LegacyUM3OutputDevice.py | 1 - 7 files changed, 23 insertions(+), 28 deletions(-) diff --git a/cura/PrinterOutput/PrintJobOutputModel.py b/cura/PrinterOutput/PrintJobOutputModel.py index b417e0aab3..25b168e6fd 100644 --- a/cura/PrinterOutput/PrintJobOutputModel.py +++ b/cura/PrinterOutput/PrintJobOutputModel.py @@ -54,7 +54,7 @@ class PrintJobOutputModel(QObject): @pyqtProperty(QUrl, notify=previewImageChanged) def previewImageUrl(self): self._preview_image_id += 1 - # There is an image provider that is called "camera". In order to ensure that the image qml object, that + # There is an image provider that is called "print_job_preview". 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://print_job_preview/" + str(self._preview_image_id) + "/" + self._key diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index b40b07e16b..f82d568e63 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -11,7 +11,6 @@ MYPY = False if MYPY: from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel from cura.PrinterOutput.PrinterOutputController import PrinterOutputController - from cura.PrinterOutput.NetworkCamera import NetworkCamera class PrinterOutputModel(QObject): diff --git a/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml b/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml index f8dd3bc467..7ec0cb880a 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml @@ -31,10 +31,10 @@ Rectangle { anchors.fill: parent; hoverEnabled: true; onClicked: { - if (OutputDevice.activeCamera !== null) { - OutputDevice.setActiveCamera(null) + if (OutputDevice.activeCameraUrl !== null) { + OutputDevice.setActiveCameraUrl(null) } else { - OutputDevice.setActiveCamera(modelData.camera); + OutputDevice.setActiveCameraUrl(modelData.cameraUrl); } } } diff --git a/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml b/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml index c79092863e..a9166432ae 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml @@ -16,7 +16,7 @@ Component { height: maximumHeight; onVisibleChanged: { if (monitorFrame != null && !monitorFrame.visible) { - OutputDevice.setActiveCamera(null); + OutputDevice.setActiveCameraUrl(null); } } width: maximumWidth; @@ -125,8 +125,8 @@ Component { PrinterVideoStream { anchors.fill: parent; - camera: OutputDevice.activeCamera; - visible: OutputDevice.activeCamera != null; + cameraUrl: OutputDevice.activeCameraUrl; + visible: OutputDevice.activeCameraUrl != null; } } } diff --git a/plugins/UM3NetworkPrinting/resources/qml/PrinterVideoStream.qml b/plugins/UM3NetworkPrinting/resources/qml/PrinterVideoStream.qml index b247034c70..9b79a8d008 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/PrinterVideoStream.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/PrinterVideoStream.qml @@ -8,7 +8,7 @@ import UM 1.3 as UM import Cura 1.0 as Cura Item { - property var camera: null; + property var cameraUrl: null; Rectangle { anchors.fill:parent; @@ -18,7 +18,7 @@ Item { MouseArea { anchors.fill: parent; - onClicked: OutputDevice.setActiveCamera(null); + onClicked: OutputDevice.setActiveCameraUrl(null); z: 0; } @@ -58,7 +58,7 @@ Item { MouseArea { anchors.fill: cameraImage; onClicked: { - OutputDevice.setActiveCamera(null); + OutputDevice.setActiveCameraUrl(null); } z: 1; } diff --git a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py index 5089b61a2a..368273102b 100644 --- a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py @@ -22,7 +22,6 @@ from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationM from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel -from cura.PrinterOutput.NetworkCamera import NetworkCamera from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController from .SendMaterialJob import SendMaterialJob @@ -47,7 +46,7 @@ i18n_catalog = i18nCatalog("cura") class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): printJobsChanged = pyqtSignal() activePrinterChanged = pyqtSignal() - activeCameraChanged = pyqtSignal() + activeCameraUrlChanged = pyqtSignal() receivedPrintJobsChanged = pyqtSignal() # This is a bit of a hack, as the notify can only use signals that are defined by the class that they are in. @@ -100,7 +99,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._latest_reply_handler = None # type: Optional[QNetworkReply] self._sending_job = None - self._active_camera = None # type: Optional[NetworkCamera] + self._active_camera_url = None # type: Optional[QUrl] def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None: self.writeStarted.emit(self) @@ -264,30 +263,28 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): def activePrinter(self) -> Optional[PrinterOutputModel]: return self._active_printer - @pyqtProperty(QObject, notify=activeCameraChanged) - def activeCamera(self) -> Optional[NetworkCamera]: - return self._active_camera + @pyqtProperty(QUrl, notify=activeCameraUrlChanged) + def activeCameraUrl(self) -> Optional[QUrl]: + return self._active_camera_url @pyqtSlot(QObject) def setActivePrinter(self, printer: Optional[PrinterOutputModel]) -> None: if self._active_printer != printer: - if self._active_printer and self._active_printer.camera: - self._active_printer.camera.stop() self._active_printer = printer self.activePrinterChanged.emit() @pyqtSlot(QObject) - def setActiveCamera(self, camera: Optional[NetworkCamera]) -> None: - if self._active_camera != camera: - if self._active_camera: - self._active_camera.stop() + def setActiveCameraUrl(self, camera_url: Optional[QUrl]) -> None: + if self._active_camera_url != camera_url: + if self._active_camera_url: + self._active_camera_url.stop() - self._active_camera = camera + self._active_camera_url = camera_url - if self._active_camera: - self._active_camera.start() + if self._active_camera_url: + self._active_camera_url.start() - self.activeCameraChanged.emit() + self.activeCameraUrlChanged.emit() def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None: if self._progress_message: diff --git a/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py index a49ec1f6a9..e45de2dbb0 100644 --- a/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py @@ -7,7 +7,6 @@ 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 cura.Settings.ContainerManager import ContainerManager from cura.Settings.ExtruderManager import ExtruderManager From 0c1b3931db7dc8a33180d7f7b7007853ab1a01c2 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 29 Oct 2018 10:40:54 +0100 Subject: [PATCH 6/7] Revert "WIP: Add custom CameraView for UM camera feed" This reverts commit b00ea4719a268ec4b000375343bbc4082783b435. --- cura/CuraApplication.py | 3 --- cura/PrinterOutput/CameraView.py | 41 -------------------------------- 2 files changed, 44 deletions(-) delete mode 100644 cura/PrinterOutput/CameraView.py diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index ba49f2b824..5323f1b0fa 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -961,9 +961,6 @@ class CuraApplication(QtApplication): qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel") qmlRegisterType(MachineManagementModel, "Cura", 1, 0, "MachineManagementModel") - from cura.PrinterOutput.CameraView import CameraView - qmlRegisterType(CameraView, "Cura", 1, 0, "CameraView") - qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0, "QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel) qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0, diff --git a/cura/PrinterOutput/CameraView.py b/cura/PrinterOutput/CameraView.py deleted file mode 100644 index 4a604b7abe..0000000000 --- a/cura/PrinterOutput/CameraView.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) 2018 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -from PyQt5.QtCore import pyqtProperty, pyqtSignal -from PyQt5.QtGui import QImage -from PyQt5.QtQuick import QQuickPaintedItem - - -# -# A custom camera view that uses QQuickPaintedItem to present (or "paint") the image frames from a printer's -# network camera feed. -# -class CameraView(QQuickPaintedItem): - - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - - self._image = QImage() - - imageChanged = pyqtSignal() - - def setImage(self, image: "QImage") -> None: - self._image = image - self.imageChanged.emit() - self.update() - - def getImage(self) -> "QImage": - return self._image - - image = pyqtProperty(QImage, fget = getImage, fset = setImage, notify = imageChanged) - - @pyqtProperty(int, notify = imageChanged) - def imageWidth(self) -> int: - return self._image.width() - - @pyqtProperty(int, notify = imageChanged) - def imageHeight(self) -> int: - return self._image.height() - - def paint(self, painter): - painter.drawImage(self.contentsBoundingRect(), self._image) From 02681a5700baa398c7d7254d70ab6226a986c839 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 29 Oct 2018 13:23:10 +0100 Subject: [PATCH 7/7] Change camera URL to non-optional QUrl Otherwise pyqt property will complain when it tries to convert a None to a QUrl. --- cura/PrinterOutput/PrinterOutputModel.py | 14 ++++++------- .../resources/qml/CameraButton.qml | 4 ++-- .../resources/qml/ClusterMonitorItem.qml | 4 ++-- .../resources/qml/PrinterVideoStream.qml | 10 ++++----- .../src/ClusterUM3OutputDevice.py | 21 +++++++------------ 5 files changed, 23 insertions(+), 30 deletions(-) diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index f82d568e63..4189b9fcbd 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -49,17 +49,21 @@ class PrinterOutputModel(QObject): self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in self._extruders] - self._camera_url = None # type: Optional[QUrl] + self._camera_url = QUrl() # type: QUrl @pyqtProperty(str, constant = True) def firmwareVersion(self) -> str: return self._firmware_version - def setCameraUrl(self, camera_url: Optional["QUrl"]) -> None: - if self._camera_url is not camera_url: + def setCameraUrl(self, camera_url: "QUrl") -> None: + if self._camera_url != camera_url: self._camera_url = camera_url self.cameraUrlChanged.emit() + @pyqtProperty(QUrl, fset = setCameraUrl, notify = cameraUrlChanged) + def cameraUrl(self) -> "QUrl": + return self._camera_url + def updateIsPreheating(self, pre_heating: bool) -> None: if self._is_preheating != pre_heating: self._is_preheating = pre_heating @@ -69,10 +73,6 @@ class PrinterOutputModel(QObject): def isPreheating(self) -> bool: return self._is_preheating - @pyqtProperty(QUrl, notify=cameraUrlChanged) - def cameraUrl(self) -> Optional["QUrl"]: - return self._camera_url - @pyqtProperty(str, notify = printerTypeChanged) def type(self) -> str: return self._printer_type diff --git a/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml b/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml index 7ec0cb880a..7e5c254e5c 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml @@ -31,8 +31,8 @@ Rectangle { anchors.fill: parent; hoverEnabled: true; onClicked: { - if (OutputDevice.activeCameraUrl !== null) { - OutputDevice.setActiveCameraUrl(null) + if (OutputDevice.activeCameraUrl != "") { + OutputDevice.setActiveCameraUrl(""); } else { OutputDevice.setActiveCameraUrl(modelData.cameraUrl); } diff --git a/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml b/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml index a9166432ae..d210ab40f3 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml @@ -16,7 +16,7 @@ Component { height: maximumHeight; onVisibleChanged: { if (monitorFrame != null && !monitorFrame.visible) { - OutputDevice.setActiveCameraUrl(null); + OutputDevice.setActiveCameraUrl(""); } } width: maximumWidth; @@ -126,7 +126,7 @@ Component { PrinterVideoStream { anchors.fill: parent; cameraUrl: OutputDevice.activeCameraUrl; - visible: OutputDevice.activeCameraUrl != null; + visible: OutputDevice.activeCameraUrl != ""; } } } diff --git a/plugins/UM3NetworkPrinting/resources/qml/PrinterVideoStream.qml b/plugins/UM3NetworkPrinting/resources/qml/PrinterVideoStream.qml index 9b79a8d008..77b481f6d8 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/PrinterVideoStream.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/PrinterVideoStream.qml @@ -8,7 +8,7 @@ import UM 1.3 as UM import Cura 1.0 as Cura Item { - property var cameraUrl: null; + property var cameraUrl: ""; Rectangle { anchors.fill:parent; @@ -18,7 +18,7 @@ Item { MouseArea { anchors.fill: parent; - onClicked: OutputDevice.setActiveCameraUrl(null); + onClicked: OutputDevice.setActiveCameraUrl(""); z: 0; } @@ -41,11 +41,11 @@ Item { height: Math.round((imageHeight === 0 ? 600 * screenScaleFactor : imageHeight) * width / imageWidth); onVisibleChanged: { if (visible) { - if (cameraUrl != null) { + if (cameraUrl != "") { start(); } } else { - if (cameraUrl != null) { + if (cameraUrl != "") { stop(); } } @@ -58,7 +58,7 @@ Item { MouseArea { anchors.fill: cameraImage; onClicked: { - OutputDevice.setActiveCameraUrl(null); + OutputDevice.setActiveCameraUrl(""); } z: 1; } diff --git a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py index 368273102b..7504d55ad9 100644 --- a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py @@ -99,7 +99,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._latest_reply_handler = None # type: Optional[QNetworkReply] self._sending_job = None - self._active_camera_url = None # type: Optional[QUrl] + self._active_camera_url = QUrl() # type: QUrl def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None: self.writeStarted.emit(self) @@ -263,27 +263,20 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): def activePrinter(self) -> Optional[PrinterOutputModel]: return self._active_printer - @pyqtProperty(QUrl, notify=activeCameraUrlChanged) - def activeCameraUrl(self) -> Optional[QUrl]: - return self._active_camera_url - @pyqtSlot(QObject) def setActivePrinter(self, printer: Optional[PrinterOutputModel]) -> None: if self._active_printer != printer: self._active_printer = printer self.activePrinterChanged.emit() - @pyqtSlot(QObject) - def setActiveCameraUrl(self, camera_url: Optional[QUrl]) -> None: + @pyqtProperty(QUrl, notify = activeCameraUrlChanged) + def activeCameraUrl(self) -> "QUrl": + return self._active_camera_url + + @pyqtSlot(QUrl) + def setActiveCameraUrl(self, camera_url: "QUrl") -> None: if self._active_camera_url != camera_url: - if self._active_camera_url: - self._active_camera_url.stop() - self._active_camera_url = camera_url - - if self._active_camera_url: - self._active_camera_url.start() - self.activeCameraUrlChanged.emit() def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None: