Skip to content

Commit 32c7978

Browse files
authored
feat: add webcam experimental feature (#410)
* 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]>
1 parent e405e32 commit 32c7978

File tree

6 files changed

+292
-1
lines changed

6 files changed

+292
-1
lines changed

Constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,10 @@
1414
GIMAGE = "mks_gimage"
1515
IS_PREVIEW_ENCODED = "mks_is_preview_encoded"
1616

17+
#camera view parameters (experimental)
18+
IS_CAMERA_VIEW = "mks_is_camera_view"
19+
CAMERA_URL = "mks_camera_url"
20+
CAMERA_VIDEO_ZOOM = "mks_camera_video_zoom"
21+
1722
# Errors
1823
EXCEPTION_MESSAGE = "An exception occurred in network connection: %s"

MKSOutputDevice.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from PyQt6.QtWidgets import QFileDialog, QMessageBox
2020
from PyQt6.QtNetwork import QNetworkRequest, QTcpSocket
21-
from PyQt6.QtCore import QTimer, pyqtSignal, pyqtProperty, pyqtSlot, QCoreApplication, QByteArray
21+
from PyQt6.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty, pyqtSlot, QCoreApplication, QByteArray
2222
from queue import Queue
2323

2424
import re # For escaping characters in the settings.
@@ -166,6 +166,9 @@ def __init__(self, instance_id: str, address: str, properties: dict,
166166
# Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
167167
CuraApplication.getInstance().getCuraSceneController(
168168
).activeBuildPlateChanged.connect(self.CreateMKSController)
169+
#Camera view (experimental)
170+
# Connect to mataDataChanged event
171+
self._active_machine.metaDataChanged.connect(self._onMetaDataChanged)
169172

170173
_translations = {}
171174

@@ -245,6 +248,45 @@ def disconnect(self):
245248

246249
def getProperties(self):
247250
return self._properties
251+
252+
#camera view (experimental), begining#
253+
cameraSettingsChanged = pyqtSignal()
254+
255+
#Execute when metaData change
256+
def _onMetaDataChanged(self):
257+
self.cameraSettingsChanged.emit()
258+
259+
@pyqtProperty(bool, notify=cameraSettingsChanged)
260+
def isCameraView(self):
261+
global_container_stack = Application.getInstance().getGlobalContainerStack()
262+
if global_container_stack:
263+
meta_data = global_container_stack.getMetaData()
264+
if Constants.IS_CAMERA_VIEW in meta_data:
265+
return True
266+
return False
267+
268+
@pyqtProperty(QUrl, notify=cameraSettingsChanged)
269+
def cameraUrl(self):
270+
global_container_stack = Application.getInstance().getGlobalContainerStack()
271+
if global_container_stack:
272+
meta_data = global_container_stack.getMetaData()
273+
if Constants.CAMERA_URL in meta_data:
274+
camera_url = global_container_stack.getMetaDataEntry(Constants.CAMERA_URL)
275+
if camera_url:
276+
return QUrl(camera_url)
277+
return QUrl("")
278+
279+
@pyqtProperty(float, notify=cameraSettingsChanged)
280+
def cameraVideoZoom(self):
281+
global_container_stack = Application.getInstance().getGlobalContainerStack()
282+
if global_container_stack:
283+
meta_data = global_container_stack.getMetaData()
284+
if Constants.CAMERA_VIDEO_ZOOM in meta_data:
285+
camera_video_zoom = global_container_stack.getMetaDataEntry(Constants.CAMERA_VIDEO_ZOOM)
286+
if camera_video_zoom: # Cura will crush if not check is the value not None
287+
return float(camera_video_zoom)
288+
return float(1.0)
289+
#camera view (experimental), end#
248290

249291
@pyqtSlot(str, result=str)
250292
def getProperty(self, key):

MachineConfig.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,65 @@ def __init__(self, parent=None):
6666
printersChanged = pyqtSignal()
6767
printersTryToConnect = pyqtSignal()
6868

69+
#camera view (experimental), begining#
70+
@pyqtSlot(result=bool)
71+
def isCameraViewEnabled(self):
72+
global_container_stack = Application.getInstance().getGlobalContainerStack()
73+
if global_container_stack:
74+
meta_data = global_container_stack.getMetaData()
75+
if Constants.IS_CAMERA_VIEW in meta_data:
76+
return True
77+
return False
78+
79+
@pyqtSlot(str)
80+
def setCameraViewEnable(self, is_camera_view_enabled):
81+
global_container_stack = Application.getInstance().getGlobalContainerStack()
82+
if global_container_stack:
83+
if is_camera_view_enabled == "true":
84+
global_container_stack.setMetaDataEntry(Constants.IS_CAMERA_VIEW, is_camera_view_enabled)
85+
else:
86+
global_container_stack.setMetaDataEntry(Constants.IS_CAMERA_VIEW, None)
87+
global_container_stack.removeMetaDataEntry(Constants.IS_CAMERA_VIEW)
88+
89+
@pyqtSlot(result=str)
90+
def getCameraUrl(self):
91+
global_container_stack = Application.getInstance().getGlobalContainerStack()
92+
if global_container_stack:
93+
meta_data = global_container_stack.getMetaData()
94+
if Constants.CAMERA_URL in meta_data:
95+
return global_container_stack.getMetaDataEntry(Constants.CAMERA_URL)
96+
return ""
97+
98+
@pyqtSlot(str)
99+
def setCameraUrl(self, camera_url):
100+
global_container_stack = Application.getInstance().getGlobalContainerStack()
101+
if global_container_stack:
102+
if camera_url != "":
103+
global_container_stack.setMetaDataEntry(Constants.CAMERA_URL, camera_url)
104+
else:
105+
global_container_stack.setMetaDataEntry(Constants.CAMERA_URL, None)
106+
global_container_stack.removeMetaDataEntry(Constants.CAMERA_URL)
107+
108+
@pyqtSlot(result=str)
109+
def getCameraVideoZoom(self):
110+
global_container_stack = Application.getInstance().getGlobalContainerStack()
111+
if global_container_stack:
112+
meta_data = global_container_stack.getMetaData()
113+
if Constants.CAMERA_VIDEO_ZOOM in meta_data:
114+
return global_container_stack.getMetaDataEntry(Constants.CAMERA_VIDEO_ZOOM)
115+
return "1.0"
116+
117+
@pyqtSlot(str)
118+
def setCameraVideoZoom(self, camera_video_zoom):
119+
global_container_stack = Application.getInstance().getGlobalContainerStack()
120+
if global_container_stack:
121+
if camera_video_zoom != "1.0":
122+
global_container_stack.setMetaDataEntry(Constants.CAMERA_VIDEO_ZOOM, camera_video_zoom)
123+
else:
124+
global_container_stack.setMetaDataEntry(Constants.CAMERA_VIDEO_ZOOM, None)
125+
global_container_stack.removeMetaDataEntry(Constants.CAMERA_VIDEO_ZOOM)
126+
#camera view (experimental), end#
127+
69128
@pyqtProperty(str, constant=True)
70129
def pluginVersion(self) -> str:
71130
return self._plugin_version

i18n/mksplugin.pot

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,23 @@ msgctxt "@label"
275275
msgid "Switch to Monitor Tab after file upload"
276276
msgstr ""
277277

278+
#camera view (experimental), begining#
279+
#: qml/MachineConfig.qml:620
280+
msgctxt "@label"
281+
msgid "Camera view (experimental feature)"
282+
msgstr ""
283+
284+
#: qml/MachineConfig.qml:646
285+
msgctxt "@label"
286+
msgid "Camera URL (MJPG-streamer stream URL)"
287+
msgstr ""
288+
289+
#: qml/MachineConfig.qml:676
290+
msgctxt "@label"
291+
msgid "Camera video zoom (1.0 by default)"
292+
msgstr ""
293+
#camera view (experimental), end#
294+
278295
#: qml/MachineConfig.qml:631
279296
msgctxt "@label"
280297
msgid "MKS WiFi Plugin is active for this printer"

qml/MachineConfig.qml

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,102 @@ Cura.MachineAction {
604604
enabled: mksSupport.checked
605605
}
606606
}
607+
608+
//camera view (experimental), begining
609+
Row {
610+
anchors
611+
{
612+
left: parent.left
613+
right: parent.right
614+
}
615+
UM.Label {
616+
width: Math.round(parent.width * 0.5)
617+
height: monitorTabAutoOpen.height
618+
verticalAlignment: Text.AlignVCenter
619+
wrapMode: Text.WordWrap
620+
text: catalog.i18nc("@label", "Camera view (experimental feature)")
621+
622+
enabled: mksSupport.checked
623+
}
624+
UM.CheckBox {
625+
id: isCameraView
626+
checked: manager.isCameraViewEnabled()
627+
628+
onCheckedChanged: {
629+
manager.setCameraViewEnable(isCameraView.checked)
630+
}
631+
632+
enabled: mksSupport.checked
633+
}
634+
}
635+
Row {
636+
anchors
637+
{
638+
left: parent.left
639+
right: parent.right
640+
}
641+
UM.Label {
642+
width: Math.round(parent.width * 0.5)
643+
height: monitorTabAutoOpen.height
644+
verticalAlignment: Text.AlignVCenter
645+
wrapMode: Text.WordWrap
646+
text: catalog.i18nc("@label", "Camera URL (MJPG-streamer stream URL)")
647+
648+
enabled: isCameraView.checked
649+
}
650+
Cura.TextField {
651+
id: cameraUrl
652+
width: Math.round(parent.width * 0.5) - UM.Theme.getSize("default_margin").width
653+
maximumLength: 1024
654+
validator: RegularExpressionValidator
655+
{
656+
//Empty string or URL begins with http:// or https://
657+
regularExpression: /^$|(^((?:http:\/\/)|(?:https:\/\/))\S{0,}$)/
658+
}
659+
660+
text: manager.getCameraUrl()
661+
662+
onEditingFinished: {
663+
manager.setCameraUrl(cameraUrl.text)
664+
}
665+
666+
enabled: isCameraView.checked
667+
}
668+
}
669+
Row {
670+
anchors
671+
{
672+
left: parent.left
673+
right: parent.right
674+
}
675+
UM.Label {
676+
width: Math.round(parent.width * 0.5)
677+
height: monitorTabAutoOpen.height
678+
verticalAlignment: Text.AlignVCenter
679+
wrapMode: Text.WordWrap
680+
text: catalog.i18nc("@label", "Camera video zoom (1.0 by default)")
681+
682+
enabled: isCameraView.checked
683+
}
684+
Cura.TextField {
685+
id: cameraVideoZoom
686+
width: Math.round(parent.width * 0.5) - UM.Theme.getSize("default_margin").width
687+
maximumLength: 3
688+
validator: RegularExpressionValidator
689+
{
690+
regularExpression: /^(?:[0-9]).(?:[0-9])$/
691+
}
692+
693+
text: manager.getCameraVideoZoom()
694+
695+
onEditingFinished: {
696+
manager.setCameraVideoZoom(cameraVideoZoom.text)
697+
}
698+
699+
enabled: isCameraView.checked
700+
}
701+
}
702+
//camera view (experimental), end
607703
}
608704
}
609705
}

