mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-23 14:44:13 -06:00
Merge pull request #4656 from fieldOfView/fix_network_camera_memory_leak
[3.6] Fix memory leak in print monitor
This commit is contained in:
commit
1623f66c93
13 changed files with 209 additions and 279 deletions
|
@ -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)
|
|
|
@ -96,7 +96,6 @@ from . import PrintInformation
|
||||||
from . import CuraActions
|
from . import CuraActions
|
||||||
from cura.Scene import ZOffsetDecorator
|
from cura.Scene import ZOffsetDecorator
|
||||||
from . import CuraSplashScreen
|
from . import CuraSplashScreen
|
||||||
from . import CameraImageProvider
|
|
||||||
from . import PrintJobPreviewImageProvider
|
from . import PrintJobPreviewImageProvider
|
||||||
from . import MachineActionManager
|
from . import MachineActionManager
|
||||||
|
|
||||||
|
@ -114,6 +113,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
|
||||||
|
|
||||||
|
@ -523,7 +524,6 @@ class CuraApplication(QtApplication):
|
||||||
CuraApplication.Created = True
|
CuraApplication.Created = True
|
||||||
|
|
||||||
def _onEngineCreated(self):
|
def _onEngineCreated(self):
|
||||||
self._qml_engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
|
||||||
self._qml_engine.addImageProvider("print_job_preview", PrintJobPreviewImageProvider.PrintJobPreviewImageProvider())
|
self._qml_engine.addImageProvider("print_job_preview", PrintJobPreviewImageProvider.PrintJobPreviewImageProvider())
|
||||||
|
|
||||||
@pyqtProperty(bool)
|
@pyqtProperty(bool)
|
||||||
|
@ -947,6 +947,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")
|
||||||
|
@ -959,9 +961,6 @@ class CuraApplication(QtApplication):
|
||||||
qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
|
qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
|
||||||
qmlRegisterType(MachineManagementModel, "Cura", 1, 0, "MachineManagementModel")
|
qmlRegisterType(MachineManagementModel, "Cura", 1, 0, "MachineManagementModel")
|
||||||
|
|
||||||
from cura.PrinterOutput.CameraView import CameraView
|
|
||||||
qmlRegisterType(CameraView, "Cura", 1, 0, "CameraView")
|
|
||||||
|
|
||||||
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
||||||
"QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)
|
"QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)
|
||||||
qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
||||||
|
|
|
@ -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)
|
|
|
@ -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()
|
|
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, QByteArray
|
||||||
|
from PyQt5.QtGui import QImage, QPainter
|
||||||
|
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 = QByteArray()
|
||||||
|
self._stream_buffer_start_index = -1
|
||||||
|
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()
|
||||||
|
|
||||||
|
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 = QByteArray()
|
||||||
|
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()
|
|
@ -54,7 +54,7 @@ class PrintJobOutputModel(QObject):
|
||||||
@pyqtProperty(QUrl, notify=previewImageChanged)
|
@pyqtProperty(QUrl, notify=previewImageChanged)
|
||||||
def previewImageUrl(self):
|
def previewImageUrl(self):
|
||||||
self._preview_image_id += 1
|
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
|
# 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.
|
# 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
|
temp = "image://print_job_preview/" + str(self._preview_image_id) + "/" + self._key
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# 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 typing import List, Dict, Optional
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||||
|
@ -11,7 +11,6 @@ MYPY = False
|
||||||
if MYPY:
|
if MYPY:
|
||||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||||
from cura.PrinterOutput.NetworkCamera import NetworkCamera
|
|
||||||
|
|
||||||
|
|
||||||
class PrinterOutputModel(QObject):
|
class PrinterOutputModel(QObject):
|
||||||
|
@ -25,7 +24,7 @@ class PrinterOutputModel(QObject):
|
||||||
keyChanged = pyqtSignal()
|
keyChanged = pyqtSignal()
|
||||||
printerTypeChanged = pyqtSignal()
|
printerTypeChanged = pyqtSignal()
|
||||||
buildplateChanged = pyqtSignal()
|
buildplateChanged = pyqtSignal()
|
||||||
cameraChanged = pyqtSignal()
|
cameraUrlChanged = pyqtSignal()
|
||||||
configurationChanged = pyqtSignal()
|
configurationChanged = pyqtSignal()
|
||||||
canUpdateFirmwareChanged = pyqtSignal()
|
canUpdateFirmwareChanged = pyqtSignal()
|
||||||
|
|
||||||
|
@ -50,16 +49,20 @@ class PrinterOutputModel(QObject):
|
||||||
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
|
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
|
||||||
self._extruders]
|
self._extruders]
|
||||||
|
|
||||||
self._camera = None # type: Optional[NetworkCamera]
|
self._camera_url = QUrl() # type: QUrl
|
||||||
|
|
||||||
@pyqtProperty(str, constant = True)
|
@pyqtProperty(str, constant = True)
|
||||||
def firmwareVersion(self) -> str:
|
def firmwareVersion(self) -> str:
|
||||||
return self._firmware_version
|
return self._firmware_version
|
||||||
|
|
||||||
def setCamera(self, camera: Optional["NetworkCamera"]) -> None:
|
def setCameraUrl(self, camera_url: "QUrl") -> None:
|
||||||
if self._camera is not camera:
|
if self._camera_url != camera_url:
|
||||||
self._camera = camera
|
self._camera_url = camera_url
|
||||||
self.cameraChanged.emit()
|
self.cameraUrlChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(QUrl, fset = setCameraUrl, notify = cameraUrlChanged)
|
||||||
|
def cameraUrl(self) -> "QUrl":
|
||||||
|
return self._camera_url
|
||||||
|
|
||||||
def updateIsPreheating(self, pre_heating: bool) -> None:
|
def updateIsPreheating(self, pre_heating: bool) -> None:
|
||||||
if self._is_preheating != pre_heating:
|
if self._is_preheating != pre_heating:
|
||||||
|
@ -70,10 +73,6 @@ class PrinterOutputModel(QObject):
|
||||||
def isPreheating(self) -> bool:
|
def isPreheating(self) -> bool:
|
||||||
return self._is_preheating
|
return self._is_preheating
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify=cameraChanged)
|
|
||||||
def camera(self) -> Optional["NetworkCamera"]:
|
|
||||||
return self._camera
|
|
||||||
|
|
||||||
@pyqtProperty(str, notify = printerTypeChanged)
|
@pyqtProperty(str, notify = printerTypeChanged)
|
||||||
def type(self) -> str:
|
def type(self) -> str:
|
||||||
return self._printer_type
|
return self._printer_type
|
||||||
|
|
|
@ -31,10 +31,10 @@ Rectangle {
|
||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
hoverEnabled: true;
|
hoverEnabled: true;
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (OutputDevice.activeCamera !== null) {
|
if (OutputDevice.activeCameraUrl != "") {
|
||||||
OutputDevice.setActiveCamera(null)
|
OutputDevice.setActiveCameraUrl("");
|
||||||
} else {
|
} else {
|
||||||
OutputDevice.setActiveCamera(modelData.camera);
|
OutputDevice.setActiveCameraUrl(modelData.cameraUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ Component {
|
||||||
height: maximumHeight;
|
height: maximumHeight;
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (monitorFrame != null && !monitorFrame.visible) {
|
if (monitorFrame != null && !monitorFrame.visible) {
|
||||||
OutputDevice.setActiveCamera(null);
|
OutputDevice.setActiveCameraUrl("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
width: maximumWidth;
|
width: maximumWidth;
|
||||||
|
@ -125,8 +125,8 @@ Component {
|
||||||
|
|
||||||
PrinterVideoStream {
|
PrinterVideoStream {
|
||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
camera: OutputDevice.activeCamera;
|
cameraUrl: OutputDevice.activeCameraUrl;
|
||||||
visible: OutputDevice.activeCamera != null;
|
visible: OutputDevice.activeCameraUrl != "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,43 +10,36 @@ Component {
|
||||||
height: maximumHeight;
|
height: maximumHeight;
|
||||||
width: maximumWidth;
|
width: maximumWidth;
|
||||||
|
|
||||||
Cura.CameraView {
|
Cura.NetworkMJPGImage {
|
||||||
id: cameraImage;
|
id: cameraImage;
|
||||||
anchors {
|
anchors {
|
||||||
horizontalCenter: parent.horizontalCenter;
|
horizontalCenter: parent.horizontalCenter;
|
||||||
verticalCenter: parent.verticalCenter;
|
verticalCenter: parent.verticalCenter;
|
||||||
}
|
}
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null) {
|
if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) {
|
||||||
OutputDevice.activePrinter.camera.start();
|
cameraImage.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
height: Math.floor((imageHeight === 0 ? 600 * screenScaleFactor : imageHeight) * width / imageWidth);
|
height: Math.floor((imageHeight === 0 ? 600 * screenScaleFactor : imageHeight) * width / imageWidth);
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null) {
|
if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) {
|
||||||
OutputDevice.activePrinter.camera.start();
|
cameraImage.start();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null) {
|
if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) {
|
||||||
OutputDevice.activePrinter.camera.stop();
|
cameraImage.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
source: {
|
||||||
|
if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) {
|
||||||
|
return OutputDevice.activePrinter.cameraUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
width: Math.min(imageWidth === 0 ? 800 * screenScaleFactor : imageWidth, maximumWidth);
|
width: Math.min(imageWidth === 0 ? 800 * screenScaleFactor : imageWidth, maximumWidth);
|
||||||
z: 1;
|
z: 1;
|
||||||
|
|
||||||
Connections
|
|
||||||
{
|
|
||||||
target: OutputDevice.activePrinter.camera;
|
|
||||||
onNewImage:
|
|
||||||
{
|
|
||||||
if (cameraImage.visible) {
|
|
||||||
cameraImage.image = OutputDevice.activePrinter.camera.latestImage;
|
|
||||||
cameraImage.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,7 +8,7 @@ import UM 1.3 as UM
|
||||||
import Cura 1.0 as Cura
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
property var camera: null;
|
property var cameraUrl: "";
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill:parent;
|
anchors.fill:parent;
|
||||||
|
@ -18,7 +18,7 @@ Item {
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
onClicked: OutputDevice.setActiveCamera(null);
|
onClicked: OutputDevice.setActiveCameraUrl("");
|
||||||
z: 0;
|
z: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,33 +34,23 @@ Item {
|
||||||
z: 999;
|
z: 999;
|
||||||
}
|
}
|
||||||
|
|
||||||
Cura.CameraView {
|
Cura.NetworkMJPGImage {
|
||||||
id: cameraImage
|
id: cameraImage
|
||||||
anchors.horizontalCenter: parent.horizontalCenter;
|
anchors.horizontalCenter: parent.horizontalCenter;
|
||||||
anchors.verticalCenter: parent.verticalCenter;
|
anchors.verticalCenter: parent.verticalCenter;
|
||||||
height: Math.round((imageHeight === 0 ? 600 * screenScaleFactor : imageHeight) * width / imageWidth);
|
height: Math.round((imageHeight === 0 ? 600 * screenScaleFactor : imageHeight) * width / imageWidth);
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
if (camera != null) {
|
if (cameraUrl != "") {
|
||||||
camera.start();
|
start();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (camera != null) {
|
if (cameraUrl != "") {
|
||||||
camera.stop();
|
stop();
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections
|
|
||||||
{
|
|
||||||
target: camera
|
|
||||||
onNewImage: {
|
|
||||||
if (cameraImage.visible) {
|
|
||||||
cameraImage.image = camera.latestImage;
|
|
||||||
cameraImage.update();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
source: cameraUrl
|
||||||
width: Math.min(imageWidth === 0 ? 800 * screenScaleFactor : imageWidth, maximumWidth);
|
width: Math.min(imageWidth === 0 ? 800 * screenScaleFactor : imageWidth, maximumWidth);
|
||||||
z: 1
|
z: 1
|
||||||
}
|
}
|
||||||
|
@ -68,7 +58,7 @@ Item {
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: cameraImage;
|
anchors.fill: cameraImage;
|
||||||
onClicked: {
|
onClicked: {
|
||||||
OutputDevice.setActiveCamera(null);
|
OutputDevice.setActiveCameraUrl("");
|
||||||
}
|
}
|
||||||
z: 1;
|
z: 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationM
|
||||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
|
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
|
||||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||||
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
|
||||||
from .SendMaterialJob import SendMaterialJob
|
from .SendMaterialJob import SendMaterialJob
|
||||||
|
@ -47,7 +46,7 @@ i18n_catalog = i18nCatalog("cura")
|
||||||
class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
printJobsChanged = pyqtSignal()
|
printJobsChanged = pyqtSignal()
|
||||||
activePrinterChanged = pyqtSignal()
|
activePrinterChanged = pyqtSignal()
|
||||||
activeCameraChanged = pyqtSignal()
|
activeCameraUrlChanged = pyqtSignal()
|
||||||
receivedPrintJobsChanged = 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.
|
# 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._latest_reply_handler = None # type: Optional[QNetworkReply]
|
||||||
self._sending_job = None
|
self._sending_job = None
|
||||||
|
|
||||||
self._active_camera = None # type: Optional[NetworkCamera]
|
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:
|
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)
|
self.writeStarted.emit(self)
|
||||||
|
@ -264,30 +263,21 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
def activePrinter(self) -> Optional[PrinterOutputModel]:
|
def activePrinter(self) -> Optional[PrinterOutputModel]:
|
||||||
return self._active_printer
|
return self._active_printer
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify=activeCameraChanged)
|
|
||||||
def activeCamera(self) -> Optional[NetworkCamera]:
|
|
||||||
return self._active_camera
|
|
||||||
|
|
||||||
@pyqtSlot(QObject)
|
@pyqtSlot(QObject)
|
||||||
def setActivePrinter(self, printer: Optional[PrinterOutputModel]) -> None:
|
def setActivePrinter(self, printer: Optional[PrinterOutputModel]) -> None:
|
||||||
if self._active_printer != printer:
|
if self._active_printer != printer:
|
||||||
if self._active_printer and self._active_printer.camera:
|
|
||||||
self._active_printer.camera.stop()
|
|
||||||
self._active_printer = printer
|
self._active_printer = printer
|
||||||
self.activePrinterChanged.emit()
|
self.activePrinterChanged.emit()
|
||||||
|
|
||||||
@pyqtSlot(QObject)
|
@pyqtProperty(QUrl, notify = activeCameraUrlChanged)
|
||||||
def setActiveCamera(self, camera: Optional[NetworkCamera]) -> None:
|
def activeCameraUrl(self) -> "QUrl":
|
||||||
if self._active_camera != camera:
|
return self._active_camera_url
|
||||||
if self._active_camera:
|
|
||||||
self._active_camera.stop()
|
|
||||||
|
|
||||||
self._active_camera = camera
|
@pyqtSlot(QUrl)
|
||||||
|
def setActiveCameraUrl(self, camera_url: "QUrl") -> None:
|
||||||
if self._active_camera:
|
if self._active_camera_url != camera_url:
|
||||||
self._active_camera.start()
|
self._active_camera_url = camera_url
|
||||||
|
self.activeCameraUrlChanged.emit()
|
||||||
self.activeCameraChanged.emit()
|
|
||||||
|
|
||||||
def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None:
|
def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None:
|
||||||
if self._progress_message:
|
if self._progress_message:
|
||||||
|
@ -548,7 +538,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
def _createPrinterModel(self, data: Dict[str, Any]) -> PrinterOutputModel:
|
def _createPrinterModel(self, data: Dict[str, Any]) -> PrinterOutputModel:
|
||||||
printer = PrinterOutputModel(output_controller = ClusterUM3PrinterOutputController(self),
|
printer = PrinterOutputModel(output_controller = ClusterUM3PrinterOutputController(self),
|
||||||
number_of_extruders = self._number_of_extruders)
|
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)
|
self._printers.append(printer)
|
||||||
return printer
|
return printer
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ 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 cura.Settings.ContainerManager import ContainerManager
|
from cura.Settings.ContainerManager import ContainerManager
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
@ -18,7 +17,7 @@ from UM.i18n import i18nCatalog
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
|
|
||||||
from PyQt5.QtNetwork import QNetworkRequest
|
from PyQt5.QtNetwork import QNetworkRequest
|
||||||
from PyQt5.QtCore import QTimer
|
from PyQt5.QtCore import QTimer, QUrl
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
from .LegacyUM3PrinterOutputController import LegacyUM3PrinterOutputController
|
from .LegacyUM3PrinterOutputController import LegacyUM3PrinterOutputController
|
||||||
|
@ -568,7 +567,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
# Quickest way to get the firmware version is to grab it from the zeroconf.
|
# 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")
|
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 = [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:
|
for extruder in self._printers[0].extruders:
|
||||||
extruder.activeMaterialChanged.connect(self.materialIdChanged)
|
extruder.activeMaterialChanged.connect(self.materialIdChanged)
|
||||||
extruder.hotendIDChanged.connect(self.hotendIdChanged)
|
extruder.hotendIDChanged.connect(self.hotendIdChanged)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue