diff --git a/Constants.py b/Constants.py index 0e15e9f..2cf5937 100644 --- a/Constants.py +++ b/Constants.py @@ -14,5 +14,10 @@ GIMAGE = "mks_gimage" IS_PREVIEW_ENCODED = "mks_is_preview_encoded" +#camera view parameters (experimental) +IS_CAMERA_VIEW = "mks_is_camera_view" +CAMERA_URL = "mks_camera_url" +CAMERA_VIDEO_ZOOM = "mks_camera_video_zoom" + # Errors EXCEPTION_MESSAGE = "An exception occurred in network connection: %s" diff --git a/MKSOutputDevice.py b/MKSOutputDevice.py index be12775..0890fee 100644 --- a/MKSOutputDevice.py +++ b/MKSOutputDevice.py @@ -18,7 +18,7 @@ from PyQt6.QtWidgets import QFileDialog, QMessageBox from PyQt6.QtNetwork import QNetworkRequest, QTcpSocket -from PyQt6.QtCore import QTimer, pyqtSignal, pyqtProperty, pyqtSlot, QCoreApplication, QByteArray +from PyQt6.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty, pyqtSlot, QCoreApplication, QByteArray from queue import Queue import re # For escaping characters in the settings. @@ -166,6 +166,9 @@ def __init__(self, instance_id: str, address: str, properties: dict, # Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged) CuraApplication.getInstance().getCuraSceneController( ).activeBuildPlateChanged.connect(self.CreateMKSController) + #Camera view (experimental) + # Connect to mataDataChanged event + self._active_machine.metaDataChanged.connect(self._onMetaDataChanged) _translations = {} @@ -245,6 +248,45 @@ def disconnect(self): def getProperties(self): return self._properties + + #camera view (experimental), begining# + cameraSettingsChanged = pyqtSignal() + + #Execute when metaData change + def _onMetaDataChanged(self): + self.cameraSettingsChanged.emit() + + @pyqtProperty(bool, notify=cameraSettingsChanged) + def isCameraView(self): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + meta_data = global_container_stack.getMetaData() + if Constants.IS_CAMERA_VIEW in meta_data: + return True + return False + + @pyqtProperty(QUrl, notify=cameraSettingsChanged) + def cameraUrl(self): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + meta_data = global_container_stack.getMetaData() + if Constants.CAMERA_URL in meta_data: + camera_url = global_container_stack.getMetaDataEntry(Constants.CAMERA_URL) + if camera_url: + return QUrl(camera_url) + return QUrl("") + + @pyqtProperty(float, notify=cameraSettingsChanged) + def cameraVideoZoom(self): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + meta_data = global_container_stack.getMetaData() + if Constants.CAMERA_VIDEO_ZOOM in meta_data: + camera_video_zoom = global_container_stack.getMetaDataEntry(Constants.CAMERA_VIDEO_ZOOM) + if camera_video_zoom: # Cura will crush if not check is the value not None + return float(camera_video_zoom) + return float(1.0) + #camera view (experimental), end# @pyqtSlot(str, result=str) def getProperty(self, key): diff --git a/MachineConfig.py b/MachineConfig.py index dedf557..e043b98 100644 --- a/MachineConfig.py +++ b/MachineConfig.py @@ -66,6 +66,65 @@ def __init__(self, parent=None): printersChanged = pyqtSignal() printersTryToConnect = pyqtSignal() + #camera view (experimental), begining# + @pyqtSlot(result=bool) + def isCameraViewEnabled(self): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + meta_data = global_container_stack.getMetaData() + if Constants.IS_CAMERA_VIEW in meta_data: + return True + return False + + @pyqtSlot(str) + def setCameraViewEnable(self, is_camera_view_enabled): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + if is_camera_view_enabled == "true": + global_container_stack.setMetaDataEntry(Constants.IS_CAMERA_VIEW, is_camera_view_enabled) + else: + global_container_stack.setMetaDataEntry(Constants.IS_CAMERA_VIEW, None) + global_container_stack.removeMetaDataEntry(Constants.IS_CAMERA_VIEW) + + @pyqtSlot(result=str) + def getCameraUrl(self): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + meta_data = global_container_stack.getMetaData() + if Constants.CAMERA_URL in meta_data: + return global_container_stack.getMetaDataEntry(Constants.CAMERA_URL) + return "" + + @pyqtSlot(str) + def setCameraUrl(self, camera_url): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + if camera_url != "": + global_container_stack.setMetaDataEntry(Constants.CAMERA_URL, camera_url) + else: + global_container_stack.setMetaDataEntry(Constants.CAMERA_URL, None) + global_container_stack.removeMetaDataEntry(Constants.CAMERA_URL) + + @pyqtSlot(result=str) + def getCameraVideoZoom(self): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + meta_data = global_container_stack.getMetaData() + if Constants.CAMERA_VIDEO_ZOOM in meta_data: + return global_container_stack.getMetaDataEntry(Constants.CAMERA_VIDEO_ZOOM) + return "1.0" + + @pyqtSlot(str) + def setCameraVideoZoom(self, camera_video_zoom): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + if camera_video_zoom != "1.0": + global_container_stack.setMetaDataEntry(Constants.CAMERA_VIDEO_ZOOM, camera_video_zoom) + else: + global_container_stack.setMetaDataEntry(Constants.CAMERA_VIDEO_ZOOM, None) + global_container_stack.removeMetaDataEntry(Constants.CAMERA_VIDEO_ZOOM) + #camera view (experimental), end# + @pyqtProperty(str, constant=True) def pluginVersion(self) -> str: return self._plugin_version diff --git a/i18n/mksplugin.pot b/i18n/mksplugin.pot index c850861..1992d20 100644 --- a/i18n/mksplugin.pot +++ b/i18n/mksplugin.pot @@ -275,6 +275,23 @@ msgctxt "@label" msgid "Switch to Monitor Tab after file upload" msgstr "" +#camera view (experimental), begining# +#: qml/MachineConfig.qml:620 +msgctxt "@label" +msgid "Camera view (experimental feature)" +msgstr "" + +#: qml/MachineConfig.qml:646 +msgctxt "@label" +msgid "Camera URL (MJPG-streamer stream URL)" +msgstr "" + +#: qml/MachineConfig.qml:676 +msgctxt "@label" +msgid "Camera video zoom (1.0 by default)" +msgstr "" +#camera view (experimental), end# + #: qml/MachineConfig.qml:631 msgctxt "@label" msgid "MKS WiFi Plugin is active for this printer" diff --git a/qml/MachineConfig.qml b/qml/MachineConfig.qml index df9f97d..9c69b2a 100644 --- a/qml/MachineConfig.qml +++ b/qml/MachineConfig.qml @@ -604,6 +604,102 @@ Cura.MachineAction { enabled: mksSupport.checked } } + + //camera view (experimental), begining + Row { + anchors + { + left: parent.left + right: parent.right + } + UM.Label { + width: Math.round(parent.width * 0.5) + height: monitorTabAutoOpen.height + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "Camera view (experimental feature)") + + enabled: mksSupport.checked + } + UM.CheckBox { + id: isCameraView + checked: manager.isCameraViewEnabled() + + onCheckedChanged: { + manager.setCameraViewEnable(isCameraView.checked) + } + + enabled: mksSupport.checked + } + } + Row { + anchors + { + left: parent.left + right: parent.right + } + UM.Label { + width: Math.round(parent.width * 0.5) + height: monitorTabAutoOpen.height + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "Camera URL (MJPG-streamer stream URL)") + + enabled: isCameraView.checked + } + Cura.TextField { + id: cameraUrl + width: Math.round(parent.width * 0.5) - UM.Theme.getSize("default_margin").width + maximumLength: 1024 + validator: RegularExpressionValidator + { + //Empty string or URL begins with http:// or https:// + regularExpression: /^$|(^((?:http:\/\/)|(?:https:\/\/))\S{0,}$)/ + } + + text: manager.getCameraUrl() + + onEditingFinished: { + manager.setCameraUrl(cameraUrl.text) + } + + enabled: isCameraView.checked + } + } + Row { + anchors + { + left: parent.left + right: parent.right + } + UM.Label { + width: Math.round(parent.width * 0.5) + height: monitorTabAutoOpen.height + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "Camera video zoom (1.0 by default)") + + enabled: isCameraView.checked + } + Cura.TextField { + id: cameraVideoZoom + width: Math.round(parent.width * 0.5) - UM.Theme.getSize("default_margin").width + maximumLength: 3 + validator: RegularExpressionValidator + { + regularExpression: /^(?:[0-9]).(?:[0-9])$/ + } + + text: manager.getCameraVideoZoom() + + onEditingFinished: { + manager.setCameraVideoZoom(cameraVideoZoom.text) + } + + enabled: isCameraView.checked + } + } + //camera view (experimental), end } } } diff --git a/qml/MonitorItem.qml b/qml/MonitorItem.qml index c0109a3..2125671 100644 --- a/qml/MonitorItem.qml +++ b/qml/MonitorItem.qml @@ -25,6 +25,78 @@ Component UM.I18nCatalog { id: catalog; name:"mksplugin" } UM.I18nCatalog { id: cura_catalog; name: "cura"} + //camera view (experimental), begining + Rectangle { + Cura.NetworkMJPGImage { + id: cameraImage + + visible: OutputDevice.isCameraView + source: OutputDevice.cameraUrl //MJPG-streamer video stream URL + property real videoZoom: OutputDevice.cameraVideoZoom + + property real videoWidth: Math.floor(imageWidth * videoZoom) + property real videoHeight: Math.floor(imageHeight * videoZoom) + property real modVideoWidth: Math.floor(videoWidth * (parent.height / videoHeight)) + property real modVideoHeight: Math.floor(videoHeight * (parent.width / videoWidth)) + + width: { + if (videoWidth < parent.width) { + if (videoHeight < parent.height) { + return videoWidth + } else { + return modVideoWidth + } + } else { + if (modVideoWidth < parent.width) { + return modVideoWidth + } else { + return parent.width + } + } + } + height: { + if (videoHeight < parent.height) { + if (videoWidth < parent.width) { + return videoHeight + } else { + return modVideoHeight + } + } else { + if (modVideoHeight < parent.height) { + return modVideoHeight + } else { + return parent.height + } + } + } + + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + + Component.onCompleted: { + if (visible) { + if(source === "") return; //check if camera URL is empty + start(); + } + } + onVisibleChanged: { + if (visible) { + if(source === "") return; //check if camera URL is empty + start(); + } else { + stop(); + } + } + } + + color: UM.Theme.getColor("detail_background") + anchors.left: parent.left + width: parent.width - (Math.floor(parent.width * 0.35) - ((Math.floor(parent.width * 0.35) % 2) ? 0 : 1)) + anchors.top: parent.top + anchors.bottom: parent.bottom + } + //camera view (experimental), end + Rectangle { color: UM.Theme.getColor("main_background")