three-slicer-viewer 0.3.1 → 0.3.3
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/VIEWER.md +26 -1
- package/dist/components.js +47 -43
- package/dist/gcode_parse.js +118 -72
- package/dist/index.js +4916 -4079
- package/dist/model_loaders-Dk9rqXFb.js +196 -0
- package/dist/model_loaders.js +9 -6
- package/dist/parse_3mf-CpbA-6Iq.js +666 -0
- package/dist/parse_3mf.worker.js +67 -5
- package/dist/toolpath.js +243 -249
- package/dist/toolpath_palette-B8xEl-qa.js +89 -0
- package/package.json +6 -1
- package/src/core/paint_tools.js +8 -0
- package/src/settings/kernel_setting_keys.js +28 -0
- package/src/settings/settings_core.js +55 -5
- package/types/gcode.d.ts +27 -1
- package/types/paint.d.ts +7 -0
- package/types/viewer.d.ts +4 -2
- package/dist/model_loaders-aHV5ix50.js +0 -188
- package/dist/parse_3mf-jEUAw8JM.js +0 -300
package/VIEWER.md
CHANGED
|
@@ -95,7 +95,8 @@ Every panel can be switched off, and the values the component owns can be seeded
|
|
|
95
95
|
resin masks on a filament bed. The one number it cannot supply is the display's PHYSICAL size: millimetres
|
|
96
96
|
appear nowhere in an SL1, so the preview is scaled by the printer set in the viewer and the import notice says
|
|
97
97
|
so. Export hands an imported archive back byte-identical rather than re-rasterizing empty geometry.
|
|
98
|
-
- **`gcode`** — G-code text drawn on the selected plate instead of a slice result
|
|
98
|
+
- **`gcode`** — G-code text drawn on the selected plate instead of a slice result, or a `{ plateIndex: text }` map
|
|
99
|
+
drawn plate by plate (what an opened `.gcode.3mf` becomes; the viewer is preview-only while one is shown). `parseGcode` recovers roles from
|
|
99
100
|
`;TYPE:` (OrcaSlicer/PrusaSlicer/Cura), from `;_EXTRUSION_ROLE:` tags and from this kernel's own feature comments;
|
|
100
101
|
bead width comes from `;WIDTH:` or, absent that, from E. What the file never states cannot be recovered: an
|
|
101
102
|
unmarked run reads as wall, and there is no print-time estimate (that needs the machine's acceleration limits).
|
|
@@ -255,6 +256,7 @@ opt-out only, everything on unless you say `false`, an unknown key stays on.
|
|
|
255
256
|
| `filePicker` | The file dialog never opens, from any of the buttons that open it. |
|
|
256
257
|
| `contextMenu` | No right-click menu. The browser's own menu stays suppressed over the canvas either way — OrbitControls preventDefaults `contextmenu` itself. |
|
|
257
258
|
| `logs` | No console output, from the component or from the slice worker. |
|
|
259
|
+
| `versionBadge` | The build number at the right end of the status bar is not drawn. It is there so a bug report can name a build; turn it off when the viewer is embedded as part of your own product. `panels.status: false` hides it too, since it rides that bar. |
|
|
258
260
|
|
|
259
261
|
### Read-only panels
|
|
260
262
|
|
|
@@ -394,6 +396,29 @@ for 143MB) against a 4GB pool budget, which gives 2 for that model and does not
|
|
|
394
396
|
manual count is capped the same way. What the browser has NOT measured yet is
|
|
395
397
|
the arachne + tree-support preset, where the harness gains were largest.
|
|
396
398
|
|
|
399
|
+
### The contour union on the GPU
|
|
400
|
+
|
|
401
|
+
The slice bar's **GPU** select (`gpu_acceleration` in the settings map: `auto`, `on`, `off`; absent is `auto`; a viewer
|
|
402
|
+
knob like `slice_workers`, not a kernel parameter) lets the slicer worker compute every layer's contour union with
|
|
403
|
+
WebGPU instead of Clipper. `auto` asks for it only when the single-threaded kernel loaded, `on` on either kernel,
|
|
404
|
+
`off` never. It applies to the selected plate's filament slice; pool workers, multi-material and resin slices compute
|
|
405
|
+
their contours themselves.
|
|
406
|
+
|
|
407
|
+
Measured in node (Dawn, Metal, 15 cores) with the kernel consuming the GPU's contours:
|
|
408
|
+
|
|
409
|
+
| model | kernel | CPU | GPU |
|
|
410
|
+
|---|---|---|---|
|
|
411
|
+
| 3M-facet scan, tree slim support, 420 layers | st | 9.6-9.8 s | 7.6-7.7 s |
|
|
412
|
+
| 1.13M-facet plate, tree hybrid support, 849 layers | st | 105 s | 102-104 s |
|
|
413
|
+
| either | mt | | within run-to-run spread of the CPU slice |
|
|
414
|
+
|
|
415
|
+
The union is about 2.5 s of the single-threaded slice and 50-100 ms on the GPU; on the threaded kernel the same union is
|
|
416
|
+
already spread over the threads, which is why `auto` leaves it alone. What it costs: the GPU's contours have the CPU
|
|
417
|
+
union's area (within 2e-10 relative) and other vertices, so the G-code is not the CPU slice's byte for byte (filament
|
|
418
|
+
within 0.3 % in those two models). The slice never depends on the GPU: without a device, or when the GPU refuses an
|
|
419
|
+
input, the kernel slices by itself, a layer whose loops the GPU leaves open gets the kernel's own union, and the stats
|
|
420
|
+
card says which happened (`Contours on the GPU`, with the count of layers that went back to the CPU).
|
|
421
|
+
|
|
397
422
|
## Theming
|
|
398
423
|
|
|
399
424
|
Both shadow roots (the viewer and `<SettingsPanel/>`) paint their chrome from one palette, `UI_TOKENS` in
|
package/dist/components.js
CHANGED
|
@@ -8,39 +8,39 @@ import { S as X } from "./shadow_host-CQ3LirzD.js";
|
|
|
8
8
|
const Y = ':host{display:contents}*{box-sizing:border-box}.row{display:grid;grid-template-columns:260px 220px 70px 1fr;gap:8px;align-items:center;padding:3px 0;border-bottom:1px dashed var(--_light-divider)}.row .lbl{color:var(--_light-text);text-decoration:none}.row .lbl:hover{color:var(--_accent-ink);text-decoration:underline}.row .key{font-size:11px;color:var(--_light-text-faint);overflow:hidden;text-overflow:ellipsis}.row input[type=text],.row input:not([type]),.row select,.row textarea{width:100%;max-width:260px;padding:2px 6px}.inputwrap{display:inline-flex;align-items:center;gap:4px}.unit{color:var(--_light-text-faint);font-size:12px;white-space:nowrap}.pts{font-size:11px;color:var(--_light-text-muted)}.badge{font-size:10px;padding:1px 6px;border-radius:8px;color:var(--_on-accent);text-align:center}.mode-simple{background:var(--_accent-ink)}.mode-advanced{background:var(--_info-ink)}.mode-expert{background:var(--_warn)}.mode-develop{background:var(--_danger)}.mode-none,.mode-undefined{background:var(--_light-text-faint)}.muted{color:var(--_light-text-faint);font-weight:400;font-size:12px}.row.custom{grid-template-columns:1fr}.row.disabled{opacity:.42}.row.disabled input,.row.disabled select,.row.disabled textarea{cursor:not-allowed;background:var(--_light-subtle)}.lbl-cell{display:inline-flex;align-items:center;gap:5px;min-width:0}.lbl-cell .lbl{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dirty-dot{flex:0 0 auto;width:7px;height:7px;border-radius:50%;background:var(--_warn);box-shadow:0 0 0 2px var(--_warn-soft)}.override-dot{flex:0 0 auto;width:9px;height:9px;border-radius:50%;background:var(--_warn);border:0;padding:0;cursor:pointer;box-shadow:0 0 0 2px var(--_warn-soft)}.override-dot:hover{background:var(--_warn-ink)}.widget-cell{display:inline-flex;align-items:center;gap:6px;min-width:0}.reset-btn{flex:0 0 auto;border:1px solid var(--_warn-border);background:var(--_warn-soft);color:var(--_warn-ink);border-radius:5px;cursor:pointer;font-size:13px;line-height:1;padding:1px 5px}.reset-btn:hover{background:var(--_warn-soft-hover)}.settings-panel{flex:0 0 400px;width:400px;border-left:1px solid var(--_light-divider);display:flex;flex-direction:column;min-height:0;background:var(--_light-surface);color:var(--_light-text);font:14px/1.5 -apple-system,Segoe UI,sans-serif}.settings-panel .sp-top{padding:10px 12px 8px;border-bottom:1px solid var(--_light-divider)}.sp-builder{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--_light-text-muted)}.sp-builder select{flex:1;padding:4px 6px}.sp-pages{display:flex;flex-wrap:wrap;gap:4px;margin-top:8px}.sp-pages button{border:1px solid var(--_light-border);background:var(--_light-subtle);border-radius:6px;padding:4px 8px;font-size:12px;cursor:pointer}.sp-pages button.on{background:var(--_accent-soft);color:var(--_accent-ink);border-color:var(--_accent-soft-border);font-weight:700}.sp-modes{display:flex;gap:4px;margin-top:8px}.sp-modes button{border:0;background:none;font-size:11px;color:var(--_light-text-faint);padding:2px 6px;border-radius:6px;cursor:pointer}.sp-modes button.on{background:var(--_accent-soft);color:var(--_accent-ink);font-weight:700}.settings-panel .sp-body{flex:1 1 auto;overflow-y:auto;padding:8px 14px 24px}.settings-panel .sp-body h2{font-size:16px}.settings-panel .row{grid-template-columns:1fr auto;grid-template-areas:"lbl badge" "key widget";row-gap:2px;padding:6px 0}.settings-panel .row .lbl{grid-area:lbl;font-weight:600}.settings-panel .row .badge{grid-area:badge;justify-self:end}.settings-panel .row .key{grid-area:key}.settings-panel .row .inputwrap,.settings-panel .row>select,.settings-panel .row>input,.settings-panel .row>textarea,.settings-panel .row>.pts,.settings-panel .row>.muted{grid-area:widget;justify-self:end}.settings-panel .row input:not([type=checkbox]):not([type=color]),.settings-panel .row select,.settings-panel .row textarea{max-width:200px}.settings-panel .row.custom{grid-template-columns:1fr;grid-template-areas:"lbl"}.sp-search{width:100%;box-sizing:border-box;padding:6px 9px;border:1px solid var(--_light-border);border-radius:6px;font-size:13px;margin-bottom:8px}:host(.sp-embedded) .settings-panel{position:static;width:auto;flex:none;border:0;height:auto}:host(.sp-embedded) .sp-body{padding:4px 2px 8px;max-height:460px}.bse{display:flex;flex-direction:column;gap:6px;min-width:0;max-width:220px}.bse-modes{display:flex;gap:4px}.bse-modes button{border:1px solid var(--_light-border);background:var(--_light-subtle);border-radius:6px;padding:2px 8px;font-size:11px;cursor:pointer}.bse-modes button.on{background:var(--_accent-soft);color:var(--_accent-ink);border-color:var(--_accent-soft-border);font-weight:700}.bse-grid{display:grid;grid-template-columns:1fr 1fr;gap:4px 8px}.bse-grid label{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--_light-text-muted);white-space:nowrap}.bse-grid input{width:56px;padding:2px 4px;font-size:12px}.bse-json{width:100%;font:11px ui-monospace,monospace;padding:4px 6px;border:1px solid var(--_light-border);border-radius:6px;box-sizing:border-box}.bse-json.err{border-color:var(--_danger);background:var(--_danger-soft)}', q = (t, a, r, o) => [[r, o], [t + r, o], [t + r, a + o], [r, a + o]], L = R.printable_area.default, V = (t) => {
|
|
9
9
|
const a = t / 2, r = 32;
|
|
10
10
|
return Array.from({ length: r }, (o, i) => {
|
|
11
|
-
const
|
|
12
|
-
return [+(a + a * Math.cos(
|
|
11
|
+
const m = 2 * Math.PI * i / r;
|
|
12
|
+
return [+(a + a * Math.cos(m)).toFixed(2), +(a + a * Math.sin(m)).toFixed(2)];
|
|
13
13
|
});
|
|
14
14
|
}, T = (t) => {
|
|
15
15
|
const a = t.map((o) => o[0]), r = t.map((o) => o[1]);
|
|
16
16
|
return { x0: Math.min(...a), y0: Math.min(...r), w: Math.max(...a) - Math.min(...a), d: Math.max(...r) - Math.min(...r) };
|
|
17
17
|
};
|
|
18
18
|
function Z({ value: t, onChange: a, disabled: r }) {
|
|
19
|
-
const o = Array.isArray(t) && t.length >= 3 ? t : L, i = T(o), [
|
|
19
|
+
const o = Array.isArray(t) && t.length >= 3 ? t : L, i = T(o), [m, d] = y(o.length === 4 ? "rect" : o.length >= 12 ? "circle" : "custom"), [c, p] = y({ w: i.w, d: i.d, ox: i.x0, oy: i.y0 }), [x, b] = y(Math.round(i.w)), [l, u] = y(JSON.stringify(o)), [_, w] = y(!1);
|
|
20
20
|
P(() => {
|
|
21
21
|
const n = T(o);
|
|
22
|
-
p({ w: n.w, d: n.d, ox: n.x0, oy: n.y0 }),
|
|
22
|
+
p({ w: n.w, d: n.d, ox: n.x0, oy: n.y0 }), b(Math.round(n.w)), u(JSON.stringify(o)), w(!1);
|
|
23
23
|
}, [JSON.stringify(t)]);
|
|
24
24
|
const v = (n) => {
|
|
25
25
|
p(n);
|
|
26
|
-
const { w:
|
|
27
|
-
|
|
28
|
-
},
|
|
29
|
-
|
|
26
|
+
const { w: g, d: f, ox: S, oy: M } = n;
|
|
27
|
+
g > 0 && f > 0 && a(q(g, f, S, M));
|
|
28
|
+
}, N = (n) => {
|
|
29
|
+
b(n), n > 0 && a(V(n));
|
|
30
30
|
}, z = (n) => {
|
|
31
|
-
|
|
31
|
+
u(n);
|
|
32
32
|
try {
|
|
33
|
-
const
|
|
34
|
-
if (!Array.isArray(
|
|
35
|
-
w(!1), a(
|
|
33
|
+
const g = JSON.parse(n);
|
|
34
|
+
if (!Array.isArray(g) || g.length < 3 || !g.every((f) => Array.isArray(f) && f.length === 2 && f.every(Number.isFinite))) throw 0;
|
|
35
|
+
w(!1), a(g);
|
|
36
36
|
} catch {
|
|
37
37
|
w(!0);
|
|
38
38
|
}
|
|
39
39
|
}, E = (n) => {
|
|
40
|
-
const
|
|
41
|
-
return Number.isFinite(
|
|
40
|
+
const g = parseFloat(n);
|
|
41
|
+
return Number.isFinite(g) ? g : 0;
|
|
42
42
|
};
|
|
43
|
-
return /* @__PURE__ */ e.createElement("div", { className: "bse", "data-testid": "bed-shape-editor" }, /* @__PURE__ */ e.createElement("div", { className: "bse-modes" }, [["rect", "Rectangular"], ["circle", "Circular"], ["custom", "Custom"]].map(([n,
|
|
43
|
+
return /* @__PURE__ */ e.createElement("div", { className: "bse", "data-testid": "bed-shape-editor" }, /* @__PURE__ */ e.createElement("div", { className: "bse-modes" }, [["rect", "Rectangular"], ["circle", "Circular"], ["custom", "Custom"]].map(([n, g]) => /* @__PURE__ */ e.createElement("button", { key: n, type: "button", className: m === n ? "on" : "", disabled: r, onClick: () => d(n) }, g))), m === "rect" && /* @__PURE__ */ e.createElement("div", { className: "bse-grid" }, /* @__PURE__ */ e.createElement("label", null, "Width ", /* @__PURE__ */ e.createElement(
|
|
44
44
|
"input",
|
|
45
45
|
{
|
|
46
46
|
type: "number",
|
|
@@ -76,7 +76,7 @@ function Z({ value: t, onChange: a, disabled: r }) {
|
|
|
76
76
|
disabled: r,
|
|
77
77
|
onChange: (n) => v({ ...c, oy: E(n.target.value) })
|
|
78
78
|
}
|
|
79
|
-
))),
|
|
79
|
+
))), m === "circle" && /* @__PURE__ */ e.createElement("div", { className: "bse-grid" }, /* @__PURE__ */ e.createElement("label", null, "Diameter ", /* @__PURE__ */ e.createElement(
|
|
80
80
|
"input",
|
|
81
81
|
{
|
|
82
82
|
type: "number",
|
|
@@ -84,12 +84,12 @@ function Z({ value: t, onChange: a, disabled: r }) {
|
|
|
84
84
|
value: x,
|
|
85
85
|
disabled: r,
|
|
86
86
|
"data-testid": "bed-dia",
|
|
87
|
-
onChange: (n) =>
|
|
87
|
+
onChange: (n) => N(E(n.target.value))
|
|
88
88
|
}
|
|
89
|
-
), " mm")),
|
|
89
|
+
), " mm")), m === "custom" && /* @__PURE__ */ e.createElement(
|
|
90
90
|
"textarea",
|
|
91
91
|
{
|
|
92
|
-
className: "bse-json" + (
|
|
92
|
+
className: "bse-json" + (_ ? " err" : ""),
|
|
93
93
|
rows: 3,
|
|
94
94
|
value: l,
|
|
95
95
|
disabled: r,
|
|
@@ -118,35 +118,39 @@ function le(t) {
|
|
|
118
118
|
function ie({ mode: t }) {
|
|
119
119
|
return /* @__PURE__ */ e.createElement("span", { className: `badge mode-${t ?? "none"}` }, t ?? "—");
|
|
120
120
|
}
|
|
121
|
-
function ce({ def: t, optKey: a, settings: r, setSettings: o, disabled: i, customWidgets:
|
|
122
|
-
const d = I(r, a), c = (l) => o((
|
|
121
|
+
function ce({ def: t, optKey: a, settings: r, setSettings: o, disabled: i, customWidgets: m }) {
|
|
122
|
+
const d = I(r, a), c = (l) => o((u) => ({ ...u, [a]: l })), p = m?.[a] ?? K[a];
|
|
123
123
|
if (p) return /* @__PURE__ */ e.createElement(p, { def: t, value: d, onChange: c, disabled: i });
|
|
124
|
-
const x = le(t),
|
|
124
|
+
const x = le(t), b = Array.isArray(d) ? d[0] : d;
|
|
125
125
|
switch (x) {
|
|
126
126
|
case "select":
|
|
127
|
-
return /* @__PURE__ */ e.createElement("select", { value:
|
|
127
|
+
return /* @__PURE__ */ e.createElement("select", { value: b ?? "", onChange: (l) => c(l.target.value), disabled: i }, t.enum_values.map((l, u) => /* @__PURE__ */ e.createElement("option", { key: l, value: l }, t.enum_labels?.[u] ?? l)));
|
|
128
128
|
case "checkbox":
|
|
129
129
|
return /* @__PURE__ */ e.createElement("input", { type: "checkbox", checked: Array.isArray(d) ? !!d[0] : !!d, onChange: (l) => c(l.target.checked), disabled: i });
|
|
130
130
|
case "color":
|
|
131
|
-
return /* @__PURE__ */ e.createElement("input", { type: "color", value: typeof
|
|
132
|
-
case "textarea":
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
return /* @__PURE__ */ e.createElement("input", { type: "color", value: typeof b == "string" && b.startsWith("#") ? b : H, onChange: (l) => c(l.target.value), disabled: i });
|
|
132
|
+
case "textarea": {
|
|
133
|
+
let l = 2;
|
|
134
|
+
t.full_width && (l = 12);
|
|
135
|
+
let u = d ?? "";
|
|
136
|
+
return Array.isArray(d) && (u = d.join(`
|
|
137
|
+
`)), /* @__PURE__ */ e.createElement("textarea", { rows: l, value: u, onChange: (_) => c(_.target.value), disabled: i });
|
|
138
|
+
}
|
|
135
139
|
case "points":
|
|
136
140
|
return /* @__PURE__ */ e.createElement("code", { className: "pts" }, JSON.stringify(d ?? []));
|
|
137
141
|
case "unknown":
|
|
138
142
|
return /* @__PURE__ */ e.createElement("span", { className: "muted" }, "No schema");
|
|
139
143
|
default:
|
|
140
|
-
return /* @__PURE__ */ e.createElement("span", { className: "inputwrap" }, /* @__PURE__ */ e.createElement("input", { value:
|
|
144
|
+
return /* @__PURE__ */ e.createElement("span", { className: "inputwrap" }, /* @__PURE__ */ e.createElement("input", { value: b ?? "", onChange: (l) => c(l.target.value), disabled: i }), t.sidetext && /* @__PURE__ */ e.createElement("span", { className: "unit" }, t.sidetext));
|
|
141
145
|
}
|
|
142
146
|
}
|
|
143
|
-
function de({ schema: t, optKey: a, settings: r, setSettings: o, disabled: i, onOptionOpen:
|
|
147
|
+
function de({ schema: t, optKey: a, settings: r, setSettings: o, disabled: i, onOptionOpen: m, customWidgets: d, overridden: c, onRevertKey: p }) {
|
|
144
148
|
if (a.startsWith("<")) return /* @__PURE__ */ e.createElement("div", { className: "row custom" }, /* @__PURE__ */ e.createElement("span", { className: "muted" }, "⚙ Custom widget ", a));
|
|
145
|
-
const x = t[a],
|
|
146
|
-
const
|
|
147
|
-
return delete
|
|
149
|
+
const x = t[a], b = i ? i[a] : void 0, l = b != null, u = oe(t, r, a), _ = () => o((v) => {
|
|
150
|
+
const N = { ...v };
|
|
151
|
+
return delete N[a], N;
|
|
148
152
|
}), w = x?.label || x?.full_label || a;
|
|
149
|
-
return /* @__PURE__ */ e.createElement("div", { className: "row" + (l ? " disabled" : ""), title: l ? `Disabled when: ${
|
|
153
|
+
return /* @__PURE__ */ e.createElement("div", { className: "row" + (l ? " disabled" : ""), title: l ? `Disabled when: ${b}` : x?.tooltip ?? "", "data-testid": `row-${a}` }, /* @__PURE__ */ e.createElement("span", { className: "lbl-cell" }, c && /* @__PURE__ */ e.createElement(
|
|
150
154
|
"button",
|
|
151
155
|
{
|
|
152
156
|
type: "button",
|
|
@@ -155,7 +159,7 @@ function de({ schema: t, optKey: a, settings: r, setSettings: o, disabled: i, on
|
|
|
155
159
|
title: "This plate's override — click to follow the global value again",
|
|
156
160
|
"data-testid": `override-${a}`
|
|
157
161
|
}
|
|
158
|
-
),
|
|
162
|
+
), u && /* @__PURE__ */ e.createElement("span", { className: "dirty-dot", title: "Changed from default", "data-testid": `dirty-${a}` }), m ? /* @__PURE__ */ e.createElement("button", { type: "button", className: "lbl lbl-link", onClick: () => m(a), title: "Details" }, w) : /* @__PURE__ */ e.createElement("span", { className: "lbl" }, w)), /* @__PURE__ */ e.createElement("code", { className: "key" }, a), /* @__PURE__ */ e.createElement(ie, { mode: x?.mode }), /* @__PURE__ */ e.createElement("span", { className: "widget-cell" }, /* @__PURE__ */ e.createElement(ce, { def: x, optKey: a, settings: r, setSettings: o, disabled: l, customWidgets: d }), u && /* @__PURE__ */ e.createElement("button", { className: "reset-btn", onClick: _, title: "Reset to default", "data-testid": `reset-${a}` }, "↺")));
|
|
159
163
|
}
|
|
160
164
|
function fe({
|
|
161
165
|
schema: t = R,
|
|
@@ -163,14 +167,14 @@ function fe({
|
|
|
163
167
|
toggle: r = re,
|
|
164
168
|
settings: o,
|
|
165
169
|
setSettings: i,
|
|
166
|
-
onOptionOpen:
|
|
170
|
+
onOptionOpen: m,
|
|
167
171
|
embedded: d = !1,
|
|
168
172
|
customWidgets: c,
|
|
169
173
|
only: p,
|
|
170
174
|
overriddenKeys: x,
|
|
171
|
-
onRevertKey:
|
|
175
|
+
onRevertKey: b
|
|
172
176
|
}) {
|
|
173
|
-
const l = j(() => ne(a), [a]), [
|
|
177
|
+
const l = j(() => ne(a), [a]), [u, _] = y(p?.builder ?? l[0] ?? ""), [w, v] = y(0), [N, z] = y("all"), [E, n] = y(""), g = W(o), f = l.filter((s) => te(s) === g), S = p?.builder ?? (f.includes(u) ? u : f[0] ?? ""), M = a[p?.builder ?? S] ?? [], A = p?.page ? M.filter((s) => s.page === p.page) : M, O = A[Math.min(w, A.length - 1)], J = j(() => r.disabledKeys(r.makeCfg(o)), [o, r]), k = E.trim().toLowerCase(), C = j(() => k ? Object.entries(t).filter(([s, h]) => s.includes(k) || h.label?.toLowerCase().includes(k) || h.full_label?.toLowerCase().includes(k)).slice(0, 60).map(([s]) => s) : [], [k]), D = (s, h) => /* @__PURE__ */ e.createElement(
|
|
174
178
|
de,
|
|
175
179
|
{
|
|
176
180
|
schema: t,
|
|
@@ -179,16 +183,16 @@ function fe({
|
|
|
179
183
|
settings: o,
|
|
180
184
|
setSettings: i,
|
|
181
185
|
disabled: J,
|
|
182
|
-
onOptionOpen:
|
|
186
|
+
onOptionOpen: m,
|
|
183
187
|
customWidgets: c,
|
|
184
188
|
overridden: x?.includes(s),
|
|
185
|
-
onRevertKey:
|
|
189
|
+
onRevertKey: b
|
|
186
190
|
}
|
|
187
191
|
);
|
|
188
|
-
return /* @__PURE__ */ e.createElement(X, { css: Q + Y, className: d ? "sp-embedded" : void 0 }, /* @__PURE__ */ e.createElement("div", { className: "settings-panel" }, !p && /* @__PURE__ */ e.createElement("div", { className: "sp-top" }, /* @__PURE__ */ e.createElement("input", { className: "sp-search", placeholder: `Search options (${Object.keys(t).length})…`, value: E, onChange: (s) => n(s.target.value), "data-testid": "opt-search" }), !
|
|
189
|
-
|
|
190
|
-
} }, f.map((s) => /* @__PURE__ */ e.createElement("option", { key: s, value: s }, B(s))))), /* @__PURE__ */ e.createElement("div", { className: "sp-pages" }, A.map((s, h) => /* @__PURE__ */ e.createElement("button", { key: s.page + h, className: h === Math.min(w, A.length - 1) ? "on" : "", onClick: () => v(h) }, s.page))), /* @__PURE__ */ e.createElement("div", { className: "sp-modes" }, ee.map((s) => /* @__PURE__ */ e.createElement("button", { key: s, className:
|
|
191
|
-
const h = s.options.filter((F) => se(t, F,
|
|
192
|
+
return /* @__PURE__ */ e.createElement(X, { css: Q + Y, className: d ? "sp-embedded" : void 0 }, /* @__PURE__ */ e.createElement("div", { className: "settings-panel" }, !p && /* @__PURE__ */ e.createElement("div", { className: "sp-top" }, /* @__PURE__ */ e.createElement("input", { className: "sp-search", placeholder: `Search options (${Object.keys(t).length})…`, value: E, onChange: (s) => n(s.target.value), "data-testid": "opt-search" }), !k && /* @__PURE__ */ e.createElement(e.Fragment, null, /* @__PURE__ */ e.createElement("label", { className: "sp-builder" }, /* @__PURE__ */ e.createElement("span", null, "Settings group"), /* @__PURE__ */ e.createElement("select", { value: S, onChange: (s) => {
|
|
193
|
+
_(s.target.value), v(0);
|
|
194
|
+
} }, f.map((s) => /* @__PURE__ */ e.createElement("option", { key: s, value: s }, B(s))))), /* @__PURE__ */ e.createElement("div", { className: "sp-pages" }, A.map((s, h) => /* @__PURE__ */ e.createElement("button", { key: s.page + h, className: h === Math.min(w, A.length - 1) ? "on" : "", onClick: () => v(h) }, s.page))), /* @__PURE__ */ e.createElement("div", { className: "sp-modes" }, ee.map((s) => /* @__PURE__ */ e.createElement("button", { key: s, className: N === s ? "on" : "", onClick: () => z(s) }, s))))), /* @__PURE__ */ e.createElement("div", { className: "sp-body" }, k ? /* @__PURE__ */ e.createElement("section", { "data-testid": "search-results" }, /* @__PURE__ */ e.createElement("h3", null, "Search: “", E, "” — ", C.length, " hit", C.length === 1 ? "" : "s", C.length === 60 ? "+" : ""), C.map(D), !C.length && /* @__PURE__ */ e.createElement("p", { className: "muted" }, "No matches")) : O ? /* @__PURE__ */ e.createElement(e.Fragment, null, !p && /* @__PURE__ */ e.createElement("h2", null, O.page, " ", /* @__PURE__ */ e.createElement("span", { className: "muted" }, "Tab.cpp:", O.line)), O.groups.map((s) => {
|
|
195
|
+
const h = s.options.filter((F) => se(t, F, N) && !(p && F.startsWith("<")));
|
|
192
196
|
return h.length ? /* @__PURE__ */ e.createElement("section", { key: s.group + s.line }, /* @__PURE__ */ e.createElement("h3", null, s.group || "(untitled group)", " ", /* @__PURE__ */ e.createElement("span", { className: "muted" }, ":", s.line)), h.map(D)) : null;
|
|
193
197
|
})) : /* @__PURE__ */ e.createElement("p", { className: "muted" }, "No builder"))));
|
|
194
198
|
}
|
package/dist/gcode_parse.js
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
|
-
import { D as
|
|
2
|
-
import { R as t, e as
|
|
3
|
-
|
|
1
|
+
import { D as Nt, a as Rt } from "./viewer_defaults-B7UUZ582.js";
|
|
2
|
+
import { R as t, e as Z } from "./toolpath_encoding-BuYs95qZ.js";
|
|
3
|
+
import { T as ft } from "./toolpath_palette-B8xEl-qa.js";
|
|
4
|
+
const K = 1e-6, It = 254, At = /^(filament|extruder)_colour\s*=\s*(.*)$/i, ut = (n) => n.split(/[;,]/).map((r) => {
|
|
5
|
+
const o = /^"?#?([0-9a-f]{6})(?:[0-9a-f]{2})?"?$/i.exec(r.trim());
|
|
6
|
+
return o ? "#" + o[1].toUpperCase() : null;
|
|
7
|
+
}), dt = (n) => "#" + ft[n % ft.length].map((r) => Math.round(r * 255).toString(16).padStart(2, "0")).join("");
|
|
8
|
+
function gt(n, r) {
|
|
9
|
+
let o = [];
|
|
10
|
+
Array.isArray(n?.colors) && (o = n.colors);
|
|
11
|
+
const d = r ?? [], u = Math.max(o.length, d.length, n?.filament_mm_by_tool?.length ?? 0);
|
|
12
|
+
return Array.from({ length: u }, (h, c) => o[c] || d[c] || dt(c));
|
|
13
|
+
}
|
|
14
|
+
function wt(n, r) {
|
|
15
|
+
return St(n.gcode, gt(n.stats, r));
|
|
16
|
+
}
|
|
17
|
+
function St(n, r) {
|
|
18
|
+
const o = String(n ?? ""), d = [r].flat().filter(Boolean);
|
|
19
|
+
if (!d.length || /^;\s*filament_colour\s*=/mi.test(o)) return o;
|
|
20
|
+
const u = "; filament_colour = " + d.join(";") + `
|
|
21
|
+
`, h = o.lastIndexOf(`
|
|
22
|
+
; CONFIG_BLOCK_END`);
|
|
23
|
+
if (h >= 0) return o.slice(0, h + 1) + u + o.slice(h + 1);
|
|
24
|
+
let c = `
|
|
25
|
+
`;
|
|
26
|
+
return (!o || o.endsWith(`
|
|
27
|
+
`)) && (c = ""), o + c + u;
|
|
28
|
+
}
|
|
29
|
+
const ht = {
|
|
4
30
|
// OrcaSlicer / PrusaSlicer ;TYPE: names
|
|
5
31
|
"outer wall": t.WALL,
|
|
6
32
|
"external perimeter": t.WALL,
|
|
@@ -43,7 +69,7 @@ const F = 1e-6, lt = {
|
|
|
43
69
|
skin: t.SOLID,
|
|
44
70
|
"support-interface": t.SUPPORT,
|
|
45
71
|
"prime-tower": t.PRIME
|
|
46
|
-
},
|
|
72
|
+
}, Pt = [
|
|
47
73
|
["skirt/brim", t.SKIRT],
|
|
48
74
|
["skirt", t.SKIRT],
|
|
49
75
|
["walls", t.WALL],
|
|
@@ -54,7 +80,7 @@ const F = 1e-6, lt = {
|
|
|
54
80
|
["support", t.SUPPORT],
|
|
55
81
|
["prime tower", t.PRIME],
|
|
56
82
|
["wipe_tower_real", t.PRIME]
|
|
57
|
-
],
|
|
83
|
+
], Ot = (n, r) => n === r || r.length < n.length && " (:/".includes(n[r.length]) && n.startsWith(r), Et = {
|
|
58
84
|
1: t.WALL,
|
|
59
85
|
2: t.WALL,
|
|
60
86
|
3: t.WALL,
|
|
@@ -73,120 +99,140 @@ const F = 1e-6, lt = {
|
|
|
73
99
|
16: t.SUPPORT,
|
|
74
100
|
17: t.PRIME
|
|
75
101
|
};
|
|
76
|
-
function
|
|
77
|
-
const
|
|
78
|
-
let
|
|
79
|
-
const
|
|
80
|
-
let
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
function Wt(n, r = {}) {
|
|
103
|
+
const o = Math.PI / 4 * (r.filamentDiameter > 0 ? r.filamentDiameter : Nt) ** 2, d = r.defaultLayerHeight > 0 ? r.defaultLayerHeight : Rt;
|
|
104
|
+
let u = 0, h = 0, c = 0, b = 0, w = !0, y = !0, g = 0, O = t.WALL, W = 0, R = null;
|
|
105
|
+
const E = [];
|
|
106
|
+
let D = !1, x = !0, v = NaN, q = 0, X = 0, $ = 0;
|
|
107
|
+
const z = /* @__PURE__ */ new Set([0]), H = [];
|
|
108
|
+
let k = [], j = [], U = !1;
|
|
109
|
+
const V = () => {
|
|
110
|
+
O = t.WALL, W = 0;
|
|
111
|
+
}, C = (s, a = !1) => {
|
|
112
|
+
R = { z: s, p: [], w: [] }, E.push(R), x = !1, v = NaN, (a || !U) && V();
|
|
113
|
+
}, mt = () => E.length > 1 ? E[E.length - 2].z : 0, J = (s, a, S, P, A, i, e) => {
|
|
114
|
+
const m = Math.hypot(P - s, A - a, i - S);
|
|
115
|
+
if (!(m < K))
|
|
116
|
+
if (e > K) {
|
|
117
|
+
$ += e, H[g] = (H[g] ?? 0) + e, x ? C(Number.isNaN(v) ? i : v) : !D && i > R.z + 1e-3 && C(i);
|
|
118
|
+
const p = Z(O, g);
|
|
119
|
+
R.p.push(s, a, S, p, P, A, i, p);
|
|
120
|
+
const f = Math.max(0.02, R.z - mt() || d), L = W > 0 ? W : e * o / (m * f) + f * (1 - Math.PI / 4);
|
|
121
|
+
R.w.push(Math.min(3, Math.max(0.05, L))), q++;
|
|
122
|
+
} else R && (R.p.push(s, a, S, Z(t.TRAVEL, g), P, A, i, Z(t.TRAVEL, g)), R.w.push(0), X++);
|
|
93
123
|
};
|
|
94
|
-
for (const
|
|
124
|
+
for (const s of n.split(`
|
|
95
125
|
`)) {
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
const e =
|
|
126
|
+
const a = s.indexOf(";"), S = (a < 0 ? s : s.slice(0, a)).trim(), P = a < 0 ? "" : s.slice(a + 1).trim();
|
|
127
|
+
if (P && !S) {
|
|
128
|
+
const e = P.toLowerCase(), m = At.exec(P);
|
|
129
|
+
if (m) {
|
|
130
|
+
m[1].toLowerCase() === "filament" ? k = ut(m[2]) : j = ut(m[2]);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
99
133
|
if (e.startsWith("type:")) {
|
|
100
|
-
|
|
134
|
+
O = ht[e.slice(5).trim()] ?? t.WALL, U = !0;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (e.startsWith("feature:")) {
|
|
138
|
+
O = ht[e.slice(8).trim()] ?? t.WALL, U = !0;
|
|
101
139
|
continue;
|
|
102
140
|
}
|
|
103
141
|
if (e.startsWith("_extrusion_role:")) {
|
|
104
|
-
|
|
142
|
+
O = Et[parseInt(e.slice(16), 10)] ?? t.WALL, U = !0;
|
|
105
143
|
continue;
|
|
106
144
|
}
|
|
107
|
-
if (e.startsWith("width:")) {
|
|
108
|
-
const
|
|
109
|
-
W =
|
|
145
|
+
if (e.startsWith("width:") || e.startsWith("line_width:")) {
|
|
146
|
+
const L = parseFloat(e.slice(e.indexOf(":") + 1));
|
|
147
|
+
W = 0, L > 0 && (W = L);
|
|
110
148
|
continue;
|
|
111
149
|
}
|
|
112
|
-
if (e.startsWith("layer_change") || e.startsWith("layer:")) {
|
|
113
|
-
|
|
150
|
+
if (e.startsWith("layer_change") || e.startsWith("change_layer") || e.startsWith("layer:")) {
|
|
151
|
+
D = !0, x = !0, V();
|
|
114
152
|
continue;
|
|
115
153
|
}
|
|
116
|
-
if (e.startsWith("z:")) {
|
|
117
|
-
const
|
|
118
|
-
|
|
154
|
+
if (e.startsWith("z:") || e.startsWith("z_height:")) {
|
|
155
|
+
const L = parseFloat(e.slice(e.indexOf(":") + 1));
|
|
156
|
+
x && L > 0 && (D = !0, C(L));
|
|
119
157
|
continue;
|
|
120
158
|
}
|
|
121
|
-
const
|
|
122
|
-
if (
|
|
123
|
-
|
|
159
|
+
const p = /^layer \d+ z([-\d.]+)/.exec(e);
|
|
160
|
+
if (p) {
|
|
161
|
+
D = !0, C(parseFloat(p[1]), !0);
|
|
124
162
|
continue;
|
|
125
163
|
}
|
|
126
|
-
const
|
|
127
|
-
|
|
164
|
+
const f = Pt.find(([L]) => Ot(e, L));
|
|
165
|
+
f && (O = f[1]);
|
|
128
166
|
continue;
|
|
129
167
|
}
|
|
130
|
-
if (!
|
|
131
|
-
const
|
|
168
|
+
if (!S) continue;
|
|
169
|
+
const A = S.split(/\s+/), i = A[0].toUpperCase();
|
|
132
170
|
if (i === "G90") {
|
|
133
|
-
|
|
171
|
+
w = !0;
|
|
134
172
|
continue;
|
|
135
173
|
}
|
|
136
174
|
if (i === "G91") {
|
|
137
|
-
|
|
175
|
+
w = !1;
|
|
138
176
|
continue;
|
|
139
177
|
}
|
|
140
178
|
if (i === "M82") {
|
|
141
|
-
|
|
179
|
+
y = !0;
|
|
142
180
|
continue;
|
|
143
181
|
}
|
|
144
182
|
if (i === "M83") {
|
|
145
|
-
|
|
183
|
+
y = !1;
|
|
146
184
|
continue;
|
|
147
185
|
}
|
|
148
186
|
if (/^T\d+$/.test(i)) {
|
|
149
|
-
|
|
187
|
+
const e = parseInt(i.slice(1), 10);
|
|
188
|
+
e <= It && (g = e, z.add(g));
|
|
150
189
|
continue;
|
|
151
190
|
}
|
|
152
191
|
if (i === "G92" || i === "G0" || i === "G1" || i === "G2" || i === "G3") {
|
|
153
|
-
let e = NaN,
|
|
154
|
-
for (let
|
|
155
|
-
const
|
|
156
|
-
Number.isNaN(
|
|
192
|
+
let e = NaN, m = NaN, p = NaN, f = NaN, L = 0, tt = 0;
|
|
193
|
+
for (let I = 1; I < A.length; I++) {
|
|
194
|
+
const l = A[I][0], N = parseFloat(A[I].slice(1));
|
|
195
|
+
Number.isNaN(N) || (l === "X" || l === "x" ? e = N : l === "Y" || l === "y" ? m = N : l === "Z" || l === "z" ? p = N : l === "E" || l === "e" ? f = N : l === "I" || l === "i" ? L = N : (l === "J" || l === "j") && (tt = N));
|
|
157
196
|
}
|
|
158
197
|
if (i === "G92") {
|
|
159
|
-
Number.isNaN(e) || (
|
|
198
|
+
Number.isNaN(e) || (u = e), Number.isNaN(m) || (h = m), Number.isNaN(p) || (c = p), Number.isNaN(f) || (b = f);
|
|
160
199
|
continue;
|
|
161
200
|
}
|
|
162
|
-
const
|
|
201
|
+
const B = Number.isNaN(e) ? u : w ? e : u + e, F = Number.isNaN(m) ? h : w ? m : h + m, Y = Number.isNaN(p) ? c : w ? p : c + p, et = Number.isNaN(f) ? 0 : y ? f - b : f;
|
|
163
202
|
if (i === "G2" || i === "G3") {
|
|
164
|
-
const
|
|
165
|
-
let
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
let
|
|
170
|
-
for (let
|
|
171
|
-
const
|
|
172
|
-
|
|
203
|
+
const I = u + L, l = h + tt, N = Math.hypot(u - I, h - l), G = Math.atan2(h - l, u - I);
|
|
204
|
+
let M = Math.atan2(F - l, B - I);
|
|
205
|
+
const it = i === "G3";
|
|
206
|
+
it && M <= G + K && (M += 2 * Math.PI), !it && M >= G - K && (M -= 2 * Math.PI);
|
|
207
|
+
const _ = Math.max(1, Math.min(360, Math.ceil(Math.abs(M - G) * N / 0.3)));
|
|
208
|
+
let nt = u, rt = h, st = c;
|
|
209
|
+
for (let T = 1; T <= _; T++) {
|
|
210
|
+
const ot = G + (M - G) * T / _, at = T === _ ? B : I + N * Math.cos(ot), lt = T === _ ? F : l + N * Math.sin(ot), ct = c + (Y - c) * T / _;
|
|
211
|
+
J(nt, rt, st, at, lt, ct, et / _), nt = at, rt = lt, st = ct;
|
|
173
212
|
}
|
|
174
213
|
} else
|
|
175
|
-
|
|
176
|
-
|
|
214
|
+
J(u, h, c, B, F, Y, et);
|
|
215
|
+
u = B, h = F, c = Y, Number.isNaN(f) || (b = y ? f : b + f);
|
|
177
216
|
}
|
|
178
217
|
}
|
|
218
|
+
const pt = Math.max(k.length, j.length), Q = Array.from({ length: pt }, (s, a) => j[a] || k[a] || null), Lt = Array.from({ length: Math.max(...z) + 1 }, (s, a) => H[a] || 0);
|
|
179
219
|
return {
|
|
180
|
-
layers:
|
|
220
|
+
layers: E.map((s) => ({ z: s.z, paths: Float32Array.from(s.p), widths: Float32Array.from(s.w) })),
|
|
181
221
|
stats: {
|
|
182
|
-
layers:
|
|
183
|
-
path_segments:
|
|
184
|
-
travel_segments:
|
|
185
|
-
filament_mm:
|
|
186
|
-
tools: [...
|
|
222
|
+
layers: E.length,
|
|
223
|
+
path_segments: q,
|
|
224
|
+
travel_segments: X,
|
|
225
|
+
filament_mm: $,
|
|
226
|
+
tools: [...z].sort((s, a) => s - a),
|
|
227
|
+
// The kernel's own stat name, so the Filament-view switch and the stats card's per-tool split work unchanged.
|
|
228
|
+
filament_mm_by_tool: Lt,
|
|
229
|
+
...Q.some(Boolean) && { colors: Q }
|
|
187
230
|
}
|
|
188
231
|
};
|
|
189
232
|
}
|
|
190
233
|
export {
|
|
191
|
-
|
|
234
|
+
wt as exportedGcode,
|
|
235
|
+
Wt as parseGcode,
|
|
236
|
+
gt as resultToolColors,
|
|
237
|
+
St as withFilamentColours
|
|
192
238
|
};
|