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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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/stream-parser": "^0.19.1",
"@codemirror/view": "^0.19.1",
"@sindarius/gcodeviewer": "^2.1.11",
"@sindarius/gcodeviewer": "^2.1.12",
"axios": "^0.21.1",
"core-js": "^3.16.0",
"echarts": "^5.1.2",
@ -1980,9 +1980,9 @@
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
},
"node_modules/@sindarius/gcodeviewer": {
"version": "2.1.11",
"resolved": "https://registry.npmjs.org/@sindarius/gcodeviewer/-/gcodeviewer-2.1.11.tgz",
"integrity": "sha512-B2qTxf+Tiv6RTIe9HULdqAJfrPIIPmvRtcChZDsjwTnNzmDuP7/l1K0VANgp9+kFvsGSzdFuM72r+auUMZyGsw==",
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@sindarius/gcodeviewer/-/gcodeviewer-2.1.12.tgz",
"integrity": "sha512-gquHWaNMvInCuV0Wrzy8nM84TD5fysg80kfImL+DqJdw8O7jrtYS3Rd11oVeL9mOMtcmd+HQ99VWI0Bz5xvCZQ==",
"dependencies": {
"@babylonjs/core": "^4.2.0",
"@babylonjs/inspector": "^4.2.0",
@ -21797,9 +21797,9 @@
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
},
"@sindarius/gcodeviewer": {
"version": "2.1.11",
"resolved": "https://registry.npmjs.org/@sindarius/gcodeviewer/-/gcodeviewer-2.1.11.tgz",
"integrity": "sha512-B2qTxf+Tiv6RTIe9HULdqAJfrPIIPmvRtcChZDsjwTnNzmDuP7/l1K0VANgp9+kFvsGSzdFuM72r+auUMZyGsw==",
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@sindarius/gcodeviewer/-/gcodeviewer-2.1.12.tgz",
"integrity": "sha512-gquHWaNMvInCuV0Wrzy8nM84TD5fysg80kfImL+DqJdw8O7jrtYS3Rd11oVeL9mOMtcmd+HQ99VWI0Bz5xvCZQ==",
"requires": {
"@babylonjs/core": "^4.2.0",
"@babylonjs/inspector": "^4.2.0",

View File

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

View File

@ -96,6 +96,9 @@
<v-list-item class="minHeight36">
<v-checkbox class="mt-0" hide-details v-model="showTravelMoves" :label="$t('GCodeViewer.ShowTravelMoves')"></v-checkbox>
</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-list-item class="minHeight36">
<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
}
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() {
let canvasElement = this.$store.state.gcodeviewer?.canvasBackup ?? null
@ -356,6 +367,26 @@ export default class Viewer extends Mixins(BaseMixin) {
this.zSlider = this.maxZSlider
this.loading = false
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) {
@ -423,8 +454,8 @@ export default class Viewer extends Mixins(BaseMixin) {
await new Promise((resolve) => setTimeout(resolve, ms))
}
loadCurrentFile() {
this.loadFile('gcodes/' + this.sdCardFilePath)
async loadCurrentFile() {
await this.loadFile('gcodes/' + this.sdCardFilePath)
this.loadedFile = this.sdCardFilePath
}
@ -532,6 +563,19 @@ export default class Viewer extends Mixins(BaseMixin) {
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() {
return this.$store.state.gui.gcodeViewer.hdRendering
}

View File

@ -73,6 +73,43 @@
</v-card-title>
</v-img>
</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">
<v-container>
<v-row>
@ -267,28 +304,47 @@
</template>
</v-card-text>
</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>
</template>
<script>
<script lang="ts">
import Component from 'vue-class-component'
import { Mixins, Watch } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import VueLoadImage from 'vue-load-image'
import MinSettingsPanel from '@/components/panels/MinSettingsPanel'
import MoonrakerStatePanel from '@/components/panels/MoonrakerStatePanel'
import KlippyStatePanel from '@/components/panels/KlippyStatePanel'
import KlipperWarningsPanel from '@/components/panels/KlipperWarningsPanel'
import MinSettingsPanel from '@/components/panels/MinSettingsPanel.vue'
import MoonrakerStatePanel from '@/components/panels/MoonrakerStatePanel.vue'
import KlippyStatePanel from '@/components/panels/KlippyStatePanel.vue'
import KlipperWarningsPanel from '@/components/panels/KlipperWarningsPanel.vue'
@Component({
components: {KlipperWarningsPanel, KlippyStatePanel, MoonrakerStatePanel, MinSettingsPanel},
VueLoadImage
components: {KlipperWarningsPanel, KlippyStatePanel, MoonrakerStatePanel, MinSettingsPanel}
})
export default class StatusPanel extends Mixins(BaseMixin) {
maxFlow = 0
boolShowObjects = false
refs = {
bigThumbnail: HTMLDivElement
cancelObjectDialog = {
bool: false,
objectName: ''
}
$refs!: {
bigThumbnail: any
}
get current_filename() {
@ -485,7 +541,7 @@ export default class StatusPanel extends Mixins(BaseMixin) {
'thumbnails' in this.current_file &&
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.height >= 32 && thumb.height <= 64
)
@ -509,7 +565,7 @@ export default class StatusPanel extends Mixins(BaseMixin) {
'thumbnails' in this.current_file &&
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) {
let relative_url = ''
@ -530,7 +586,7 @@ export default class StatusPanel extends Mixins(BaseMixin) {
'thumbnails' in this.current_file &&
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) {
return thumbnail.height
@ -545,7 +601,7 @@ export default class StatusPanel extends Mixins(BaseMixin) {
'thumbnails' in this.current_file &&
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) {
return thumbnail.width
@ -561,6 +617,18 @@ export default class StatusPanel extends Mixins(BaseMixin) {
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() {
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'})
}
formatTime(seconds) {
formatTime(seconds: number) {
let h = Math.floor(seconds / 3600)
seconds %= 3600
let m = ('0' + Math.floor(seconds / 60)).slice(-2)
@ -594,19 +662,17 @@ export default class StatusPanel extends Mixins(BaseMixin) {
return h+':'+m+':'+s
}
formatDateTime(msec) {
formatDateTime(msec: number) {
const date = new Date(msec)
const h = date.getHours() >= 10 ? date.getHours() : '0'+date.getHours()
const m = date.getMinutes() >= 10 ? date.getMinutes() : '0'+date.getMinutes()
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')
live_flowChanged(newVal) {
newVal = parseFloat(newVal)
live_flowChanged(newVal: number) {
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() {
const isFocused = (document.activeElement === this.$refs.bigThumbnail?.$el)
if (isFocused) this.focusBigThumbnail()

View File

@ -336,7 +336,13 @@
"Total": "Total",
"Slicer": "Slicer",
"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": {
"Headline": "Temperatures",
@ -613,7 +619,8 @@
"Ultra" : "Ultra",
"Max" : "Max",
"ShowToolhead":"Show Toolhead",
"ShowTravelMoves":"ShowTravelMoves",
"ShowTravelMoves":"Show Travel Moves",
"ShowObjectSelection":"Show Object Selection",
"HDRendering":"HD Rendering (Round Extrusions)",
"ForceLineRendering":"Force Line Rendering",
"Transparency":"Transparency",

View File

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