Skip to content

Commit

Permalink
Webcam: rotation & mirror (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
emtrax-ltd committed Nov 28, 2022
1 parent 77f8a51 commit 43d76d8
Show file tree
Hide file tree
Showing 8 changed files with 275 additions and 62 deletions.
18 changes: 17 additions & 1 deletion MoonrakerConnection/MoonrakerMachineAction.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ def _onGlobalContainerStackChanged(self) -> None:
self.settingsTranslateOutputChanged.emit()
self.settingsTranslateRemoveChanged.emit()
self.settingsCameraUrlChanged.emit()
self.settingsCameraImageRotationChanged.emit()
self.settingsCameraImageMirrorChanged.emit()

def _onContainerAdded(self, container) -> None:
# Add this action as a supported action to all machine definitions
Expand All @@ -64,6 +66,8 @@ def _reset(self) -> None:
self.settingsTranslateOutputChanged.emit()
self.settingsTranslateRemoveChanged.emit()
self.settingsCameraUrlChanged.emit()
self.settingsCameraImageRotationChanged.emit()
self.settingsCameraImageMirrorChanged.emit()

settingsUrlChanged = pyqtSignal()
settingsApiKeyChanged = pyqtSignal()
Expand All @@ -79,6 +83,8 @@ def _reset(self) -> None:
settingsTranslateOutputChanged = pyqtSignal()
settingsTranslateRemoveChanged = pyqtSignal()
settingsCameraUrlChanged = pyqtSignal()
settingsCameraImageRotationChanged = pyqtSignal()
settingsCameraImageMirrorChanged = pyqtSignal()

@pyqtProperty(str, notify = settingsUrlChanged)
def settingsUrl(self) -> Optional[str]:
Expand Down Expand Up @@ -108,7 +114,7 @@ def settingsFrontendUrl(self) -> Optional[str]:
@pyqtProperty(str, notify = settingsOutputFormatChanged)
def settingsOutputFormat(self) -> Optional[str]:
config = getConfig()
return config.get("output_format", "gcode" if config else "gcode")
return config.get("output_format", "gcode") if config else "gcode"

@pyqtProperty(bool, notify = settingsUploadDialogChanged)
def settingsUploadDialog(self) -> Optional[bool]:
Expand Down Expand Up @@ -150,6 +156,16 @@ def settingsCameraUrl(self) -> Optional[str]:
config = getConfig()
return config.get("camera_url", "") if config else ""

@pyqtProperty(str, notify = settingsCameraImageRotationChanged)
def settingsCameraImageRotation(self) -> Optional[str]:
config = getConfig()
rotation = config.get("camera_image_rotation", "0") if config else "0"
return rotation if rotation == "90" or rotation == "180" or rotation == "270" else "0"

@pyqtProperty(bool, notify = settingsCameraImageMirrorChanged)
def settingsCameraImageMirror(self) -> Optional[bool]:
config = getConfig()
return config.get("camera_image_mirror", False) if config else False

@pyqtSlot(QVariant)
def saveConfig(self, paramsQJSValObj):
Expand Down
8 changes: 6 additions & 2 deletions MoonrakerConnection/MoonrakerOutputDevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionType
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel

from UM.i18n import i18nCatalog
from UM.Logger import Logger
Expand All @@ -28,6 +27,7 @@
from UM.OutputDevice import OutputDeviceError

from .MoonrakerOutputController import MoonrakerOutputController
from .MoonrakerOutputModel import MoonrakerOutputModel
from .MoonrakerSettings import getConfig, saveConfig, validateUrl

try:
Expand All @@ -53,7 +53,7 @@ def __init__(self, deviceId: str, canConnect: bool = True) -> None:
super().__init__(device_id = "MoonrakerOutputDevice@" + deviceId, connection_type = ConnectionType.NetworkConnection if canConnect else ConnectionType.NotConnected)
# init controller and model for output
globalContainerStack = CuraApplication.getInstance().getGlobalContainerStack()
self._printers = [PrinterOutputModel(output_controller = MoonrakerOutputController(self), number_of_extruders = globalContainerStack.getProperty("machine_extruder_count", "value"))];
self._printers = [MoonrakerOutputModel(output_controller = MoonrakerOutputController(self), number_of_extruders = globalContainerStack.getProperty("machine_extruder_count", "value"))];
Logger.log("d", "number_of_extruders: {}".format(globalContainerStack.getProperty("machine_extruder_count", "value")))
self._printers[0].updateName(globalContainerStack.getName());
self._printers[0].updateUniqueName(globalContainerStack.getId())
Expand Down Expand Up @@ -149,6 +149,8 @@ def updateConfig(self, config: dict = None) -> None:
self._translateOutput = self._config.get("trans_output", "")
self._translateRemove = self._config.get("trans_remove", "")
self._cameraUrl = self._config.get("camera_url", "").strip()
self._cameraImageRotation = self._config.get("camera_image_rotation", "0")
self._cameraImageMirror = self._config.get("camera_image_mirror", False)

# Configure address and webcam
self._address = self._url
Expand All @@ -165,6 +167,8 @@ def updateConfig(self, config: dict = None) -> None:
self.activePrinter.setCameraUrl(cameraUrl)
else:
self.activePrinter.setCameraUrl(QUrl())
self.activePrinter.setCameraImageRotation(self._cameraImageRotation)
self.activePrinter.setCameraImageMirror(self._cameraImageMirror)

# Configure ui components
globalContainerStack = CuraApplication.getInstance().getGlobalContainerStack()
Expand Down
41 changes: 41 additions & 0 deletions MoonrakerConnection/MoonrakerOutputModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
USE_QT5 = False
try:
from PyQt6.QtCore import pyqtProperty, pyqtSignal
except ImportError:
from PyQt5.QtCore import pyqtProperty, pyqtSignal
USE_QT5 = True

from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel

from UM.Logger import Logger

from .MoonrakerOutputController import MoonrakerOutputController

class MoonrakerOutputModel(PrinterOutputModel):

cameraImageRotationChanged = pyqtSignal()
cameraImageMirrorChanged = pyqtSignal()

def __init__(self, output_controller: MoonrakerOutputController, number_of_extruders: int = 1) -> None:
super().__init__(output_controller, number_of_extruders)
self._camera_image_rotation = "0"
self._camera_image_mirror = False
Logger.log("d", "MoonrakerOutputModel [number_of_extruders: {}] created.".format(number_of_extruders))

def setCameraImageRotation(self, camera_image_rotation: str) -> None:
if self._camera_image_rotation != camera_image_rotation:
self._camera_image_rotation = camera_image_rotation
self.cameraImageRotationChanged.emit()

@pyqtProperty(str, fset = setCameraImageRotation, notify = cameraImageRotationChanged)
def cameraImageRotation(self) -> str:
return self._camera_image_rotation

def setCameraImageMirror(self, camera_image_mirror: bool) -> None:
if self._camera_image_mirror != camera_image_mirror:
self._camera_image_mirror = camera_image_mirror
self.cameraImageMirrorChanged.emit()

@pyqtProperty(bool, fset = setCameraImageMirror, notify = cameraImageMirrorChanged)
def cameraImageMirror(self) -> str:
return self._camera_image_mirror
4 changes: 2 additions & 2 deletions MoonrakerConnection/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"name": "Klipper/Moonraker Connection",
"author": "emtrax",
"description": "Upload and Print with Klipper via Moonraker API.",
"version": "1.6.4",
"supported_sdk_versions": ["7.3.0", "7.4.0", "7.5.0", "8.0.0"]
"version": "1.6.5",
"supported_sdk_versions": ["7.3.0", "7.4.0", "7.5.0", "7.6.0", "7.7.0", "7.8.0", "7.9.0", "8.0.0", "8.1.0", "8.2.0"]
}
93 changes: 81 additions & 12 deletions MoonrakerConnection/resources/qml/qt5/MoonrakerConfiguration.qml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ Cura.MachineAction {
return outputFormatUfp.checked ? "ufp" : "gcode"
}

