Skip to content

Commit

Permalink
feat: add webcam experimental feature (#410)
Browse files Browse the repository at this point in the history
* feat: add webcam function
* feat: add webcam experimental feature, changes: empty camera url by default and NetworkMJPGImage update when settings are changed
* feat: add webcam experimental feature, changes: made simple cleanup

---------

Signed-off-by: Iskander Sultanov <[email protected]>
  • Loading branch information
BigIskander authored Mar 12, 2024
1 parent e405e32 commit 32c7978
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 1 deletion.
5 changes: 5 additions & 0 deletions Constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
44 changes: 43 additions & 1 deletion MKSOutputDevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 = {}

Expand Down Expand Up @@ -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):
Expand Down
59 changes: 59 additions & 0 deletions MachineConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions i18n/mksplugin.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
96 changes: 96 additions & 0 deletions qml/MachineConfig.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Expand Down
72 changes: 72 additions & 0 deletions qml/MonitorItem.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down

0 comments on commit 32c7978

Please sign in to comment.