Fix download progress calculation in FileManager and improve error handling in PrinterDatabase. Refactor PrinterClient to manage current filament state more effectively.
Some checks failed
farmcontrol/farmcontrol-server/pipeline/head There was a failure building this commit

This commit is contained in:
Tom Butcher 2026-07-26 16:54:18 +01:00
parent b57d269f9b
commit 57e0c5a30d
3 changed files with 86 additions and 103 deletions

View File

@ -142,13 +142,13 @@ export class FileManager {
onDownloadProgress: (progressEvent) => { onDownloadProgress: (progressEvent) => {
console.log(progressEvent); console.log(progressEvent);
const percent = Math.round( const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total (progressEvent.loaded * 100) / (progressEvent.total || 1),
); );
logger.debug(`Downloading file ${fileId}: ${percent}%`); logger.debug(`Downloading file ${fileId}: ${percent}%`);
this.files = _.unionBy( this.files = _.unionBy(
[{ ...fileObject, state: { type: "downloading", percent } }], [{ ...fileObject, state: { type: "downloading", percent } }],
this.files, this.files,
"_id" "_id",
); );
sendIPC("setFiles", this.files); sendIPC("setFiles", this.files);
if (this.progressCallbacks.has(fileId)) { if (this.progressCallbacks.has(fileId)) {
@ -166,7 +166,7 @@ export class FileManager {
this.files = _.unionBy( this.files = _.unionBy(
[{ ...fileObject, state: { type: "downloaded" } }], [{ ...fileObject, state: { type: "downloaded" } }],
this.files, this.files,
"_id" "_id",
); );
if (this.progressCallbacks.has(fileId)) { if (this.progressCallbacks.has(fileId)) {
const progressCallbacks = this.progressCallbacks.get(fileId); const progressCallbacks = this.progressCallbacks.get(fileId);
@ -193,7 +193,7 @@ export class FileManager {
this.files = _.unionBy( this.files = _.unionBy(
[{ ...fileObject, state: { type: "error" } }], [{ ...fileObject, state: { type: "error" } }],
this.files, this.files,
"_id" "_id",
); );
sendIPC("setFiles", this.files); sendIPC("setFiles", this.files);
} }

View File

@ -12,8 +12,6 @@ export class PrinterDatabase {
this.socketClient = socketClient; this.socketClient = socketClient;
this.printer = printer; this.printer = printer;
this.id = this.printer._id; this.id = this.printer._id;
this.filamentStock = null; // Store current filament stock
this.existingEvent = null; // Store existing stock event
// Initialize cache with 30 second TTL // Initialize cache with 30 second TTL
this.printerCache = new NodeCache({ stdTTL: 30 }); this.printerCache = new NodeCache({ stdTTL: 30 });
logger.info("Initialized PrinterDatabase with socket manager"); logger.info("Initialized PrinterDatabase with socket manager");
@ -106,7 +104,7 @@ export class PrinterDatabase {
if (!printers || printers.length === 0) { if (!printers || printers.length === 0) {
logger.error( logger.error(
`Printer with ID ${this.id} not found when getting config` `Printer with ID ${this.id} not found when getting config`,
); );
return null; return null;
} }
@ -114,7 +112,7 @@ export class PrinterDatabase {
const printer = printers[0]; const printer = printers[0];
logger.debug( logger.debug(
`Retrieved printer config for ${this.id}:`, `Retrieved printer config for ${this.id}:`,
printer.moonraker printer.moonraker,
); );
return printer.moonraker; return printer.moonraker;
} catch (error) { } catch (error) {
@ -132,7 +130,7 @@ export class PrinterDatabase {
if (state.type === "printing" && state.progress === undefined) { if (state.type === "printing" && state.progress === undefined) {
logger.debug( logger.debug(
`Setting default progress for printing state on printer ${this.printer.name}` `Setting default progress for printing state on printer ${this.printer.name}`,
); );
state.progress = 0; state.progress = 0;
} }
@ -158,7 +156,7 @@ export class PrinterDatabase {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to update printer state for ${this.printer.name}:`, `Failed to update printer state for ${this.printer.name}:`,
error error,
); );
throw error; throw error;
} }
@ -276,7 +274,7 @@ export class PrinterDatabase {
// If any subjob is printing, the overall state should be printing // If any subjob is printing, the overall state should be printing
if (stateCounts.deploying > 0) { if (stateCounts.deploying > 0) {
logger.debug( logger.debug(
"Job state determined as 'deploying' due to active deploying subjobs" "Job state determined as 'deploying' due to active deploying subjobs",
); );
return "deploying"; return "deploying";
} }
@ -284,14 +282,14 @@ export class PrinterDatabase {
// If any subjob is printing, the overall state should be printing // If any subjob is printing, the overall state should be printing
if (stateCounts.printing > 0) { if (stateCounts.printing > 0) {
logger.debug( logger.debug(
"Job state determined as 'printing' due to active printing subjobs" "Job state determined as 'printing' due to active printing subjobs",
); );
return "printing"; return "printing";
} }
if (stateCounts.failed > 0 || stateCounts.cancelled > 0) { if (stateCounts.failed > 0 || stateCounts.cancelled > 0) {
logger.debug( logger.debug(
"Job state determined as 'failed' due to failed or cancelled subjobs" "Job state determined as 'failed' due to failed or cancelled subjobs",
); );
return "failed"; return "failed";
} }
@ -324,7 +322,7 @@ export class PrinterDatabase {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to set current job and subjob for printer ${this.id}:`, `Failed to set current job and subjob for printer ${this.id}:`,
error error,
); );
throw error; throw error;
} }
@ -342,7 +340,7 @@ export class PrinterDatabase {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to update display status for printer ${this.id}:`, `Failed to update display status for printer ${this.id}:`,
error error,
); );
throw error; throw error;
} }
@ -352,18 +350,18 @@ export class PrinterDatabase {
try { try {
logger.debug( logger.debug(
`Updating firmware version for printer ${this.id}:`, `Updating firmware version for printer ${this.id}:`,
firmwareVersion firmwareVersion,
); );
this.printer.firmware = firmwareVersion; this.printer.firmware = firmwareVersion;
await this.editPrinter({ firmware: firmwareVersion }); await this.editPrinter({ firmware: firmwareVersion });
logger.info( logger.info(
`Updated firmware version for printer ${this.id}:`, `Updated firmware version for printer ${this.id}:`,
firmwareVersion firmwareVersion,
); );
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to update firmware version for printer ${this.id}:`, `Failed to update firmware version for printer ${this.id}:`,
error error,
); );
throw error; throw error;
} }
@ -399,7 +397,7 @@ export class PrinterDatabase {
if (alertId) { if (alertId) {
filteredAlerts = filteredAlerts.filter( filteredAlerts = filteredAlerts.filter(
(alert) => alert._id !== alertId (alert) => alert._id !== alertId,
); );
} }
@ -414,7 +412,7 @@ export class PrinterDatabase {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to clear alert ${alertId} for printer ${this.id}:`, `Failed to clear alert ${alertId} for printer ${this.id}:`,
error error,
); );
throw error; throw error;
} }
@ -451,7 +449,7 @@ export class PrinterDatabase {
if (!updatedPrinter) { if (!updatedPrinter) {
logger.error( logger.error(
`Printer with ID ${this.id} not found when clearing alerts` `Printer with ID ${this.id} not found when clearing alerts`,
); );
return null; return null;
} }
@ -477,14 +475,14 @@ export class PrinterDatabase {
logger.info( logger.info(
`Updated current filament stock for printer ${this.id}:`, `Updated current filament stock for printer ${this.id}:`,
filamentStock filamentStock,
); );
return updatedPrinter.currentFilamentStock; return updatedPrinter.currentFilamentStock;
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to set current filament stock for printer ${this.id}:`, `Failed to set current filament stock for printer ${this.id}:`,
error error,
); );
throw error; throw error;
} }
@ -494,12 +492,12 @@ export class PrinterDatabase {
filamentStock, filamentStock,
weight, weight,
subJob = null, subJob = null,
job = null job = null,
) { ) {
const subJobId = subJob._id || subJob.id; const subJobId = subJob._id || subJob.id;
const jobId = job._id || job.id; const jobId = job._id || job.id;
console.log("WEIGHT", weight);
try { try {
// Get or fetch filament stock
const filamentStockStockEvents = await this.socketClient.listObjects({ const filamentStockStockEvents = await this.socketClient.listObjects({
objectType: "stockEvent", objectType: "stockEvent",
filter: { filter: {
@ -509,43 +507,20 @@ export class PrinterDatabase {
populate: ["owner"], populate: ["owner"],
}); });
// Calculate new weights immediately
const totalEventWeight =
filamentStockStockEvents.reduce((sum, event) => {
// Skip the existing event if it exists
if (
this.existingEvent &&
event._id.toString() === this.existingEvent._id.toString()
) {
return sum;
}
return sum + event.value;
}, 0) + weight;
const newNetWeight = filamentStock.startingWeight.net + totalEventWeight;
const newGrossWeight =
filamentStock.startingWeight.gross + totalEventWeight;
const remainingPercent = newNetWeight / filamentStock.startingWeight.net;
const state = {
type: newNetWeight <= 0 ? "depleted" : "used",
progress: (1 - remainingPercent).toFixed(2),
};
// Check if a stock event already exists for this subJobId and jobId
const stockEvents = filamentStockStockEvents.filter( const stockEvents = filamentStockStockEvents.filter(
(event) => (event) =>
event.owner._id.toString() === subJobId.toString() && event.owner._id.toString() === subJobId.toString() &&
event.ownerType === "subJob" event.ownerType === "subJob",
); );
let stockEvent; let stockEvent;
if (stockEvents && stockEvents.length > 0) { if (stockEvents && stockEvents.length > 0) {
// Update existing event const existingEvent = stockEvents[0];
this.existingEvent = stockEvents[0];
logger.trace( logger.trace(
`Updating existing stock event for subJobId ${subJobId} and jobId ${jobId}` `Updating existing stock event for subJobId ${subJobId} and jobId ${jobId}`,
); );
stockEvent = await this.socketClient.editObject({ stockEvent = await this.socketClient.editObject({
_id: this.existingEvent._id, _id: existingEvent._id,
objectType: "stockEvent", objectType: "stockEvent",
updateData: { updateData: {
value: weight, value: weight,
@ -554,9 +529,8 @@ export class PrinterDatabase {
auditLog: false, auditLog: false,
}); });
} else { } else {
// Create new stock event
logger.debug( logger.debug(
`Creating new stock event for subJobId ${subJobId} and jobId ${jobId}` `Creating new stock event for subJobId ${subJobId} and jobId ${jobId}`,
); );
stockEvent = await this.socketClient.newObject({ stockEvent = await this.socketClient.newObject({
objectType: "stockEvent", objectType: "stockEvent",
@ -572,33 +546,19 @@ export class PrinterDatabase {
}); });
} }
const updatedFilamentStock = await this.socketClient.editObject({ logger.trace(
_id: filamentStock._id, `Updated stock event for filament stock ${filamentStock._id}:`,
objectType: "filamentStock", {
updateData: { value: weight,
currentWeight: { net: newNetWeight, gross: newGrossWeight }, updatedExistingEvent: stockEvents.length > 0,
state,
}, },
auditLog: false, );
});
// Update the cached filament stock return stockEvent;
this.filamentStock = updatedFilamentStock;
logger.trace(`Updated filament stock ${filamentStock._id}:`, {
newGrossWeight: newGrossWeight,
newNetWeight: newNetWeight,
eventCount: filamentStockStockEvents.length,
updatedExistingEvent: !!this.existingEvent,
remainingPercent: remainingPercent.toFixed(2),
consumedPercent: state.percent,
});
return updatedFilamentStock;
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to update filament stock weight for ${filamentStock._id}:`, `Failed to update stock event for filament stock ${filamentStock._id}:`,
error error,
); );
throw error; throw error;
} }
@ -607,7 +567,7 @@ export class PrinterDatabase {
async queueSubJob(subJob, moonrakerJobId) { async queueSubJob(subJob, moonrakerJobId) {
try { try {
logger.info( logger.info(
`Queueing subjob ${subJob._id} for printer ${this.id} with Moonraker ID ${moonrakerJobId}` `Queueing subjob ${subJob._id} for printer ${this.id} with Moonraker ID ${moonrakerJobId}`,
); );
// 1. Update SubJob with Moonraker ID and State // 1. Update SubJob with Moonraker ID and State
@ -640,7 +600,7 @@ export class PrinterDatabase {
try { try {
logger.debug( logger.debug(
`Setting queued subjobs for printer ${this.id}:`, `Setting queued subjobs for printer ${this.id}:`,
subJobs.map((subJob) => subJob._id).join(", ") subJobs.map((subJob) => subJob._id).join(", "),
); );
const updatedPrinter = await this.editPrinter({ const updatedPrinter = await this.editPrinter({
queue: subJobs, queue: subJobs,
@ -649,7 +609,7 @@ export class PrinterDatabase {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to set queued subjobs for printer ${this.id}:`, `Failed to set queued subjobs for printer ${this.id}:`,
error error,
); );
throw error; throw error;
} }
@ -663,7 +623,7 @@ export class PrinterDatabase {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to get queued subjobs for printer ${this.id}:`, `Failed to get queued subjobs for printer ${this.id}:`,
error error,
); );
throw error; throw error;
} }
@ -680,7 +640,7 @@ export class PrinterDatabase {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to get job by ID ${jobId} for printer ${this.id}:`, `Failed to get job by ID ${jobId} for printer ${this.id}:`,
error error,
); );
throw error; throw error;
} }
@ -689,7 +649,7 @@ export class PrinterDatabase {
async postSubJobPartStockItems(subJobId) { async postSubJobPartStockItems(subJobId) {
try { try {
logger.info( logger.info(
`Posting subjob part stock items for subjob ${subJobId} for printer ${this.id}` `Posting subjob part stock items for subjob ${subJobId} for printer ${this.id}`,
); );
logger.debug(`Getting subjob ${subJobId} for printer ${this.id}`); logger.debug(`Getting subjob ${subJobId} for printer ${this.id}`);
const subJob = await this.socketClient.getObject({ const subJob = await this.socketClient.getObject({
@ -698,7 +658,7 @@ export class PrinterDatabase {
}); });
logger.debug(`Subjob ${subJobId} for printer ${this.id}:`, subJob); logger.debug(`Subjob ${subJobId} for printer ${this.id}:`, subJob);
logger.debug( logger.debug(
`Getting gcode file for subjob ${subJobId} for printer ${this.id}` `Getting gcode file for subjob ${subJobId} for printer ${this.id}`,
); );
const gcodeFile = await this.socketClient.getObject({ const gcodeFile = await this.socketClient.getObject({
objectType: "gcodeFile", objectType: "gcodeFile",
@ -710,7 +670,7 @@ export class PrinterDatabase {
logger.info( logger.info(
`Posting part stock item for part ${ `Posting part stock item for part ${
partItem.part?.name || partItem.part._id partItem.part?.name || partItem.part._id
} for subjob ${subJobId} for printer ${this.id}` } for subjob ${subJobId} for printer ${this.id}`,
); );
const quantity = partItem.quantity; const quantity = partItem.quantity;
@ -734,7 +694,7 @@ export class PrinterDatabase {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to post subjob part stock items for subjob ${subJobId} for printer ${this.id}:`, `Failed to post subjob part stock items for subjob ${subJobId} for printer ${this.id}:`,
error error,
); );
throw error; throw error;
} }
@ -743,7 +703,7 @@ export class PrinterDatabase {
async setSubJobStartedAt(subJobId, date) { async setSubJobStartedAt(subJobId, date) {
try { try {
logger.debug( logger.debug(
`Setting started at for subjob ${subJobId} for printer ${this.id}` `Setting started at for subjob ${subJobId} for printer ${this.id}`,
); );
const updatedSubJob = await this.socketClient.editObject({ const updatedSubJob = await this.socketClient.editObject({
_id: subJobId, _id: subJobId,
@ -754,7 +714,7 @@ export class PrinterDatabase {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to set started at for subjob ${subJobId} for printer ${this.id}:`, `Failed to set started at for subjob ${subJobId} for printer ${this.id}:`,
error error,
); );
throw error; throw error;
} }
@ -763,7 +723,7 @@ export class PrinterDatabase {
async setSubJobFinishedAt(subJobId, date) { async setSubJobFinishedAt(subJobId, date) {
try { try {
logger.debug( logger.debug(
`Setting finished at for subjob ${subJobId} for printer ${this.id}` `Setting finished at for subjob ${subJobId} for printer ${this.id}`,
); );
const updatedSubJob = await this.socketClient.editObject({ const updatedSubJob = await this.socketClient.editObject({
_id: subJobId, _id: subJobId,
@ -774,7 +734,7 @@ export class PrinterDatabase {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to set finished at for subjob ${subJobId} for printer ${this.id}:`, `Failed to set finished at for subjob ${subJobId} for printer ${this.id}:`,
error error,
); );
throw error; throw error;
} }
@ -783,7 +743,7 @@ export class PrinterDatabase {
async setJobStartedAt(jobId, date) { async setJobStartedAt(jobId, date) {
try { try {
logger.debug( logger.debug(
`Setting started at for job ${jobId} for printer ${this.id}` `Setting started at for job ${jobId} for printer ${this.id}`,
); );
const updatedJob = await this.socketClient.editObject({ const updatedJob = await this.socketClient.editObject({
_id: jobId, _id: jobId,
@ -794,7 +754,7 @@ export class PrinterDatabase {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to set started at for job ${jobId} for printer ${this.id}:`, `Failed to set started at for job ${jobId} for printer ${this.id}:`,
error error,
); );
throw error; throw error;
} }
@ -803,7 +763,7 @@ export class PrinterDatabase {
async setJobFinishedAt(jobId, date) { async setJobFinishedAt(jobId, date) {
try { try {
logger.debug( logger.debug(
`Setting finished at for job ${jobId} for printer ${this.id}` `Setting finished at for job ${jobId} for printer ${this.id}`,
); );
const updatedJob = await this.socketClient.editObject({ const updatedJob = await this.socketClient.editObject({
_id: jobId, _id: jobId,
@ -814,7 +774,7 @@ export class PrinterDatabase {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to set finished at for job ${jobId} for printer ${this.id}:`, `Failed to set finished at for job ${jobId} for printer ${this.id}:`,
error error,
); );
throw error; throw error;
} }

View File

@ -41,6 +41,7 @@ export class PrinterClient {
this.motionObject = {}; this.motionObject = {};
this.miscObject = {}; this.miscObject = {};
this.currentFilamentStock = printer.currentFilamentStock; this.currentFilamentStock = printer.currentFilamentStock;
this.currentFilament = null;
this.currentFilamentSku = null; this.currentFilamentSku = null;
this.currentFilamentUsed = 0; this.currentFilamentUsed = 0;
this.registerEventHandlers(); this.registerEventHandlers();
@ -244,6 +245,7 @@ export class PrinterClient {
await this.getInfo(); await this.getInfo();
await this.getFiles(); await this.getFiles();
await this.getCurrentFilament(); await this.getCurrentFilament();
await this.loadCurrentFilament();
await this.updateSubscriptions(); await this.updateSubscriptions();
await this.getPrinterState(); await this.getPrinterState();
await this.syncSubJobs(); await this.syncSubJobs();
@ -286,7 +288,7 @@ export class PrinterClient {
_id: "klippyError", _id: "klippyError",
type: "error", type: "error",
message: klippyResult.state_message, message: klippyResult.state_message,
actions: ["restartPrinter"], actions: ["restart", "restartFirmware"],
}), }),
); );
} }
@ -298,7 +300,7 @@ export class PrinterClient {
_id: "klippyError", _id: "klippyError",
type: "error", type: "error",
message: klippyResult.state_message, message: klippyResult.state_message,
actions: ["restartPrinter"], actions: ["restart", "restartFirmware"],
}), }),
); );
} }
@ -360,6 +362,23 @@ export class PrinterClient {
return this.currentFilamentSku; return this.currentFilamentSku;
} }
async loadCurrentFilament() {
if (!this.currentFilamentStock?.filament?._id) {
this.currentFilament = null;
return null;
}
this.currentFilament = await this.socketClient.getObject({
objectType: "filament",
_id: this.currentFilamentStock.filament._id,
});
return this.currentFilament;
}
async setCurrentFilamentStock(filamentStock) {
this.currentFilamentStock = filamentStock;
await this.loadCurrentFilament();
}
async getQueuedJobIds() { async getQueuedJobIds() {
logger.info(`Getting queued jobs info for (${this.printer.name})`); logger.info(`Getting queued jobs info for (${this.printer.name})`);
const result = await this.sendPrinterCommand({ const result = await this.sendPrinterCommand({
@ -511,6 +530,7 @@ export class PrinterClient {
// Calculate weight in grams // Calculate weight in grams
const filamentWeightG = const filamentWeightG =
filamentVolumeCm3 * this.currentFilament?.density || 0; filamentVolumeCm3 * this.currentFilament?.density || 0;
console.log("FILAMENT WEIGHT", this.currentFilament);
this.currentFilamentUsed = -1 * filamentWeightG; this.currentFilamentUsed = -1 * filamentWeightG;
} }
@ -701,7 +721,7 @@ export class PrinterClient {
newFilamentDetected == true && newFilamentDetected == true &&
this.currentFilamentStock == null this.currentFilamentStock == null
) { ) {
this.currentFilamentStock = null; await this.setCurrentFilamentStock(null);
await this.database.setCurrentFilamentStock(null); await this.database.setCurrentFilamentStock(null);
await this.database.removeAlert("noFilamentSelected"); await this.database.removeAlert("noFilamentSelected");
await this.database.addAlert({ await this.database.addAlert({
@ -731,7 +751,7 @@ export class PrinterClient {
actions: ["loadFilamentStock"], actions: ["loadFilamentStock"],
canDismiss: false, canDismiss: false,
}); });
this.currentFilamentStock = null; await this.setCurrentFilamentStock(null);
await this.database.setCurrentFilamentStock(null); await this.database.setCurrentFilamentStock(null);
} }
} }
@ -1144,19 +1164,21 @@ export class PrinterClient {
"to printer:", "to printer:",
this.id, this.id,
"with gcode file:", "with gcode file:",
`${subJob.gcodeFile.name}`, `${subJob.gcodeFile._id}`,
); );
console.log("subJob", subJob);
const gcodeFile = await this.socketClient.getObject({ const gcodeFile = await this.socketClient.getObject({
objectType: "gcodeFile", objectType: "gcodeFile",
_id: subJob.gcodeFile, _id: subJob.gcodeFile._id,
}); });
if (!gcodeFile) { if (!gcodeFile) {
throw new Error("G-code file not found"); throw new Error("G-code file not found");
} }
console.log("gcodeFile", gcodeFile);
const file = await this.socketClient.fileManager.getFile( const file = await this.socketClient.fileManager.getFile(
gcodeFile.file, gcodeFile.file?._id || gcodeFile.file,
async (progress) => { async (progress) => {
await this.database.updateSubJobState(subJob._id, { await this.database.updateSubJobState(subJob._id, {
type: "deploying", type: "deploying",
@ -1495,10 +1517,11 @@ export class PrinterClient {
async loadFilamentStock(filamentStock) { async loadFilamentStock(filamentStock) {
if (filamentStock == null) { if (filamentStock == null) {
await this.database.setCurrentFilamentStock(null); await this.database.setCurrentFilamentStock(null);
await this.setCurrentFilamentStock(null);
return true; return true;
} }
await this.database.setCurrentFilamentStock(filamentStock); await this.database.setCurrentFilamentStock(filamentStock);
this.currentFilamentStock = filamentStock; await this.setCurrentFilamentStock(filamentStock);
await this.getCurrentFilament(); await this.getCurrentFilament();
await this.database.removeAlert("noFilamentSelected"); await this.database.removeAlert("noFilamentSelected");
await this.database.removeAlert("noFilamentLoaded"); await this.database.removeAlert("noFilamentLoaded");