1172 lines
42 KiB
Vue
1172 lines
42 KiB
Vue
<style scoped>
|
|
.rename-profile {
|
|
text-transform: none;
|
|
}
|
|
|
|
.currentMeshName {
|
|
cursor: pointer;
|
|
color: var(--v-primary-base);
|
|
}
|
|
|
|
.currentMeshName .v-icon {
|
|
opacity: 0;
|
|
}
|
|
|
|
.currentMeshName:hover .v-icon {
|
|
opacity: 1;
|
|
}
|
|
|
|
.rowProfile {
|
|
}
|
|
|
|
.rowProfile .colActions {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.rowProfile .colName,
|
|
.rowProfile .colVariance {
|
|
line-height: 48px;
|
|
}
|
|
|
|
.rowProfile .colName span.current {
|
|
font-weight: bold;
|
|
color: var(--v-primary-base);
|
|
}
|
|
|
|
.rowProfile .colActions .v-btn {
|
|
height: 48px;
|
|
width: 48px;
|
|
}
|
|
</style>
|
|
|
|
<template>
|
|
<div>
|
|
<v-row v-if="klipperReadyForGui">
|
|
<v-col class="col-12 col-md-8 pb-0">
|
|
<panel card-class="heightmap-map-panel" :title="$t('Heightmap.Heightmap')" :icon="mdiGrid">
|
|
<template #buttons>
|
|
<v-btn
|
|
icon
|
|
tile
|
|
class="d-none d-sm-flex"
|
|
:disabled="printerIsPrinting"
|
|
:color="homedAxes.includes('xyz') ? 'primary' : 'warning'"
|
|
:loading="loadings.includes('homeAll')"
|
|
:title="$t('Heightmap.TitleHomeAll')"
|
|
:ripple="true"
|
|
@click="homePrinter">
|
|
<v-icon>{{ mdiHome }}</v-icon>
|
|
</v-btn>
|
|
<v-btn
|
|
v-if="meshLoaded"
|
|
text
|
|
tile
|
|
class="d-none d-sm-flex"
|
|
:loading="loadings.includes('bedMeshClear')"
|
|
:title="$t('Heightmap.TitleClear')"
|
|
@click="clearBedMesh">
|
|
{{ $t('Heightmap.Clear') }}
|
|
</v-btn>
|
|
<v-btn
|
|
text
|
|
tile
|
|
class="d-none d-sm-flex"
|
|
:loading="loadings.includes('bedMeshCalibrate')"
|
|
:disabled="printerIsPrinting"
|
|
:title="$t('Heightmap.TitleCalibrate')"
|
|
@click="openCalibrateMesh">
|
|
{{ $t('Heightmap.Calibrate') }}
|
|
</v-btn>
|
|
</template>
|
|
<v-card-text class="d-sm-none text-center pb-0">
|
|
<v-item-group tile class="v-btn-toggle" name="controllers">
|
|
<v-btn
|
|
text
|
|
small
|
|
class="px-2 minwidth-0"
|
|
:disabled="printerIsPrinting"
|
|
:color="homedAxes.includes('xyz') ? 'primary' : 'warning'"
|
|
:loading="loadings.includes('homeAll')"
|
|
:title="$t('Heightmap.TitleHomeAll')"
|
|
@click="homePrinter">
|
|
<v-icon :color="homedAxes.includes('xyz') ? 'primary' : 'warning'" small>
|
|
{{ mdiHome }}
|
|
</v-icon>
|
|
</v-btn>
|
|
<v-btn
|
|
v-if="bed_mesh"
|
|
text
|
|
small
|
|
class="px-2 minwidth-0"
|
|
color="primary"
|
|
:loading="loadings.includes('bedMeshClear')"
|
|
:title="$t('Heightmap.TitleClear')"
|
|
@click="clearBedMesh">
|
|
{{ $t('Heightmap.Clear') }}
|
|
</v-btn>
|
|
<v-btn
|
|
text
|
|
small
|
|
class="px-2 minwidth-0"
|
|
color="primary"
|
|
:loading="loadings.includes('bedMeshCalibrate')"
|
|
:disabled="printerIsPrinting"
|
|
:title="$t('Heightmap.TitleCalibrate')"
|
|
@click="calibrateDialog = true">
|
|
{{ $t('Heightmap.Calibrate') }}
|
|
</v-btn>
|
|
</v-item-group>
|
|
</v-card-text>
|
|
<template v-if="!meshLoaded">
|
|
<v-card-text class="text-center py-3 font-italic">
|
|
{{ $t('Heightmap.NoBedMeshHasBeenLoadedYet') }}
|
|
</v-card-text>
|
|
</template>
|
|
<template v-else>
|
|
<v-card-text class="py-0 px-0">
|
|
<v-row>
|
|
<v-col class="">
|
|
<e-chart
|
|
ref="heightmap"
|
|
:option="chartOptions"
|
|
:init-options="{ renderer: 'canvas' }"
|
|
style="height: 400px; width: 100%; overflow: hidden"></e-chart>
|
|
</v-col>
|
|
</v-row>
|
|
<v-row>
|
|
<v-col
|
|
class="col-12 col-sm-auto pt-0 pb-0 pl-lg-6 d-flex justify-center justify-sm-start">
|
|
<v-switch
|
|
v-model="scaleVisualMap"
|
|
:label="$t('Heightmap.ScaleGradient')"
|
|
class="mt-0 ml-5"></v-switch>
|
|
</v-col>
|
|
<v-col class="d-flex justify-center pt-0 pb-6 pb-lg-3">
|
|
<v-checkbox
|
|
v-model="showProbed"
|
|
:label="$t('Heightmap.Probed')"
|
|
hide-details
|
|
class="mx-3 mt-0"></v-checkbox>
|
|
<v-checkbox
|
|
v-model="showMesh"
|
|
:label="$t('Heightmap.Mesh')"
|
|
hide-details
|
|
class="mx-3 mt-0"></v-checkbox>
|
|
<v-checkbox
|
|
v-model="showFlat"
|
|
:label="$t('Heightmap.Flat')"
|
|
hide-details
|
|
class="mx-3 mt-0"></v-checkbox>
|
|
<v-checkbox
|
|
v-model="wireframe"
|
|
:label="$t('Heightmap.Wireframe')"
|
|
hide-details
|
|
class="mx-3 mt-0"></v-checkbox>
|
|
</v-col>
|
|
</v-row>
|
|
</v-card-text>
|
|
<v-divider></v-divider>
|
|
<v-card-text class="pt-0 pb-3">
|
|
<v-row>
|
|
<v-col>
|
|
<v-slider
|
|
v-model="heightmapScale"
|
|
:label="$t('Heightmap.ScaleZMax')"
|
|
:min="heightmapRangeLimit[0]"
|
|
:max="heightmapRangeLimit[1]"
|
|
:step="0.1"
|
|
ticks="always"
|
|
class="mt-4"
|
|
hide-details></v-slider>
|
|
</v-col>
|
|
</v-row>
|
|
</v-card-text>
|
|
</template>
|
|
</panel>
|
|
</v-col>
|
|
<v-col class="col-12 col-md-4">
|
|
<panel
|
|
v-if="currentProfile"
|
|
:title="$t('Heightmap.CurrentMesh.Headline')"
|
|
card-class="heightmap-current-mesh-panel"
|
|
:icon="mdiInformation"
|
|
:collapsible="true"
|
|
class="mt-0">
|
|
<v-card-text class="py-3 px-0">
|
|
<v-row class="px-3">
|
|
<v-col>{{ $t('Heightmap.CurrentMesh.Name') }}</v-col>
|
|
<v-col class="text-right">
|
|
<span class="currentMeshName font-weight-bold" @click="openRenameProfile()">
|
|
<v-icon left small color="primary">{{ mdiPencil }}</v-icon>
|
|
{{ currentProfileName }}
|
|
</span>
|
|
</v-col>
|
|
</v-row>
|
|
<v-divider class="my-3"></v-divider>
|
|
<v-row v-if="'data' in currentProfile" class="px-3">
|
|
<v-col>{{ $t('Heightmap.CurrentMesh.Size') }}</v-col>
|
|
<v-col class="text-right">
|
|
{{ currentProfile.data.x_count }}x{{ currentProfile.data.y_count }}
|
|
</v-col>
|
|
</v-row>
|
|
<v-divider class="my-3"></v-divider>
|
|
<v-row class="px-3">
|
|
<v-col>
|
|
{{ $t('Heightmap.CurrentMesh.Max') }} [{{ bedMeshMaxPoint.positionX }},
|
|
{{ bedMeshMaxPoint.positionY }}]
|
|
</v-col>
|
|
<v-col class="text-right">{{ currentProfile.max.toFixed(3) }} mm</v-col>
|
|
</v-row>
|
|
<v-divider class="my-3"></v-divider>
|
|
<v-row class="px-3">
|
|
<v-col>
|
|
{{ $t('Heightmap.CurrentMesh.Min') }} [{{ bedMeshMinPoint.positionX }},
|
|
{{ bedMeshMinPoint.positionY }}]
|
|
</v-col>
|
|
<v-col class="text-right">{{ currentProfile.min.toFixed(3) }} mm</v-col>
|
|
</v-row>
|
|
<v-divider class="my-3"></v-divider>
|
|
<v-row class="px-3">
|
|
<v-col>{{ $t('Heightmap.CurrentMesh.Variance') }}</v-col>
|
|
<v-col class="text-right">{{ currentProfile.variance.toFixed(3) }} mm</v-col>
|
|
</v-row>
|
|
</v-card-text>
|
|
</panel>
|
|
<panel
|
|
:title="$t('Heightmap.Profiles')"
|
|
card-class="heightmap-profiles-panel"
|
|
:icon="mdiStackOverflow"
|
|
:collapsible="true"
|
|
class="mt-6 mt-md-0">
|
|
<v-card-text v-if="profiles.length" class="px-0 py-3">
|
|
<template v-for="(profile, index) in profiles">
|
|
<v-divider v-if="index" :key="'deliver_' + index" class="my-3"></v-divider>
|
|
<v-row :key="index" class="rowProfile">
|
|
<v-col class="pl-6 py-0 colName">
|
|
<span
|
|
:class="profile.is_active ? 'current' : ''"
|
|
style="cursor: pointer"
|
|
@click="profile.is_active ? openRenameProfile() : loadProfile(profile.name)">
|
|
{{ profile.name }}
|
|
</span>
|
|
</v-col>
|
|
<v-col class="text-center py-0 colVariance">
|
|
<v-tooltip top color="rgba(0,0,0,0.8)">
|
|
<template #activator="{ on, attrs }">
|
|
<small v-bind="attrs" v-on="on">
|
|
{{ profile.variance.toFixed(3) }}
|
|
</small>
|
|
</template>
|
|
<span>
|
|
max: {{ profile.max }}
|
|
<br />
|
|
min: {{ profile.min }}
|
|
</span>
|
|
</v-tooltip>
|
|
</v-col>
|
|
<v-col class="py-0 colActions">
|
|
<v-btn
|
|
v-if="!profile.is_active"
|
|
text
|
|
tile
|
|
class="px-2 minwidth-0"
|
|
:loading="loadings.includes('bedMeshLoad_' + profile.name)"
|
|
@click="loadProfile(profile.name)">
|
|
<v-icon>{{ mdiProgressUpload }}</v-icon>
|
|
</v-btn>
|
|
<v-btn
|
|
v-else
|
|
text
|
|
tile
|
|
class="px-2 minwidth-0"
|
|
:loading="loadings.includes('bedMeshLoad_' + profile.name)"
|
|
@click="openRenameProfile">
|
|
<v-icon>{{ mdiPencil }}</v-icon>
|
|
</v-btn>
|
|
<v-btn
|
|
text
|
|
tile
|
|
class="px-2 minwidth-0"
|
|
:loading="loadings.includes('bedMeshRemove_' + profile.name)"
|
|
:title="$t('Heightmap.DeleteBedMeshProfile')"
|
|
@click="openRemoveProfile(profile.name)">
|
|
<v-icon>{{ mdiDelete }}</v-icon>
|
|
</v-btn>
|
|
</v-col>
|
|
</v-row>
|
|
</template>
|
|
</v-card-text>
|
|
<v-card-text v-else>
|
|
<p class="mb-0">{{ $t('Heightmap.NoProfile') }}</p>
|
|
</v-card-text>
|
|
</panel>
|
|
</v-col>
|
|
</v-row>
|
|
<v-row v-else>
|
|
<v-alert
|
|
border="left"
|
|
colored-border
|
|
type="warning"
|
|
elevation="2"
|
|
class="mx-auto mt-6"
|
|
max-width="500"
|
|
:icon="mdiLockOutline">
|
|
{{ $t('Heightmap.ErrorKlipperNotReady') }}
|
|
</v-alert>
|
|
</v-row>
|
|
<v-dialog v-model="renameDialog" persistent :max-width="400" @keydown.esc="renameDialog = false">
|
|
<panel
|
|
:title="$t('Heightmap.RenameBedMeshProfile')"
|
|
:icon="mdiGrid"
|
|
card-class="heightmap-rename-dialog"
|
|
:margin-bottom="false">
|
|
<template #buttons>
|
|
<v-btn icon tile @click="renameDialog = false">
|
|
<v-icon>{{ mdiCloseThick }}</v-icon>
|
|
</v-btn>
|
|
</template>
|
|
<v-card-text>
|
|
<v-text-field
|
|
ref="inputDialogRenameHeightmapName"
|
|
v-model="newName"
|
|
:label="$t('Heightmap.Name')"
|
|
required
|
|
@keyup.enter="renameProfile"></v-text-field>
|
|
</v-card-text>
|
|
<v-card-actions>
|
|
<v-spacer></v-spacer>
|
|
<v-btn text @click="renameDialog = false">{{ $t('Heightmap.Abort') }}</v-btn>
|
|
<v-btn color="primary" text @click="renameProfile">{{ $t('Heightmap.Rename') }}</v-btn>
|
|
</v-card-actions>
|
|
</panel>
|
|
</v-dialog>
|
|
<v-dialog
|
|
v-model="calibrateDialog.boolShow"
|
|
persistent
|
|
:max-width="400"
|
|
@keydown.esc="calibrateDialog.boolShow = false">
|
|
<panel
|
|
:title="$t('Heightmap.BedMeshCalibrate')"
|
|
:icon="mdiGrid"
|
|
card-class="heightmap-calibrate-dialog"
|
|
:margin-bottom="false">
|
|
<template #buttons>
|
|
<v-btn icon tile @click="calibrateDialog.boolShow = false">
|
|
<v-icon>{{ mdiCloseThick }}</v-icon>
|
|
</v-btn>
|
|
</template>
|
|
<v-card-text>
|
|
<v-text-field
|
|
ref="inputFieldCalibrateBedMeshName"
|
|
v-model="calibrateDialog.name"
|
|
:label="$t('Heightmap.Name')"
|
|
required
|
|
@keyup.enter="calibrateMesh"></v-text-field>
|
|
</v-card-text>
|
|
<v-card-actions>
|
|
<v-spacer></v-spacer>
|
|
<v-btn text @click="calibrateDialog.boolShow = false">{{ $t('Heightmap.Abort') }}</v-btn>
|
|
<v-btn color="primary" text @click="calibrateMesh">{{ $t('Heightmap.Calibrate') }}</v-btn>
|
|
</v-card-actions>
|
|
</panel>
|
|
</v-dialog>
|
|
<v-dialog v-model="removeDialog" persistent :max-width="400" @keydown.esc="removeDialog = false">
|
|
<panel
|
|
:title="$t('Heightmap.BedMeshRemove')"
|
|
:icon="mdiGrid"
|
|
card-class="heightmap-remove-dialog"
|
|
:margin-bottom="false">
|
|
<template #buttons>
|
|
<v-btn icon tile @click="removeDialog = false">
|
|
<v-icon>{{ mdiCloseThick }}</v-icon>
|
|
</v-btn>
|
|
</template>
|
|
<v-card-text>
|
|
<p class="mb-0">{{ $t('Heightmap.DoYouReallyWantToDelete', { name: removeDialogProfile }) }}</p>
|
|
</v-card-text>
|
|
<v-card-actions>
|
|
<v-spacer></v-spacer>
|
|
<v-btn text @click="removeDialog = false">{{ $t('Heightmap.Abort') }}</v-btn>
|
|
<v-btn color="error" text @click="removeProfile">{{ $t('Heightmap.Remove') }}</v-btn>
|
|
</v-card-actions>
|
|
</panel>
|
|
</v-dialog>
|
|
<v-dialog v-model="saveConfigDialog" persistent :max-width="400" @keydown.esc="saveConfigDialog = false">
|
|
<panel
|
|
:title="$t('Heightmap.SAVE_CONFIG')"
|
|
:icon="mdiGrid"
|
|
card-class="heightmap-remove-save-dialog"
|
|
:margin-bottom="false">
|
|
<template #buttons>
|
|
<v-btn icon tile @click="saveConfigDialog = false">
|
|
<v-icon>{{ mdiCloseThick }}</v-icon>
|
|
</v-btn>
|
|
</template>
|
|
<v-card-text>
|
|
<p class="mb-0">{{ $t('Heightmap.RemoveSaveDescription') }}</p>
|
|
</v-card-text>
|
|
<v-card-actions>
|
|
<v-spacer></v-spacer>
|
|
<template v-if="printerIsPrinting">
|
|
<v-btn text @click="saveConfigDialog = false">{{ $t('Heightmap.Ok') }}</v-btn>
|
|
</template>
|
|
<template v-else>
|
|
<v-btn color="primary" text @click="saveConfig">
|
|
{{ $t('Heightmap.SAVE_CONFIG') }}
|
|
</v-btn>
|
|
<v-btn text @click="saveConfigDialog = false">{{ $t('Heightmap.Later') }}</v-btn>
|
|
</template>
|
|
</v-card-actions>
|
|
</panel>
|
|
</v-dialog>
|
|
</div>
|
|
</template>
|
|
<script lang="ts">
|
|
import { Component, Mixins, Watch } from 'vue-property-decorator'
|
|
import BaseMixin from '@/components/mixins/base'
|
|
import ControlMixin from '@/components/mixins/control'
|
|
|
|
import Panel from '@/components/ui/Panel.vue'
|
|
import {
|
|
mdiCloseThick,
|
|
mdiDelete,
|
|
mdiGrid,
|
|
mdiHome,
|
|
mdiInformation,
|
|
mdiLockOutline,
|
|
mdiProgressUpload,
|
|
mdiPencil,
|
|
mdiStackOverflow,
|
|
} from '@mdi/js'
|
|
|
|
import { use } from 'echarts/core'
|
|
|
|
// import ECharts modules manually to reduce bundle size
|
|
import { CanvasRenderer } from 'echarts/renderers'
|
|
import { VisualMapComponent } from 'echarts/components'
|
|
|
|
// @ts-ignore
|
|
import { Grid3DComponent } from 'echarts-gl/components'
|
|
//type definitions for echarts-gl do not exist
|
|
// @ts-ignore
|
|
import { SurfaceChart } from 'echarts-gl/charts'
|
|
import type { ECharts } from 'echarts'
|
|
import { PrinterStateBedMesh } from '@/store/printer/types'
|
|
|
|
use([CanvasRenderer, VisualMapComponent, Grid3DComponent, SurfaceChart])
|
|
|
|
interface HeightmapSerie {
|
|
type: string
|
|
name: string
|
|
data: number[][]
|
|
dataShape?: number[]
|
|
itemStyle?: {
|
|
opacity?: number
|
|
color?: number[]
|
|
}
|
|
wireframe: {
|
|
show: boolean
|
|
}
|
|
}
|
|
|
|
@Component({
|
|
components: {
|
|
Panel,
|
|
},
|
|
})
|
|
export default class PageHeightmap extends Mixins(BaseMixin, ControlMixin) {
|
|
declare $refs: {
|
|
// eslint-disable-next-line
|
|
heightmap: any
|
|
inputDialogRenameHeightmapName: HTMLInputElement
|
|
inputFieldCalibrateBedMeshName: HTMLInputElement
|
|
}
|
|
|
|
/**
|
|
* Icons
|
|
*/
|
|
mdiGrid = mdiGrid
|
|
mdiHome = mdiHome
|
|
mdiInformation = mdiInformation
|
|
mdiStackOverflow = mdiStackOverflow
|
|
mdiLockOutline = mdiLockOutline
|
|
mdiCloseThick = mdiCloseThick
|
|
mdiPencil = mdiPencil
|
|
mdiDelete = mdiDelete
|
|
mdiProgressUpload = mdiProgressUpload
|
|
|
|
private renameDialog = false
|
|
private removeDialogProfile = ''
|
|
private removeDialog = false
|
|
private saveConfigDialog = false
|
|
private calibrateDialog: {
|
|
boolShow: boolean
|
|
name: string
|
|
} = {
|
|
boolShow: false,
|
|
name: 'default',
|
|
}
|
|
private newName = ''
|
|
private oldName = ''
|
|
|
|
private heightmapScale = 0.5
|
|
private probedOpacity = 1
|
|
private meshOpacity = 1
|
|
private flatOpacity = 0.5
|
|
|
|
private colorAxisName = 'rgba(255,255,255,0.5)'
|
|
private colorAxisLabel = 'rgba(255,255,255,0.5)'
|
|
private colorAxisLine = 'rgba(255,255,255,0.2)'
|
|
private colorAxisTick = 'rgba(255,255,255,0.2)'
|
|
private colorSplitLine = 'rgba(255,255,255,0.2)'
|
|
|
|
private colorAxisPointer = 'rgba(255,255,255,0.8)'
|
|
|
|
private colorVisualMap = 'rgba(255,255,255,0.8)'
|
|
private fontSizeVisualMap = 14
|
|
|
|
get chartOptions() {
|
|
return {
|
|
tooltip: {
|
|
backgroundColor: 'rgba(0,0,0,0.9)',
|
|
borderWidth: 0,
|
|
textStyle: {
|
|
color: '#fff',
|
|
fontSize: '14px',
|
|
},
|
|
padding: 15,
|
|
formatter: this.tooltipFormatter,
|
|
},
|
|
darkMode: true,
|
|
animation: false,
|
|
legend: {
|
|
show: false,
|
|
selected: this.selected,
|
|
},
|
|
visualMap: {
|
|
show: true,
|
|
min: this.visualMapRange[0],
|
|
max: this.visualMapRange[1],
|
|
calculable: true,
|
|
dimension: 2,
|
|
inRange: {
|
|
color: [
|
|
'#313695',
|
|
'#4575b4',
|
|
'#74add1',
|
|
'#abd9e9',
|
|
'#e0f3f8',
|
|
'#ffffbf',
|
|
'#fee090',
|
|
'#fdae61',
|
|
'#f46d43',
|
|
'#d73027',
|
|
'#a50026',
|
|
],
|
|
},
|
|
seriesIndex: this.visualMapSeriesIndex,
|
|
left: this.isMobile ? 10 : 30,
|
|
top: 20,
|
|
bottom: 0,
|
|
itemWidth: this.isMobile ? 10 : 30,
|
|
itemHeight: 350,
|
|
precision: 3,
|
|
textStyle: {
|
|
color: this.colorVisualMap,
|
|
fontSize: this.fontSizeVisualMap,
|
|
},
|
|
},
|
|
xAxis3D: {
|
|
type: 'value',
|
|
nameTextStyle: {
|
|
color: this.colorAxisName,
|
|
},
|
|
min: this.rangeX[0],
|
|
max: this.rangeX[1],
|
|
minInterval: 1,
|
|
},
|
|
yAxis3D: {
|
|
type: 'value',
|
|
nameTextStyle: {
|
|
color: this.colorAxisName,
|
|
},
|
|
min: this.rangeY[0],
|
|
max: this.rangeY[1],
|
|
},
|
|
zAxis3D: {
|
|
type: 'value',
|
|
min: this.heightmapScale * -1,
|
|
max: this.heightmapScale,
|
|
nameTextStyle: {
|
|
color: this.colorAxisName,
|
|
},
|
|
axisPointer: {
|
|
label: {
|
|
formatter: function (value: any) {
|
|
value = parseFloat(value)
|
|
return value.toFixed(2)
|
|
},
|
|
},
|
|
},
|
|
},
|
|
grid3D: {
|
|
axisLabel: {
|
|
textStyle: {
|
|
color: this.colorAxisLabel,
|
|
},
|
|
},
|
|
axisLine: {
|
|
lineStyle: {
|
|
color: this.colorAxisLine,
|
|
},
|
|
},
|
|
axisTick: {
|
|
lineStyle: {
|
|
color: this.colorAxisTick,
|
|
},
|
|
},
|
|
splitLine: {
|
|
lineStyle: {
|
|
color: this.colorSplitLine,
|
|
},
|
|
},
|
|
axisPointer: {
|
|
lineStyle: {
|
|
color: this.colorAxisPointer,
|
|
},
|
|
label: {
|
|
textStyle: {
|
|
color: this.colorAxisPointer,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
series: this.series,
|
|
}
|
|
}
|
|
|
|
get chart(): ECharts | null {
|
|
return this.$refs.heightmap ?? null
|
|
}
|
|
|
|
get profiles() {
|
|
return this.$store.getters['printer/getBedMeshProfiles']
|
|
}
|
|
|
|
get bed_mesh() {
|
|
return this.$store.state.printer.bed_mesh ?? null
|
|
}
|
|
|
|
get currentProfileName() {
|
|
return this.bed_mesh?.profile_name ?? ''
|
|
}
|
|
|
|
get currentProfile() {
|
|
return this.profiles.find((profile: PrinterStateBedMesh) => profile.name === this.currentProfileName)
|
|
}
|
|
|
|
@Watch('bed_mesh', { deep: true })
|
|
bed_meshChanged() {
|
|
this.chart?.setOption(this.chartOptions)
|
|
}
|
|
|
|
get showProbed(): boolean {
|
|
return this.$store.state.gui.view.heightmap.probed ?? true
|
|
}
|
|
|
|
set showProbed(newVal) {
|
|
this.$store.dispatch('gui/saveSetting', { name: 'view.heightmap.probed', value: newVal })
|
|
}
|
|
|
|
get showMesh(): boolean {
|
|
return this.$store.state.gui.view.heightmap.mesh ?? true
|
|
}
|
|
|
|
set showMesh(newVal) {
|
|
this.$store.dispatch('gui/saveSetting', { name: 'view.heightmap.mesh', value: newVal })
|
|
}
|
|
|
|
get showFlat(): boolean {
|
|
return this.$store.state.gui.view.heightmap.flat ?? true
|
|
}
|
|
|
|
set showFlat(newVal) {
|
|
this.$store.dispatch('gui/saveSetting', { name: 'view.heightmap.flat', value: newVal })
|
|
}
|
|
|
|
get wireframe(): boolean {
|
|
return this.$store.state.gui.view.heightmap.wireframe ?? true
|
|
}
|
|
|
|
set wireframe(newVal) {
|
|
this.$store.dispatch('gui/saveSetting', { name: 'view.heightmap.wireframe', value: newVal })
|
|
}
|
|
|
|
get scale(): boolean {
|
|
return this.$store.state.gui.view.heightmap.scale ?? true
|
|
}
|
|
|
|
set scale(newVal) {
|
|
this.$store.dispatch('gui/saveSetting', { name: 'view.heightmap.scale', value: newVal })
|
|
}
|
|
|
|
get scaleVisualMap(): boolean {
|
|
return this.$store.state.gui.view.heightmap.scaleVisualMap ?? false
|
|
}
|
|
|
|
set scaleVisualMap(newVal) {
|
|
this.$store.dispatch('gui/saveSetting', { name: 'view.heightmap.scaleVisualMap', value: newVal })
|
|
}
|
|
|
|
get rangeX(): number[] {
|
|
const axis_minimum = this.$store.state.printer.toolhead?.axis_minimum
|
|
const axis_maximum = this.$store.state.printer.toolhead?.axis_maximum
|
|
|
|
return [axis_minimum[0] ?? 0, axis_maximum[0] ?? 0]
|
|
}
|
|
|
|
get rangeY(): number[] {
|
|
const axis_minimum = this.$store.state.printer.toolhead?.axis_minimum ?? [0, 0]
|
|
const axis_maximum = this.$store.state.printer.toolhead?.axis_maximum ?? [0, 0]
|
|
|
|
return [axis_minimum[1] ?? 0, axis_maximum[1] ?? 0]
|
|
}
|
|
|
|
get heightmapLimit(): number[] {
|
|
let min = 0
|
|
let max = 0
|
|
|
|
if (this.bed_mesh) {
|
|
const points = []
|
|
if (this.showProbed) {
|
|
for (const row of this.bed_mesh.probed_matrix) for (const col of row) points.push(col)
|
|
}
|
|
if (this.showMesh) {
|
|
for (const row of this.bed_mesh.mesh_matrix) for (const col of row) points.push(col)
|
|
}
|
|
|
|
min = Math.min(min, ...points)
|
|
max = Math.max(max, ...points)
|
|
}
|
|
|
|
return [min, max]
|
|
}
|
|
|
|
get probedHeightmapLimit(): number[] {
|
|
let min = 0
|
|
let max = 0
|
|
|
|
if (this.currentProfile) {
|
|
min = this.currentProfile.min
|
|
max = this.currentProfile.max
|
|
}
|
|
|
|
return [min, max]
|
|
}
|
|
|
|
get heightmapRangeLimit(): number[] {
|
|
const [min, max] = this.heightmapLimit
|
|
|
|
const minRange = Math.round(Math.max(Math.abs(min), Math.abs(max)) * 10) / 10
|
|
const maxRange = Math.max(minRange, 1)
|
|
|
|
return [minRange, maxRange]
|
|
}
|
|
|
|
get selected(): { [key: string]: boolean } {
|
|
return {
|
|
probed: this.showProbed,
|
|
mesh: this.showMesh,
|
|
flat: this.showFlat,
|
|
}
|
|
}
|
|
|
|
get series(): HeightmapSerie[] {
|
|
const series = []
|
|
|
|
if (this.bed_mesh) {
|
|
series.push(this.seriesProbed)
|
|
series.push(this.seriesMesh)
|
|
series.push(this.seriesFlat)
|
|
}
|
|
|
|
return series
|
|
}
|
|
|
|
get seriesProbed(): HeightmapSerie {
|
|
const serie: HeightmapSerie = {
|
|
type: 'surface',
|
|
name: 'probed',
|
|
data: [],
|
|
itemStyle: {
|
|
opacity: this.probedOpacity,
|
|
},
|
|
wireframe: {
|
|
show: this.wireframe,
|
|
},
|
|
}
|
|
|
|
if (this.bed_mesh) {
|
|
const xCount = this.bed_mesh.probed_matrix[0].length
|
|
const yCount = this.bed_mesh.probed_matrix.length
|
|
const xMin = this.bed_mesh.mesh_min[0]
|
|
const xMax = this.bed_mesh.mesh_max[0]
|
|
const yMin = this.bed_mesh.mesh_min[1]
|
|
const yMax = this.bed_mesh.mesh_max[1]
|
|
const xStep = (xMax - xMin) / (xCount - 1)
|
|
const yStep = (yMax - yMin) / (yCount - 1)
|
|
|
|
const data: any[] = []
|
|
|
|
let yPoint = 0
|
|
this.bed_mesh.probed_matrix.forEach((meshRow: number[]) => {
|
|
let xPoint = 0
|
|
meshRow.forEach((value: number) => {
|
|
data.push([xMin + xStep * xPoint, yMin + yStep * yPoint, value])
|
|
xPoint++
|
|
})
|
|
yPoint++
|
|
})
|
|
|
|
serie.data = data
|
|
serie.dataShape = [yCount, xCount]
|
|
}
|
|
|
|
return serie
|
|
}
|
|
|
|
get seriesMesh(): HeightmapSerie {
|
|
const serie: HeightmapSerie = {
|
|
type: 'surface',
|
|
name: 'mesh',
|
|
data: [],
|
|
itemStyle: {
|
|
opacity: this.meshOpacity,
|
|
},
|
|
wireframe: {
|
|
show: this.wireframe,
|
|
},
|
|
}
|
|
|
|
if (this.bed_mesh) {
|
|
const xCount = this.bed_mesh.mesh_matrix[0].length
|
|
const yCount = this.bed_mesh.mesh_matrix.length
|
|
const xMin = this.bed_mesh.mesh_min[0]
|
|
const xMax = this.bed_mesh.mesh_max[0]
|
|
const yMin = this.bed_mesh.mesh_min[1]
|
|
const yMax = this.bed_mesh.mesh_max[1]
|
|
const xStep = (xMax - xMin) / (xCount - 1)
|
|
const yStep = (yMax - yMin) / (yCount - 1)
|
|
|
|
const data: any[] = []
|
|
|
|
let yPoint = 0
|
|
this.bed_mesh.mesh_matrix.forEach((meshRow: number[]) => {
|
|
let xPoint = 0
|
|
meshRow.forEach((value: number) => {
|
|
data.push([xMin + xStep * xPoint, yMin + yStep * yPoint, value])
|
|
xPoint++
|
|
})
|
|
yPoint++
|
|
})
|
|
|
|
serie.data = data
|
|
serie.dataShape = [yCount, xCount]
|
|
}
|
|
|
|
return serie
|
|
}
|
|
|
|
get seriesFlat(): HeightmapSerie {
|
|
const serie: HeightmapSerie = {
|
|
type: 'surface',
|
|
name: 'flat',
|
|
data: [],
|
|
itemStyle: {
|
|
color: [1, 1, 1, 1],
|
|
opacity: this.flatOpacity,
|
|
},
|
|
wireframe: {
|
|
show: this.wireframe,
|
|
},
|
|
}
|
|
|
|
const config = this.$store.state.printer.configfile?.settings?.bed_mesh
|
|
if (config) {
|
|
let probe_count = [1, 1]
|
|
if (config.probe_count && typeof config.probe_count === 'string') {
|
|
probe_count = config.probe_count.split(',')
|
|
} else if (config.probe_count) {
|
|
probe_count = config.probe_count
|
|
} else if (config.round_probe_count) {
|
|
probe_count = [config.round_probe_count, config.round_probe_count]
|
|
}
|
|
|
|
let mesh_min = []
|
|
let mesh_max = []
|
|
|
|
if (config.mesh_min && config.mesh_max) {
|
|
// is no delta
|
|
mesh_min = typeof config.mesh_min === 'string' ? config.mesh_min.split(',') : config.mesh_min
|
|
mesh_max = typeof config.mesh_max === 'string' ? config.mesh_max.split(',') : config.mesh_max
|
|
} else {
|
|
// delta min/max
|
|
mesh_min = [config.mesh_radius * -1, config.mesh_radius * -1]
|
|
|
|
mesh_max = [config.mesh_radius, config.mesh_radius]
|
|
}
|
|
|
|
const xCount = probe_count[0]
|
|
const yCount = probe_count[1]
|
|
const xMin = parseFloat(mesh_min[0])
|
|
const xMax = parseFloat(mesh_max[0])
|
|
const yMin = parseFloat(mesh_min[1])
|
|
const yMax = parseFloat(mesh_max[1])
|
|
const xStep = (xMax - xMin) / (xCount - 1)
|
|
const yStep = (yMax - yMin) / (yCount - 1)
|
|
|
|
const data: number[][] = []
|
|
|
|
for (let y = 0; y < yCount; y++) {
|
|
for (let x = 0; x < xCount; x++) {
|
|
data.push([xMin + xStep * x, yMin + yStep * y, 0])
|
|
}
|
|
}
|
|
|
|
serie.data = data
|
|
serie.dataShape = [yCount, xCount]
|
|
}
|
|
|
|
return serie
|
|
}
|
|
|
|
get visualMapRange(): number[] {
|
|
if (!this.scaleVisualMap) return [-0.1, 0.1]
|
|
|
|
return this.heightmapLimit
|
|
}
|
|
|
|
get visualMapSeriesIndex(): number[] {
|
|
const output = []
|
|
|
|
if (this.showProbed) output.push(0)
|
|
else if (this.showMesh) output.push(1)
|
|
|
|
return output
|
|
}
|
|
|
|
get bedMeshMaxPoint() {
|
|
if (this.bed_mesh.profile_name === '') return { row: 0, col: 0, positionX: 0, positionY: 0, value: 0 }
|
|
|
|
const [, max] = this.probedHeightmapLimit
|
|
|
|
let row = 0
|
|
let col = 0
|
|
this.bed_mesh.probed_matrix.forEach((rowPoints: number[], index: number) => {
|
|
if (Math.max(...rowPoints) === max) {
|
|
row = index + 1
|
|
col = rowPoints.findIndex((point: number) => point === max) + 1
|
|
}
|
|
})
|
|
|
|
const positionX =
|
|
Math.round(
|
|
(this.bed_mesh.mesh_min[0] +
|
|
((this.bed_mesh.mesh_max[0] - this.bed_mesh.mesh_min[0]) / this.bed_mesh.probed_matrix[0].length) *
|
|
(col - 1)) *
|
|
10
|
|
) / 10
|
|
const positionY =
|
|
Math.round(
|
|
(this.bed_mesh.mesh_min[1] +
|
|
((this.bed_mesh.mesh_max[1] - this.bed_mesh.mesh_min[1]) / this.bed_mesh.probed_matrix.length) *
|
|
(row - 1)) *
|
|
10
|
|
) / 10
|
|
|
|
return {
|
|
row,
|
|
col,
|
|
positionX,
|
|
positionY,
|
|
value: max,
|
|
}
|
|
}
|
|
|
|
get bedMeshMinPoint() {
|
|
if (this.bed_mesh.profile_name === '') return { row: 0, col: 0, positionX: 0, positionY: 0, value: 0 }
|
|
|
|
const [min] = this.probedHeightmapLimit
|
|
|
|
let row = 0
|
|
let col = 0
|
|
this.bed_mesh.probed_matrix.forEach((rowPoints: number[], index: number) => {
|
|
if (Math.min(...rowPoints) === min) {
|
|
row = index + 1
|
|
col = rowPoints.findIndex((point: number) => point === min) + 1
|
|
}
|
|
})
|
|
|
|
const positionX =
|
|
Math.round(
|
|
(this.bed_mesh.mesh_min[0] +
|
|
((this.bed_mesh.mesh_max[0] - this.bed_mesh.mesh_min[0]) / this.bed_mesh.probed_matrix[0].length) *
|
|
(col - 1)) *
|
|
10
|
|
) / 10
|
|
const positionY =
|
|
Math.round(
|
|
(this.bed_mesh.mesh_min[1] +
|
|
((this.bed_mesh.mesh_max[1] - this.bed_mesh.mesh_min[1]) / this.bed_mesh.probed_matrix.length) *
|
|
(row - 1)) *
|
|
10
|
|
) / 10
|
|
|
|
return {
|
|
row,
|
|
col,
|
|
positionX,
|
|
positionY,
|
|
value: min,
|
|
}
|
|
}
|
|
|
|
get meshLoaded() {
|
|
if (this.bed_mesh !== null) {
|
|
return this.bed_mesh.profile_name !== ''
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
tooltipFormatter(data: any): string {
|
|
const outputArray: string[] = []
|
|
outputArray.push('<b>' + data.seriesName + '</b>')
|
|
|
|
Object.keys(data.encode)
|
|
.sort()
|
|
.forEach((axisName: string) => {
|
|
outputArray.push(
|
|
'<b>' +
|
|
axisName.toUpperCase() +
|
|
'</b>: ' +
|
|
data.data[data.encode[axisName][0]].toFixed(axisName === 'z' ? 3 : 1) +
|
|
' mm'
|
|
)
|
|
})
|
|
|
|
return outputArray.join('<br />')
|
|
}
|
|
|
|
loadProfile(name: string): void {
|
|
const gcode = 'BED_MESH_PROFILE LOAD="' + name + '"'
|
|
|
|
this.$store.dispatch('server/addEvent', { message: gcode, type: 'command' })
|
|
this.$socket.emit('printer.gcode.script', { script: gcode }, { loading: 'bedMeshLoad_' + name })
|
|
}
|
|
|
|
openRenameProfile(): void {
|
|
this.newName = this.bed_mesh?.profile_name ?? ''
|
|
this.oldName = this.bed_mesh.profile_name
|
|
this.renameDialog = true
|
|
|
|
setTimeout(() => {
|
|
this.$refs.inputDialogRenameHeightmapName?.focus()
|
|
}, 200)
|
|
}
|
|
|
|
renameProfile(): void {
|
|
this.renameDialog = false
|
|
const gcodeNew = 'BED_MESH_PROFILE SAVE="' + this.newName + '"'
|
|
const gcodeOld = 'BED_MESH_PROFILE REMOVE="' + this.oldName + '"'
|
|
|
|
this.$store.dispatch('server/addEvent', {
|
|
message: gcodeNew,
|
|
type: 'command',
|
|
})
|
|
this.$store.dispatch('server/addEvent', {
|
|
message: gcodeOld,
|
|
type: 'command',
|
|
})
|
|
|
|
this.$socket.emit('printer.gcode.script', { script: gcodeNew }, { loading: 'bedMeshRename' })
|
|
this.$socket.emit('printer.gcode.script', { script: gcodeOld }, { loading: 'bedMeshRename' })
|
|
|
|
this.newName = ''
|
|
this.oldName = ''
|
|
}
|
|
|
|
openRemoveProfile(name: string): void {
|
|
this.removeDialogProfile = name
|
|
this.removeDialog = true
|
|
}
|
|
|
|
removeProfile(): void {
|
|
this.removeDialog = false
|
|
const gcode = 'BED_MESH_PROFILE REMOVE="' + this.removeDialogProfile + '"'
|
|
|
|
this.$store.dispatch('server/addEvent', {
|
|
message: gcode,
|
|
type: 'command',
|
|
})
|
|
this.$socket.emit(
|
|
'printer.gcode.script',
|
|
{ script: gcode },
|
|
{
|
|
action: 'printer/removeBedMeshProfile',
|
|
actionPayload: { name: this.removeDialogProfile },
|
|
loading: 'bedMeshRename_' + this.removeDialogProfile,
|
|
}
|
|
)
|
|
this.removeDialogProfile = ''
|
|
|
|
this.saveConfigDialog = true
|
|
}
|
|
|
|
homePrinter(): void {
|
|
const gcode = 'G28'
|
|
|
|
this.$store.dispatch('server/addEvent', { message: gcode, type: 'command' })
|
|
this.$socket.emit('printer.gcode.script', { script: gcode }, { loading: 'homeAll' })
|
|
}
|
|
|
|
clearBedMesh(): void {
|
|
const gcode = 'BED_MESH_CLEAR'
|
|
|
|
this.$store.dispatch('server/addEvent', { message: gcode, type: 'command' })
|
|
this.$socket.emit('printer.gcode.script', { script: gcode }, { loading: 'bedMeshClear' })
|
|
}
|
|
|
|
openCalibrateMesh() {
|
|
this.calibrateDialog.name = 'default'
|
|
this.calibrateDialog.boolShow = true
|
|
|
|
setTimeout(() => {
|
|
this.$refs.inputFieldCalibrateBedMeshName?.focus()
|
|
}, 200)
|
|
}
|
|
|
|
calibrateMesh(): void {
|
|
this.calibrateDialog.boolShow = false
|
|
const gcode = 'BED_MESH_CALIBRATE PROFILE="' + this.calibrateDialog.name + '"'
|
|
this.$store.dispatch('server/addEvent', { message: gcode, type: 'command' })
|
|
this.$socket.emit('printer.gcode.script', { script: gcode }, { loading: 'bedMeshCalibrate' })
|
|
}
|
|
|
|
saveConfig() {
|
|
const gcode = 'SAVE_CONFIG'
|
|
this.$store.dispatch('server/addEvent', { message: gcode, type: 'command' })
|
|
this.$socket.emit('printer.gcode.script', { script: gcode }, { loading: 'topbarSaveConfig' })
|
|
this.saveConfigDialog = false
|
|
}
|
|
|
|
beforeDestroy(): void {
|
|
if (typeof window === 'undefined') return
|
|
if (this.chart) this.chart.dispose()
|
|
}
|
|
}
|
|
</script>
|