three-slicer-viewer 0.3.2 → 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 CHANGED
@@ -256,6 +256,7 @@ opt-out only, everything on unless you say `false`, an unknown key stays on.
256
256
  | `filePicker` | The file dialog never opens, from any of the buttons that open it. |
257
257
  | `contextMenu` | No right-click menu. The browser's own menu stays suppressed over the canvas either way — OrbitControls preventDefaults `contextmenu` itself. |
258
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. |
259
260
 
260
261
  ### Read-only panels
261
262
 
@@ -395,6 +396,29 @@ for 143MB) against a 4GB pool budget, which gives 2 for that model and does not
395
396
  manual count is capped the same way. What the browser has NOT measured yet is
396
397
  the arachne + tree-support preset, where the harness gains were largest.
397
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
+
398
422
  ## Theming
399
423
 
400
424
  Both shadow roots (the viewer and `<SettingsPanel/>`) paint their chrome from one palette, `UI_TOKENS` in
@@ -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 u = 2 * Math.PI * i / r;
12
- return [+(a + a * Math.cos(u)).toFixed(2), +(a + a * Math.sin(u)).toFixed(2)];
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), [u, 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, g] = y(Math.round(i.w)), [l, b] = y(JSON.stringify(o)), [k, w] = y(!1);
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 }), g(Math.round(n.w)), b(JSON.stringify(o)), w(!1);
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: m, d: f, ox: S, oy: M } = n;
27
- m > 0 && f > 0 && a(q(m, f, S, M));
28
- }, _ = (n) => {
29
- g(n), n > 0 && a(V(n));
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
- b(n);
31
+ u(n);
32
32
  try {
33
- const m = JSON.parse(n);
34
- if (!Array.isArray(m) || m.length < 3 || !m.every((f) => Array.isArray(f) && f.length === 2 && f.every(Number.isFinite))) throw 0;
35
- w(!1), a(m);
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 m = parseFloat(n);
41
- return Number.isFinite(m) ? m : 0;
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, m]) => /* @__PURE__ */ e.createElement("button", { key: n, type: "button", className: u === n ? "on" : "", disabled: r, onClick: () => d(n) }, m))), u === "rect" && /* @__PURE__ */ e.createElement("div", { className: "bse-grid" }, /* @__PURE__ */ e.createElement("label", null, "Width ", /* @__PURE__ */ e.createElement(
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
- ))), u === "circle" && /* @__PURE__ */ e.createElement("div", { className: "bse-grid" }, /* @__PURE__ */ e.createElement("label", null, "Diameter ", /* @__PURE__ */ e.createElement(
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) => _(E(n.target.value))
87
+ onChange: (n) => N(E(n.target.value))
88
88
  }
89
- ), " mm")), u === "custom" && /* @__PURE__ */ e.createElement(
89
+ ), " mm")), m === "custom" && /* @__PURE__ */ e.createElement(
90
90
  "textarea",
91
91
  {
92
- className: "bse-json" + (k ? " err" : ""),
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: u }) {
122
- const d = I(r, a), c = (l) => o((b) => ({ ...b, [a]: l })), p = u?.[a] ?? K[a];
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), g = Array.isArray(d) ? d[0] : d;
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: g ?? "", onChange: (l) => c(l.target.value), disabled: i }, t.enum_values.map((l, b) => /* @__PURE__ */ e.createElement("option", { key: l, value: l }, t.enum_labels?.[b] ?? l)));
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 g == "string" && g.startsWith("#") ? g : H, onChange: (l) => c(l.target.value), disabled: i });
132
- case "textarea":
133
- return /* @__PURE__ */ e.createElement("textarea", { rows: 2, value: Array.isArray(d) ? d.join(`
134
- `) : d ?? "", onChange: (l) => c(l.target.value), disabled: i });
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: g ?? "", onChange: (l) => c(l.target.value), disabled: i }), t.sidetext && /* @__PURE__ */ e.createElement("span", { className: "unit" }, t.sidetext));
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: u, customWidgets: d, overridden: c, onRevertKey: p }) {
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], g = i ? i[a] : void 0, l = g != null, b = oe(t, r, a), k = () => o((v) => {
146
- const _ = { ...v };
147
- return delete _[a], _;
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: ${g}` : x?.tooltip ?? "", "data-testid": `row-${a}` }, /* @__PURE__ */ e.createElement("span", { className: "lbl-cell" }, c && /* @__PURE__ */ e.createElement(
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
- ), b && /* @__PURE__ */ e.createElement("span", { className: "dirty-dot", title: "Changed from default", "data-testid": `dirty-${a}` }), u ? /* @__PURE__ */ e.createElement("button", { type: "button", className: "lbl lbl-link", onClick: () => u(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 }), b && /* @__PURE__ */ e.createElement("button", { className: "reset-btn", onClick: k, title: "Reset to default", "data-testid": `reset-${a}` }, "↺")));
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: u,
170
+ onOptionOpen: m,
167
171
  embedded: d = !1,
168
172
  customWidgets: c,
169
173
  only: p,
170
174
  overriddenKeys: x,
171
- onRevertKey: g
175
+ onRevertKey: b
172
176
  }) {
173
- const l = j(() => ne(a), [a]), [b, k] = y(p?.builder ?? l[0] ?? ""), [w, v] = y(0), [_, z] = y("all"), [E, n] = y(""), m = W(o), f = l.filter((s) => te(s) === m), S = p?.builder ?? (f.includes(b) ? b : 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]), N = E.trim().toLowerCase(), C = j(() => N ? Object.entries(t).filter(([s, h]) => s.includes(N) || h.label?.toLowerCase().includes(N) || h.full_label?.toLowerCase().includes(N)).slice(0, 60).map(([s]) => s) : [], [N]), D = (s, h) => /* @__PURE__ */ e.createElement(
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: u,
186
+ onOptionOpen: m,
183
187
  customWidgets: c,
184
188
  overridden: x?.includes(s),
185
- onRevertKey: g
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" }), !N && /* @__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) => {
189
- k(s.target.value), v(0);
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: _ === s ? "on" : "", onClick: () => z(s) }, s))))), /* @__PURE__ */ e.createElement("div", { className: "sp-body" }, N ? /* @__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) => {
191
- const h = s.options.filter((F) => se(t, F, _) && !(p && F.startsWith("<")));
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
  }
@@ -1,32 +1,32 @@
1
- import { D as Lt, a as Nt } from "./viewer_defaults-B7UUZ582.js";
1
+ import { D as Nt, a as Rt } from "./viewer_defaults-B7UUZ582.js";
2
2
  import { R as t, e as Z } from "./toolpath_encoding-BuYs95qZ.js";
3
3
  import { T as ft } from "./toolpath_palette-B8xEl-qa.js";
4
- const F = 1e-6, Rt = 254, It = /^(filament|extruder)_colour\s*=\s*(.*)$/i, ut = (i) => i.split(/[;,]/).map((r) => {
5
- const s = /^"?#?([0-9a-f]{6})(?:[0-9a-f]{2})?"?$/i.exec(r.trim());
6
- return s ? "#" + s[1].toUpperCase() : null;
7
- }), At = (i) => "#" + ft[i % ft.length].map((r) => Math.round(r * 255).toString(16).padStart(2, "0")).join("");
8
- function gt(i, r) {
9
- let s = [];
10
- Array.isArray(i?.colors) && (s = i.colors);
11
- const g = r ?? [], u = Math.max(s.length, g.length, i?.filament_mm_by_tool?.length ?? 0);
12
- return Array.from({ length: u }, (m, c) => s[c] || g[c] || At(c));
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
13
  }
14
- function wt(i, r) {
15
- return dt(i.gcode, gt(i.stats, r));
14
+ function wt(n, r) {
15
+ return St(n.gcode, gt(n.stats, r));
16
16
  }
17
- function dt(i, r) {
18
- const s = String(i ?? ""), g = [r].flat().filter(Boolean);
19
- if (!g.length || /^;\s*filament_colour\s*=/mi.test(s)) return s;
20
- const u = "; filament_colour = " + g.join(";") + `
21
- `, m = s.lastIndexOf(`
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
22
  ; CONFIG_BLOCK_END`);
23
- if (m >= 0) return s.slice(0, m + 1) + u + s.slice(m + 1);
23
+ if (h >= 0) return o.slice(0, h + 1) + u + o.slice(h + 1);
24
24
  let c = `
25
25
  `;
26
- return (!s || s.endsWith(`
27
- `)) && (c = ""), s + c + u;
26
+ return (!o || o.endsWith(`
27
+ `)) && (c = ""), o + c + u;
28
28
  }
29
- const St = {
29
+ const ht = {
30
30
  // OrcaSlicer / PrusaSlicer ;TYPE: names
31
31
  "outer wall": t.WALL,
32
32
  "external perimeter": t.WALL,
@@ -80,7 +80,7 @@ const St = {
80
80
  ["support", t.SUPPORT],
81
81
  ["prime tower", t.PRIME],
82
82
  ["wipe_tower_real", t.PRIME]
83
- ], Et = (i, r) => i === r || r.length < i.length && " (:/".includes(i[r.length]) && i.startsWith(r), Ot = {
83
+ ], Ot = (n, r) => n === r || r.length < n.length && " (:/".includes(n[r.length]) && n.startsWith(r), Et = {
84
84
  1: t.WALL,
85
85
  2: t.WALL,
86
86
  3: t.WALL,
@@ -99,136 +99,140 @@ const St = {
99
99
  16: t.SUPPORT,
100
100
  17: t.PRIME
101
101
  };
102
- function Gt(i, r = {}) {
103
- const s = Math.PI / 4 * (r.filamentDiameter > 0 ? r.filamentDiameter : Lt) ** 2, g = r.defaultLayerHeight > 0 ? r.defaultLayerHeight : Nt;
104
- let u = 0, m = 0, c = 0, b = 0, _ = !0, W = !0, d = 0, w = t.WALL, y = 0, R = null;
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
105
  const E = [];
106
- let D = !1, x = !0, K = NaN, q = 0, X = 0, $ = 0;
107
- const v = /* @__PURE__ */ new Set([0]), H = [];
108
- let z = [], k = [], j = !1;
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
109
  const V = () => {
110
- w = t.WALL, y = 0;
111
- }, U = (o, a = !1) => {
112
- R = { z: o, p: [], w: [] }, E.push(R), x = !1, K = NaN, (a || !j) && V();
113
- }, mt = () => E.length > 1 ? E[E.length - 2].z : 0, J = (o, a, S, P, A, n, e) => {
114
- const p = Math.hypot(P - o, A - a, n - S);
115
- if (!(p < F))
116
- if (e > F) {
117
- $ += e, H[d] = (H[d] ?? 0) + e, x ? U(Number.isNaN(K) ? n : K) : !D && n > R.z + 1e-3 && U(n);
118
- const h = Z(w, d);
119
- R.p.push(o, a, S, h, P, A, n, h);
120
- const f = Math.max(0.02, R.z - mt() || g), L = y > 0 ? y : e * s / (p * f) + f * (1 - Math.PI / 4);
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
121
  R.w.push(Math.min(3, Math.max(0.05, L))), q++;
122
- } else R && (R.p.push(o, a, S, Z(t.TRAVEL, d), P, A, n, Z(t.TRAVEL, d)), R.w.push(0), X++);
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++);
123
123
  };
124
- for (const o of i.split(`
124
+ for (const s of n.split(`
125
125
  `)) {
126
- const a = o.indexOf(";"), S = (a < 0 ? o : o.slice(0, a)).trim(), P = a < 0 ? "" : o.slice(a + 1).trim();
126
+ const a = s.indexOf(";"), S = (a < 0 ? s : s.slice(0, a)).trim(), P = a < 0 ? "" : s.slice(a + 1).trim();
127
127
  if (P && !S) {
128
- const e = P.toLowerCase(), p = It.exec(P);
129
- if (p) {
130
- p[1].toLowerCase() === "filament" ? z = ut(p[2]) : k = ut(p[2]);
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
131
  continue;
132
132
  }
133
133
  if (e.startsWith("type:")) {
134
- w = St[e.slice(5).trim()] ?? t.WALL, j = !0;
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;
135
139
  continue;
136
140
  }
137
141
  if (e.startsWith("_extrusion_role:")) {
138
- w = Ot[parseInt(e.slice(16), 10)] ?? t.WALL, j = !0;
142
+ O = Et[parseInt(e.slice(16), 10)] ?? t.WALL, U = !0;
139
143
  continue;
140
144
  }
141
- if (e.startsWith("width:")) {
142
- const L = parseFloat(e.slice(6));
143
- y = L > 0 ? L : 0;
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);
144
148
  continue;
145
149
  }
146
- if (e.startsWith("layer_change") || e.startsWith("layer:")) {
150
+ if (e.startsWith("layer_change") || e.startsWith("change_layer") || e.startsWith("layer:")) {
147
151
  D = !0, x = !0, V();
148
152
  continue;
149
153
  }
150
- if (e.startsWith("z:")) {
151
- const L = parseFloat(e.slice(2));
152
- x && L > 0 && (D = !0, U(L));
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));
153
157
  continue;
154
158
  }
155
- const h = /^layer \d+ z([-\d.]+)/.exec(e);
156
- if (h) {
157
- D = !0, U(parseFloat(h[1]), !0);
159
+ const p = /^layer \d+ z([-\d.]+)/.exec(e);
160
+ if (p) {
161
+ D = !0, C(parseFloat(p[1]), !0);
158
162
  continue;
159
163
  }
160
- const f = Pt.find(([L]) => Et(e, L));
161
- f && (w = f[1]);
164
+ const f = Pt.find(([L]) => Ot(e, L));
165
+ f && (O = f[1]);
162
166
  continue;
163
167
  }
164
168
  if (!S) continue;
165
- const A = S.split(/\s+/), n = A[0].toUpperCase();
166
- if (n === "G90") {
167
- _ = !0;
169
+ const A = S.split(/\s+/), i = A[0].toUpperCase();
170
+ if (i === "G90") {
171
+ w = !0;
168
172
  continue;
169
173
  }
170
- if (n === "G91") {
171
- _ = !1;
174
+ if (i === "G91") {
175
+ w = !1;
172
176
  continue;
173
177
  }
174
- if (n === "M82") {
175
- W = !0;
178
+ if (i === "M82") {
179
+ y = !0;
176
180
  continue;
177
181
  }
178
- if (n === "M83") {
179
- W = !1;
182
+ if (i === "M83") {
183
+ y = !1;
180
184
  continue;
181
185
  }
182
- if (/^T\d+$/.test(n)) {
183
- const e = parseInt(n.slice(1), 10);
184
- e <= Rt && (d = e, v.add(d));
186
+ if (/^T\d+$/.test(i)) {
187
+ const e = parseInt(i.slice(1), 10);
188
+ e <= It && (g = e, z.add(g));
185
189
  continue;
186
190
  }
187
- if (n === "G92" || n === "G0" || n === "G1" || n === "G2" || n === "G3") {
188
- let e = NaN, p = NaN, h = NaN, f = NaN, L = 0, tt = 0;
191
+ if (i === "G92" || i === "G0" || i === "G1" || i === "G2" || i === "G3") {
192
+ let e = NaN, m = NaN, p = NaN, f = NaN, L = 0, tt = 0;
189
193
  for (let I = 1; I < A.length; I++) {
190
194
  const l = A[I][0], N = parseFloat(A[I].slice(1));
191
- Number.isNaN(N) || (l === "X" || l === "x" ? e = N : l === "Y" || l === "y" ? p = N : l === "Z" || l === "z" ? h = N : l === "E" || l === "e" ? f = N : l === "I" || l === "i" ? L = N : (l === "J" || l === "j") && (tt = N));
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));
192
196
  }
193
- if (n === "G92") {
194
- Number.isNaN(e) || (u = e), Number.isNaN(p) || (m = p), Number.isNaN(h) || (c = h), Number.isNaN(f) || (b = f);
197
+ if (i === "G92") {
198
+ Number.isNaN(e) || (u = e), Number.isNaN(m) || (h = m), Number.isNaN(p) || (c = p), Number.isNaN(f) || (b = f);
195
199
  continue;
196
200
  }
197
- const C = Number.isNaN(e) ? u : _ ? e : u + e, B = Number.isNaN(p) ? m : _ ? p : m + p, Y = Number.isNaN(h) ? c : _ ? h : c + h, et = Number.isNaN(f) ? 0 : W ? f - b : f;
198
- if (n === "G2" || n === "G3") {
199
- const I = u + L, l = m + tt, N = Math.hypot(u - I, m - l), G = Math.atan2(m - l, u - I);
200
- let O = Math.atan2(B - l, C - I);
201
- const nt = n === "G3";
202
- nt && O <= G + F && (O += 2 * Math.PI), !nt && O >= G - F && (O -= 2 * Math.PI);
203
- const M = Math.max(1, Math.min(360, Math.ceil(Math.abs(O - G) * N / 0.3)));
204
- let it = u, rt = m, ot = c;
205
- for (let T = 1; T <= M; T++) {
206
- const st = G + (O - G) * T / M, at = T === M ? C : I + N * Math.cos(st), lt = T === M ? B : l + N * Math.sin(st), ct = c + (Y - c) * T / M;
207
- J(it, rt, ot, at, lt, ct, et / M), it = at, rt = lt, ot = ct;
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;
202
+ if (i === "G2" || i === "G3") {
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;
208
212
  }
209
213
  } else
210
- J(u, m, c, C, B, Y, et);
211
- u = C, m = B, c = Y, Number.isNaN(f) || (b = W ? f : b + f);
214
+ J(u, h, c, B, F, Y, et);
215
+ u = B, h = F, c = Y, Number.isNaN(f) || (b = y ? f : b + f);
212
216
  }
213
217
  }
214
- const pt = Math.max(z.length, k.length), Q = Array.from({ length: pt }, (o, a) => k[a] || z[a] || null), ht = Array.from({ length: Math.max(...v) + 1 }, (o, a) => H[a] || 0);
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);
215
219
  return {
216
- layers: E.map((o) => ({ z: o.z, paths: Float32Array.from(o.p), widths: Float32Array.from(o.w) })),
220
+ layers: E.map((s) => ({ z: s.z, paths: Float32Array.from(s.p), widths: Float32Array.from(s.w) })),
217
221
  stats: {
218
222
  layers: E.length,
219
223
  path_segments: q,
220
224
  travel_segments: X,
221
225
  filament_mm: $,
222
- tools: [...v].sort((o, a) => o - a),
226
+ tools: [...z].sort((s, a) => s - a),
223
227
  // The kernel's own stat name, so the Filament-view switch and the stats card's per-tool split work unchanged.
224
- filament_mm_by_tool: ht,
228
+ filament_mm_by_tool: Lt,
225
229
  ...Q.some(Boolean) && { colors: Q }
226
230
  }
227
231
  };
228
232
  }
229
233
  export {
230
234
  wt as exportedGcode,
231
- Gt as parseGcode,
235
+ Wt as parseGcode,
232
236
  gt as resultToolColors,
233
- dt as withFilamentColours
237
+ St as withFilamentColours
234
238
  };