@chestnutlabs/gcode-dialects 0.15.0 → 0.17.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/dist/annotate.d.ts +19 -5
- package/dist/annotate.d.ts.map +1 -1
- package/dist/annotate.js +36 -10
- package/dist/cura.js +2 -2
- package/dist/ideamaker.d.ts +3 -0
- package/dist/ideamaker.d.ts.map +1 -0
- package/dist/ideamaker.js +80 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/object-markers.d.ts +45 -0
- package/dist/object-markers.d.ts.map +1 -0
- package/dist/object-markers.js +78 -0
- package/dist/orca-bambu.d.ts.map +1 -1
- package/dist/orca-bambu.js +41 -26
- package/dist/prusaslicer.d.ts.map +1 -1
- package/dist/prusaslicer.js +15 -2
- package/dist/registry.d.ts +0 -13
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +20 -0
- package/dist/simplify3d.d.ts +3 -0
- package/dist/simplify3d.d.ts.map +1 -0
- package/dist/simplify3d.js +71 -0
- package/package.json +2 -2
package/dist/annotate.d.ts
CHANGED
|
@@ -19,24 +19,38 @@ 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
|
|
23
|
-
export interface
|
|
22
|
+
/** One end of a housekeeping bracket (DD-016/DD-026 D3): source byte + whether it OPENED the bracket. */
|
|
23
|
+
export interface BracketMark {
|
|
24
24
|
srcByte: number;
|
|
25
25
|
open: boolean;
|
|
26
26
|
}
|
|
27
|
+
/** One end of a wipe bracket (DD-016, #182). Structurally a {@link BracketMark}; kept for compatibility. */
|
|
28
|
+
export type WipeMark = BracketMark;
|
|
27
29
|
/**
|
|
28
30
|
* Classify a comment as a PrusaSlicer/OrcaSlicer wipe-bracket marker. The convention is bare
|
|
29
31
|
* `;WIPE_START` / `;WIPE_END` (the semicolon is already stripped before onComment); tolerant of a
|
|
30
32
|
* leading space and casing. Returns null for any other comment.
|
|
31
33
|
*/
|
|
32
34
|
export declare function matchWipeComment(comment: string): 'start' | 'end' | null;
|
|
35
|
+
/**
|
|
36
|
+
* Classify a comment as a bare `<NAME>_START` / `<NAME>_END` bracket marker (semicolon already stripped
|
|
37
|
+
* before onComment; case/space tolerant). Used for wipe (DD-016) and the DD-026 D3 housekeeping brackets
|
|
38
|
+
* (`FLUSH_START/END`, `WIPE_TOWER_START/END`). Returns null for any other comment.
|
|
39
|
+
*/
|
|
40
|
+
export declare function matchBracketComment(comment: string, name: string): 'start' | 'end' | null;
|
|
33
41
|
/**
|
|
34
42
|
* 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.
|
|
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.
|
|
43
|
+
* (DD-016 §4.2). Segments emitted between a START and its END are the wipe moves. Returns ranges applied.
|
|
38
44
|
*/
|
|
39
45
|
export declare function applyWipeRanges(ir: ToolpathIR, marks: WipeMark[], sink: AnnotationSink): number;
|
|
46
|
+
/**
|
|
47
|
+
* Resolve a housekeeping bracket (DD-026 D3) — e.g. Bambu `FLUSH_START/END` → `FeatureRole.Purge`,
|
|
48
|
+
* `WIPE_TOWER_START/END` → `FeatureRole.WipeTower` — to segment ranges and set `role` on the enclosed
|
|
49
|
+
* segments. Apply this AFTER the `;TYPE:`/`; FEATURE:` markers so the explicit bracket wins over the
|
|
50
|
+
* surrounding role (the classifier then excludes the whole bracket from `modelBounds`). Returns ranges
|
|
51
|
+
* applied.
|
|
52
|
+
*/
|
|
53
|
+
export declare function applyFeatureBracketRanges(ir: ToolpathIR, marks: BracketMark[], role: number, sink: AnnotationSink): number;
|
|
40
54
|
export interface SegMarker {
|
|
41
55
|
segIndex: number;
|
|
42
56
|
value: number;
|
package/dist/annotate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
1
|
+
{"version":3,"file":"annotate.d.ts","sourceRoot":"","sources":["../src/annotate.ts"],"names":[],"mappings":"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"}
|
package/dist/annotate.js
CHANGED
|
@@ -45,20 +45,29 @@ export function applyMarkerRanges(ir, markers, write) {
|
|
|
45
45
|
* leading space and casing. Returns null for any other comment.
|
|
46
46
|
*/
|
|
47
47
|
export function matchWipeComment(comment) {
|
|
48
|
+
return matchBracketComment(comment, 'WIPE');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Classify a comment as a bare `<NAME>_START` / `<NAME>_END` bracket marker (semicolon already stripped
|
|
52
|
+
* before onComment; case/space tolerant). Used for wipe (DD-016) and the DD-026 D3 housekeeping brackets
|
|
53
|
+
* (`FLUSH_START/END`, `WIPE_TOWER_START/END`). Returns null for any other comment.
|
|
54
|
+
*/
|
|
55
|
+
export function matchBracketComment(comment, name) {
|
|
48
56
|
const t = comment.trim().toUpperCase();
|
|
49
|
-
|
|
57
|
+
const n = name.toUpperCase();
|
|
58
|
+
if (t === `${n}_START`)
|
|
50
59
|
return 'start';
|
|
51
|
-
if (t ===
|
|
60
|
+
if (t === `${n}_END`)
|
|
52
61
|
return 'end';
|
|
53
62
|
return null;
|
|
54
63
|
}
|
|
55
64
|
/**
|
|
56
|
-
* Resolve
|
|
57
|
-
*
|
|
58
|
-
* markers degrade safely: a stray END is ignored, a second START before an END
|
|
59
|
-
* bracket, and a START left open at end-of-file closes at the last segment. Returns ranges
|
|
65
|
+
* Resolve balanced open/close bracket marks to inclusive segment ranges and hand each to `emit`. Shared
|
|
66
|
+
* by the wipe (MoveKind) and DD-026 housekeeping-role (FeatureRole) brackets. Unbalanced or nested
|
|
67
|
+
* markers degrade safely: a stray END is ignored, a second START before an END folds into the open
|
|
68
|
+
* bracket, and a START left open at end-of-file closes at the last segment. Returns ranges emitted.
|
|
60
69
|
*/
|
|
61
|
-
|
|
70
|
+
function forEachBracketRange(ir, marks, emit) {
|
|
62
71
|
let applied = 0;
|
|
63
72
|
let openByte = -1;
|
|
64
73
|
for (const mark of marks) {
|
|
@@ -68,9 +77,9 @@ export function applyWipeRanges(ir, marks, sink) {
|
|
|
68
77
|
}
|
|
69
78
|
else if (openByte >= 0) {
|
|
70
79
|
const start = segAtOrAfterByte(ir, openByte);
|
|
71
|
-
const end = segAtOrAfterByte(ir, mark.srcByte); // first segment AFTER the
|
|
80
|
+
const end = segAtOrAfterByte(ir, mark.srcByte); // first segment AFTER the block
|
|
72
81
|
if (end - 1 >= start) {
|
|
73
|
-
|
|
82
|
+
emit(start, end - 1);
|
|
74
83
|
applied++;
|
|
75
84
|
}
|
|
76
85
|
openByte = -1;
|
|
@@ -79,12 +88,29 @@ export function applyWipeRanges(ir, marks, sink) {
|
|
|
79
88
|
if (openByte >= 0) {
|
|
80
89
|
const start = segAtOrAfterByte(ir, openByte);
|
|
81
90
|
if (ir.segments.count - 1 >= start) {
|
|
82
|
-
|
|
91
|
+
emit(start, ir.segments.count - 1);
|
|
83
92
|
applied++;
|
|
84
93
|
}
|
|
85
94
|
}
|
|
86
95
|
return applied;
|
|
87
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Resolve WIPE_START/END byte marks to segment ranges and add `MoveKind.Wipe` additively via the sink
|
|
99
|
+
* (DD-016 §4.2). Segments emitted between a START and its END are the wipe moves. Returns ranges applied.
|
|
100
|
+
*/
|
|
101
|
+
export function applyWipeRanges(ir, marks, sink) {
|
|
102
|
+
return forEachBracketRange(ir, marks, (start, end) => sink.addMoveKind(start, end, MoveKind.Wipe));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Resolve a housekeeping bracket (DD-026 D3) — e.g. Bambu `FLUSH_START/END` → `FeatureRole.Purge`,
|
|
106
|
+
* `WIPE_TOWER_START/END` → `FeatureRole.WipeTower` — to segment ranges and set `role` on the enclosed
|
|
107
|
+
* segments. Apply this AFTER the `;TYPE:`/`; FEATURE:` markers so the explicit bracket wins over the
|
|
108
|
+
* surrounding role (the classifier then excludes the whole bracket from `modelBounds`). Returns ranges
|
|
109
|
+
* applied.
|
|
110
|
+
*/
|
|
111
|
+
export function applyFeatureBracketRanges(ir, marks, role, sink) {
|
|
112
|
+
return forEachBracketRange(ir, marks, (start, end) => sink.setFeature(start, end, role));
|
|
113
|
+
}
|
|
88
114
|
/**
|
|
89
115
|
* Apply SEGMENT-indexed markers (CommandEvent.segIndex — exact, no byte
|
|
90
116
|
* resolution needed) as contiguous ranges; value 0 terminates (DD-005 phase 5).
|
package/dist/cura.js
CHANGED
|
@@ -17,8 +17,8 @@ const TYPE_MAP = {
|
|
|
17
17
|
support: FeatureRole.Support,
|
|
18
18
|
'support-interface': FeatureRole.Support,
|
|
19
19
|
skirt: FeatureRole.Skirt,
|
|
20
|
-
raft: FeatureRole.
|
|
21
|
-
'prime-tower': FeatureRole.
|
|
20
|
+
raft: FeatureRole.Raft,
|
|
21
|
+
'prime-tower': FeatureRole.PrimeTower,
|
|
22
22
|
bridge: FeatureRole.Bridge
|
|
23
23
|
};
|
|
24
24
|
const state = new WeakMap();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ideamaker.d.ts","sourceRoot":"","sources":["../src/ideamaker.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAmCrE,wBAAgB,SAAS,IAAI,cAAc,CA6C1C"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ideaMaker dialect adapter (DD-026 T1, RR-007 §5.6).
|
|
3
|
+
*
|
|
4
|
+
* Annotates `;TYPE:` feature roles (ideaMaker's UPPERCASE vocabulary) and object membership from the
|
|
5
|
+
* `;PRINTING: <name>` + `;PRINTING_ID: <n>` STATE channel — `PRINTING_ID: -1` (with `PRINTING:
|
|
6
|
+
* NON-OBJECT`) is housekeeping, `PRINTING_ID: <n≥0>` is model — → `object` channel + `objects:'known'`.
|
|
7
|
+
* ideaMaker emits Marlin-flavoured motion; the marlin firmware adapter composes for firmware identity.
|
|
8
|
+
*/
|
|
9
|
+
import { FeatureRole } from '@chestnutlabs/toolpath-core';
|
|
10
|
+
import { applyMarkerRanges } from './annotate.js';
|
|
11
|
+
import { PrintingObjectTracker } from './object-markers.js';
|
|
12
|
+
/** ideaMaker `;TYPE:` names → DD-001 FeatureRole (unknown names → Custom, honestly generic). */
|
|
13
|
+
const TYPE_MAP = {
|
|
14
|
+
'wall-outer': FeatureRole.ExternalPerimeter,
|
|
15
|
+
'wall-inner': FeatureRole.Perimeter,
|
|
16
|
+
fill: FeatureRole.Infill,
|
|
17
|
+
'solid-fill': FeatureRole.SolidInfill,
|
|
18
|
+
support: FeatureRole.Support,
|
|
19
|
+
skirt: FeatureRole.Skirt,
|
|
20
|
+
raft: FeatureRole.Raft,
|
|
21
|
+
'wipe-tower': FeatureRole.WipeTower,
|
|
22
|
+
bridge: FeatureRole.Bridge
|
|
23
|
+
};
|
|
24
|
+
const state = new WeakMap();
|
|
25
|
+
function stateFor(sink) {
|
|
26
|
+
let s = state.get(sink);
|
|
27
|
+
if (s === undefined) {
|
|
28
|
+
s = { markers: [], objectTracker: new PrintingObjectTracker() };
|
|
29
|
+
state.set(sink, s);
|
|
30
|
+
}
|
|
31
|
+
return s;
|
|
32
|
+
}
|
|
33
|
+
export function ideaMaker() {
|
|
34
|
+
return {
|
|
35
|
+
id: 'ideamaker',
|
|
36
|
+
displayName: 'ideaMaker',
|
|
37
|
+
kind: 'slicer',
|
|
38
|
+
detect(input) {
|
|
39
|
+
const m = /Sliced by (ideaMaker[^\n\r]*)/.exec(input.headText);
|
|
40
|
+
if (m !== null) {
|
|
41
|
+
return { dialectId: 'ideamaker', kind: 'slicer', confidence: 'known', evidence: `header: ${m[1].trim()}` };
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
},
|
|
45
|
+
onComment(comment, srcByte, sink) {
|
|
46
|
+
const s = stateFor(sink);
|
|
47
|
+
const trimmed = comment.trim();
|
|
48
|
+
if (trimmed.startsWith('TYPE:')) {
|
|
49
|
+
const role = TYPE_MAP[trimmed.slice(5).trim().toLowerCase()];
|
|
50
|
+
s.markers.push({ srcByte, value: role ?? FeatureRole.Custom });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Object membership STATE channel. `;PRINTING:` names the object (or NON-OBJECT), then
|
|
54
|
+
// `;PRINTING_ID:<n>` opens object n (n≥0) or closes to housekeeping (n<0).
|
|
55
|
+
if (trimmed.startsWith('PRINTING_ID:')) {
|
|
56
|
+
const id = trimmed.slice('PRINTING_ID:'.length).trim();
|
|
57
|
+
if (/^\d+$/.test(id)) {
|
|
58
|
+
s.objectTracker.markStart(id, s.pendingName, srcByte);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
s.objectTracker.markEnd(srcByte); // `-1` (NON-OBJECT) or any non-numeric → close
|
|
62
|
+
}
|
|
63
|
+
s.pendingName = undefined;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (trimmed.startsWith('PRINTING:')) {
|
|
67
|
+
const name = trimmed.slice('PRINTING:'.length).trim();
|
|
68
|
+
s.pendingName = /^non-object$/i.test(name) ? undefined : name;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
finalize(ir, sink) {
|
|
73
|
+
const s = stateFor(sink);
|
|
74
|
+
const applied = applyMarkerRanges(ir, s.markers, (a, b, v) => sink.setFeature(a, b, v));
|
|
75
|
+
if (applied > 0)
|
|
76
|
+
sink.upgradeCapability('featureRoles', 'known');
|
|
77
|
+
s.objectTracker.apply(ir, sink);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export { BufferedAnnotationSink } from './sink.js';
|
|
|
14
14
|
export { prusaSlicer } from './prusaslicer.js';
|
|
15
15
|
export { orcaBambu } from './orca-bambu.js';
|
|
16
16
|
export { cura } from './cura.js';
|
|
17
|
+
export { ideaMaker } from './ideamaker.js';
|
|
18
|
+
export { simplify3d } from './simplify3d.js';
|
|
17
19
|
export { klipper, marlin, repRap } from './firmware.js';
|
|
18
20
|
export { grblLaser, grblMill, linuxCnc } from './cnc.js';
|
|
19
21
|
export type { ValidationTier, MachineClass } from './cnc.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,UAAU,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzD,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7D,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,aAAa,EACb,YAAY,EACZ,kBAAkB,EACnB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,UAAU,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzD,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7D,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,aAAa,EACb,YAAY,EACZ,kBAAkB,EACnB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@ export { BufferedAnnotationSink } from './sink.js';
|
|
|
3
3
|
export { prusaSlicer } from './prusaslicer.js';
|
|
4
4
|
export { orcaBambu } from './orca-bambu.js';
|
|
5
5
|
export { cura } from './cura.js';
|
|
6
|
+
export { ideaMaker } from './ideamaker.js';
|
|
7
|
+
export { simplify3d } from './simplify3d.js';
|
|
6
8
|
export { klipper, marlin, repRap } from './firmware.js';
|
|
7
9
|
export { grblLaser, grblMill, linuxCnc } from './cnc.js';
|
|
8
10
|
export { segAtOrAfterByte, applyMarkerRanges, parseKeyValue, parseAreaPoints, bedFromPoints, decodeBase64, ThumbnailCollector } from './annotate.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared parsing for slicer object-membership comments (`; printing object …` / `; stop printing
|
|
3
|
+
* object …`), used by every Prusa/Orca/Bambu-lineage adapter. Grounded in the real marker formats
|
|
4
|
+
* catalogued in RR-007 §5:
|
|
5
|
+
* - Bambu Studio: `start printing object, unique label id: 89` (name may follow as `name: …`)
|
|
6
|
+
* - OrcaSlicer: `printing object <name> id:729618461984033312` (no "start"; id can exceed Uint32)
|
|
7
|
+
* - Anycubic / Prusa-lineage: `printing object "<name>" id:0 copy 0`
|
|
8
|
+
*
|
|
9
|
+
* The `object` channel is a `Uint32Array` (0 = none); raw slicer ids can exceed 2^32 and repeat per
|
|
10
|
+
* layer, so each DISTINCT id is mapped to a sequential 1-based channel value, reused across
|
|
11
|
+
* re-bracketing of the same object.
|
|
12
|
+
*/
|
|
13
|
+
import type { ToolpathIR } from '@chestnutlabs/toolpath-core';
|
|
14
|
+
import type { AnnotationSink } from './contracts.js';
|
|
15
|
+
/**
|
|
16
|
+
* Match an object-start comment (the leading `; ` is already stripped) across all lineage formats,
|
|
17
|
+
* returning the raw object id (stable per object) + optional name, or null.
|
|
18
|
+
*/
|
|
19
|
+
export declare function matchPrintingObjectStart(trimmed: string): {
|
|
20
|
+
id: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
} | null;
|
|
23
|
+
/**
|
|
24
|
+
* Collects `; printing object` / `; stop printing object` markers during a parse and applies them as
|
|
25
|
+
* object-channel ranges at finalize. One instance per parse (per adapter state).
|
|
26
|
+
*/
|
|
27
|
+
export declare class PrintingObjectTracker {
|
|
28
|
+
private readonly markers;
|
|
29
|
+
private readonly idToValue;
|
|
30
|
+
private readonly names;
|
|
31
|
+
private counter;
|
|
32
|
+
/** Try to consume a `; printing object` / `; stop printing object` comment; true if it was one. */
|
|
33
|
+
handle(trimmed: string, srcByte: number): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Open (or reopen) an object range by its stable slicer id — for adapters whose membership uses a
|
|
36
|
+
* non-comment marker convention (e.g. ideaMaker's `;PRINTING_ID:<n>` state). The id maps to a
|
|
37
|
+
* sequential 1-based channel value, reused if seen before.
|
|
38
|
+
*/
|
|
39
|
+
markStart(id: string, name: string | undefined, srcByte: number): void;
|
|
40
|
+
/** Close the current object range (back to "no object"). */
|
|
41
|
+
markEnd(srcByte: number): void;
|
|
42
|
+
/** Apply the collected object ranges + names; upgrades the `objects` capability if any resolved. */
|
|
43
|
+
apply(ir: ToolpathIR, sink: AnnotationSink): number;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=object-markers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-markers.d.ts","sourceRoot":"","sources":["../src/object-markers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAgB9F;AAED;;;GAGG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6B;IACnD,OAAO,CAAC,OAAO,CAAK;IAEpB,mGAAmG;IACnG,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAajD;;;;OAIG;IACH,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAUtE,4DAA4D;IAC5D,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI9B,oGAAoG;IACpG,KAAK,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,GAAG,MAAM;CAQpD"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { applyMarkerRanges } from './annotate.js';
|
|
2
|
+
/**
|
|
3
|
+
* Match an object-start comment (the leading `; ` is already stripped) across all lineage formats,
|
|
4
|
+
* returning the raw object id (stable per object) + optional name, or null.
|
|
5
|
+
*/
|
|
6
|
+
export function matchPrintingObjectStart(trimmed) {
|
|
7
|
+
if (!/^(?:start\s+)?printing object\b/i.test(trimmed))
|
|
8
|
+
return null;
|
|
9
|
+
const idm = /\bid:?\s*(\d+)/i.exec(trimmed);
|
|
10
|
+
if (idm === null)
|
|
11
|
+
return null;
|
|
12
|
+
// Name: prefer an explicit `name:<…>` suffix (Bambu), else a quoted or bare name BEFORE the id
|
|
13
|
+
// (OrcaSlicer / Anycubic), rejecting `unique` / `unique label` filler.
|
|
14
|
+
let name = '';
|
|
15
|
+
const suffix = /\bname:\s*(.+?)\s*$/i.exec(trimmed);
|
|
16
|
+
if (suffix !== null) {
|
|
17
|
+
name = suffix[1].trim();
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
const pre = /printing object[,:]?\s*(?:"([^"]*)"|(.+?))\s+(?:unique\s+(?:label\s+)?)?id:?/i.exec(trimmed);
|
|
21
|
+
name = (pre?.[1] ?? pre?.[2] ?? '').trim();
|
|
22
|
+
if (/^unique(\s+label)?$/i.test(name))
|
|
23
|
+
name = '';
|
|
24
|
+
}
|
|
25
|
+
return { id: idm[1], name: name.length > 0 ? name : undefined };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Collects `; printing object` / `; stop printing object` markers during a parse and applies them as
|
|
29
|
+
* object-channel ranges at finalize. One instance per parse (per adapter state).
|
|
30
|
+
*/
|
|
31
|
+
export class PrintingObjectTracker {
|
|
32
|
+
markers = [];
|
|
33
|
+
idToValue = new Map();
|
|
34
|
+
names = new Map();
|
|
35
|
+
counter = 0;
|
|
36
|
+
/** Try to consume a `; printing object` / `; stop printing object` comment; true if it was one. */
|
|
37
|
+
handle(trimmed, srcByte) {
|
|
38
|
+
const start = matchPrintingObjectStart(trimmed);
|
|
39
|
+
if (start !== null) {
|
|
40
|
+
this.markStart(start.id, start.name, srcByte);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (/^stop printing object/i.test(trimmed)) {
|
|
44
|
+
this.markEnd(srcByte);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Open (or reopen) an object range by its stable slicer id — for adapters whose membership uses a
|
|
51
|
+
* non-comment marker convention (e.g. ideaMaker's `;PRINTING_ID:<n>` state). The id maps to a
|
|
52
|
+
* sequential 1-based channel value, reused if seen before.
|
|
53
|
+
*/
|
|
54
|
+
markStart(id, name, srcByte) {
|
|
55
|
+
let value = this.idToValue.get(id);
|
|
56
|
+
if (value === undefined) {
|
|
57
|
+
value = ++this.counter;
|
|
58
|
+
this.idToValue.set(id, value);
|
|
59
|
+
if (name !== undefined)
|
|
60
|
+
this.names.set(value, name);
|
|
61
|
+
}
|
|
62
|
+
this.markers.push({ srcByte, value });
|
|
63
|
+
}
|
|
64
|
+
/** Close the current object range (back to "no object"). */
|
|
65
|
+
markEnd(srcByte) {
|
|
66
|
+
this.markers.push({ srcByte, value: 0 });
|
|
67
|
+
}
|
|
68
|
+
/** Apply the collected object ranges + names; upgrades the `objects` capability if any resolved. */
|
|
69
|
+
apply(ir, sink) {
|
|
70
|
+
const applied = applyMarkerRanges(ir, this.markers, (a, b, v) => sink.setObject(a, b, v));
|
|
71
|
+
if (applied > 0) {
|
|
72
|
+
for (const [value, name] of this.names)
|
|
73
|
+
sink.defineObject(value, name);
|
|
74
|
+
sink.upgradeCapability('objects', 'known');
|
|
75
|
+
}
|
|
76
|
+
return applied;
|
|
77
|
+
}
|
|
78
|
+
}
|
package/dist/orca-bambu.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orca-bambu.d.ts","sourceRoot":"","sources":["../src/orca-bambu.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"orca-bambu.d.ts","sourceRoot":"","sources":["../src/orca-bambu.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AA8ErE,wBAAgB,SAAS,IAAI,cAAc,CA0H1C"}
|
package/dist/orca-bambu.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OrcaSlicer / Bambu Studio dialect adapter (DD-005 phase 3, issue #75).
|
|
3
3
|
*
|
|
4
|
-
* Annotates: `; FEATURE:`
|
|
5
|
-
*
|
|
4
|
+
* Annotates: feature roles from `; FEATURE:` (Bambu Studio) **or** `;TYPE:`
|
|
5
|
+
* (real OrcaSlicer / AnycubicSlicerNext — same vocabulary, different prefix;
|
|
6
|
+
* RR-007 §5.8) → `featureRoles: 'known'`; object start/stop comments across all
|
|
7
|
+
* lineage formats (Bambu `start printing object, unique label id:`, Orca
|
|
8
|
+
* `printing object <name> id:<id>`) → `object` channel + `objects: 'known'`; bed geometry
|
|
6
9
|
* from `printable_area` config comments (bare .gcode files — inside a
|
|
7
10
|
* `.gcode.3mf` the container's machine config already provides `known`
|
|
8
11
|
* geometry and outranks this), and printer identity. Detection also accepts
|
|
@@ -10,7 +13,8 @@
|
|
|
10
13
|
*/
|
|
11
14
|
import { FeatureRole } from '@chestnutlabs/toolpath-core';
|
|
12
15
|
import { parseFilamentTotals, parseHmsToSeconds } from './prusaslicer.js';
|
|
13
|
-
import {
|
|
16
|
+
import { PrintingObjectTracker } from './object-markers.js';
|
|
17
|
+
import { ThumbnailCollector, applyFeatureBracketRanges, applyMarkerRanges, applyWipeRanges, bedFromPoints, matchBracketComment, matchWipeComment, parseAreaPoints, parseKeyValue } from './annotate.js';
|
|
14
18
|
/** Orca/Bambu `; FEATURE:` names → DD-001 FeatureRole. */
|
|
15
19
|
const FEATURE_MAP = {
|
|
16
20
|
'inner wall': FeatureRole.Perimeter,
|
|
@@ -27,8 +31,10 @@ const FEATURE_MAP = {
|
|
|
27
31
|
'support transition': FeatureRole.Support,
|
|
28
32
|
skirt: FeatureRole.Skirt,
|
|
29
33
|
brim: FeatureRole.Brim,
|
|
30
|
-
|
|
34
|
+
raft: FeatureRole.Raft,
|
|
35
|
+
'prime tower': FeatureRole.PrimeTower,
|
|
31
36
|
'gap infill': FeatureRole.Infill,
|
|
37
|
+
'internal bridge': FeatureRole.Bridge,
|
|
32
38
|
custom: FeatureRole.Custom
|
|
33
39
|
};
|
|
34
40
|
const state = new WeakMap();
|
|
@@ -37,9 +43,10 @@ function stateFor(sink) {
|
|
|
37
43
|
if (s === undefined) {
|
|
38
44
|
s = {
|
|
39
45
|
features: [],
|
|
40
|
-
objects: [],
|
|
41
46
|
wipes: [],
|
|
42
|
-
|
|
47
|
+
flushes: [],
|
|
48
|
+
wipeTowers: [],
|
|
49
|
+
objectTracker: new PrintingObjectTracker(),
|
|
43
50
|
bedPoints: null,
|
|
44
51
|
thumbs: new ThumbnailCollector()
|
|
45
52
|
};
|
|
@@ -70,28 +77,37 @@ export function orcaBambu() {
|
|
|
70
77
|
onComment(comment, srcByte, sink) {
|
|
71
78
|
const s = stateFor(sink);
|
|
72
79
|
const trimmed = comment.trim();
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
// Feature roles: Bambu Studio emits `; FEATURE:<vocab>`; real OrcaSlicer / AnycubicSlicerNext
|
|
81
|
+
// emit `;TYPE:<vocab>` — the same Orca vocabulary, different prefix (RR-007 §5.8). Accept both.
|
|
82
|
+
const featureName = trimmed.startsWith('FEATURE:')
|
|
83
|
+
? trimmed.slice(8)
|
|
84
|
+
: trimmed.startsWith('TYPE:')
|
|
85
|
+
? trimmed.slice(5)
|
|
86
|
+
: null;
|
|
87
|
+
if (featureName !== null) {
|
|
88
|
+
const role = FEATURE_MAP[featureName.trim().toLowerCase()];
|
|
75
89
|
s.features.push({ srcByte, value: role ?? FeatureRole.Custom });
|
|
76
90
|
return;
|
|
77
91
|
}
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
const value = parseInt(start[1]) + 1; // object channel is 1-based (0 = none)
|
|
81
|
-
s.objects.push({ srcByte, value });
|
|
82
|
-
if (start[2] !== undefined)
|
|
83
|
-
s.objectNames.set(value, start[2].trim());
|
|
92
|
+
// Object membership across all lineage formats (shared object-marker parser).
|
|
93
|
+
if (s.objectTracker.handle(trimmed, srcByte))
|
|
84
94
|
return;
|
|
85
|
-
}
|
|
86
|
-
if (/^stop printing object/i.test(trimmed)) {
|
|
87
|
-
s.objects.push({ srcByte, value: 0 }); // range terminator
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
95
|
const wipe = matchWipeComment(comment);
|
|
91
96
|
if (wipe !== null) {
|
|
92
97
|
s.wipes.push({ srcByte, open: wipe === 'start' });
|
|
93
98
|
return;
|
|
94
99
|
}
|
|
100
|
+
// DD-026 D3 housekeeping brackets (checked after WIPE — WIPE_TOWER is a distinct token).
|
|
101
|
+
const flush = matchBracketComment(comment, 'FLUSH');
|
|
102
|
+
if (flush !== null) {
|
|
103
|
+
s.flushes.push({ srcByte, open: flush === 'start' });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const wipeTower = matchBracketComment(comment, 'WIPE_TOWER');
|
|
107
|
+
if (wipeTower !== null) {
|
|
108
|
+
s.wipeTowers.push({ srcByte, open: wipeTower === 'start' });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
95
111
|
if (s.thumbs.isActive || trimmed.toLowerCase().startsWith('thumbnail')) {
|
|
96
112
|
s.thumbs.feed(comment, sink);
|
|
97
113
|
return;
|
|
@@ -135,14 +151,13 @@ export function orcaBambu() {
|
|
|
135
151
|
finalize(ir, sink) {
|
|
136
152
|
const s = stateFor(sink);
|
|
137
153
|
const features = applyMarkerRanges(ir, s.features, (a, b, v) => sink.setFeature(a, b, v));
|
|
138
|
-
|
|
154
|
+
// Housekeeping brackets (DD-026 D3) override the surrounding ;TYPE: role — apply them AFTER the
|
|
155
|
+
// feature markers so the bracket range wins (setFeature fills, last write per segment holds).
|
|
156
|
+
const flush = applyFeatureBracketRanges(ir, s.flushes, FeatureRole.Purge, sink);
|
|
157
|
+
const wipeTower = applyFeatureBracketRanges(ir, s.wipeTowers, FeatureRole.WipeTower, sink);
|
|
158
|
+
if (features + flush + wipeTower > 0)
|
|
139
159
|
sink.upgradeCapability('featureRoles', 'known');
|
|
140
|
-
|
|
141
|
-
if (objects > 0) {
|
|
142
|
-
for (const [value, name] of s.objectNames)
|
|
143
|
-
sink.defineObject(value, name);
|
|
144
|
-
sink.upgradeCapability('objects', 'known');
|
|
145
|
-
}
|
|
160
|
+
s.objectTracker.apply(ir, sink);
|
|
146
161
|
if (applyWipeRanges(ir, s.wipes, sink) > 0)
|
|
147
162
|
sink.upgradeCapability('wipeMoves', 'known');
|
|
148
163
|
if (s.bedPoints !== null) {
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAkCrE,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;AA8BD,wBAAgB,WAAW,IAAI,cAAc,CAuF5C"}
|
package/dist/prusaslicer.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { FeatureRole } from '@chestnutlabs/toolpath-core';
|
|
10
10
|
import { ThumbnailCollector, applyMarkerRanges, applyWipeRanges, bedFromPoints, matchWipeComment, parseAreaPoints, parseKeyValue } from './annotate.js';
|
|
11
|
+
import { PrintingObjectTracker } from './object-markers.js';
|
|
11
12
|
/** Prusa `;TYPE:` names → DD-001 FeatureRole (unknown names → Custom, honestly generic). */
|
|
12
13
|
const TYPE_MAP = {
|
|
13
14
|
perimeter: FeatureRole.Perimeter,
|
|
@@ -23,7 +24,8 @@ const TYPE_MAP = {
|
|
|
23
24
|
'skirt/brim': FeatureRole.Skirt,
|
|
24
25
|
skirt: FeatureRole.Skirt,
|
|
25
26
|
brim: FeatureRole.Brim,
|
|
26
|
-
|
|
27
|
+
raft: FeatureRole.Raft,
|
|
28
|
+
'wipe tower': FeatureRole.WipeTower,
|
|
27
29
|
custom: FeatureRole.Custom
|
|
28
30
|
};
|
|
29
31
|
/** Parse PrusaSlicer/Orca `<h>h <m>m <s>s` (any part optional) → seconds; null when zero/unparsed. */
|
|
@@ -62,7 +64,13 @@ const state = new WeakMap();
|
|
|
62
64
|
function stateFor(sink) {
|
|
63
65
|
let s = state.get(sink);
|
|
64
66
|
if (s === undefined) {
|
|
65
|
-
s = {
|
|
67
|
+
s = {
|
|
68
|
+
markers: [],
|
|
69
|
+
wipes: [],
|
|
70
|
+
objectTracker: new PrintingObjectTracker(),
|
|
71
|
+
bedPoints: null,
|
|
72
|
+
thumbs: new ThumbnailCollector()
|
|
73
|
+
};
|
|
66
74
|
state.set(sink, s);
|
|
67
75
|
}
|
|
68
76
|
return s;
|
|
@@ -90,6 +98,10 @@ export function prusaSlicer() {
|
|
|
90
98
|
s.markers.push({ srcByte, value: role ?? FeatureRole.Custom });
|
|
91
99
|
return;
|
|
92
100
|
}
|
|
101
|
+
// Object membership (`; printing object …` / `; stop printing object …`) when Label objects is
|
|
102
|
+
// enabled — RR-007 §5.1 / DD-026 T1. The comment retains a leading space, so trim for the parser.
|
|
103
|
+
if (s.objectTracker.handle(comment.trim(), srcByte))
|
|
104
|
+
return;
|
|
93
105
|
const wipe = matchWipeComment(comment);
|
|
94
106
|
if (wipe !== null) {
|
|
95
107
|
s.wipes.push({ srcByte, open: wipe === 'start' });
|
|
@@ -135,6 +147,7 @@ export function prusaSlicer() {
|
|
|
135
147
|
const applied = applyMarkerRanges(ir, s.markers, (a, b, v) => sink.setFeature(a, b, v));
|
|
136
148
|
if (applied > 0)
|
|
137
149
|
sink.upgradeCapability('featureRoles', 'known');
|
|
150
|
+
s.objectTracker.apply(ir, sink);
|
|
138
151
|
if (applyWipeRanges(ir, s.wipes, sink) > 0)
|
|
139
152
|
sink.upgradeCapability('wipeMoves', 'known');
|
|
140
153
|
if (s.bedPoints !== null) {
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dialect registry & run composition (DD-005 §4.1/§4.5, as amended).
|
|
3
|
-
*
|
|
4
|
-
* Explicit, tree-shakeable: `createDialectRunner(adapters)` — no global
|
|
5
|
-
* side-effect registry. The returned factory matches the parser's structural
|
|
6
|
-
* `DialectRunnerFactory` shape (the parser never imports this package;
|
|
7
|
-
* worker entries compose the two).
|
|
8
|
-
*
|
|
9
|
-
* Composition (amendment 1): detection picks at most ONE adapter per `kind`
|
|
10
|
-
* (highest confidence wins; ties select none for that kind — never a guess),
|
|
11
|
-
* and all selected adapters run together. Adapter exceptions are contained:
|
|
12
|
-
* a throwing adapter is disabled for the run with an `adapter-failed` warning.
|
|
13
|
-
*/
|
|
14
1
|
import type { DialectDetection, DialectMetadata, ToolpathIR, Warning } from '@chestnutlabs/toolpath-core';
|
|
15
2
|
import type { CommandEvent, DialectAdapter } from './contracts.js';
|
|
16
3
|
export interface DialectRunInput {
|
package/dist/registry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAC1G,OAAO,KAAK,EAAE,YAAY,EAAe,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAKhF,MAAM,WAAW,eAAe;IAC9B,oFAAoF;IACpF,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,SAAS,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,EAAE,EAAE,UAAU,GAAG,gBAAgB,CAAC;CAC5C;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,KAAK,EAAE,eAAe,GAAG,UAAU,GAAG,IAAI,CAAC;CACtD;AAUD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,oBAAoB,CA4FpF"}
|
package/dist/registry.js
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialect registry & run composition (DD-005 §4.1/§4.5, as amended).
|
|
3
|
+
*
|
|
4
|
+
* Explicit, tree-shakeable: `createDialectRunner(adapters)` — no global
|
|
5
|
+
* side-effect registry. The returned factory matches the parser's structural
|
|
6
|
+
* `DialectRunnerFactory` shape (the parser never imports this package;
|
|
7
|
+
* worker entries compose the two).
|
|
8
|
+
*
|
|
9
|
+
* Composition (amendment 1): detection picks at most ONE adapter per `kind`
|
|
10
|
+
* (highest confidence wins; ties select none for that kind — never a guess),
|
|
11
|
+
* and all selected adapters run together. Adapter exceptions are contained:
|
|
12
|
+
* a throwing adapter is disabled for the run with an `adapter-failed` warning.
|
|
13
|
+
*/
|
|
14
|
+
import { classifyModelBounds } from '@chestnutlabs/toolpath-core';
|
|
1
15
|
import { BufferedAnnotationSink } from './sink.js';
|
|
2
16
|
const CONFIDENCE_RANK = { known: 3, inferred: 2, approximated: 1, unavailable: 0 };
|
|
3
17
|
export function createDialectRunner(adapters) {
|
|
@@ -76,6 +90,12 @@ export function createDialectRunner(adapters) {
|
|
|
76
90
|
contain(a, () => a.adapter.finalize?.(ir, a.sink, a.config));
|
|
77
91
|
a.sink.apply(ir, metadata, (code, message) => conflicts.push({ code, message, severity: 'warn', count: 1 }));
|
|
78
92
|
}
|
|
93
|
+
// Now that every adapter has settled the object/feature channels, refresh the non-model
|
|
94
|
+
// classification authoritatively (DD-026 D4/D5/D7, lifecycle §5). Parse time saw empty
|
|
95
|
+
// channels; this is where membership/roles actually exist.
|
|
96
|
+
const { modelBounds, classification } = classifyModelBounds(ir.segments, ir.header.originOffset);
|
|
97
|
+
ir.modelBounds = modelBounds;
|
|
98
|
+
ir.header.capabilities.nonModelClassification = classification;
|
|
79
99
|
for (const w of conflicts)
|
|
80
100
|
ir.header.warnings.push(w);
|
|
81
101
|
ir.header.dialects.push(...active.map((a) => ({ id: a.detection.dialectId, confidence: a.detection.confidence })));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simplify3d.d.ts","sourceRoot":"","sources":["../src/simplify3d.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAsCrE,wBAAgB,UAAU,IAAI,cAAc,CA6B3C"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simplify3D dialect adapter (DD-026 T1, RR-007 §5.5).
|
|
3
|
+
*
|
|
4
|
+
* Annotates `; feature <lowercase token>` roles — Simplify3D's lowercase feature vocabulary. There is
|
|
5
|
+
* NO object-membership channel in Simplify3D output (RR-007 §5.5): the prime line is arbitrary
|
|
6
|
+
* Starting-Script geometry and the prime pillar / ooze shield are feature roles, not object brackets.
|
|
7
|
+
* So `objects` stays honestly `unavailable` and framing falls back to feature-role classification
|
|
8
|
+
* (DD-026 T2), never a fabricated membership. Simplify3D emits Marlin-flavoured motion; the marlin
|
|
9
|
+
* firmware adapter composes for firmware identity.
|
|
10
|
+
*/
|
|
11
|
+
import { FeatureRole } from '@chestnutlabs/toolpath-core';
|
|
12
|
+
import { applyMarkerRanges } from './annotate.js';
|
|
13
|
+
/** Simplify3D `; feature <token>` names → DD-001 FeatureRole (unknown names → Custom, honestly generic). */
|
|
14
|
+
const FEATURE_MAP = {
|
|
15
|
+
skirt: FeatureRole.Skirt,
|
|
16
|
+
brim: FeatureRole.Brim,
|
|
17
|
+
raft: FeatureRole.Raft,
|
|
18
|
+
'outer perimeter': FeatureRole.ExternalPerimeter,
|
|
19
|
+
'inner perimeter': FeatureRole.Perimeter,
|
|
20
|
+
perimeter: FeatureRole.Perimeter,
|
|
21
|
+
infill: FeatureRole.Infill,
|
|
22
|
+
'solid layer': FeatureRole.SolidInfill,
|
|
23
|
+
'gap fill': FeatureRole.SolidInfill,
|
|
24
|
+
support: FeatureRole.Support,
|
|
25
|
+
'dense support': FeatureRole.Support,
|
|
26
|
+
bridge: FeatureRole.Bridge,
|
|
27
|
+
'prime pillar': FeatureRole.PrimeTower,
|
|
28
|
+
// Ooze shield is an unmapped housekeeping wall — kept as generic `Custom` (stays in-frame, the safe
|
|
29
|
+
// direction per DD-026 §13) rather than guessed into a tower/purge role.
|
|
30
|
+
'ooze shield': FeatureRole.Custom
|
|
31
|
+
};
|
|
32
|
+
const state = new WeakMap();
|
|
33
|
+
function stateFor(sink) {
|
|
34
|
+
let s = state.get(sink);
|
|
35
|
+
if (s === undefined) {
|
|
36
|
+
s = { markers: [] };
|
|
37
|
+
state.set(sink, s);
|
|
38
|
+
}
|
|
39
|
+
return s;
|
|
40
|
+
}
|
|
41
|
+
export function simplify3d() {
|
|
42
|
+
return {
|
|
43
|
+
id: 'simplify3d',
|
|
44
|
+
displayName: 'Simplify3D',
|
|
45
|
+
kind: 'slicer',
|
|
46
|
+
detect(input) {
|
|
47
|
+
const m = /generated by (Simplify3D[^\n\r]*)/i.exec(input.headText);
|
|
48
|
+
if (m !== null) {
|
|
49
|
+
return { dialectId: 'simplify3d', kind: 'slicer', confidence: 'known', evidence: `header: ${m[1].trim()}` };
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
},
|
|
53
|
+
onComment(comment, srcByte, sink) {
|
|
54
|
+
const s = stateFor(sink);
|
|
55
|
+
// Space-prefixed lowercase feature comment: `; feature outer perimeter`. The leading space is
|
|
56
|
+
// already stripped by the parser before onComment, so `comment` begins `feature `.
|
|
57
|
+
const trimmed = comment.trim();
|
|
58
|
+
if (/^feature\b/i.test(trimmed)) {
|
|
59
|
+
const token = trimmed.slice('feature'.length).trim().toLowerCase();
|
|
60
|
+
const role = FEATURE_MAP[token];
|
|
61
|
+
s.markers.push({ srcByte, value: role ?? FeatureRole.Custom });
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
finalize(ir, sink) {
|
|
65
|
+
const s = stateFor(sink);
|
|
66
|
+
const applied = applyMarkerRanges(ir, s.markers, (a, b, v) => sink.setFeature(a, b, v));
|
|
67
|
+
if (applied > 0)
|
|
68
|
+
sink.upgradeCapability('featureRoles', 'known');
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chestnutlabs/gcode-dialects",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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.
|
|
48
|
+
"@chestnutlabs/toolpath-core": "0.17.0"
|
|
49
49
|
}
|
|
50
50
|
}
|