function cameraImageRotation() {
return cameraImageRotation90.checked ? "90" : cameraImageRotation180.checked ? "180" : cameraImageRotation270.checked ? "270" : "0"
}

function updateConfig() {
manager.saveConfig({
url: urlField.text,
Expand All @@ -36,7 +40,9 @@ Cura.MachineAction {
trans_input: translateInputField.text,
trans_output: translateOutputField.text,
trans_remove: translateRemoveField.text,
camera_url: cameraUrlField.text
camera_url: cameraUrlField.text,
camera_image_rotation: cameraImageRotation(),
camera_image_mirror: cameraImageMirror.checked
})
}

Expand Down Expand Up @@ -378,7 +384,7 @@ Cura.MachineAction {

text: catalog.i18nc("@label", "Automatic start of print job after upload")
checked: manager.settingsUploadStartPrintJob
visible: uploadDialogOverride.checked
visible: uploadDialogBypass.checked
onClicked: { updateConfig() }
}
Cura.CheckBox {
Expand All @@ -401,7 +407,7 @@ Cura.MachineAction {
font: UM.Theme.getFont("default")
text: catalog.i18nc("@label", "Auto hide messagebox for successful upload (30 seconds)")
checked: manager.settingsUploadAutohideMessagebox
onClicked: { updateConfig() }
onClicked: { updateConfig() }
}

Item {
Expand Down Expand Up @@ -531,27 +537,90 @@ Cura.MachineAction {
width: parent.width
height: 10
}
RowLayout {
width: parent.width
Label {
x: 15
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
text: catalog.i18nc("@label", "Camera")
renderType: Text.NativeRendering
}

Label {
text: catalog.i18nc("@label", "Camera (URL - absolute or path relative to Connection-URL)")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
}
Label {
x: 25
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
text: catalog.i18nc("@label", "URL (absolute or path relative to Connection-URL)")
renderType: Text.NativeRendering
}
Cura.TextField {
id: cameraUrlField

width: parent.width - 40
x: 25
x: 35
text: manager.settingsCameraUrl
maximumLength: 1024
onEditingFinished: { updateConfig() }
}

ButtonGroup {
id: cameraImageRotationValue
}
RowLayout {
x: 25

Cura.RadioButton {
ButtonGroup.group: cameraImageRotationValue

id: cameraImageRotation0

text: catalog.i18nc("@label", "")
checked: !(manager.settingsCameraImageRotation == "90" || manager.settingsCameraImageRotation == "180" || manager.settingsCameraImageRotation == "270")
onClicked: { updateConfig() }
}
Cura.RadioButton {
ButtonGroup.group: cameraImageRotationValue

id: cameraImageRotation90

text: catalog.i18nc("@label", "90°")
checked: manager.settingsCameraImageRotation == "90"
onClicked: { updateConfig() }
}
Cura.RadioButton {
ButtonGroup.group: cameraImageRotationValue

id: cameraImageRotation180

text: catalog.i18nc("@label", "180°")
checked: manager.settingsCameraImageRotation == "180"
onClicked: { updateConfig() }
}
Cura.RadioButton {
ButtonGroup.group: cameraImageRotationValue

id: cameraImageRotation270

text: catalog.i18nc("@label", "270°")
checked: manager.settingsCameraImageRotation == "270"
onClicked: { updateConfig() }
}
Label {
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
text: catalog.i18nc("@label", " Rotation")
renderType: Text.NativeRendering
}
}
Cura.CheckBox {
id: cameraImageMirror

x: 25
height: UM.Theme.getSize("checkbox").height
font: UM.Theme.getFont("default")
text: catalog.i18nc("@label", "Mirror")
checked: manager.settingsCameraImageMirror
onClicked: { updateConfig() }
}
}
}

Expand Down
47 changes: 29 additions & 18 deletions MoonrakerConnection/resources/qml/qt5/MoonrakerMonitor.qml
Original file line number Diff line number Diff line change
Expand Up @@ -29,55 +29,66 @@ Component {
radius: UM.Theme.getSize("default_radius").width
cornerSide: Cura.RoundedRectangle.Direction.Left

property bool cameraConfigured: OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null && OutputDevice.activePrinter.cameraUrl != ""

Label {
id: cameraLabel

anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
}
color: UM.Theme.getColor(OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null && OutputDevice.activePrinter.cameraUrl != "" ? "text" : "text_inactive")
color: UM.Theme.getColor(parent.cameraConfigured ? "text" : "text_inactive")
font: UM.Theme.getFont("large_bold")
text: OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null && OutputDevice.activePrinter.cameraUrl != "" ? "Camera" : "Camera not configured"
text: parent.cameraConfigured ? "Camera" : "Camera not configured"
}
Label {
id: cameraLabelUrl

visible: OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null && OutputDevice.activePrinter.cameraUrl != ""
visible: parent.cameraConfigured
anchors {
horizontalCenter: cameraLabel.horizontalCenter;
top: cameraLabel.bottom;
}
color: UM.Theme.getColor("text_inactive")
font: UM.Theme.getFont("small")
text: "Url: " + (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null ? OutputDevice.activePrinter.cameraUrl : "Null")
text: "Url: " + (parent.cameraConfigured ? OutputDevice.activePrinter.cameraUrl : "None")
}

Cura.NetworkMJPGImage {
property real scale: Math.min(Math.min((parent.width - 2 * UM.Theme.getSize("default_margin").width) / imageWidth, (parent.height - 2 * UM.Theme.getSize("default_margin").height) / imageHeight), 2);
Cura.NetworkMJPGImage {
property bool imageRotated: OutputDevice.activePrinter.cameraImageRotation == "90" || OutputDevice.activePrinter.cameraImageRotation == "270"
property real maxViewWidth: parent.width - 2 * UM.Theme.getSize("default_margin").width
property real maxViewHeight: parent.height - 2 * UM.Theme.getSize("default_margin").height
property real scaleFactor: {
if (imageRotated) {
Math.min(Math.min(maxViewWidth / imageHeight, maxViewHeight / imageWidth), 2)
} else {
Math.min(Math.min(maxViewWidth / imageWidth, maxViewHeight / imageHeight), 2)
}
}

id: cameraImage;
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
}
width: Math.floor(imageWidth * scale)
height: Math.floor(imageHeight * scale)
source: OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null ? OutputDevice.activePrinter.cameraUrl : ""
width: Math.floor(imageWidth * scaleFactor)
height: Math.floor(imageHeight * scaleFactor)
source: parent.cameraConfigured ? OutputDevice.activePrinter.cameraUrl : ""
rotation: OutputDevice.activePrinter.cameraImageRotation
mirror: OutputDevice.activePrinter.cameraImageMirror
onVisibleChanged: {
if (visible) {
if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) {
cameraImage.start();
}
} else {
if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) {
cameraImage.stop();
if (source != "") {
if (visible) {
start();
} else {
stop();
}
}
}
Component.onCompleted: {
if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) {
cameraImage.start();
if (source != "") {
start();
}
}
}
Expand Down
Loading

0 comments on commit 43d76d8

Please sign in to comment.