@chestnutlabs/gcode-dialects 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@ enforces it).
7
7
 
8
8
  Supported dialects: **PrusaSlicer, OrcaSlicer/Bambu Studio, Cura, Klipper, Marlin,
9
9
  RepRap-flavor** — see the evidence-dated
10
- [compatibility matrix](../../docs/compatibility/dialects-and-containers.md).
10
+ [compatibility matrix](https://github.com/ChestnutLabs/gcode-preview/blob/dev/docs/compatibility/dialects-and-containers.md).
11
11
 
12
12
  Every annotation is capability-tagged (`known | inferred | approximated | unavailable`); consumers
13
13
  render what is known and disclose what is not.
@@ -5,7 +5,7 @@
5
5
  * IR's byte-ordered sourceIndex resolves them to segment ranges in `finalize`
6
6
  * (DD-005 §4.3: "annotation ranges are [segAtEvent, segBeforeNextEvent]").
7
7
  */
8
- import type { MachineGeometry, Point2, ToolpathIR } from '@chestnutlabs/toolpath-core';
8
+ import { type MachineGeometry, type Point2, type ToolpathIR } from '@chestnutlabs/toolpath-core';
9
9
  import type { AnnotationSink } from './contracts.js';
10
10
  /** First segment whose producing command starts at or after `byte`. */
11
11
  export declare function segAtOrAfterByte(ir: ToolpathIR, byte: number): number;
@@ -19,6 +19,24 @@ export interface RangeMarker {
19
19
  * value of 0 acts as a range terminator (back to unknown/none).
20
20
  */
21
21
  export declare function applyMarkerRanges(ir: ToolpathIR, markers: RangeMarker[], write: (segStart: number, segEnd: number, value: number) => void): number;
22
+ /** One end of a wipe bracket (DD-016, #182): the source byte and whether it OPENED the bracket. */
23
+ export interface WipeMark {
24
+ srcByte: number;
25
+ open: boolean;
26
+ }
27
+ /**
28
+ * Classify a comment as a PrusaSlicer/OrcaSlicer wipe-bracket marker. The convention is bare
29
+ * `;WIPE_START` / `;WIPE_END` (the semicolon is already stripped before onComment); tolerant of a
30
+ * leading space and casing. Returns null for any other comment.
31
+ */
32
+ export declare function matchWipeComment(comment: string): 'start' | 'end' | null;
33
+ /**
34
+ * Resolve WIPE_START/END byte marks to segment ranges and add `MoveKind.Wipe` additively via the sink
35
+ * (DD-016 §4.2). Segments emitted between a START and its END are the wipe moves. Unbalanced or nested
36
+ * markers degrade safely: a stray END is ignored, a second START before an END is folded into the open
37
+ * bracket, and a START left open at end-of-file closes at the last segment. Returns ranges applied.
38
+ */
39
+ export declare function applyWipeRanges(ir: ToolpathIR, marks: WipeMark[], sink: AnnotationSink): number;
22
40
  export interface SegMarker {
23
41
  segIndex: number;
24
42
  value: number;
@@ -1 +1 @@
1
- {"version":3,"file":"annotate.d.ts","sourceRoot":"","sources":["../src/annotate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACvF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,uEAAuE;AACvE,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAUrE;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,WAAW,EAAE,EACtB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAC/D,MAAM,CAYR;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,SAAS,EAAE,EACpB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAC/D,MAAM,CAYR;AAED,uGAAuG;AACvG,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAGpF;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAU9D;AAED,mFAAmF;AACnF,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EAAE,EAChB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,EAAE,MAAM,GAAG,SAAS,GAC1B,eAAe,CAejB;AASD,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAkB5D;AAED,mGAAmG;AACnG,qBAAa,kBAAkB;IAEjB,OAAO,CAAC,QAAQ,CAAC,oBAAoB;IADjD,OAAO,CAAC,MAAM,CAAgF;gBACjE,oBAAoB,SAAa;IAE9D,uFAAuF;IACvF,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,mFAAmF;IACnF,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI;CA8BlD"}
1
+ {"version":3,"file":"annotate.d.ts","sourceRoot":"","sources":["../src/annotate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAY,KAAK,eAAe,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC3G,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,uEAAuE;AACvE,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAUrE;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,WAAW,EAAE,EACtB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAC/D,MAAM,CAYR;AAED,mGAAmG;AACnG,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,IAAI,CAKxE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,cAAc,GAAG,MAAM,CAwB/F;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,SAAS,EAAE,EACpB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAC/D,MAAM,CAYR;AAED,uGAAuG;AACvG,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAGpF;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAU9D;AAED,mFAAmF;AACnF,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EAAE,EAChB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,EAAE,MAAM,GAAG,SAAS,GAC1B,eAAe,CAejB;AASD,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAkB5D;AAED,mGAAmG;AACnG,qBAAa,kBAAkB;IAEjB,OAAO,CAAC,QAAQ,CAAC,oBAAoB;IADjD,OAAO,CAAC,MAAM,CAAgF;gBACjE,oBAAoB,SAAa;IAE9D,uFAAuF;IACvF,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,mFAAmF;IACnF,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI;CA8BlD"}
package/dist/annotate.js CHANGED
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Shared annotation helpers for slicer adapters (DD-005 phase 3, issue #75).
3
+ *
4
+ * Comment markers arrive with a source-byte position but no segment index; the
5
+ * IR's byte-ordered sourceIndex resolves them to segment ranges in `finalize`
6
+ * (DD-005 §4.3: "annotation ranges are [segAtEvent, segBeforeNextEvent]").
7
+ */
8
+ import { MoveKind } from '@chestnutlabs/toolpath-core';
1
9
  /** First segment whose producing command starts at or after `byte`. */
2
10
  export function segAtOrAfterByte(ir, byte) {
3
11
  const offsets = ir.sourceIndex.byteOffsets;
@@ -31,6 +39,52 @@ export function applyMarkerRanges(ir, markers, write) {
31
39
  }
32
40
  return applied;
33
41
  }
42
+ /**
43
+ * Classify a comment as a PrusaSlicer/OrcaSlicer wipe-bracket marker. The convention is bare
44
+ * `;WIPE_START` / `;WIPE_END` (the semicolon is already stripped before onComment); tolerant of a
45
+ * leading space and casing. Returns null for any other comment.
46
+ */
47
+ export function matchWipeComment(comment) {
48
+ const t = comment.trim().toUpperCase();
49
+ if (t === 'WIPE_START')
50
+ return 'start';
51
+ if (t === 'WIPE_END')
52
+ return 'end';
53
+ return null;
54
+ }
55
+ /**
56
+ * Resolve WIPE_START/END byte marks to segment ranges and add `MoveKind.Wipe` additively via the sink
57
+ * (DD-016 §4.2). Segments emitted between a START and its END are the wipe moves. Unbalanced or nested
58
+ * markers degrade safely: a stray END is ignored, a second START before an END is folded into the open
59
+ * bracket, and a START left open at end-of-file closes at the last segment. Returns ranges applied.
60
+ */
61
+ export function applyWipeRanges(ir, marks, sink) {
62
+ let applied = 0;
63
+ let openByte = -1;
64
+ for (const mark of marks) {
65
+ if (mark.open) {
66
+ if (openByte < 0)
67
+ openByte = mark.srcByte;
68
+ }
69
+ else if (openByte >= 0) {
70
+ const start = segAtOrAfterByte(ir, openByte);
71
+ const end = segAtOrAfterByte(ir, mark.srcByte); // first segment AFTER the wipe block
72
+ if (end - 1 >= start) {
73
+ sink.addMoveKind(start, end - 1, MoveKind.Wipe);
74
+ applied++;
75
+ }
76
+ openByte = -1;
77
+ }
78
+ }
79
+ if (openByte >= 0) {
80
+ const start = segAtOrAfterByte(ir, openByte);
81
+ if (ir.segments.count - 1 >= start) {
82
+ sink.addMoveKind(start, ir.segments.count - 1, MoveKind.Wipe);
83
+ applied++;
84
+ }
85
+ }
86
+ return applied;
87
+ }
34
88
  /**
35
89
  * Apply SEGMENT-indexed markers (CommandEvent.segIndex — exact, no byte
36
90
  * resolution needed) as contiguous ranges; value 0 terminates (DD-005 phase 5).
@@ -9,7 +9,7 @@
9
9
  * Data contracts (MachineGeometry, DialectMetadata, DialectDetection) live in
10
10
  * @chestnutlabs/toolpath-core (see its metadata.ts for the rationale).
11
11
  */
12
- import type { Confidence, DialectDetection, MachineGeometry, FilamentInfo, ThumbnailData, ToolpathIR } from '@chestnutlabs/toolpath-core';
12
+ import type { Confidence, DialectDetection, MachineGeometry, FilamentInfo, FilamentUsage, PrintEstimate, ThumbnailData, ToolpathIR } from '@chestnutlabs/toolpath-core';
13
13
  /** Read-only normalized command event — mirror of the parser's hook payload. */
14
14
  export interface CommandEvent {
15
15
  gcode: string;
@@ -29,11 +29,22 @@ export interface DetectInput {
29
29
  export interface AnnotationSink {
30
30
  /** Write a FeatureRole index for segments [segStart, segEnd] (bounds validated at apply). */
31
31
  setFeature(segStart: number, segEnd: number, role: number): void;
32
+ /**
33
+ * Additively OR an annotation move-kind bit onto segments [segStart, segEnd] (DD-016 §4.2).
34
+ * Allow-listed to `MoveKind.Wipe | MoveKind.Seam` — the ONLY exception to the sink's kind
35
+ * immutability, and additive-only: it never clears an existing Extrude/Travel bit and never
36
+ * reclassifies a move. Non-allow-listed bits are dropped with a warning (bounded failure).
37
+ */
38
+ addMoveKind(segStart: number, segEnd: number, kindBits: number): void;
32
39
  /** `objectValue` is the 1-based object-channel value (DD-001: v indexes ir.objects[v-1]). */
33
40
  setObject(segStart: number, segEnd: number, objectValue: number): void;
34
41
  defineObject(objectValue: number, name: string): void;
35
42
  setMachine(machine: MachineGeometry): void;
36
43
  setFilament(info: FilamentInfo): void;
44
+ /** Slicer-reported total filament consumption (#183). Later calls merge non-undefined fields. */
45
+ setFilamentUsage(usage: FilamentUsage): void;
46
+ /** The slicer's own print-time estimate (#183). Prefer the 'normal'/default mode when several. */
47
+ setPrintEstimate(estimate: PrintEstimate): void;
37
48
  /** Enrich a tool's identity (material, '#RRGGBB' color) — merged into ir.tools (DD-005 phase 5). */
38
49
  setToolInfo(tool: number, info: {
39
50
  material?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EACV,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,aAAa,EACb,UAAU,EACX,MAAM,6BAA6B,CAAC;AAErC,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,cAAc;IAC7B,6FAA6F;IAC7F,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACjE,6FAA6F;IAC7F,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACvE,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACtD,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IAC3C,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IACtC,oGAAoG;IACpG,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAChF,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACzC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC7D,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7D;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;IACxC,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,gBAAgB,GAAG,IAAI,CAAC;IACpD,gFAAgF;IAChF,SAAS,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3F,SAAS,CAAC,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9E,QAAQ,CAAC,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CACzE"}
1
+ {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"cura.d.ts","sourceRoot":"","sources":["../src/cura.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAiCrE,wBAAgB,IAAI,IAAI,cAAc,CAkCrC"}
1
+ {"version":3,"file":"cura.d.ts","sourceRoot":"","sources":["../src/cura.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAiCrE,wBAAgB,IAAI,IAAI,cAAc,CAqDrC"}
package/dist/cura.js CHANGED
@@ -54,6 +54,25 @@ export function cura() {
54
54
  s.printerName = trimmed.slice('TARGET_MACHINE.NAME:'.length).trim();
55
55
  return;
56
56
  }
57
+ // Cura print-time comment: `;TIME:<seconds>` (total; per-layer `;TIME_ELAPSED:` is ignored).
58
+ if (trimmed.startsWith('TIME:')) {
59
+ const sec = Number(trimmed.slice('TIME:'.length).trim());
60
+ if (Number.isFinite(sec) && sec > 0) {
61
+ sink.setPrintEstimate({ seconds: sec, source: { adapterId: 'cura', evidence: ';TIME:', srcByte } });
62
+ }
63
+ return;
64
+ }
65
+ // Cura filament comment: `;Filament used: 2.22m` (metres; multi-material → first value).
66
+ if (trimmed.toLowerCase().startsWith('filament used:')) {
67
+ const metres = parseFloat(trimmed.slice('filament used:'.length).trim());
68
+ if (Number.isFinite(metres)) {
69
+ sink.setFilamentUsage({
70
+ lengthMm: metres * 1000,
71
+ source: { adapterId: 'cura', evidence: ';Filament used:', srcByte }
72
+ });
73
+ }
74
+ return;
75
+ }
57
76
  const kv = parseKeyValue(comment);
58
77
  if (kv !== null && kv.key === 'target_machine')
59
78
  s.printerName = kv.value;
@@ -1 +1 @@
1
- {"version":3,"file":"orca-bambu.d.ts","sourceRoot":"","sources":["../src/orca-bambu.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAuDrE,wBAAgB,SAAS,IAAI,cAAc,CA8F1C"}
1
+ {"version":3,"file":"orca-bambu.d.ts","sourceRoot":"","sources":["../src/orca-bambu.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAmErE,wBAAgB,SAAS,IAAI,cAAc,CAiH1C"}
@@ -9,7 +9,8 @@
9
9
  * container metadata as evidence (DD-005 §4.1 DetectInput.containerMeta).
10
10
  */
11
11
  import { FeatureRole } from '@chestnutlabs/toolpath-core';
12
- import { ThumbnailCollector, applyMarkerRanges, bedFromPoints, parseAreaPoints, parseKeyValue } from './annotate.js';
12
+ import { parseFilamentTotals, parseHmsToSeconds } from './prusaslicer.js';
13
+ import { ThumbnailCollector, applyMarkerRanges, applyWipeRanges, bedFromPoints, matchWipeComment, parseAreaPoints, parseKeyValue } from './annotate.js';
13
14
  /** Orca/Bambu `; FEATURE:` names → DD-001 FeatureRole. */
14
15
  const FEATURE_MAP = {
15
16
  'inner wall': FeatureRole.Perimeter,
@@ -34,7 +35,14 @@ const state = new WeakMap();
34
35
  function stateFor(sink) {
35
36
  let s = state.get(sink);
36
37
  if (s === undefined) {
37
- s = { features: [], objects: [], objectNames: new Map(), bedPoints: null, thumbs: new ThumbnailCollector() };
38
+ s = {
39
+ features: [],
40
+ objects: [],
41
+ wipes: [],
42
+ objectNames: new Map(),
43
+ bedPoints: null,
44
+ thumbs: new ThumbnailCollector()
45
+ };
38
46
  state.set(sink, s);
39
47
  }
40
48
  return s;
@@ -79,10 +87,29 @@ export function orcaBambu() {
79
87
  s.objects.push({ srcByte, value: 0 }); // range terminator
80
88
  return;
81
89
  }
90
+ const wipe = matchWipeComment(comment);
91
+ if (wipe !== null) {
92
+ s.wipes.push({ srcByte, open: wipe === 'start' });
93
+ return;
94
+ }
82
95
  if (s.thumbs.isActive || trimmed.toLowerCase().startsWith('thumbnail')) {
83
96
  s.thumbs.feed(comment, sink);
84
97
  return;
85
98
  }
99
+ // Orca/Bambu print-time comments are colon-separated (`; total estimated time: 1h 2m 3s`).
100
+ const timeMatch = /^(?:total estimated time|model printing time)\s*:\s*(.+)$/i.exec(trimmed);
101
+ if (timeMatch !== null) {
102
+ const sec = parseHmsToSeconds(timeMatch[1]);
103
+ if (sec !== null) {
104
+ sink.setPrintEstimate({
105
+ seconds: sec,
106
+ source: { adapterId: 'orca-bambu', evidence: `; ${trimmed}`, srcByte }
107
+ });
108
+ }
109
+ return;
110
+ }
111
+ if (parseFilamentTotals(comment, srcByte, 'orca-bambu', sink))
112
+ return;
86
113
  const kv = parseKeyValue(comment);
87
114
  if (kv === null)
88
115
  return;
@@ -116,6 +143,8 @@ export function orcaBambu() {
116
143
  sink.defineObject(value, name);
117
144
  sink.upgradeCapability('objects', 'known');
118
145
  }
146
+ if (applyWipeRanges(ir, s.wipes, sink) > 0)
147
+ sink.upgradeCapability('wipeMoves', 'known');
119
148
  if (s.bedPoints !== null) {
120
149
  const points = parseAreaPoints(s.bedPoints);
121
150
  if (points !== null) {
@@ -1,3 +1,11 @@
1
- import type { DialectAdapter } from './contracts.js';
1
+ import type { AnnotationSink, DialectAdapter } from './contracts.js';
2
+ /** Parse PrusaSlicer/Orca `<h>h <m>m <s>s` (any part optional) → seconds; null when zero/unparsed. */
3
+ export declare function parseHmsToSeconds(text: string): number | null;
4
+ /**
5
+ * Prusa-style filament totals (`; filament used [mm|cm3] = X`, `; total filament used [g] = X`) —
6
+ * shared by PrusaSlicer and the Prusa-derived Orca/Bambu adapter (#183). Multi-word bracketed keys
7
+ * that `parseKeyValue` can't tokenize. Returns true when the comment was a filament total.
8
+ */
9
+ export declare function parseFilamentTotals(comment: string, srcByte: number, adapterId: string, sink: AnnotationSink): boolean;
2
10
  export declare function prusaSlicer(): DialectAdapter;
3
11
  //# sourceMappingURL=prusaslicer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prusaslicer.d.ts","sourceRoot":"","sources":["../src/prusaslicer.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAiDrE,wBAAgB,WAAW,IAAI,cAAc,CA+D5C"}
1
+ {"version":3,"file":"prusaslicer.d.ts","sourceRoot":"","sources":["../src/prusaslicer.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAgCrE,sGAAsG;AACtG,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK7D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,cAAc,GACnB,OAAO,CAkBT;AAuBD,wBAAgB,WAAW,IAAI,cAAc,CAmF5C"}
@@ -7,7 +7,7 @@
7
7
  * `; thumbnail begin/end` PNG thumbnails. Geometry is untouchable by contract.
8
8
  */
9
9
  import { FeatureRole } from '@chestnutlabs/toolpath-core';
10
- import { ThumbnailCollector, applyMarkerRanges, bedFromPoints, parseAreaPoints, parseKeyValue } from './annotate.js';
10
+ import { ThumbnailCollector, applyMarkerRanges, applyWipeRanges, bedFromPoints, matchWipeComment, parseAreaPoints, parseKeyValue } from './annotate.js';
11
11
  /** Prusa `;TYPE:` names → DD-001 FeatureRole (unknown names → Custom, honestly generic). */
12
12
  const TYPE_MAP = {
13
13
  perimeter: FeatureRole.Perimeter,
@@ -26,11 +26,43 @@ const TYPE_MAP = {
26
26
  'wipe tower': FeatureRole.Custom,
27
27
  custom: FeatureRole.Custom
28
28
  };
29
+ /** Parse PrusaSlicer/Orca `<h>h <m>m <s>s` (any part optional) → seconds; null when zero/unparsed. */
30
+ export function parseHmsToSeconds(text) {
31
+ const m = /^\s*(?:(\d+)\s*h)?\s*(?:(\d+)\s*m)?\s*(?:(\d+)\s*s)?\s*$/.exec(text);
32
+ if (m === null)
33
+ return null;
34
+ const total = Number(m[1] ?? 0) * 3600 + Number(m[2] ?? 0) * 60 + Number(m[3] ?? 0);
35
+ return total > 0 ? total : null;
36
+ }
37
+ /**
38
+ * Prusa-style filament totals (`; filament used [mm|cm3] = X`, `; total filament used [g] = X`) —
39
+ * shared by PrusaSlicer and the Prusa-derived Orca/Bambu adapter (#183). Multi-word bracketed keys
40
+ * that `parseKeyValue` can't tokenize. Returns true when the comment was a filament total.
41
+ */
42
+ export function parseFilamentTotals(comment, srcByte, adapterId, sink) {
43
+ const source = { adapterId, evidence: '; filament used comments', srcByte };
44
+ let m = /^\s*filament used \[mm\]\s*=\s*([\d.]+)/i.exec(comment);
45
+ if (m !== null) {
46
+ sink.setFilamentUsage({ lengthMm: parseFloat(m[1]), source });
47
+ return true;
48
+ }
49
+ m = /^\s*filament used \[cm3\]\s*=\s*([\d.]+)/i.exec(comment);
50
+ if (m !== null) {
51
+ sink.setFilamentUsage({ volumeCm3: parseFloat(m[1]), source });
52
+ return true;
53
+ }
54
+ m = /^\s*total filament used \[g\]\s*=\s*([\d.]+)/i.exec(comment);
55
+ if (m !== null) {
56
+ sink.setFilamentUsage({ weightG: parseFloat(m[1]), source });
57
+ return true;
58
+ }
59
+ return false;
60
+ }
29
61
  const state = new WeakMap();
30
62
  function stateFor(sink) {
31
63
  let s = state.get(sink);
32
64
  if (s === undefined) {
33
- s = { markers: [], bedPoints: null, thumbs: new ThumbnailCollector() };
65
+ s = { markers: [], wipes: [], bedPoints: null, thumbs: new ThumbnailCollector() };
34
66
  state.set(sink, s);
35
67
  }
36
68
  return s;
@@ -58,10 +90,30 @@ export function prusaSlicer() {
58
90
  s.markers.push({ srcByte, value: role ?? FeatureRole.Custom });
59
91
  return;
60
92
  }
93
+ const wipe = matchWipeComment(comment);
94
+ if (wipe !== null) {
95
+ s.wipes.push({ srcByte, open: wipe === 'start' });
96
+ return;
97
+ }
61
98
  if (s.thumbs.isActive || comment.trimStart().toLowerCase().startsWith('thumbnail')) {
62
99
  s.thumbs.feed(comment, sink);
63
100
  return;
64
101
  }
102
+ // Filament totals + print time — multi-word keys `parseKeyValue` can't tokenize (#183).
103
+ if (parseFilamentTotals(comment, srcByte, 'prusaslicer', sink))
104
+ return;
105
+ const time = /^\s*estimated printing time \(normal mode\)\s*=\s*(.+)$/i.exec(comment);
106
+ if (time !== null) {
107
+ const sec = parseHmsToSeconds(time[1].trim());
108
+ if (sec !== null) {
109
+ sink.setPrintEstimate({
110
+ seconds: sec,
111
+ mode: 'normal',
112
+ source: { adapterId: 'prusaslicer', evidence: '; estimated printing time', srcByte }
113
+ });
114
+ }
115
+ return;
116
+ }
65
117
  const kv = parseKeyValue(comment);
66
118
  if (kv === null)
67
119
  return;
@@ -83,6 +135,8 @@ export function prusaSlicer() {
83
135
  const applied = applyMarkerRanges(ir, s.markers, (a, b, v) => sink.setFeature(a, b, v));
84
136
  if (applied > 0)
85
137
  sink.upgradeCapability('featureRoles', 'known');
138
+ if (applyWipeRanges(ir, s.wipes, sink) > 0)
139
+ sink.upgradeCapability('wipeMoves', 'known');
86
140
  if (s.bedPoints !== null) {
87
141
  const points = parseAreaPoints(s.bedPoints);
88
142
  if (points !== null) {
package/dist/sink.d.ts CHANGED
@@ -5,9 +5,11 @@
5
5
  * exist until `finish()`), then applied to the finished IR in one validated
6
6
  * pass. Bounds are clamped, budgets enforced, and the sink physically cannot
7
7
  * reach geometry — it only ever touches `feature`, `object`, header
8
- * capabilities/warnings, and the metadata object beside the IR.
8
+ * capabilities/warnings, the metadata object beside the IR, and (DD-016 §4.2)
9
+ * an ADDITIVE allow-listed move-kind bit (`Wipe`/`Seam`) that never clears or
10
+ * reclassifies a move.
9
11
  */
10
- import type { Confidence, DialectMetadata, FilamentInfo, MachineGeometry, ThumbnailData, ToolpathIR } from '@chestnutlabs/toolpath-core';
12
+ import { type Confidence, type DialectMetadata, type FilamentInfo, type FilamentUsage, type MachineGeometry, type PrintEstimate, type ThumbnailData, type ToolpathIR } from '@chestnutlabs/toolpath-core';
11
13
  import type { AnnotationSink } from './contracts.js';
12
14
  export declare class BufferedAnnotationSink implements AnnotationSink {
13
15
  private readonly adapterId;
@@ -15,6 +17,8 @@ export declare class BufferedAnnotationSink implements AnnotationSink {
15
17
  private objects;
16
18
  private machine;
17
19
  private filaments;
20
+ private filamentUsage;
21
+ private printEstimate;
18
22
  private toolInfos;
19
23
  private thumbnails;
20
24
  private raw;
@@ -25,9 +29,12 @@ export declare class BufferedAnnotationSink implements AnnotationSink {
25
29
  private pushOp;
26
30
  setFeature(segStart: number, segEnd: number, role: number): void;
27
31
  setObject(segStart: number, segEnd: number, objectId: number): void;
32
+ addMoveKind(segStart: number, segEnd: number, kindBits: number): void;
28
33
  defineObject(id: number, name: string): void;
29
34
  setMachine(machine: MachineGeometry): void;
30
35
  setFilament(info: FilamentInfo): void;
36
+ setFilamentUsage(usage: FilamentUsage): void;
37
+ setPrintEstimate(estimate: PrintEstimate): void;
31
38
  setToolInfo(tool: number, info: {
32
39
  material?: string;
33
40
  colorHex?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"sink.d.ts","sourceRoot":"","sources":["../src/sink.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EACf,YAAY,EACZ,eAAe,EACf,aAAa,EACb,UAAU,EAEX,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAWrD,qBAAa,sBAAuB,YAAW,cAAc;IAY/C,OAAO,CAAC,QAAQ,CAAC,SAAS;IAXtC,OAAO,CAAC,GAAG,CAAiB;IAC5B,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,SAAS,CAA+D;IAChF,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,YAAY,CAAiC;IACrD,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,QAAQ,CAAS;gBAEI,SAAS,EAAE,MAAM;IAE9C,OAAO,CAAC,MAAM;IAYd,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAIhE,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAInE,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAI5C,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAI1C,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAIrC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAI/E,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAIxC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAIxC,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,IAAI;IAI5D,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAS3D;;;;;OAKG;IACH,KAAK,CAAC,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;CAwD9G"}
1
+ {"version":3,"file":"sink.d.ts","sourceRoot":"","sources":["../src/sink.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,UAAU,EAEhB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAcrD,qBAAa,sBAAuB,YAAW,cAAc;IAc/C,OAAO,CAAC,QAAQ,CAAC,SAAS;IAbtC,OAAO,CAAC,GAAG,CAAiB;IAC5B,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,SAAS,CAA+D;IAChF,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,YAAY,CAAiC;IACrD,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,QAAQ,CAAS;gBAEI,SAAS,EAAE,MAAM;IAE9C,OAAO,CAAC,MAAM;IAYd,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAIhE,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAInE,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAcrE,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAI5C,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAI1C,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAIrC,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAW5C,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IAK/C,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAI/E,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAIxC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAIxC,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,IAAI;IAI5D,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAS3D;;;;;OAKG;IACH,KAAK,CAAC,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;CAoE9G"}
package/dist/sink.js CHANGED
@@ -1,3 +1,17 @@
1
+ /**
2
+ * AnnotationSink implementation (DD-005 §4.1).
3
+ *
4
+ * Writes are BUFFERED as ops during the parse (the IR's final channels don't
5
+ * exist until `finish()`), then applied to the finished IR in one validated
6
+ * pass. Bounds are clamped, budgets enforced, and the sink physically cannot
7
+ * reach geometry — it only ever touches `feature`, `object`, header
8
+ * capabilities/warnings, the metadata object beside the IR, and (DD-016 §4.2)
9
+ * an ADDITIVE allow-listed move-kind bit (`Wipe`/`Seam`) that never clears or
10
+ * reclassifies a move.
11
+ */
12
+ import { MoveKind } from '@chestnutlabs/toolpath-core';
13
+ /** The only move-kind bits an adapter may add (DD-016 §4.2/D6) — additive annotation kinds, never motion. */
14
+ const ALLOWED_ANNOTATION_KINDS = MoveKind.Wipe | MoveKind.Seam;
1
15
  const MAX_RANGE_OPS = 1_000_000;
2
16
  const MAX_OBJECTS = 10_000;
3
17
  const MAX_RAW_ENTRIES = 512;
@@ -10,6 +24,8 @@ export class BufferedAnnotationSink {
10
24
  objects = new Map();
11
25
  machine = null;
12
26
  filaments = [];
27
+ filamentUsage = null;
28
+ printEstimate = null;
13
29
  toolInfos = new Map();
14
30
  thumbnails = [];
15
31
  raw = new Map();
@@ -37,6 +53,16 @@ export class BufferedAnnotationSink {
37
53
  setObject(segStart, segEnd, objectId) {
38
54
  this.pushOp({ channel: 'object', start: segStart, end: segEnd, value: objectId >>> 0 });
39
55
  }
56
+ addMoveKind(segStart, segEnd, kindBits) {
57
+ // Allow-list guard (DD-016 D6): only additive Wipe/Seam bits; anything else is a contract
58
+ // violation, dropped with a bounded warning rather than corrupting the kind channel.
59
+ const bits = kindBits & ALLOWED_ANNOTATION_KINDS;
60
+ if (bits === 0 || (kindBits & ~ALLOWED_ANNOTATION_KINDS) !== 0) {
61
+ this.warn('dialect-kind-not-allowlisted', `adapter '${this.adapterId}' tried to set non-allow-listed move-kind bits 0x${(kindBits >>> 0).toString(16)}`);
62
+ return;
63
+ }
64
+ this.pushOp({ channel: 'kind', start: segStart, end: segEnd, value: bits });
65
+ }
40
66
  defineObject(id, name) {
41
67
  if (this.objects.size < MAX_OBJECTS)
42
68
  this.objects.set(id, name.slice(0, 256));
@@ -48,6 +74,21 @@ export class BufferedAnnotationSink {
48
74
  if (this.filaments.length < 64)
49
75
  this.filaments.push(info);
50
76
  }
77
+ setFilamentUsage(usage) {
78
+ // Merge non-undefined fields (a slicer emits length/volume/weight on separate comment lines).
79
+ const prev = this.filamentUsage;
80
+ this.filamentUsage = {
81
+ lengthMm: usage.lengthMm ?? prev?.lengthMm,
82
+ volumeCm3: usage.volumeCm3 ?? prev?.volumeCm3,
83
+ weightG: usage.weightG ?? prev?.weightG,
84
+ source: prev?.source ?? usage.source
85
+ };
86
+ }
87
+ setPrintEstimate(estimate) {
88
+ // First estimate wins (adapters send the default/'normal' mode first; ignore later silent-mode).
89
+ if (this.printEstimate === null)
90
+ this.printEstimate = estimate;
91
+ }
51
92
  setToolInfo(tool, info) {
52
93
  if (this.toolInfos.size < 64)
53
94
  this.toolInfos.set(tool, info);
@@ -86,6 +127,13 @@ export class BufferedAnnotationSink {
86
127
  const end = Math.min(op.end + 1, count);
87
128
  if (end <= start)
88
129
  continue;
130
+ if (op.channel === 'kind') {
131
+ // DD-016: additive OR — preserve the base Extrude/Travel classification, never overwrite it.
132
+ const kind = ir.segments.kind;
133
+ for (let i = start; i < end; i++)
134
+ kind[i] |= op.value;
135
+ continue;
136
+ }
89
137
  ir.segments[op.channel].fill(op.value, start, end);
90
138
  touched[op.channel] = true;
91
139
  }
@@ -130,6 +178,12 @@ export class BufferedAnnotationSink {
130
178
  if (this.filaments.length > 0) {
131
179
  metadata.filaments = [...(metadata.filaments ?? []), ...this.filaments];
132
180
  }
181
+ if (this.filamentUsage !== null && metadata.filamentUsage === undefined) {
182
+ metadata.filamentUsage = this.filamentUsage;
183
+ }
184
+ if (this.printEstimate !== null && metadata.printEstimate === undefined) {
185
+ metadata.printEstimate = this.printEstimate;
186
+ }
133
187
  if (this.thumbnails.length > 0) {
134
188
  metadata.thumbnails = [...(metadata.thumbnails ?? []), ...this.thumbnails];
135
189
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chestnutlabs/gcode-dialects",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Slicer/firmware dialect adapters for ToolpathIR annotation (DD-005). Adapters annotate metadata and optional channels — they can never alter geometry.",
5
5
  "keywords": [
6
6
  "gcode",
@@ -45,6 +45,6 @@
45
45
  "test": "vitest run"
46
46
  },
47
47
  "dependencies": {
48
- "@chestnutlabs/toolpath-core": "0.2.0"
48
+ "@chestnutlabs/toolpath-core": "0.3.0"
49
49
  }
50
50
  }