UNPKG

@fdm-monster/server

Version:

FDM Monster is a bulk OctoPrint, Klipper, PrusaLink and BambuLab manager to set up, configure and monitor 3D printers. Our aim is to provide neat overview over your farm.

151 lines (150 loc) 7.09 kB
import { BgCodeBlockTypeName, BgCodeBlockTypes, BgCodeCompressionName } from "../bgcode/bgcode.types.js"; import { processThumbnail } from "../bgcode/bgcode-thumbnail.parser.js"; import { decompressBlock, extractMetadataFromBlocks, getBlockData, parseBlockHeaders, parseFileHeader } from "../bgcode/bgcode.utils.js"; import path from "node:path"; import fs, { open } from "node:fs/promises"; //#region src/utils/parsers/bgcode.parser.ts /** * BGCode parser for extracting metadata from .bgcode files * BGCode is a binary G-code format used by Prusa printers */ var BGCodeParser = class { async parse(filePath) { const stats = await fs.stat(filePath); const fileName = path.basename(filePath); const fileHandle = await open(filePath, "r"); try { const { version, checksumType } = await parseFileHeader(fileHandle); if (version !== 1) throw new Error(`Unsupported BGCode version: ${version}`); const blockHeaders = await parseBlockHeaders(fileHandle, stats.size, checksumType, true); const metadata = await extractMetadataFromBlocks(fileHandle, blockHeaders); const thumbnails = await this.extractThumbnailsFromBlocks(fileHandle, blockHeaders); const isMmu = this.isMmuData(metadata.nozzle_diameter) || this.isMmuData(metadata.temperature) || this.isMmuData(metadata.filament_used_mm) || this.isMmuData(metadata.bed_temperature) || this.isMmuData(metadata.filament_type, ";"); const normalized = { fileName, fileFormat: "bgcode", fileSize: stats.size, producer: metadata.producer, producedOn: metadata.produced_on, checksumType: checksumType === 1 ? "CRC32" : "None", isMmu: isMmu || void 0, gcodePrintTimeSeconds: this.parseTime(metadata.estimated_printing_time_normal_mode || metadata.print_time), gcodePrintTimeSecondsSilent: this.parseTime(metadata.estimated_printing_time_silent_mode), nozzleDiameterMm: isMmu ? this.parseNumberArray(metadata.nozzle_diameter) : this.parseFirstValue(metadata.nozzle_diameter), filamentDiameterMm: isMmu ? this.parseNumberArray(metadata.filament_diameter) : this.parseFirstValue(metadata.filament_diameter) || 1.75, filamentDensityGramsCm3: isMmu ? this.parseNumberArray(metadata.filament_density) : this.parseFirstValue(metadata.filament_density), filamentUsedMm: isMmu ? this.parseNumberArray(metadata.filament_used_mm) : this.parseFirstValue(metadata.filament_used_mm), filamentUsedCm3: isMmu ? this.parseNumberArray(metadata.filament_used_cm3) : this.parseFirstValue(metadata.filament_used_cm3), filamentUsedGrams: isMmu ? this.parseNumberArray(metadata.filament_used_g) : this.parseFirstValue(metadata.filament_used_g), totalFilamentUsedGrams: isMmu ? this.sumNumberArray(this.parseNumberArray(metadata.filament_used_g)) : this.parseFirstValue(metadata.filament_used_g), layerHeight: this.parseFloat(metadata.layer_height), firstLayerHeight: this.parseFloat(metadata.first_layer_height || metadata.initial_layer_height), bedTemperature: isMmu ? this.parseNumberArray(metadata.bed_temperature) : this.parseFirstValue(metadata.bed_temperature), nozzleTemperature: isMmu ? this.parseNumberArray(metadata.temperature) : this.parseFirstValue(metadata.temperature), fillDensity: metadata.fill_density || null, filamentType: isMmu ? this.parseStringArray(metadata.filament_type, ";") : this.parseFirstCsvValue(metadata.filament_type), printerModel: metadata.printer_model || null, slicerVersion: metadata.producer || null, maxLayerZ: this.parseFloat(metadata.max_layer_z), totalLayers: this.parseInt(metadata.total_layers || metadata.layer_count), thumbnails: thumbnails.length > 0 ? thumbnails.map((t) => ({ width: t.width, height: t.height, format: t.format, dataLength: t.data?.length || 0 })) : void 0, blocks: blockHeaders.map((b) => ({ type: BgCodeBlockTypeName[b.type] || `Unknown(${b.type})`, compressedSize: b.compressedSize, uncompressedSize: b.uncompressedSize, compression: BgCodeCompressionName[b.compression] || `Unknown(${b.compression})` })) }; return { raw: { _thumbnails: thumbnails, blocks: blockHeaders.map((b) => ({ type: BgCodeBlockTypeName[b.type] || `Unknown(${b.type})`, compressedSize: b.compressedSize, uncompressedSize: b.uncompressedSize, compression: BgCodeCompressionName[b.compression] || `Unknown(${b.compression})` })) }, normalized }; } finally { await fileHandle.close(); } } async extractThumbnailsFromBlocks(fileHandle, blockHeaders) { const thumbnails = []; const thumbnailBlocks = blockHeaders.filter((b) => b.type === BgCodeBlockTypes.Thumbnail); for (const header of thumbnailBlocks) { const parameters = header.parameters; const blockData = await getBlockData(fileHandle, header); const processed = processThumbnail(decompressBlock(header.compression, blockData), parameters); thumbnails.push({ width: parameters.width, height: parameters.height, format: processed.extension, data: processed.data.toString("base64") }); } return thumbnails; } parseNumber(value, parser) { if (!value) return null; const num = parser(value); return Number.isNaN(num) ? null : num; } parseFloat(value) { return this.parseNumber(value, Number.parseFloat); } parseInt(value) { return this.parseNumber(value, (val) => Number.parseInt(val, 10)); } parseTime(value) { if (!value) return null; let totalSeconds = 0; const hours = (/* @__PURE__ */ new RegExp(/(\d+)h/)).exec(value); const minutes = (/* @__PURE__ */ new RegExp(/(\d+)m/)).exec(value); const seconds = (/* @__PURE__ */ new RegExp(/(\d+)s/)).exec(value); if (hours) totalSeconds += Number.parseInt(hours[1]) * 3600; if (minutes) totalSeconds += Number.parseInt(minutes[1]) * 60; if (seconds) totalSeconds += Number.parseInt(seconds[1]); if (hours || minutes || seconds) return totalSeconds; const num = Number.parseFloat(value); return Number.isNaN(num) ? null : num; } parseFirstValue(value) { if (!value) return null; const firstValue = value.split(",")[0].trim(); const num = Number.parseFloat(firstValue); return Number.isNaN(num) ? null : num; } parseFirstCsvValue(value) { if (!value) return null; return value.trim(); } parseNumberArray(value) { if (!value) return null; const values = value.split(",").map((v) => Number.parseFloat(v.trim())).filter((n) => !Number.isNaN(n)); return values.length > 0 ? values : null; } parseStringArray(value, separator = ";") { if (!value) return null; const values = value.split(separator).map((v) => v.trim()).filter((v) => v.length > 0); return values.length > 0 ? values : null; } isMmuData(value, separator = ",") { if (!value) return false; return value.split(separator).map((v) => v.trim()).filter((v) => v.length > 0).length > 1; } sumNumberArray(values) { if (!values || values.length === 0) return null; return values.reduce((sum, val) => sum + val, 0); } }; //#endregion export { BGCodeParser }; //# sourceMappingURL=bgcode.parser.js.map