diff --git a/src/components/widgets/exclude-objects/ExcludeObjects.vue b/src/components/widgets/exclude-objects/ExcludeObjects.vue
new file mode 100644
index 0000000000..32801c5cb2
--- /dev/null
+++ b/src/components/widgets/exclude-objects/ExcludeObjects.vue
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/widgets/exclude-objects/ExcludeObjectsDialog.vue b/src/components/widgets/exclude-objects/ExcludeObjectsDialog.vue
new file mode 100644
index 0000000000..b1580cccbb
--- /dev/null
+++ b/src/components/widgets/exclude-objects/ExcludeObjectsDialog.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
+ $cancelled
+
+
+ {{ $t('app.gcode.label.exclude_object') }}
+
+
+
+
+
+
+
+
+ {{ part.name }}
+ |
+
+
+
+ $cancelled
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+ {{ $t('app.general.btn.close') }}
+
+
+
+
+
+
+
+
+
diff --git a/src/components/widgets/gcode-preview/GcodePreview.vue b/src/components/widgets/gcode-preview/GcodePreview.vue
index 6e91d1b34f..2c7b3c028b 100644
--- a/src/components/widgets/gcode-preview/GcodePreview.vue
+++ b/src/components/widgets/gcode-preview/GcodePreview.vue
@@ -193,6 +193,10 @@
:stroke-width="extrusionLineWidth"
/>
+
@@ -204,8 +208,13 @@ import StateMixin from '@/mixins/state'
import panzoom, { PanZoom } from 'panzoom'
import { BBox, LayerNr, LayerPaths } from '@/store/gcodePreview/types'
import { GcodePreviewConfig } from '@/store/config/types'
+import ExcludeObjects from '@/components/widgets/exclude-objects/ExcludeObjects.vue'
-@Component({})
+@Component({
+ components: {
+ ExcludeObjects
+ }
+})
export default class GcodePreview extends Mixins(StateMixin) {
@Prop({ type: Boolean, default: true })
public disabled!: boolean
@@ -276,6 +285,21 @@ export default class GcodePreview extends Mixins(StateMixin) {
return this.panning ? 'optimizeSpeed' : 'geometricPrecision'
}
+ get showExcludeObjects () {
+ if (!(this.printerPrinting || this.printerPaused)) return false
+
+ const file = this.$store.getters['gcodePreview/getFile']
+ if (!file) {
+ return true
+ }
+ const printerFile = this.$store.state.printer.printer.current_file
+
+ if (printerFile.filename) {
+ return (file.path + '/' + file.filename) === (printerFile.path + '/' + printerFile.filename)
+ }
+ return false
+ }
+
get flipX (): boolean {
return this.$store.state.config.uiSettings.gcodePreview.flip.horizontal
}
diff --git a/src/components/widgets/gcode-preview/GcodePreviewCard.vue b/src/components/widgets/gcode-preview/GcodePreviewCard.vue
index 8dd9ed9797..84a33d75d2 100644
--- a/src/components/widgets/gcode-preview/GcodePreviewCard.vue
+++ b/src/components/widgets/gcode-preview/GcodePreviewCard.vue
@@ -16,6 +16,18 @@
+
+ $cancelled
+
+
+
+
@@ -139,13 +159,15 @@ import GcodePreviewParserProgressDialog from '@/components/widgets/gcode-preview
import { MinMax } from '@/store/gcodePreview/types'
import AppBtnCollapseGroup from '@/components/ui/AppBtnCollapseGroup.vue'
import { AxiosResponse } from 'axios'
+import ExcludeObjectsDialog from '@/components/widgets/exclude-objects/ExcludeObjectsDialog.vue'
@Component({
components: {
AppBtnCollapseGroup,
GcodePreviewParserProgressDialog,
GcodePreview,
- GcodePreviewControls
+ GcodePreviewControls,
+ ExcludeObjectsDialog
}
})
export default class GcodePreviewCard extends Mixins(StateMixin, FilesMixin) {
@@ -157,6 +179,7 @@ export default class GcodePreviewCard extends Mixins(StateMixin, FilesMixin) {
currentLayer = 0
moveProgress = 0
+ excludeObjectDialog = false
get visibleLayer () {
return this.currentLayer + 1
@@ -226,6 +249,15 @@ export default class GcodePreviewCard extends Mixins(StateMixin, FilesMixin) {
}
}
+ @Watch('fileLoaded')
+ onFileLoaded () {
+ if (this.fileLoaded &&
+ this.$store.state.config?.uiSettings.gcodePreview.autoFollowOnFileLoad &&
+ this.printerFileLoaded) {
+ this.$store.commit('gcodePreview/setViewerState', { followProgress: true }, { root: true })
+ }
+ }
+
get file (): AppFile | undefined {
return this.$store.getters['gcodePreview/getFile']
}
@@ -348,5 +380,22 @@ export default class GcodePreviewCard extends Mixins(StateMixin, FilesMixin) {
get autoLoadOnPrintStart () {
return this.$store.state.config.uiSettings.gcodePreview.autoLoadOnPrintStart
}
+
+ async cancelObject (id: string) {
+ const reqId = id.toUpperCase().replace(/\s/g, '_')
+
+ const res = await this.$confirm(
+ this.$tc('app.general.simple_form.msg.confirm_exclude_object'),
+ { title: this.$tc('app.general.label.confirm'), color: 'card-heading', icon: '$error' }
+ )
+
+ if (res) {
+ this.sendGcode(`EXCLUDE_OBJECT NAME=${reqId}`)
+ }
+ }
+
+ get parts () {
+ return Object.values(this.$store.getters['parts/getParts'])
+ }
}
diff --git a/src/locales/de.yaml b/src/locales/de.yaml
index de2ca882f6..93e02a50df 100644
--- a/src/locales/de.yaml
+++ b/src/locales/de.yaml
@@ -110,6 +110,7 @@ app:
load_current_file: Aktuelle Datei laden
label:
current_layer_height: Aktuelle Schichthöhe
+ exclude_object: Objekt ausschließen
follow_progress: Fortschritt folgen
layer: Schicht
layers: Schichten
@@ -292,6 +293,7 @@ app:
Probe/Bltouch Z-Offset wird aus dem aktuellen Z-Offset berechnet und
aktualisiert.
confirm: Sind Sie sicher?
+ confirm_exclude_object: Sind Sie sicher, dass Sie dieses Objekt vom Druck ausschließen wollen?
confirm_power_device_toggle: Sind Sie sicher? Dies wird das Gerät ein- bzw. ausschalten.
confirm_reboot_host: Sind Sie sicher? Das Host-System wird neu gestartet.
confirm_service_restart: Möchten Sie den Dienst %{name} wirklich neu starten?
diff --git a/src/locales/en.yaml b/src/locales/en.yaml
index 2a9078c58f..c1cb914a14 100644
--- a/src/locales/en.yaml
+++ b/src/locales/en.yaml
@@ -109,6 +109,7 @@ app:
load_current_file: Load Current File
label:
current_layer_height: Current Layer Height
+ exclude_object: Exclude Object
follow_progress: Follow progress
layer: Layer
layers: Layers
@@ -296,6 +297,7 @@ app:
required: Required
msg:
confirm: Are you sure?
+ confirm_exclude_object: Are you sure you want to exclude this object from printing?
confirm_reboot_host: Are you sure? This will reboot your host system.
confirm_shutdown_host: Are you sure? This will shutdown your host system.
confirm_service_restart: Are you sure you want to restart the %{name} service?
diff --git a/src/store/config/index.ts b/src/store/config/index.ts
index 0898e5a264..ef0f3245a3 100644
--- a/src/store/config/index.ts
+++ b/src/store/config/index.ts
@@ -122,7 +122,7 @@ export const defaultState = (): ConfigState => {
drawBackground: true,
showAnimations: true,
groupLowerLayers: false,
- autoLoadOnPrintStart: true,
+ autoLoadOnPrintStart: false,
autoFollowOnFileLoad: true,
flip: {
horizontal: false,
diff --git a/src/store/gcodePreview/actions.ts b/src/store/gcodePreview/actions.ts
index 9490ddd0b5..92b2c5e616 100644
--- a/src/store/gcodePreview/actions.ts
+++ b/src/store/gcodePreview/actions.ts
@@ -23,7 +23,7 @@ export const actions: ActionTree = {
}
},
- async loadGcode ({ commit, getters, state, rootState }, payload: { file: AppFile; gcode: string }) {
+ async loadGcode ({ commit, getters, state }, payload: { file: AppFile; gcode: string }) {
const worker = await spawn(new Worker(new URL('@/workers/parseGcode.worker.ts', import.meta.url) as any))
commit('setParserWorker', worker)
@@ -46,10 +46,6 @@ export const actions: ActionTree = {
commit('setMoves', [])
commit('setFile', payload.file)
- if (rootState.config?.uiSettings.gcodePreview.autoFollowOnFileLoad) {
- // check if loaded file equals printed file is handled downstream
- commit('setViewerState', { followProgress: true })
- }
try {
commit('setMoves', await Promise.race([abort, worker.parse(payload.gcode)]))
diff --git a/src/store/helpers.ts b/src/store/helpers.ts
index 501a6b014a..c73668bc95 100644
--- a/src/store/helpers.ts
+++ b/src/store/helpers.ts
@@ -49,7 +49,23 @@ export const getThumb = (thumbnails: Thumbnail[], path: string, large = true) =>
}
}
-export const handlePrintStateChange = (payload: any) => {
+export const handleExcludeObjectChange = (payload: any, state: any, dispatch: any) => {
+ // For every notify - if print_stats.state changes from standby -> printing,
+ // then record an entry in our print history.
+ // If the state changes from printing -> complete, then record the finish time.
+ if ('exclude_object' in payload) {
+ dispatch('parts/onPartUpdate', payload.exclude_object, { root: true })
+ }
+
+ if (
+ 'print_stats' in payload &&
+ ('state' in payload.print_stats || 'filename' in payload.print_stats)
+ ) {
+ dispatch('parts/onPrintStatsUpdate', payload.print_stats, { root: true })
+ }
+}
+
+export const handlePrintStateChange = (payload: any, state: any, dispatch: any) => {
// For every notify - if print_stats.state changes from standby -> printing,
// then record an entry in our print history.
// If the state changes from printing -> complete, then record the finish time.
@@ -58,23 +74,23 @@ export const handlePrintStateChange = (payload: any) => {
'state' in payload.print_stats
) {
if (
- store.state.printer?.printer.print_stats.state === 'standby' &&
+ state.printer?.printer.print_stats.state !== 'printing' &&
payload.print_stats.state === 'printing'
) {
// This is a new print starting...
- store.dispatch('printer/onPrintStart', payload)
+ dispatch('printer/onPrintStart', payload, { root: true })
} else if (
- store.state.printer?.printer.print_stats.state === 'printing' &&
+ state.printer?.printer.print_stats.state === 'printing' &&
payload.print_stats.state === 'complete'
) {
// This is a completed print...
- store.dispatch('printer/onPrintEnd', payload)
+ dispatch('printer/onPrintEnd', payload, { root: true })
} else if (
- store.state.printer?.printer.print_stats.state === 'printing' &&
+ state.printer?.printer.print_stats.state === 'printing' &&
payload.print_stats.state === 'standby'
) {
// This is a cancelled print...
- store.dispatch('printer/onPrintEnd', payload)
+ dispatch('printer/onPrintEnd', payload, { root: true })
}
}
}
diff --git a/src/store/index.ts b/src/store/index.ts
index d4df048f5b..0fad07d9e1 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -25,6 +25,7 @@ import { announcements } from './announcements'
import { wait } from './wait'
import { gcodePreview } from './gcodePreview'
import { timelapse } from './timelapse'
+import { parts } from './parts'
Vue.use(Vuex)
@@ -51,7 +52,8 @@ export default new Vuex.Store({
announcements,
wait,
gcodePreview,
- timelapse
+ timelapse,
+ parts
},
mutations: {},
actions: {
diff --git a/src/store/parts/actions.ts b/src/store/parts/actions.ts
new file mode 100644
index 0000000000..236328daf9
--- /dev/null
+++ b/src/store/parts/actions.ts
@@ -0,0 +1,20 @@
+import { ActionTree } from 'vuex'
+import { PartsState } from './types'
+import { RootState } from '../types'
+
+export const actions: ActionTree = {
+ /**
+ * Reset our store
+ */
+ async reset ({ commit }) {
+ commit('setReset')
+ },
+
+ async onPartUpdate ({ commit }, payload) {
+ commit('partUpdate', payload)
+ },
+
+ async onPrintStatsUpdate ({ commit }, payload) {
+ commit('printStatsUpdate', payload)
+ }
+}
diff --git a/src/store/parts/getters.ts b/src/store/parts/getters.ts
new file mode 100644
index 0000000000..814ad0fba6
--- /dev/null
+++ b/src/store/parts/getters.ts
@@ -0,0 +1,42 @@
+import { GetterTree } from 'vuex'
+import { Point, Part, PartsState } from './types'
+import { RootState } from '../types'
+
+export const getters: GetterTree = {
+ getParts: (state): {[key: string]: Part} => {
+ return state.parts
+ },
+
+ getIsPartCurrent: (state) => (partName: string): boolean => {
+ return state.currentPart === partName
+ },
+
+ getIsPartExcluded: (state) => (partName: string): boolean => {
+ return state.excludedParts.includes(partName)
+ },
+
+ getPartPos: (state, getters) => (partName: string): Point => {
+ const p = getters.getParts[partName]
+ return p.target
+ },
+
+ getPartSVG: (state, getters) => (partName: string): string => {
+ const part = getters.getParts[partName]
+
+ let svg = ''
+ let op = 'M'
+
+ part.outline.forEach((p: Point) => {
+ svg += `${op}${p.x},${p.y}`
+ op = 'L'
+ })
+
+ svg += 'z'
+
+ return svg
+ },
+
+ getPrintState: (state): string => {
+ return state.printState
+ }
+}
diff --git a/src/store/parts/index.ts b/src/store/parts/index.ts
new file mode 100644
index 0000000000..c92bf9b112
--- /dev/null
+++ b/src/store/parts/index.ts
@@ -0,0 +1,29 @@
+import { Module } from 'vuex'
+import { getters } from './getters'
+import { actions } from './actions'
+import { mutations } from './mutations'
+import { PartsState } from './types'
+import { RootState } from '../types'
+
+/**
+ * Maintains the state of the console
+ */
+export const defaultState = (): PartsState => {
+ return {
+ parts: {},
+ excludedParts: [],
+ printState: 'unknown'
+ }
+}
+
+export const state = defaultState()
+
+const namespaced = true
+
+export const parts: Module = {
+ namespaced,
+ state,
+ getters,
+ actions,
+ mutations
+}
diff --git a/src/store/parts/mutations.ts b/src/store/parts/mutations.ts
new file mode 100644
index 0000000000..f8956c7a48
--- /dev/null
+++ b/src/store/parts/mutations.ts
@@ -0,0 +1,54 @@
+import { MutationTree } from 'vuex'
+import { defaultState } from '.'
+import { Part, PartObject, PartsState } from './types'
+import Vue from 'vue'
+
+export const mutations: MutationTree = {
+ /**
+ * Reset state
+ */
+ setReset (state) {
+ Object.assign(state, defaultState())
+ },
+
+ partUpdate (state, payload) {
+ if ('current_object' in payload) {
+ Vue.set(state, 'currentPart', payload.current_object)
+ }
+
+ if ('excluded_objects' in payload) {
+ Vue.set(state, 'excludedParts', payload.excluded_objects)
+ }
+
+ if ('objects' in payload) {
+ const partMap: { [key: string]: Part} = {}
+ payload.objects.forEach((obj: PartObject) => {
+ const name = obj.name
+ const part: Part = {
+ name,
+ outline: [],
+ target: null
+ }
+
+ if ('center' in obj && obj.center.length === 2) {
+ part.target = { x: obj.center[0], y: obj.center[1] }
+ }
+
+ if ('polygon' in obj) {
+ part.outline = obj.polygon.map(p => {
+ return { x: p[0], y: p[1] }
+ })
+ }
+
+ partMap[name] = part
+ })
+ Vue.set(state, 'parts', Object.freeze(partMap))
+ }
+ },
+
+ printStatsUpdate (state, payload) {
+ if ('state' in payload) {
+ Vue.set(state, 'printState', payload.state)
+ }
+ }
+}
diff --git a/src/store/parts/types.ts b/src/store/parts/types.ts
new file mode 100644
index 0000000000..2eb9dc4766
--- /dev/null
+++ b/src/store/parts/types.ts
@@ -0,0 +1,23 @@
+export interface PartsState {
+ parts: { [key: string]: Part};
+ excludedParts: string[];
+ printState: string;
+ currentPart?: string;
+}
+
+export interface Point {
+ x: number;
+ y: number;
+}
+
+export interface Part {
+ name: string;
+ outline: Point[];
+ target: Point | null;
+}
+
+export interface PartObject {
+ name: string;
+ polygon: [number, number][];
+ center: [number, number] | [number, number, number];
+}
diff --git a/src/store/printer/actions.ts b/src/store/printer/actions.ts
index 2f42a542f4..ce5b61d7a1 100644
--- a/src/store/printer/actions.ts
+++ b/src/store/printer/actions.ts
@@ -1,7 +1,7 @@
import { ActionTree } from 'vuex'
import { PrinterState } from './types'
import { RootState } from '../types'
-import { handlePrintStateChange, handleCurrentFileChange } from '../helpers'
+import { handlePrintStateChange, handleCurrentFileChange, handleExcludeObjectChange } from '../helpers'
import { handleAddChartEntry, handleSystemStatsChange, handleMcuStatsChange } from '../chart_helpers'
import { SocketActions } from '@/api/socketActions'
import { Globals } from '@/globals'
@@ -115,7 +115,7 @@ export const actions: ActionTree = {
*/
/** Automated notify events via socket */
- async onNotifyStatusUpdate ({ rootState, commit, getters }, payload) {
+ async onNotifyStatusUpdate ({ rootState, commit, getters, dispatch }, payload) {
// TODO: We potentially get many updates here.
// Consider caching the updates and sending them every .
// We don't want to miss an update - but also don't need all of them
@@ -129,7 +129,8 @@ export const actions: ActionTree = {
// We do this prior to commiting the notify so we can
// compare the before and after.
handleCurrentFileChange(payload)
- handlePrintStateChange(payload)
+ handlePrintStateChange(payload, rootState, dispatch)
+ handleExcludeObjectChange(payload, rootState, dispatch)
handleSystemStatsChange(payload)
handleMcuStatsChange(payload)
diff --git a/src/store/types.ts b/src/store/types.ts
index 4e86c76eb0..bbd50539c0 100644
--- a/src/store/types.ts
+++ b/src/store/types.ts
@@ -10,6 +10,7 @@ import { DevicePowerState } from './power/types'
import { HistoryState } from './history/types'
import { VersionState } from './version/types'
import { GcodePreviewState } from './gcodePreview/types'
+import { PartsState } from './parts/types'
import { LayoutState } from './layout/types'
import { MeshState } from './mesh/types'
import { NotificationsState } from './notifications/types'
@@ -33,6 +34,7 @@ export interface RootState {
history?: HistoryState;
version?: VersionState;
gcodePreview?: GcodePreviewState;
+ parts?: PartsState;
notifications?: NotificationsState;
announcements?: AnnouncementsState;
timelapse?: TimelapseState;