qml/MonitorItem.qml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,78 @@ Component
2525
UM.I18nCatalog { id: catalog; name:"mksplugin" }
2626
UM.I18nCatalog { id: cura_catalog; name: "cura"}
2727

28+
//camera view (experimental), begining
29+
Rectangle {
30+
Cura.NetworkMJPGImage {
31+
id: cameraImage
32+
33+
visible: OutputDevice.isCameraView
34+
source: OutputDevice.cameraUrl //MJPG-streamer video stream URL
35+
property real videoZoom: OutputDevice.cameraVideoZoom
36+
37+
property real videoWidth: Math.floor(imageWidth * videoZoom)
38+
property real videoHeight: Math.floor(imageHeight * videoZoom)
39+
property real modVideoWidth: Math.floor(videoWidth * (parent.height / videoHeight))
40+
property real modVideoHeight: Math.floor(videoHeight * (parent.width / videoWidth))
41+
42+
width: {
43+
if (videoWidth < parent.width) {
44+
if (videoHeight < parent.height) {
45+
return videoWidth
46+
} else {
47+
return modVideoWidth
48+
}
49+
} else {
50+
if (modVideoWidth < parent.width) {
51+
return modVideoWidth
52+
} else {
53+
return parent.width
54+
}
55+
}
56+
}
57+
height: {
58+
if (videoHeight < parent.height) {
59+
if (videoWidth < parent.width) {
60+
return videoHeight
61+
} else {
62+
return modVideoHeight
63+
}
64+
} else {
65+
if (modVideoHeight < parent.height) {
66+
return modVideoHeight
67+
} else {
68+
return parent.height
69+
}
70+
}
71+
}
72+
73+
anchors.horizontalCenter: parent.horizontalCenter
74+
anchors.verticalCenter: parent.verticalCenter
75+
76+
Component.onCompleted: {
77+
if (visible) {
78+
if(source === "") return; //check if camera URL is empty
79+
start();
80+
}
81+
}
82+
onVisibleChanged: {
83+
if (visible) {
84+
if(source === "") return; //check if camera URL is empty
85+
start();
86+
} else {
87+
stop();
88+
}
89+
}
90+
}
91+
92+
color: UM.Theme.getColor("detail_background")
93+
anchors.left: parent.left
94+
width: parent.width - (Math.floor(parent.width * 0.35) - ((Math.floor(parent.width * 0.35) % 2) ? 0 : 1))
95+
anchors.top: parent.top
96+
anchors.bottom: parent.bottom
97+
}
98+
//camera view (experimental), end
99+
28100
Rectangle {
29101
color: UM.Theme.getColor("main_background")
30102

0 commit comments

Comments
 (0)