Feature: exclude objects (#362)

* feature: add exclude objects on the dashboard
* chore: fix ts issues from status panel
* chore: update gcodeviewer to v2.1.12
* feature: add object selection to gcode viewer

Signed-off-by: Stefan Dej <meteyou@gmail.com>
This commit is contained in:
Stefan Dej
2021-09-26 17:32:11 +02:00
committed by GitHub
parent dc9d3361a0
commit eadc796e01
6 changed files with 160 additions and 32 deletions

14
package-lock.json generated
View File

@@ -16,7 +16,7 @@
"@codemirror/state": "^0.19.1", "@codemirror/state": "^0.19.1",
"@codemirror/stream-parser": "^0.19.1", "@codemirror/stream-parser": "^0.19.1",
"@codemirror/view": "^0.19.1", "@codemirror/view": "^0.19.1",
"@sindarius/gcodeviewer": "^2.1.11", "@sindarius/gcodeviewer": "^2.1.12",
"axios": "^0.21.1", "axios": "^0.21.1",
"core-js": "^3.16.0", "core-js": "^3.16.0",
"echarts": "^5.1.2", "echarts": "^5.1.2",
@@ -1980,9 +1980,9 @@
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
}, },
"node_modules/@sindarius/gcodeviewer": { "node_modules/@sindarius/gcodeviewer": {
"version": "2.1.11", "version": "2.1.12",
"resolved": "https://registry.npmjs.org/@sindarius/gcodeviewer/-/gcodeviewer-2.1.11.tgz", "resolved": "https://registry.npmjs.org/@sindarius/gcodeviewer/-/gcodeviewer-2.1.12.tgz",
"integrity": "sha512-B2qTxf+Tiv6RTIe9HULdqAJfrPIIPmvRtcChZDsjwTnNzmDuP7/l1K0VANgp9+kFvsGSzdFuM72r+auUMZyGsw==", "integrity": "sha512-gquHWaNMvInCuV0Wrzy8nM84TD5fysg80kfImL+DqJdw8O7jrtYS3Rd11oVeL9mOMtcmd+HQ99VWI0Bz5xvCZQ==",
"dependencies": { "dependencies": {
"@babylonjs/core": "^4.2.0", "@babylonjs/core": "^4.2.0",
"@babylonjs/inspector": "^4.2.0", "@babylonjs/inspector": "^4.2.0",
@@ -21797,9 +21797,9 @@
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
}, },
"@sindarius/gcodeviewer": { "@sindarius/gcodeviewer": {
"version": "2.1.11", "version": "2.1.12",
"resolved": "https://registry.npmjs.org/@sindarius/gcodeviewer/-/gcodeviewer-2.1.11.tgz", "resolved": "https://registry.npmjs.org/@sindarius/gcodeviewer/-/gcodeviewer-2.1.12.tgz",
"integrity": "sha512-B2qTxf+Tiv6RTIe9HULdqAJfrPIIPmvRtcChZDsjwTnNzmDuP7/l1K0VANgp9+kFvsGSzdFuM72r+auUMZyGsw==", "integrity": "sha512-gquHWaNMvInCuV0Wrzy8nM84TD5fysg80kfImL+DqJdw8O7jrtYS3Rd11oVeL9mOMtcmd+HQ99VWI0Bz5xvCZQ==",
"requires": { "requires": {
"@babylonjs/core": "^4.2.0", "@babylonjs/core": "^4.2.0",
"@babylonjs/inspector": "^4.2.0", "@babylonjs/inspector": "^4.2.0",

View File

@@ -22,7 +22,7 @@
"@codemirror/state": "^0.19.1", "@codemirror/state": "^0.19.1",
"@codemirror/stream-parser": "^0.19.1", "@codemirror/stream-parser": "^0.19.1",
"@codemirror/view": "^0.19.1", "@codemirror/view": "^0.19.1",
"@sindarius/gcodeviewer": "^2.1.11", "@sindarius/gcodeviewer": "^2.1.12",
"axios": "^0.21.1", "axios": "^0.21.1",
"core-js": "^3.16.0", "core-js": "^3.16.0",
"echarts": "^5.1.2", "echarts": "^5.1.2",

View File

@@ -96,6 +96,9 @@
<v-list-item class="minHeight36"> <v-list-item class="minHeight36">
<v-checkbox class="mt-0" hide-details v-model="showTravelMoves" :label="$t('GCodeViewer.ShowTravelMoves')"></v-checkbox> <v-checkbox class="mt-0" hide-details v-model="showTravelMoves" :label="$t('GCodeViewer.ShowTravelMoves')"></v-checkbox>
</v-list-item> </v-list-item>
<v-list-item class="minHeight36" v-if="loadedFile === sdCardFilePath && printing_objects.length > 1">
<v-checkbox class="mt-0" hide-details v-model="showObjectSelection" :label="$t('GCodeViewer.ShowObjectSelection')"></v-checkbox>
</v-list-item>
<v-divider></v-divider> <v-divider></v-divider>
<v-list-item class="minHeight36"> <v-list-item class="minHeight36">
<v-checkbox class="mt-0" hide-details v-model="hdRendering" :label="$t('GCodeViewer.HDRendering')"></v-checkbox> <v-checkbox class="mt-0" hide-details v-model="hdRendering" :label="$t('GCodeViewer.HDRendering')"></v-checkbox>
@@ -260,6 +263,14 @@ export default class Viewer extends Mixins(BaseMixin) {
return this.printerIsPrinting && this.sdCardFilePath === this.loadedFile return this.printerIsPrinting && this.sdCardFilePath === this.loadedFile
} }
get printing_objects() {
return this.$store.state.printer.exclude_object?.objects ?? []
}
get excluded_objects() {
return this.$store.state.printer.exclude_object?.excluded_objects ?? []
}
async init() { async init() {
let canvasElement = this.$store.state.gcodeviewer?.canvasBackup ?? null let canvasElement = this.$store.state.gcodeviewer?.canvasBackup ?? null
@@ -356,6 +367,26 @@ export default class Viewer extends Mixins(BaseMixin) {
this.zSlider = this.maxZSlider this.zSlider = this.maxZSlider
this.loading = false this.loading = false
viewer.setCursorVisiblity(this.showCursor) viewer.setCursorVisiblity(this.showCursor)
viewer.gcodeProcessor.updateFilePosition(viewer.fileSize)
if (this.loadedFile === this.sdCardFilePath && this.printing_objects.length) {
let objects: any = []
this.printing_objects.forEach((object: any) => {
const xValues = object.polygon.map((point: number[]) => point[0])
const yValues = object.polygon.map((point: number[]) => point[1])
objects.push({
cancelled: this.excluded_objects.includes(object.name),
name: object.name,
x: [Math.min(...xValues), Math.max(...xValues)],
y: [Math.min(...yValues), Math.max(...yValues)],
})
})
viewer.buildObjects.loadObjectBoundaries(objects)
viewer.buildObjects.showObjectSelection(this.showObjectSelection)
}
} }
async fileSelected(e: any) { async fileSelected(e: any) {
@@ -423,8 +454,8 @@ export default class Viewer extends Mixins(BaseMixin) {
await new Promise((resolve) => setTimeout(resolve, ms)) await new Promise((resolve) => setTimeout(resolve, ms))
} }
loadCurrentFile() { async loadCurrentFile() {
this.loadFile('gcodes/' + this.sdCardFilePath) await this.loadFile('gcodes/' + this.sdCardFilePath)
this.loadedFile = this.sdCardFilePath this.loadedFile = this.sdCardFilePath
} }
@@ -532,6 +563,19 @@ export default class Viewer extends Mixins(BaseMixin) {
viewer?.toggleTravels(newVal) viewer?.toggleTravels(newVal)
} }
get showObjectSelection(): boolean {
return this.$store.state.gui.gcodeViewer.showObjectSelection ?? false
}
set showObjectSelection(newVal: boolean) {
this.$store.dispatch('gui/saveSetting', {name: 'gcodeViewer.showObjectSelection', value: newVal})
}
@Watch('showObjectSelection')
showObjectSelectionChanged(newVal: boolean) {
viewer?.buildObjects.showObjectSelection(newVal)
}
get hdRendering() { get hdRendering() {
return this.$store.state.gui.gcodeViewer.hdRendering return this.$store.state.gui.gcodeViewer.hdRendering
} }

View File

@@ -73,6 +73,43 @@
</v-card-title> </v-card-title>
</v-img> </v-img>
</template> </template>
<template v-if="['printing', 'paused'].includes(printer_state) && printing_objects.length">
<v-container>
<v-row>
<v-col class="py-2">
<span class="subtitle-2 d-block px-0 text--disabled"><v-icon class="mr-2" small>mdi-printer-3d-nozzle</v-icon>{{ current_object !== null ? current_object : '--' }}</span>
</v-col>
<v-col class="col-auto py-2">
<v-icon class="text--disabled cursor-pointer" @click="openCancelObjectDialog(current_object)" small v-if="current_object !== null">mdi-close-circle</v-icon>
</v-col>
</v-row>
</v-container>
<v-divider class="mt-0 mb-0" ></v-divider>
<template v-if="boolShowObjects">
<div v-for="object in printing_objects" v-bind:key="object.name">
<v-container>
<v-row>
<v-col class="py-2">
<span class="subtitle-2 d-block px-0 text--disabled">{{ object.name }}</span>
</v-col>
<v-col class="col-auto py-2">
<v-chip pill small class="text--disabled" v-if="excluded_objects.includes(object.name)">{{ $t('Panels.StatusPanel.Canceled') }}</v-chip>
<v-icon class="text--disabled cursor-pointer" @click="openCancelObjectDialog(object.name)" small v-else>mdi-close-circle</v-icon>
</v-col>
</v-row>
</v-container>
<v-divider class="mt-0 mb-0" ></v-divider>
</div>
</template>
<v-container>
<v-row>
<v-col class="py-1 font-italic text-center">
<span @click="boolShowObjects = !boolShowObjects" class="text--disabled cursor-pointer"><v-icon class="mr-3 text--disabled" small>mdi-chevron-{{ boolShowObjects ? 'up' : 'down' }}</v-icon><small>{{ $t('Panels.StatusPanel.CountObjects', {count: printing_objects.length}) }}</small><v-icon class="ml-3 text--disabled" small>mdi-chevron-{{ boolShowObjects ? 'up' : 'down' }}</v-icon></span>
</v-col>
</v-row>
</v-container>
<v-divider class="mt-0 mb-0" ></v-divider>
</template>
<template v-if="display_message || print_stats_message"> <template v-if="display_message || print_stats_message">
<v-container> <v-container>
<v-row> <v-row>
@@ -267,28 +304,47 @@
</template> </template>
</v-card-text> </v-card-text>
</v-card> </v-card>
<v-dialog v-model="cancelObjectDialog.bool" max-width="290">
<v-card>
<v-toolbar flat dense >
<v-toolbar-title>
<span class="subheading"><v-icon class="mdi mdi-information" left></v-icon>{{ $t("Panels.StatusPanel.ExcludeObjectHeadline") }}</span>
</v-toolbar-title>
</v-toolbar>
<v-card-text class="mt-3">{{ $t("Panels.StatusPanel.ExcludeObjectText", {name: cancelObjectDialog.objectName}) }}</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="cancelObjectDialog.bool = false">{{ $t("Panels.StatusPanel.Cancel") }}</v-btn>
<v-btn color="primary" text @click="cancelObject">{{ $t("Panels.StatusPanel.ExcludeObject") }}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div> </div>
</template> </template>
<script> <script lang="ts">
import Component from 'vue-class-component' import Component from 'vue-class-component'
import { Mixins, Watch } from 'vue-property-decorator' import { Mixins, Watch } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base' import BaseMixin from '@/components/mixins/base'
import VueLoadImage from 'vue-load-image' import MinSettingsPanel from '@/components/panels/MinSettingsPanel.vue'
import MinSettingsPanel from '@/components/panels/MinSettingsPanel' import MoonrakerStatePanel from '@/components/panels/MoonrakerStatePanel.vue'
import MoonrakerStatePanel from '@/components/panels/MoonrakerStatePanel' import KlippyStatePanel from '@/components/panels/KlippyStatePanel.vue'
import KlippyStatePanel from '@/components/panels/KlippyStatePanel' import KlipperWarningsPanel from '@/components/panels/KlipperWarningsPanel.vue'
import KlipperWarningsPanel from '@/components/panels/KlipperWarningsPanel'
@Component({ @Component({
components: {KlipperWarningsPanel, KlippyStatePanel, MoonrakerStatePanel, MinSettingsPanel}, components: {KlipperWarningsPanel, KlippyStatePanel, MoonrakerStatePanel, MinSettingsPanel}
VueLoadImage
}) })
export default class StatusPanel extends Mixins(BaseMixin) { export default class StatusPanel extends Mixins(BaseMixin) {
maxFlow = 0 maxFlow = 0
boolShowObjects = false
refs = { cancelObjectDialog = {
bigThumbnail: HTMLDivElement bool: false,
objectName: ''
}
$refs!: {
bigThumbnail: any
} }
get current_filename() { get current_filename() {
@@ -485,7 +541,7 @@ export default class StatusPanel extends Mixins(BaseMixin) {
'thumbnails' in this.current_file && 'thumbnails' in this.current_file &&
this.current_file.thumbnails.length this.current_file.thumbnails.length
) { ) {
const thumbnail = this.current_file.thumbnails.find(thumb => const thumbnail = this.current_file.thumbnails.find((thumb: any) =>
thumb.width >= 32 && thumb.width <= 64 && thumb.width >= 32 && thumb.width <= 64 &&
thumb.height >= 32 && thumb.height <= 64 thumb.height >= 32 && thumb.height <= 64
) )
@@ -509,7 +565,7 @@ export default class StatusPanel extends Mixins(BaseMixin) {
'thumbnails' in this.current_file && 'thumbnails' in this.current_file &&
this.current_file.thumbnails.length this.current_file.thumbnails.length
) { ) {
const thumbnail = this.current_file.thumbnails.find(thumb => thumb.width >= 300 && thumb.width <= 400) const thumbnail = this.current_file.thumbnails.find((thumb: any) => thumb.width >= 300 && thumb.width <= 400)
if (thumbnail && 'relative_path' in thumbnail) { if (thumbnail && 'relative_path' in thumbnail) {
let relative_url = '' let relative_url = ''
@@ -530,7 +586,7 @@ export default class StatusPanel extends Mixins(BaseMixin) {
'thumbnails' in this.current_file && 'thumbnails' in this.current_file &&
this.current_file.thumbnails.length this.current_file.thumbnails.length
) { ) {
const thumbnail = this.current_file.thumbnails.find(thumb => thumb.width >= 300 && thumb.width <= 400) const thumbnail = this.current_file.thumbnails.find((thumb: any) => thumb.width >= 300 && thumb.width <= 400)
if (thumbnail && 'height' in thumbnail) { if (thumbnail && 'height' in thumbnail) {
return thumbnail.height return thumbnail.height
@@ -545,7 +601,7 @@ export default class StatusPanel extends Mixins(BaseMixin) {
'thumbnails' in this.current_file && 'thumbnails' in this.current_file &&
this.current_file.thumbnails.length this.current_file.thumbnails.length
) { ) {
const thumbnail = this.current_file.thumbnails.find(thumb => thumb.width >= 300 && thumb.width <= 400) const thumbnail = this.current_file.thumbnails.find((thumb: any) => thumb.width >= 300 && thumb.width <= 400)
if (thumbnail && 'width' in thumbnail) { if (thumbnail && 'width' in thumbnail) {
return thumbnail.width return thumbnail.width
@@ -561,6 +617,18 @@ export default class StatusPanel extends Mixins(BaseMixin) {
return this.current_filename && setting && this.thumbnailBig return this.current_filename && setting && this.thumbnailBig
} }
get printing_objects() {
return this.$store.state.printer.exclude_object?.objects ?? []
}
get current_object() {
return this.$store.state.printer.exclude_object?.current_object ?? null
}
get excluded_objects() {
return this.$store.state.printer.exclude_object?.excluded_objects ?? []
}
btnPauseJob() { btnPauseJob() {
this.$socket.emit('printer.print.pause', { }, { loading: 'statusPrintPause' }) this.$socket.emit('printer.print.pause', { }, { loading: 'statusPrintPause' })
} }
@@ -585,7 +653,7 @@ export default class StatusPanel extends Mixins(BaseMixin) {
this.$socket.emit('printer.gcode.script', {script: 'M117'}) this.$socket.emit('printer.gcode.script', {script: 'M117'})
} }
formatTime(seconds) { formatTime(seconds: number) {
let h = Math.floor(seconds / 3600) let h = Math.floor(seconds / 3600)
seconds %= 3600 seconds %= 3600
let m = ('0' + Math.floor(seconds / 60)).slice(-2) let m = ('0' + Math.floor(seconds / 60)).slice(-2)
@@ -594,19 +662,17 @@ export default class StatusPanel extends Mixins(BaseMixin) {
return h+':'+m+':'+s return h+':'+m+':'+s
} }
formatDateTime(msec) { formatDateTime(msec: number) {
const date = new Date(msec) const date = new Date(msec)
const h = date.getHours() >= 10 ? date.getHours() : '0'+date.getHours() const h = date.getHours() >= 10 ? date.getHours() : '0'+date.getHours()
const m = date.getMinutes() >= 10 ? date.getMinutes() : '0'+date.getMinutes() const m = date.getMinutes() >= 10 ? date.getMinutes() : '0'+date.getMinutes()
const diff = msec - new Date().getTime() const diff = msec - new Date().getTime()
return h+':'+m+((diff > 60*60*24*1000) ? '+'+parseInt(diff / (60*60*24*1000)) : '') return h+':'+m+((diff > 60*60*24*1000) ? '+'+Math.round(diff / (60*60*24*1000)) : '')
} }
@Watch('live_flow') @Watch('live_flow')
live_flowChanged(newVal) { live_flowChanged(newVal: number) {
newVal = parseFloat(newVal)
if (newVal && this.maxFlow < newVal) this.maxFlow = newVal if (newVal && this.maxFlow < newVal) this.maxFlow = newVal
} }
@@ -626,6 +692,16 @@ export default class StatusPanel extends Mixins(BaseMixin) {
} }
} }
openCancelObjectDialog(objectName: string) {
this.cancelObjectDialog.objectName = objectName
this.cancelObjectDialog.bool = true
}
cancelObject() {
this.$socket.emit('printer.gcode.script', {script: 'EXCLUDE_OBJECT NAME='+this.cancelObjectDialog.objectName})
this.cancelObjectDialog.bool = false
}
onResize() { onResize() {
const isFocused = (document.activeElement === this.$refs.bigThumbnail?.$el) const isFocused = (document.activeElement === this.$refs.bigThumbnail?.$el)
if (isFocused) this.focusBigThumbnail() if (isFocused) this.focusBigThumbnail()

View File

@@ -336,7 +336,13 @@
"Total": "Total", "Total": "Total",
"Slicer": "Slicer", "Slicer": "Slicer",
"ETA": "ETA", "ETA": "ETA",
"Unknown": "Unknown" "Unknown": "Unknown",
"ExcludeObjectHeadline": "Exclude Object",
"ExcludeObjectText": "Do you really want to exclude \"{name}\"?",
"Cancel": "cancel",
"ExcludeObject": "Exclude object",
"CountObjects": "{count} Objects",
"Canceled": "Canceled"
}, },
"ToolsPanel": { "ToolsPanel": {
"Headline": "Temperatures", "Headline": "Temperatures",
@@ -613,7 +619,8 @@
"Ultra" : "Ultra", "Ultra" : "Ultra",
"Max" : "Max", "Max" : "Max",
"ShowToolhead":"Show Toolhead", "ShowToolhead":"Show Toolhead",
"ShowTravelMoves":"ShowTravelMoves", "ShowTravelMoves":"Show Travel Moves",
"ShowObjectSelection":"Show Object Selection",
"HDRendering":"HD Rendering (Round Extrusions)", "HDRendering":"HD Rendering (Round Extrusions)",
"ForceLineRendering":"Force Line Rendering", "ForceLineRendering":"Force Line Rendering",
"Transparency":"Transparency", "Transparency":"Transparency",

View File

@@ -178,6 +178,7 @@ export const getDefaultState = (): GuiState => {
progressColor : '#FFFFFF', progressColor : '#FFFFFF',
showCursor: true, showCursor: true,
showTravelMoves: false, showTravelMoves: false,
showObjectSelection: false,
hdRendering: false, hdRendering: false,
forceLineRendering: false, forceLineRendering: false,
transparency: false, transparency: false,