feat(Webcam): add a optional overlay for IDEX calibration (#2053)
This commit is contained in:
parent
34f3f08bd6
commit
04433fc397
@ -48,11 +48,11 @@ body {
|
|||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-abs {
|
.position-absolute {
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-rel {
|
.position-relative {
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,9 +45,9 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row dense>
|
<v-row dense>
|
||||||
<v-col cols="3" class="p-rel">
|
<v-col cols="3" class="position-relative">
|
||||||
<v-btn
|
<v-btn
|
||||||
class="btnMinWidthAuto fill-width p-abs"
|
class="btnMinWidthAuto fill-width position-absolute"
|
||||||
style="top: -50%; width: calc(100% - 8px)"
|
style="top: -50%; width: calc(100% - 8px)"
|
||||||
:disabled="
|
:disabled="
|
||||||
!xAxisHomed ||
|
!xAxisHomed ||
|
||||||
@ -72,9 +72,9 @@
|
|||||||
<v-icon>{{ mdiChevronDown }}</v-icon>
|
<v-icon>{{ mdiChevronDown }}</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="3" class="p-rel">
|
<v-col cols="3" class="position-relative">
|
||||||
<v-btn
|
<v-btn
|
||||||
class="btnMinWidthAuto fill-width p-abs"
|
class="btnMinWidthAuto fill-width position-absolute"
|
||||||
style="top: -50%; width: calc(100% - 8px)"
|
style="top: -50%; width: calc(100% - 8px)"
|
||||||
:disabled="
|
:disabled="
|
||||||
!xAxisHomed ||
|
!xAxisHomed ||
|
||||||
|
@ -143,6 +143,58 @@
|
|||||||
:label="$t('Settings.WebcamsTab.Vertically')" />
|
:label="$t('Settings.WebcamsTab.Vertically')" />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
<template v-if="nozzleCrosshairAvialable">
|
||||||
|
<v-row>
|
||||||
|
<v-col class="pt-3 pb-3">
|
||||||
|
<div class="v-label v-label--active theme--dark text-subtitle-1">
|
||||||
|
{{ $t('Settings.WebcamsTab.NozzleCrosshair') }}:
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row class="mt-0">
|
||||||
|
<v-col class="py-0">
|
||||||
|
<v-checkbox
|
||||||
|
v-model="nozzleCrosshair"
|
||||||
|
class="mt-1"
|
||||||
|
hide-details
|
||||||
|
:label="$t('Settings.WebcamsTab.Enable')" />
|
||||||
|
</v-col>
|
||||||
|
<v-col v-if="nozzleCrosshair" class="py-0">
|
||||||
|
<v-menu bottom left offset-y :close-on-content-click="false">
|
||||||
|
<template #activator="{ on, attrs }">
|
||||||
|
<v-btn
|
||||||
|
v-bind="attrs"
|
||||||
|
:color="nozzleCrosshairColor"
|
||||||
|
class="minwidth-0 px-5"
|
||||||
|
small
|
||||||
|
v-on="on" />
|
||||||
|
</template>
|
||||||
|
<v-color-picker
|
||||||
|
:value="nozzleCrosshairColor"
|
||||||
|
hide-mode-switch
|
||||||
|
mode="rgba"
|
||||||
|
@update:color="updateLogoColor" />
|
||||||
|
</v-menu>
|
||||||
|
<div
|
||||||
|
class="v-label v-label--active theme--dark text-subtitle-1 d-inline-block ml-2 mt-2">
|
||||||
|
{{ $t('Settings.WebcamsTab.Color') }}
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="nozzleCrosshair">
|
||||||
|
<v-col>
|
||||||
|
<v-slider
|
||||||
|
v-model="nozzleCrosshairSize"
|
||||||
|
:max="1"
|
||||||
|
:min="0.01"
|
||||||
|
:step="0.01"
|
||||||
|
thumb-label
|
||||||
|
thumb-size="24"
|
||||||
|
hide-details
|
||||||
|
:label="$t('Settings.WebcamsTab.Size')" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col class="col-12 col-sm-6 text-center" align-self="center">
|
<v-col class="col-12 col-sm-6 text-center" align-self="center">
|
||||||
<webcam-wrapper :webcam="webcam" page="settings" />
|
<webcam-wrapper :webcam="webcam" page="settings" />
|
||||||
@ -311,6 +363,52 @@ export default class WebcamForm extends Mixins(BaseMixin, WebcamMixin) {
|
|||||||
this.webcam.extra_data.enableAudio = newVal
|
this.webcam.extra_data.enableAudio = newVal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get nozzleCrosshairAvialable() {
|
||||||
|
return ['mjpegstreamer', 'mjpegstreamer-adaptive', 'webrtc-camerastreamer'].includes(this.webcam.service)
|
||||||
|
}
|
||||||
|
|
||||||
|
get nozzleCrosshair() {
|
||||||
|
return this.webcam.extra_data?.nozzleCrosshair ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
set nozzleCrosshair(newVal) {
|
||||||
|
const extraData = { ...(this.webcam.extra_data ?? {}) }
|
||||||
|
extraData.nozzleCrosshair = newVal
|
||||||
|
|
||||||
|
this.webcam.extra_data = extraData
|
||||||
|
}
|
||||||
|
|
||||||
|
get nozzleCrosshairColor() {
|
||||||
|
return this.webcam.extra_data?.nozzleCrosshairColor ?? '#ff0000'
|
||||||
|
}
|
||||||
|
|
||||||
|
set nozzleCrosshairColor(newVal: string) {
|
||||||
|
const extraData = { ...(this.webcam.extra_data ?? {}) }
|
||||||
|
extraData.nozzleCrosshairColor = newVal
|
||||||
|
|
||||||
|
this.webcam.extra_data = extraData
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLogoColor(color: string | { hex: string }) {
|
||||||
|
if (typeof color === 'object') {
|
||||||
|
this.nozzleCrosshairColor = color.hex
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nozzleCrosshairColor = color
|
||||||
|
}
|
||||||
|
|
||||||
|
get nozzleCrosshairSize() {
|
||||||
|
return this.webcam.extra_data?.nozzleCrosshairSize ?? 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
set nozzleCrosshairSize(newVal: number) {
|
||||||
|
const extraData = { ...(this.webcam.extra_data ?? {}) }
|
||||||
|
extraData.nozzleCrosshairSize = newVal
|
||||||
|
|
||||||
|
this.webcam.extra_data = extraData
|
||||||
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.oldWebcamName = this.webcam.name
|
this.oldWebcamName = this.webcam.name
|
||||||
}
|
}
|
||||||
|
94
src/components/webcams/WebcamNozzleCrosshair.vue
Normal file
94
src/components/webcams/WebcamNozzleCrosshair.vue
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="container" class="crosshair-container">
|
||||||
|
<div class="line horizontal" :style="styleLines" />
|
||||||
|
<div class="line vertical" :style="styleLines" />
|
||||||
|
<div class="circle" :style="styleCircle" />
|
||||||
|
<resize-observer @notify="handleResize" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Component from 'vue-class-component'
|
||||||
|
import { Mixins, Prop, Ref } from 'vue-property-decorator'
|
||||||
|
import BaseMixin from '@/components/mixins/base'
|
||||||
|
import { GuiWebcamStateWebcam } from '@/store/gui/webcams/types'
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class WebcamWrapper extends Mixins(BaseMixin) {
|
||||||
|
@Prop({ type: Object, required: true }) webcam!: GuiWebcamStateWebcam
|
||||||
|
@Ref() container!: HTMLDivElement
|
||||||
|
|
||||||
|
clientHeight = 0
|
||||||
|
|
||||||
|
get color() {
|
||||||
|
return this.webcam.extra_data?.nozzleCrosshairColor ?? '#ff0000'
|
||||||
|
}
|
||||||
|
|
||||||
|
get styleLines() {
|
||||||
|
return {
|
||||||
|
backgroundColor: this.color,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get styleCircle() {
|
||||||
|
const nozzleCrosshairSize = this.webcam.extra_data?.nozzleCrosshairSize ?? 0.1
|
||||||
|
const size = this.clientHeight * nozzleCrosshairSize
|
||||||
|
|
||||||
|
return {
|
||||||
|
borderColor: this.color,
|
||||||
|
width: `${size}px`,
|
||||||
|
height: `${size}px`,
|
||||||
|
marginLeft: `-${size / 2}px`,
|
||||||
|
marginTop: `-${size / 2}px`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.handleResize()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResize() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.clientHeight = this.container.clientHeight
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.crosshair-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
position: absolute;
|
||||||
|
background-color: #ff0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal {
|
||||||
|
height: 1px;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical {
|
||||||
|
left: 50%;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
position: absolute;
|
||||||
|
border: 1px solid #ff0000;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
@ -12,6 +12,7 @@
|
|||||||
<span v-if="showFpsCounter && status === 'connected'" class="webcamFpsOutput">
|
<span v-if="showFpsCounter && status === 'connected'" class="webcamFpsOutput">
|
||||||
{{ $t('Panels.WebcamPanel.FPS') }}: {{ fpsOutput }}
|
{{ $t('Panels.WebcamPanel.FPS') }}: {{ fpsOutput }}
|
||||||
</span>
|
</span>
|
||||||
|
<webcam-nozzle-crosshair v-if="showNozzleCrosshair" :webcam="camSettings" />
|
||||||
<v-row v-if="status !== 'connected'">
|
<v-row v-if="status !== 'connected'">
|
||||||
<v-col class="_webcam_mjpegstreamer_output text-center d-flex flex-column justify-center align-center">
|
<v-col class="_webcam_mjpegstreamer_output text-center d-flex flex-column justify-center align-center">
|
||||||
<v-progress-circular v-if="status === 'connecting'" indeterminate color="primary" class="mb-3" />
|
<v-progress-circular v-if="status === 'connecting'" indeterminate color="primary" class="mb-3" />
|
||||||
@ -100,6 +101,12 @@ export default class Mjpegstreamer extends Mixins(BaseMixin, WebcamMixin) {
|
|||||||
return this.$store.getters['gui/getPanelExpand']('webcam-panel', this.viewport) ?? false
|
return this.$store.getters['gui/getPanelExpand']('webcam-panel', this.viewport) ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get showNozzleCrosshair() {
|
||||||
|
const nozzleCrosshair = this.camSettings.extra_data?.nozzleCrosshair ?? false
|
||||||
|
|
||||||
|
return nozzleCrosshair && this.status === 'connected'
|
||||||
|
}
|
||||||
|
|
||||||
// start or stop the video when the expanded state changes
|
// start or stop the video when the expanded state changes
|
||||||
@Watch('expanded', { immediate: true })
|
@Watch('expanded', { immediate: true })
|
||||||
expandChanged(newExpanded: boolean): void {
|
expandChanged(newExpanded: boolean): void {
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
<span v-if="status === 'connected' && showFpsCounter" class="webcamFpsOutput">
|
<span v-if="status === 'connected' && showFpsCounter" class="webcamFpsOutput">
|
||||||
{{ $t('Panels.WebcamPanel.FPS') }}: {{ fpsOutput }}
|
{{ $t('Panels.WebcamPanel.FPS') }}: {{ fpsOutput }}
|
||||||
</span>
|
</span>
|
||||||
|
<webcam-nozzle-crosshair v-if="showNozzleCrosshair" :webcam="camSettings" />
|
||||||
<v-row v-if="status !== 'connected'">
|
<v-row v-if="status !== 'connected'">
|
||||||
<v-col class="_webcam_mjpegstreamer_output text-center d-flex flex-column justify-center align-center">
|
<v-col class="_webcam_mjpegstreamer_output text-center d-flex flex-column justify-center align-center">
|
||||||
<v-progress-circular v-if="status === 'connecting'" indeterminate color="primary" class="mb-3" />
|
<v-progress-circular v-if="status === 'connecting'" indeterminate color="primary" class="mb-3" />
|
||||||
@ -101,6 +102,12 @@ export default class MjpegstreamerAdaptive extends Mixins(BaseMixin, WebcamMixin
|
|||||||
return this.isVisibleDocument && this.isVisibleViewport
|
return this.isVisibleDocument && this.isVisibleViewport
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get showNozzleCrosshair() {
|
||||||
|
const nozzleCrosshair = this.camSettings.extra_data?.nozzleCrosshair ?? false
|
||||||
|
|
||||||
|
return nozzleCrosshair && this.status === 'connected'
|
||||||
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
document.addEventListener('visibilitychange', this.documentVisibilityChanged)
|
document.addEventListener('visibilitychange', this.documentVisibilityChanged)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="position-relative d-flex">
|
||||||
<video
|
<video
|
||||||
v-show="status === 'connected'"
|
v-show="status === 'connected'"
|
||||||
ref="stream"
|
ref="stream"
|
||||||
@ -8,6 +8,7 @@
|
|||||||
autoplay
|
autoplay
|
||||||
muted
|
muted
|
||||||
playsinline />
|
playsinline />
|
||||||
|
<webcam-nozzle-crosshair v-if="nozzleCrosshair" :webcam="camSettings" />
|
||||||
<v-row v-if="status !== 'connected'">
|
<v-row v-if="status !== 'connected'">
|
||||||
<v-col class="_webcam_webrtc_output text-center d-flex flex-column justify-center align-center">
|
<v-col class="_webcam_webrtc_output text-center d-flex flex-column justify-center align-center">
|
||||||
<v-progress-circular v-if="status === 'connecting'" indeterminate color="primary" class="mb-3" />
|
<v-progress-circular v-if="status === 'connecting'" indeterminate color="primary" class="mb-3" />
|
||||||
@ -23,6 +24,7 @@ import BaseMixin from '@/components/mixins/base'
|
|||||||
import { GuiWebcamStateWebcam } from '@/store/gui/webcams/types'
|
import { GuiWebcamStateWebcam } from '@/store/gui/webcams/types'
|
||||||
import WebcamMixin from '@/components/mixins/webcam'
|
import WebcamMixin from '@/components/mixins/webcam'
|
||||||
import { capitalize } from '@/plugins/helpers'
|
import { capitalize } from '@/plugins/helpers'
|
||||||
|
import WebcamNozzleCrosshair from '@/components/webcams/WebcamNozzleCrosshair.vue'
|
||||||
|
|
||||||
interface CameraStreamerResponse extends RTCSessionDescriptionInit {
|
interface CameraStreamerResponse extends RTCSessionDescriptionInit {
|
||||||
id: string
|
id: string
|
||||||
@ -30,7 +32,7 @@ interface CameraStreamerResponse extends RTCSessionDescriptionInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
methods: { capitalize },
|
components: { WebcamNozzleCrosshair },
|
||||||
})
|
})
|
||||||
export default class WebrtcCameraStreamer extends Mixins(BaseMixin, WebcamMixin) {
|
export default class WebrtcCameraStreamer extends Mixins(BaseMixin, WebcamMixin) {
|
||||||
capitalize = capitalize
|
capitalize = capitalize
|
||||||
@ -65,6 +67,10 @@ export default class WebrtcCameraStreamer extends Mixins(BaseMixin, WebcamMixin)
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get nozzleCrosshair() {
|
||||||
|
return this.camSettings.extra_data?.nozzleCrosshair ?? false
|
||||||
|
}
|
||||||
|
|
||||||
get expanded(): boolean {
|
get expanded(): boolean {
|
||||||
if (this.page !== 'dashboard') return true
|
if (this.page !== 'dashboard') return true
|
||||||
|
|
||||||
|
@ -1292,9 +1292,11 @@
|
|||||||
"Update": "update",
|
"Update": "update",
|
||||||
"WebcamsTab": {
|
"WebcamsTab": {
|
||||||
"AddWebcam": "add webcam",
|
"AddWebcam": "add webcam",
|
||||||
|
"Color": "Color",
|
||||||
"CreateWebcam": "Create Webcam",
|
"CreateWebcam": "Create Webcam",
|
||||||
"EditCrowsnestConf": "Edit crowsnest.conf",
|
"EditCrowsnestConf": "Edit crowsnest.conf",
|
||||||
"EditWebcam": "Edit Webcam",
|
"EditWebcam": "Edit Webcam",
|
||||||
|
"Enable": "Enable",
|
||||||
"EnableAudio": "Enable audio",
|
"EnableAudio": "Enable audio",
|
||||||
"FlipWebcam": "Flip webcam image:",
|
"FlipWebcam": "Flip webcam image:",
|
||||||
"HideFps": "Hide FPS counter",
|
"HideFps": "Hide FPS counter",
|
||||||
@ -1314,10 +1316,12 @@
|
|||||||
"MjpegstreamerAdaptive": "Adaptive MJPEG-Streamer (experimental)",
|
"MjpegstreamerAdaptive": "Adaptive MJPEG-Streamer (experimental)",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"NameAlreadyExists": "Name already exists",
|
"NameAlreadyExists": "Name already exists",
|
||||||
|
"NozzleCrosshair": "Nozzle Crosshair",
|
||||||
"Required": "required",
|
"Required": "required",
|
||||||
"Rotate": "Rotate",
|
"Rotate": "Rotate",
|
||||||
"SaveWebcam": "Save Webcam",
|
"SaveWebcam": "Save Webcam",
|
||||||
"Service": "Service",
|
"Service": "Service",
|
||||||
|
"Size": "Size",
|
||||||
"TargetFPS": "Target FPS",
|
"TargetFPS": "Target FPS",
|
||||||
"UpdateWebcam": "Update Webcam",
|
"UpdateWebcam": "Update Webcam",
|
||||||
"UrlSnapshot": "URL Snapshot",
|
"UrlSnapshot": "URL Snapshot",
|
||||||
|
@ -28,6 +28,9 @@ export interface GuiWebcamStateWebcam {
|
|||||||
extra_data?: {
|
extra_data?: {
|
||||||
enableAudio?: boolean
|
enableAudio?: boolean
|
||||||
hideFps?: boolean
|
hideFps?: boolean
|
||||||
|
nozzleCrosshair?: boolean
|
||||||
|
nozzleCrosshairColor?: string
|
||||||
|
nozzleCrosshairSize?: number
|
||||||
}
|
}
|
||||||
source?: 'config' | 'database'
|
source?: 'config' | 'database'
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user