UNPKG

@ankhzet/goo

Version:

Elegoo .goo file format reader/writer

186 lines (185 loc) 6.78 kB
import { assert, rgba8888Buffer, formatBytes } from './utils.js'; import { GOO_VERSION, GOO_MAGIC, GOO_DELIMITER } from './magics.js'; import { rleDecode } from './rle/index.js'; import { CRC8 } from './CRC8.js'; export class GooReader { constructor(reader) { this.reader = reader; } async read() { const version = await this.reader.string(4); assert(version === GOO_VERSION, `Version "${version}" is not supported`); await this.reader.assert(GOO_MAGIC, 'File signature mismatch'); const header = await this.readHeader(); const layers = []; let i = header.layers; while (i-- > 0) { layers.push(await this.readLayer()); } assert((await this.reader.u24()) === 0, 'File tail signature mismatch'); await this.reader.assert(GOO_MAGIC, 'File tail signature mismatch'); return { header, layers, }; } async readHeader() { const generator = await this.reader.struct({}, { description: () => this.reader.string(32), version: () => this.reader.string(24), }); const date = new Date(await this.reader.string(24)); const printJob = await this.reader.struct({ grayscale: true }, { name: () => this.reader.string(32), type: () => this.reader.string(32), resinProfile: () => this.reader.string(32), antialiasing: () => this.reader.u16(), gray: () => this.reader.u16(), blur: () => this.reader.u16(), }); const previews = []; for (const size of [116, 290]) { previews.push(await this.readPreview({ x: size, y: size })); } const layers = await this.reader.u32(); const printer = await this.reader.struct(printJob, { resolution: () => this.reader.struct({}, { x: () => this.reader.u16(), y: () => this.reader.u16(), }), mirror: () => this.reader.struct({}, { x: () => this.reader.bool(), y: () => this.reader.bool(), }), platform: () => this.reader.struct({}, { x: () => this.reader.f32(), y: () => this.reader.f32(), z: () => this.reader.f32(), }) }); const layerConfig = await this.readLayerConfig(); const summary = await this.readSummary(); const next = await this.reader.u32(); printer.grayscale = await this.reader.bool(); layerConfig.transitionLayers = await this.reader.u16(); return { date, generator, printer, previews, layers, layerConfig, summary, next, }; } async readLayerConfig() { return this.reader.struct({ transitionLayers: 0 }, { thickness: () => this.reader.f32(), commonExposure: () => this.reader.f32(), exposureDelay: () => this.reader.bool(), turnOffTime: () => this.reader.f32(), timings: () => this.readGlobalConfig(() => this.readMotionTimes()), bottomExposure: () => this.reader.f32(), bottomLayers: () => this.reader.u32(), motions: () => this.readMotions(() => (this.readLiftRetract(() => (this.readGlobalConfig(() => this.readMotionConfig()))))), pwm: () => this.reader.struct({}, { bottom: () => this.reader.u16(), common: () => this.reader.u16(), }), advance: () => this.reader.bool(), }); } async readSummary() { return this.reader.struct({}, { time: () => this.reader.u32(), volume: () => this.reader.f32(), weight: () => this.reader.f32(), price: () => this.reader.f32(), currency: () => this.reader.string(8), }); } async readGlobalConfig(map) { return { bottom: await map(), common: await map(), }; } async readMotions(map) { return { first: await map(), second: await map(), }; } async readLiftRetract(map) { return { lift: await map(), retract: await map(), }; } async readMotionTimes() { return this.reader.struct({}, { before: () => this.reader.struct({}, { lift: () => this.reader.f32(), }), after: () => this.reader.struct({}, { lift: () => this.reader.f32(), retract: () => this.reader.f32(), }), }); } async readMotionConfig() { return { distance: await this.reader.f32(), speed: await this.reader.f32(), }; } async readPreview(dimensions) { const { buffer } = await this.reader.binary(2 * dimensions.x * dimensions.y); await this.readDelimiter(); return { dimensions, input: { buffer: rgba8888Buffer(buffer), channels: 4, }, }; } async readDelimiter() { assert((await this.reader.u16()) === GOO_DELIMITER, 'Delimiter expected'); } async readLayer() { const definition = await this.readLayerDefinition(); const offset = await this.reader.u32(); const marker = await this.reader.u8(); assert(marker === 0x55, `Layer data marker expected, got ${formatBytes(marker)}`); const { buffer } = await this.reader.binary(offset - 2); const crc = await this.reader.u8(); const checksum = (new CRC8()).checksum(buffer); await this.readDelimiter(); assert(checksum === crc, `CRC mismatch, expected ${formatBytes(crc)}, got ${formatBytes(checksum)}`); return { definition, slice: { buffer: rleDecode(buffer), channels: 1, }, }; } async readLayerDefinition() { const definition = await this.reader.struct({}, { pause: () => this.reader.struct({}, { mode: () => this.reader.u16(), z: () => this.reader.f32(), }), z: () => this.reader.f32(), exposure: () => this.reader.f32(), offTime: () => this.reader.f32(), times: () => this.readMotionTimes(), motions: () => this.readLiftRetract(() => (this.readMotions(() => (this.readMotionConfig())))), pwm: () => this.reader.u16(), }); await this.readDelimiter(); return definition; } }