Skip to content

Commit

Permalink
GCodeProcessor: inspectGCode now performs analysis for files that don…
Browse files Browse the repository at this point in the history
…'t require transformation. (#41)

* GCodeProcessor: fixOtherLayerTemperature only applies when processing for IDEX.

* GCodeProcessor: refactor some tests.

* GCodeProcessor: inspectGCode now performs analysis for files that don't require transformation.
  • Loading branch information
tg73 authored Dec 10, 2024
1 parent e9115b6 commit 34860d0
Show file tree
Hide file tree
Showing 7 changed files with 2,296 additions and 553,899 deletions.
363 changes: 199 additions & 164 deletions src/__tests__/server/gcode-processor/GCodeFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,102 +19,111 @@ import { describe, test, expect } from 'vitest';
import { GCodeFlavour } from '@/server/gcode-processor/GCodeInfo';
import semver, { SemVer } from 'semver';
import path from 'path';
import { GCodeFile } from '@/server/gcode-processor/GCodeFile';
import { GCodeFile, Printability } from '@/server/gcode-processor/GCodeFile';

describe('tryParseHeader', async () => {
test('PrusaSlicer 2.8.0', () => {
const header = '; generated by PrusaSlicer 2.8.0+win64 on 2024-09-06 at 08:32:07 UTC\n\n';
const parsed = GCodeFile.tryParseHeader(header);

expect(parsed).not.toBeNull();
expect(parsed!.flavour).toEqual(GCodeFlavour.PrusaSlicer);
expect(parsed!.generator).toEqual('PrusaSlicer');
expect(parsed!.generatorTimestamp).toEqual(new Date('2024-09-06 08:32:07 UTC'));
expect(parsed!.generatorVersion).toEqual(semver.coerce('2.8.0+win64'));
expect(parsed!.ratosDialectVersion).toBeUndefined();
expect(parsed!.postProcessorVersion).toBeUndefined();
expect(parsed!.postProcessorTimestamp).toBeUndefined();
});
const defaultExpectedInfo = {
ratosDialectVersion: undefined,
postProcessorVersion: undefined,
postProcessorTimestamp: undefined,
fileFormatVersion: undefined,
isProcessedValue: false,
processedForIdex: undefined,
analysisResult: undefined,
ratosMetaFileOffset: undefined,
};

test('SuperSlicer 2.5.59.13', async () => {
const header = '; generated by SuperSlicer 2.5.59.13 on 2024-09-14 at 09:35:09 UTC\n\n';
const parsed = GCodeFile.tryParseHeader(header);

expect(parsed).not.toBeNull();
expect(parsed!.flavour).toEqual(GCodeFlavour.SuperSlicer);
expect(parsed!.generator).toEqual('SuperSlicer');
expect(parsed!.generatorTimestamp).toEqual(new Date('2024-09-14 09:35:09 UTC'));
expect(parsed!.generatorVersion).toEqual(semver.coerce('2.5.59.13'));
expect(parsed!.ratosDialectVersion).toBeUndefined();
expect(parsed!.postProcessorVersion).toBeUndefined();
expect(parsed!.postProcessorTimestamp).toBeUndefined();
});
const defaultExpectedFile = {
canDeprocess: undefined,
printability: undefined,
printabilityReasons: [],
info: defaultExpectedInfo,
};

test('OrcaSlicer 2.1.1', async () => {
const header =
describe('tryParseHeader', async () => {
test.each([
[
'PrusaSlicer 2.8.0',
'; generated by PrusaSlicer 2.8.0+win64 on 2024-09-06 at 08:32:07 UTC\n\n',
{
...defaultExpectedInfo,
flavour: GCodeFlavour.PrusaSlicer,
generator: 'PrusaSlicer',
generatorTimestamp: new Date('2024-09-06 08:32:07 UTC'),
generatorVersion: semver.coerce('2.8.0+win64'),
},
],
[
'SuperSlicer 2.5.59.13',
'; generated by SuperSlicer 2.5.59.13 on 2024-09-14 at 09:35:09 UTC\n\n',
{
...defaultExpectedInfo,
flavour: GCodeFlavour.SuperSlicer,
generator: 'SuperSlicer',
generatorTimestamp: new Date('2024-09-14 09:35:09 UTC'),
generatorVersion: semver.coerce('2.5.59.13'),
},
],
[
'OrcaSlicer 2.1.1',
'; HEADER_BLOCK_START\n' +
'; generated by OrcaSlicer 2.1.1 on 2024-09-12 at 14:57:24\n' +
'; total layer number: 240';

const parsed = GCodeFile.tryParseHeader(header);

expect(parsed).not.toBeNull();
expect(parsed!.flavour).toEqual(GCodeFlavour.OrcaSlicer);
expect(parsed!.generator).toEqual('OrcaSlicer');
expect(parsed!.generatorTimestamp).toEqual(new Date('2024-09-12 14:57:24'));
expect(parsed!.generatorVersion).toEqual(semver.coerce('2.1.1'));
expect(parsed!.ratosDialectVersion).toBeUndefined();
expect(parsed!.postProcessorVersion).toBeUndefined();
expect(parsed!.postProcessorTimestamp).toBeUndefined();
});

test('CustomGenerator in RatOS dialect', async () => {
const header = '; generated by CustomGenerator 0.1-alpha in RatOS dialect 0.1 on 2025-01-01 at 08:32:07 UTC\n\n';
const parsed = GCodeFile.tryParseHeader(header);

expect(parsed).not.toBeNull();
expect(parsed!.flavour).toEqual(GCodeFlavour.RatOS);
expect(parsed!.generator).toEqual('CustomGenerator');
expect(parsed!.generatorTimestamp).toEqual(new Date('2025-01-01 08:32:07 UTC'));
expect(parsed!.generatorVersion).toEqual(semver.coerce('0.1-alpha'));
expect(parsed!.ratosDialectVersion).toEqual(semver.coerce('0.1'));
expect(parsed!.postProcessorVersion).toBeUndefined();
expect(parsed!.postProcessorTimestamp).toBeUndefined();
});

test('CustomGenerator without RatOS dialect', async () => {
const header = '; generated by CustomGenerator 0.1-alpha on 2025-01-01 at 08:32:07 UTC\n\n';
const parsed = GCodeFile.tryParseHeader(header);

expect(parsed).not.toBeNull();
expect(parsed!.flavour).toEqual(GCodeFlavour.Unknown);
expect(parsed!.generator).toEqual('CustomGenerator');
expect(parsed!.generatorTimestamp).toEqual(new Date('2025-01-01 08:32:07 UTC'));
expect(parsed!.generatorVersion).toEqual(semver.coerce('0.1-alpha'));
expect(parsed!.ratosDialectVersion).toBeUndefined();
expect(parsed!.postProcessorVersion).toBeUndefined();
expect(parsed!.postProcessorTimestamp).toBeUndefined();
});

test('already processed v1', async () => {
const header =
'; generated by OrcaSlicer 2.1.1 on 2024-09-12 at 14:57:24\n' +
'; total layer number: 240',
{
...defaultExpectedInfo,
flavour: GCodeFlavour.OrcaSlicer,
generator: 'OrcaSlicer',
generatorTimestamp: new Date('2024-09-12 14:57:24'),
generatorVersion: semver.coerce('2.1.1'),
},
],
[
'CustomGenerator in RatOS dialect',
'; generated by CustomGenerator 0.1-alpha in RatOS dialect 0.1 on 2025-01-01 at 08:32:07 UTC\n\n',
{
...defaultExpectedInfo,
flavour: GCodeFlavour.RatOS,
generator: 'CustomGenerator',
generatorTimestamp: new Date('2025-01-01 08:32:07 UTC'),
generatorVersion: semver.coerce('0.1-alpha'),
ratosDialectVersion: semver.coerce('0.1'),
},
],
[
'CustomGenerator without RatOS dialect',
'; generated by CustomGenerator 0.1-alpha on 2025-01-01 at 08:32:07 UTC\n\n',
{
...defaultExpectedInfo,
flavour: GCodeFlavour.Unknown,
generator: 'CustomGenerator',
generatorTimestamp: new Date('2025-01-01 08:32:07 UTC'),
generatorVersion: semver.coerce('0.1-alpha'),
},
],
[
'already processed, format v1',
'; generated by PrusaSlicer 2.8.0+win64 on 2024-09-06 at 08:32:07 UTC\n' +
'; processed by RatOS 2.0.2-518-g4ffef464 on 2025-01-01 at 08:30:00Z\n';
const parsed = GCodeFile.tryParseHeader(header);

expect(parsed).not.toBeNull();
expect(parsed!.flavour).toEqual(GCodeFlavour.PrusaSlicer);
expect(parsed!.generator).toEqual('PrusaSlicer');
expect(parsed!.generatorTimestamp).toEqual(new Date('2024-09-06 08:32:07 UTC'));
expect(parsed!.generatorVersion).toEqual(semver.coerce('2.8.0+win64'));
expect(parsed!.ratosDialectVersion).toBeUndefined();
expect(parsed!.postProcessorVersion).toEqual(semver.coerce('2.0.2-518-g4ffef464'));
expect(parsed!.postProcessorTimestamp).toEqual(new Date('2025-01-01 08:30:00Z'));
});

test('no match', async () => {
let parsed = GCodeFile.tryParseHeader('blah blah blah');
expect(parsed).toBeNull();
'; processed by RatOS 2.0.2-518-g4ffef464 on 2025-01-01 at 08:30:00Z\n',
{
...defaultExpectedInfo,
fileFormatVersion: 1,
flavour: GCodeFlavour.PrusaSlicer,
generator: 'PrusaSlicer',
generatorTimestamp: new Date('2024-09-06 08:32:07 UTC'),
generatorVersion: semver.coerce('2.8.0+win64'),
postProcessorVersion: semver.coerce('2.0.2-518-g4ffef464'),
postProcessorTimestamp: new Date('2025-01-01 08:30:00Z'),
isProcessedValue: true,
},
],
['no match', 'blah blah blah', null],
])('%s', (name, header, expected) => {
const gci = GCodeFile.tryParseHeader(header);
if (!expected) {
expect(gci).toStrictEqual(expected);
} else {
const test = { ...gci, isProcessedValue: gci?.isProcessed };
expect(test).toMatchObject(expected);
}
});

test('getProcessedByRatosHeader', async () => {
Expand All @@ -138,89 +147,115 @@ describe('tryParseHeader', async () => {
});
});

describe('fromFile', async () => {
test('unprocessed', async () => {
const gcf = await GCodeFile.inspect(
describe('inspect', async () => {
const defaultExpected = {
canDeprocess: undefined,
printability: undefined,
printabilityReasons: [],
info: {
ratosDialectVersion: undefined,
postProcessorVersion: undefined,
postProcessorTimestamp: undefined,
fileFormatVersion: undefined,
isProcessedValue: false,
processedForIdex: undefined,
analysisResult: undefined,
ratosMetaFileOffset: undefined,
},
};
test.each([
[
'unprocessed valid, for idex',
path.join(__dirname, 'fixtures', 'slicer_output', '001', 'SS_IDEX_MultiColor_WipeTower.gcode'),
{
...defaultExpectedFile,
printability: Printability.MUST_PROCESS,
info: {
...defaultExpectedInfo,
flavour: GCodeFlavour.SuperSlicer,
generator: 'SuperSlicer',
generatorTimestamp: new Date('2024-10-31T03:29:37.000Z'),
generatorVersion: semver.coerce('2.5.60'),
},
},
{ printerHasIdex: true },
],
[
'unprocessed valid, not for idex',
path.join(__dirname, 'fixtures', 'slicer_output', '001', 'SS_IDEX_MultiColor_WipeTower.gcode'),
{
...defaultExpectedFile,
printability: Printability.READY,
info: {
...defaultExpectedInfo,
flavour: GCodeFlavour.SuperSlicer,
generator: 'SuperSlicer',
generatorTimestamp: new Date('2024-10-31T03:29:37.000Z'),
generatorVersion: semver.coerce('2.5.60'),
},
},
],
[
'legacy processed',
path.join(__dirname, 'fixtures', 'other', 'legacy_processed.gcode'),
{
...defaultExpectedFile,
printability: Printability.NOT_SUPPORTED,
printabilityReasons: [
expect.stringContaining('The file format is from an old version of RatOS which is no longer supported'),
],
info: {
...defaultExpectedInfo,
fileFormatVersion: 0,
flavour: GCodeFlavour.SuperSlicer,
generator: 'SuperSlicer',
generatorTimestamp: new Date('2024-10-31T03:29:37.000Z'),
generatorVersion: semver.coerce('2.5.60'),
postProcessorVersion: new SemVer('0.1.0-legacy'),
isProcessedValue: true,
},
},
],
[
'without ratos_meta block, format v3',
path.join(__dirname, 'fixtures', 'other', 'without_ratos_meta.gcode'),
{},
);
const parsed = gcf.info;
expect(parsed).not.toBeNull();
expect(parsed!.flavour).toEqual(GCodeFlavour.SuperSlicer);
expect(parsed!.generator).toEqual('SuperSlicer');
expect(parsed!.generatorTimestamp).toEqual(new Date('2024-10-31T03:29:37.000Z'));
expect(parsed!.generatorVersion).toEqual(semver.coerce('2.5.60'));
expect(parsed!.ratosDialectVersion).toBeUndefined();
expect(parsed!.postProcessorVersion).toBeUndefined();
expect(parsed!.postProcessorTimestamp).toBeUndefined();
});

test('legacy processed', async () => {
const gcf = await GCodeFile.inspect(path.join(__dirname, 'fixtures', 'other', 'legacy_processed.gcode'), {});
const parsed = gcf.info;
expect(parsed).not.toBeNull();
expect(parsed!.flavour).toEqual(GCodeFlavour.SuperSlicer);
expect(parsed!.generator).toEqual('SuperSlicer');
expect(parsed!.generatorTimestamp).toEqual(new Date('2024-10-31T03:29:37.000Z'));
expect(parsed!.generatorVersion).toEqual(semver.coerce('2.5.60'));
expect(parsed!.ratosDialectVersion).toBeUndefined();
expect(parsed!.postProcessorVersion).toEqual(new SemVer('0.1.0-legacy'));
expect(parsed!.postProcessorTimestamp).toBeUndefined();
});

test('v2 without ratos_meta block', async () => {
const warnings: string[] = [];
const gcf = await GCodeFile.inspect(path.join(__dirname, 'fixtures', 'other', 'without_ratos_meta.gcode'), {
onWarning: (c, m) => {
const msg = `${c}: ${m}`;
warnings.push(msg);
//console.log(msg);
{ expectedWarnings: ['INVALID_METADATA: The ratos_meta block was not found.'] },
],
[
'without ratos_meta begin, format v3',
path.join(__dirname, 'fixtures', 'other', 'without_ratos_meta_begin.gcode'),
{},
{ expectedWarnings: ['INVALID_METADATA: Failed to parse ratos_meta block: the begin marker was not found.'] },
],
[
'wrong ratos_meta data length, format v3',
path.join(__dirname, 'fixtures', 'other', 'ratos_meta_wrong_data_length.gcode'),
{},
{
expectedWarnings: [
'INVALID_METADATA: Failed to parse ratos_meta block: expected 999 base64 characters, but found 34.',
],
},
});
const parsed = gcf.info;
expect(parsed).not.toBeNull();
expect(parsed!.postProcessorVersion).not.toBeUndefined();
expect(parsed!.postProcessorTimestamp).not.toBeUndefined();
expect(warnings).toHaveLength(1);
expect(warnings[0]).toEqual('INVALID_METADATA: The ratos_meta block was not found.');
});

test('v2 without ratos_meta begin', async () => {
],
])('%s', async (name, path, expected, opts?: { printerHasIdex?: boolean; expectedWarnings?: any[] }) => {
const warnings: string[] = [];
const gcf = await GCodeFile.inspect(path.join(__dirname, 'fixtures', 'other', 'without_ratos_meta_begin.gcode'), {
const gcf = await GCodeFile.inspect(path, {
printerHasIdex: !!opts?.printerHasIdex,
onWarning: (c, m) => {
const msg = `${c}: ${m}`;
warnings.push(msg);
//console.log(msg);
},
});
const parsed = gcf.info;
expect(parsed).not.toBeNull();
expect(parsed!.postProcessorVersion).not.toBeUndefined();
expect(parsed!.postProcessorTimestamp).not.toBeUndefined();
expect(warnings).toHaveLength(1);
expect(warnings[0]).toEqual('INVALID_METADATA: Failed to parse ratos_meta block: the begin marker was not found.');
});

test('v2 wrong ratos_meta data length', async () => {
const warnings: string[] = [];
const gcf = await GCodeFile.inspect(
path.join(__dirname, 'fixtures', 'other', 'ratos_meta_wrong_data_length.gcode'),
{
onWarning: (c, m) => {
const msg = `${c}: ${m}`;
warnings.push(msg);
//console.log(msg);
},
},
);
const parsed = gcf.info;
expect(parsed).not.toBeNull();
expect(parsed!.postProcessorVersion).not.toBeUndefined();
expect(parsed!.postProcessorTimestamp).not.toBeUndefined();
expect(warnings).toHaveLength(1);
expect(warnings[0]).toEqual(
'INVALID_METADATA: Failed to parse ratos_meta block: expected 999 base64 characters, but found 34.',
);
if (gcf.info) {
const test = { ...gcf, info: { ...gcf.info, isProcessedValue: gcf.info?.isProcessed } };
expect(test).toMatchObject(expected);
} else {
expect(gcf).toMatchObject(expected);
}

expect(warnings).toMatchObject(opts?.expectedWarnings ?? []);
});
});
Loading

0 comments on commit 34860d0

Please sign in to comment.