Merge pull request #4656 from fieldOfView/fix_network_camera_memory_leak

[3.6] Fix memory leak in print monitor
This commit is contained in:
Lipu Fei 2018-10-29 14:12:31 +01:00 committed by GitHub
commit 1623f66c93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 209 additions and 279 deletions

View file

@ -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)

View file

@ -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
@ -114,6 +113,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
@ -523,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)
@ -947,6 +947,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")
@ -959,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,

View file

@ -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)

View file

@ -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()

View 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()

View file

@ -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

View file

@ -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
@ -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):
@ -25,7 +24,7 @@ class PrinterOutputModel(QObject):
keyChanged = pyqtSignal()
printerTypeChanged = pyqtSignal()
buildplateChanged = pyqtSignal()
cameraChanged = pyqtSignal()
cameraUrlChanged = pyqtSignal()
configurationChanged = pyqtSignal()
canUpdateFirmwareChanged = pyqtSignal()
@ -50,16 +49,20 @@ class PrinterOutputModel(QObject):
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
self._extruders]
self._camera = None # type: Optional[NetworkCamera]
self._camera_url = QUrl() # type: 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: "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:
@ -70,10 +73,6 @@ class PrinterOutputModel(QObject):
def isPreheating(self) -> bool:
return self._is_preheating
@pyqtProperty(QObject, notify=cameraChanged)
def camera(self) -> Optional["NetworkCamera"]:
return self._camera
@pyqtProperty(str, notify = printerTypeChanged)
def type(self) -> str:
return self._printer_type

View file

@ -31,10 +31,10 @@ Rectangle {
anchors.fill: parent;
hoverEnabled: true;
onClicked: {
if (OutputDevice.activeCamera !== null) {
OutputDevice.setActiveCamera(null)
if (OutputDevice.activeCameraUrl != "") {
OutputDevice.setActiveCameraUrl("");
} else {
OutputDevice.setActiveCamera(modelData.camera);
OutputDevice.setActiveCameraUrl(modelData.cameraUrl);
}
}
}

View file

@ -16,7 +16,7 @@ Component {
height: maximumHeight;
onVisibleChanged: {
if (monitorFrame != null && !monitorFrame.visible) {
OutputDevice.setActiveCamera(null);
OutputDevice.setActiveCameraUrl("");
}
}
width: maximumWidth;
@ -125,8 +125,8 @@ Component {
PrinterVideoStream {
anchors.fill: parent;
camera: OutputDevice.activeCamera;
visible: OutputDevice.activeCamera != null;
cameraUrl: OutputDevice.activeCameraUrl;
visible: OutputDevice.activeCameraUrl != "";
}
}
}

View file

@ -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();
}
}
}
}
}
}

View file

@ -8,7 +8,7 @@ import UM 1.3 as UM
import Cura 1.0 as Cura
Item {
property var camera: null;
property var cameraUrl: "";
Rectangle {
anchors.fill:parent;
@ -18,7 +18,7 @@ Item {
MouseArea {
anchors.fill: parent;
onClicked: OutputDevice.setActiveCamera(null);
onClicked: OutputDevice.setActiveCameraUrl("");
z: 0;
}
@ -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 != "") {
start();
}
} else {
if (camera != null) {
camera.stop();
}
}
}
Connections
{
target: camera
onNewImage: {
if (cameraImage.visible) {
cameraImage.image = camera.latestImage;
cameraImage.update();
if (cameraUrl != "") {
stop();
}
}
}
source: cameraUrl
width: Math.min(imageWidth === 0 ? 800 * screenScaleFactor : imageWidth, maximumWidth);
z: 1
}
@ -68,7 +58,7 @@ Item {
MouseArea {
anchors.fill: cameraImage;
onClicked: {
OutputDevice.setActiveCamera(null);
OutputDevice.setActiveCameraUrl("");
}
z: 1;
}

View file

@ -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 = 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)
@ -264,30 +263,21 @@ 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
@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()
@pyqtProperty(QUrl, notify = activeCameraUrlChanged)
def activeCameraUrl(self) -> "QUrl":
return self._active_camera_url
self._active_camera = camera
if self._active_camera:
self._active_camera.start()
self.activeCameraChanged.emit()
@pyqtSlot(QUrl)
def setActiveCameraUrl(self, camera_url: "QUrl") -> None:
if self._active_camera_url != camera_url:
self._active_camera_url = camera_url
self.activeCameraUrlChanged.emit()
def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None:
if self._progress_message:
@ -548,7 +538,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

View file

@ -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
@ -18,7 +17,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 +567,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)