diff --git a/src/components/dialogs/AfcChangeSpoolDialog.vue b/src/components/dialogs/AfcChangeSpoolDialog.vue new file mode 100644 index 000000000..cf49f4508 --- /dev/null +++ b/src/components/dialogs/AfcChangeSpoolDialog.vue @@ -0,0 +1,400 @@ + + + + diff --git a/src/components/panels/Afc/AfcExtruderTools.vue b/src/components/panels/Afc/AfcExtruderTools.vue new file mode 100644 index 000000000..88f00f66a --- /dev/null +++ b/src/components/panels/Afc/AfcExtruderTools.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/src/components/panels/Afc/AfcExtruderToolsItem.vue b/src/components/panels/Afc/AfcExtruderToolsItem.vue new file mode 100644 index 000000000..fc44f2c26 --- /dev/null +++ b/src/components/panels/Afc/AfcExtruderToolsItem.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/src/components/panels/Afc/AfcUnits.vue b/src/components/panels/Afc/AfcUnits.vue new file mode 100644 index 000000000..730253eab --- /dev/null +++ b/src/components/panels/Afc/AfcUnits.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/src/components/panels/Afc/AfcUnitsItem.vue b/src/components/panels/Afc/AfcUnitsItem.vue new file mode 100644 index 000000000..44dc669af --- /dev/null +++ b/src/components/panels/Afc/AfcUnitsItem.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/src/components/panels/Afc/AfcUnitsItemLane.vue b/src/components/panels/Afc/AfcUnitsItemLane.vue new file mode 100644 index 000000000..df267891e --- /dev/null +++ b/src/components/panels/Afc/AfcUnitsItemLane.vue @@ -0,0 +1,363 @@ + + + + + diff --git a/src/components/panels/AfcPanel.vue b/src/components/panels/AfcPanel.vue new file mode 100644 index 000000000..e7fb16c2d --- /dev/null +++ b/src/components/panels/AfcPanel.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/src/components/ui/AFCLogo.vue b/src/components/ui/AFCLogo.vue new file mode 100644 index 000000000..8878a453f --- /dev/null +++ b/src/components/ui/AFCLogo.vue @@ -0,0 +1,47 @@ + + + diff --git a/src/components/ui/BoxTurtleIcon.vue b/src/components/ui/BoxTurtleIcon.vue new file mode 100644 index 000000000..43ec8fde2 --- /dev/null +++ b/src/components/ui/BoxTurtleIcon.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/components/ui/FilamentReelIcon.vue b/src/components/ui/FilamentReelIcon.vue new file mode 100644 index 000000000..1edb656e4 --- /dev/null +++ b/src/components/ui/FilamentReelIcon.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/src/components/ui/InfinityIcon.vue b/src/components/ui/InfinityIcon.vue new file mode 100644 index 000000000..c5f239aec --- /dev/null +++ b/src/components/ui/InfinityIcon.vue @@ -0,0 +1,38 @@ + + + + diff --git a/src/components/ui/NightOwlIcon.vue b/src/components/ui/NightOwlIcon.vue new file mode 100644 index 000000000..3a1b0c167 --- /dev/null +++ b/src/components/ui/NightOwlIcon.vue @@ -0,0 +1,40 @@ + + + diff --git a/src/components/ui/SpoolIcon.vue b/src/components/ui/SpoolIcon.vue index d0b65e6c8..2e9442c66 100644 --- a/src/components/ui/SpoolIcon.vue +++ b/src/components/ui/SpoolIcon.vue @@ -1,48 +1,48 @@ - - - + + + diff --git a/src/locales/en.json b/src/locales/en.json index c897531ac..27f6f4a61 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -608,6 +608,32 @@ "Headline": "Manual Probe" }, "Panels": { + "AfcPanel": { + "Detected": "Detected", + "Eject": "Eject Filament", + "Empty": "Empty", + "ExtruderTools": "Extruder Tools", + "Headline": "Automated Filament Control", + "Hub": "Hub", + "HubStatus": "{unit} Hub Load Status", + "InfiniteSpool": "Next lane to load if the current lane runs out", + "LaneCommands": "Lane Controls", + "LaneEmpty": "Empty", + "LaneErrorLoad": "Check Lane, Filament not detected at the extruder", + "LaneLoaded": "Loaded", + "LaneLoadedNone": "NONE", + "LaneMap": "Remap current lane to selected T command", + "Load": "Load Lane", + "PostExtruderSensor": "Post-Extruder Sensor", + "PreExtruderSensor": "Pre-Extruder Sensor", + "Unload": "Unload Lane" + }, + "AfcSpoolPanel": { + "FilamentType": "Filament Type", + "RemainingWeight": "Remaining Weight", + "SpoolColor": "Filament Color", + "UpdateSpool": "Update Spool" + }, "ExtruderControlPanel": { "Allowed": "Allowed", "CleanNozzle": "Clean Nozzle", diff --git a/src/pages/Dashboard.vue b/src/pages/Dashboard.vue index 2b531c980..bc1c6d77d 100644 --- a/src/pages/Dashboard.vue +++ b/src/pages/Dashboard.vue @@ -96,6 +96,7 @@ import StatusPanel from '@/components/panels/StatusPanel.vue' import ToolheadControlPanel from '@/components/panels/ToolheadControlPanel.vue' import TemperaturePanel from '@/components/panels/TemperaturePanel.vue' import WebcamPanel from '@/components/panels/WebcamPanel.vue' +import AfcPanel from '@/components/panels/AfcPanel.vue' @Component({ components: { @@ -112,6 +113,7 @@ import WebcamPanel from '@/components/panels/WebcamPanel.vue' ToolheadControlPanel, TemperaturePanel, WebcamPanel, + AfcPanel, }, }) export default class PageDashboard extends Mixins(DashboardMixin) { diff --git a/src/store/gui/getters.ts b/src/store/gui/getters.ts index 5975dab59..08c3fbd35 100644 --- a/src/store/gui/getters.ts +++ b/src/store/gui/getters.ts @@ -91,6 +91,11 @@ export const getters: GetterTree = { allPanels = allPanels.filter((name) => name !== 'spoolman') } + // remove afc panel, if no AFC section exists + if (!rootState.printer?.AFC) { + allPanels = allPanels.filter((name) => name !== 'afc') + } + return allPanels }, diff --git a/src/store/server/afc/actions.ts b/src/store/server/afc/actions.ts new file mode 100644 index 000000000..8892dec39 --- /dev/null +++ b/src/store/server/afc/actions.ts @@ -0,0 +1,85 @@ +import { ActionTree } from 'vuex' +import { RootState } from '@/store/types' +import { AFCState, Unit, Lane } from '@/store/server/afc/types' + +export const actions: ActionTree = { + reset({ commit }) { + commit('reset') + }, + + fetchAFCData({ commit, rootState }) { + const afcData = rootState.printer?.AFC ? JSON.parse(JSON.stringify(rootState.printer.AFC)) : null + + if (!afcData || !afcData.system || !afcData.system.num_units) { + commit('reset') + return + } + + const units: Unit[] = [] + const mapSet = new Set() + const laneSet = new Set() + + Object.entries(afcData).forEach(([unitName, unitData]) => { + if (unitName !== 'system') { + const lanes: Lane[] = [] + Object.entries(unitData as { [key: string]: any }).forEach(([laneName, laneData]) => { + if (laneName !== 'system') { + const lane = { + ...laneData, + unitName, + laneName, + empty: '#2e2e2e', + spool: { + material: laneData.material || '', + spool_id: laneData.spool_id || '', + color: laneData.color || '#000000', + weight: laneData.weight || 0, + }, + } as Lane + lanes.push(lane) + if (lane.LANE !== undefined) { + laneSet.add(lane.laneName) + } + if (lane.map) { + mapSet.add(lane.map) + } + } + }) + const unit: Unit = { + system: (unitData as any).system, + unitName, + lanes, + } + units.push(unit) + } + }) + + const laneList = Array.from(laneSet).sort() + laneList.unshift('NONE') + const mapList = Array.from(mapSet).sort() + + commit('updateSystem', afcData.system) + commit('setLaneList', laneList) + commit('setMapList', mapList) + commit('setUnits', units) + }, + + setActiveUnit({ commit }, unit: Unit | null) { + commit('setActiveUnit', unit) + }, + + setActiveLane({ commit }, lane: Lane | null) { + commit('setActiveLane', lane) + }, + + updateLaneSpoolInfo({ commit, state }, { laneName, spool }) { + const unit = state.data.units.find((unit) => unit.lanes.some((lane) => lane.laneName === laneName)) + if (unit) { + const lane = unit.lanes.find((lane) => lane.laneName === laneName) + if (lane) { + lane.spool = { ...lane.spool, ...spool } + commit('setUnits', [...state.data.units]) + } + } + }, +} diff --git a/src/store/server/afc/getters.ts b/src/store/server/afc/getters.ts new file mode 100644 index 000000000..980952193 --- /dev/null +++ b/src/store/server/afc/getters.ts @@ -0,0 +1,46 @@ +import { GetterTree } from 'vuex' +import { AFCState, Unit, Lane, System, Extruder } from '@/store/server/afc/types' +import { RootState } from '@/store/types' + +export const getters: GetterTree = { + getUnits: (state): Unit[] => { + return state.data.units + }, + + getLane: + (state) => + (unitName: string, laneName: string): Lane | null => { + const unit = state.data.units.find((unit) => unit.system.type === unitName) + return unit?.lanes.find((lane) => lane.laneName === laneName) || null + }, + + getLaneList: (state): string[] => { + return state.data.laneList + }, + + getMapList: (state): string[] => { + return state.data.mapList + }, + + getActiveUnit: (state): Unit | null => { + return state.activeUnit + }, + + getActiveLane: (state): Lane | null => { + return state.activeLane + }, + + getSystemInfo: (state): System => { + return state.data.system + }, + + getExtruders: (state): any[] => { + return state.data.system.extruders || [] + }, + + getExtruder: + (state) => + (extruderName: string): Extruder | null => { + return state.data.system.extruders?.find((extruder) => extruder.name === extruderName) || null + }, +} diff --git a/src/store/server/afc/index.ts b/src/store/server/afc/index.ts new file mode 100644 index 000000000..d2888f3f0 --- /dev/null +++ b/src/store/server/afc/index.ts @@ -0,0 +1,36 @@ +import { Module } from 'vuex' +import { AFCState } from '@/store/server/afc/types' +import { actions } from '@/store/server/afc/actions' +import { mutations } from '@/store/server/afc/mutations' +import { getters } from '@/store/server/afc/getters' + +export const getDefaultState = (): AFCState => { + return { + data: { + units: [], + system: { + current_load: null, + num_units: 0, + num_lanes: 0, + num_extruders: 0, + extruders: [], + }, + laneList: [], + mapList: [], + }, + activeUnit: null, + activeLane: null, + } +} + +// Initial state +const state = getDefaultState() + +// Vuex Module +export const afc: Module = { + namespaced: true, + state, + mutations, + getters, + actions, +} diff --git a/src/store/server/afc/mutations.ts b/src/store/server/afc/mutations.ts new file mode 100644 index 000000000..8aa42ff8a --- /dev/null +++ b/src/store/server/afc/mutations.ts @@ -0,0 +1,34 @@ +import { getDefaultState } from './index' +import Vue from 'vue' +import { MutationTree } from 'vuex' +import { AFCState, Unit, Lane, System } from '@/store/server/afc/types' + +export const mutations: MutationTree = { + reset(state) { + Object.assign(state, getDefaultState()) + }, + + updateSystem(state, system: System | null) { + Vue.set(state.data, 'system', system) + }, + + setLaneList(state, laneList) { + Vue.set(state.data, 'laneList', laneList) + }, + + setMapList(state, mapList) { + Vue.set(state.data, 'mapList', mapList) + }, + + setUnits(state, units: Unit[]) { + Vue.set(state.data, 'units', units) + }, + + setActiveUnit(state, unit: Unit | null) { + Vue.set(state.data, 'activeUnit', unit) + }, + + setActiveLane(state, lane: Lane | null) { + Vue.set(state.data, 'activeLane', lane) + }, +} diff --git a/src/store/server/afc/types.ts b/src/store/server/afc/types.ts new file mode 100644 index 000000000..bd2f9ca1e --- /dev/null +++ b/src/store/server/afc/types.ts @@ -0,0 +1,64 @@ +export interface AFCState { + data: AFCRoot + activeUnit: Unit | null + activeLane: Lane | null +} + +export interface AFCRoot { + units: Unit[] + system: System + laneList: string[] + mapList: string[] +} + +export interface Spool { + material: string + spool_id: string + color: string + weight: number +} + +export interface Lane { + LANE: number + map: string + load: boolean + prep: boolean + tool_loaded: boolean + loaded_to_hub: boolean + spool: Spool + runout_lane: string + filament_status: string + filament_status_led: string + status: string + unitName: string + laneName: string + empty: string +} + +export interface Unit { + system: { + type: string + hub_loaded: boolean + can_cut: boolean + screen: string + } + unitName: string + lanes: Lane[] +} + +export interface Extruder { + name: string + lane_loaded: string + tool_start_sensor: boolean + tool_end_sensor: boolean + buffer: string + buffer_status: string +} + +export interface System { + current_load: string | null + num_units: number + num_lanes: number + num_extruders: number + extruders: Extruder[] +} diff --git a/src/store/server/index.ts b/src/store/server/index.ts index 5e90dc7dc..90908c3be 100644 --- a/src/store/server/index.ts +++ b/src/store/server/index.ts @@ -12,6 +12,7 @@ import { timelapse } from '@/store/server/timelapse' import { jobQueue } from '@/store/server/jobQueue' import { announcements } from '@/store/server/announcements' import { spoolman } from '@/store/server/spoolman' +import { afc } from '@/store/server/afc' import { sensor } from '@/store/server/sensor' // create getDefaultState @@ -56,6 +57,7 @@ export const server: Module = { actions, mutations, modules: { + afc, power, updateManager, history, diff --git a/src/store/variables.ts b/src/store/variables.ts index 74dc2a847..9a1633709 100644 --- a/src/store/variables.ts +++ b/src/store/variables.ts @@ -95,6 +95,7 @@ export const allDashboardPanels = [ 'spoolman', 'temperature', 'webcam', + 'afc', ] export const thumbnailSmallMin = 30