three-slicer 0.1.0 → 0.1.2

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.
Files changed (64) hide show
  1. package/README.md +342 -24
  2. package/components/README.md +1 -1
  3. package/components/dist/SettingsPanel.js +111 -112
  4. package/components/styles.css +9 -9
  5. package/data/invalidation-map.json +1 -1
  6. package/engine/examples/headless.mjs +2 -2
  7. package/engine/index.d.ts +3 -1
  8. package/engine/index.js +2 -2
  9. package/engine/src/data.js +12 -0
  10. package/engine/src/settings.js +35 -35
  11. package/engine/src/slicer.worker.js +25 -22
  12. package/engine/src/slicer_core.js +0 -0
  13. package/engine/src/slicer_core.mt.js +0 -0
  14. package/engine/src/toggle_eval.js +12 -13
  15. package/package.json +54 -26
  16. package/types/components.d.ts +19 -0
  17. package/types/data/config-schema.json.d.ts +55 -0
  18. package/types/data/invalidation-map.json.d.ts +15 -0
  19. package/types/data/toggle-rules.json.d.ts +20 -0
  20. package/types/data/ui-tree.json.d.ts +19 -0
  21. package/types/data.d.ts +17 -0
  22. package/types/gen_settings_types.mjs +70 -0
  23. package/types/settings-keys.d.ts +1704 -0
  24. package/types/settings.d.ts +19 -0
  25. package/types/toggle.d.ts +22 -0
  26. package/types/viewer-loaders.d.ts +33 -0
  27. package/types/viewer-toolpath.d.ts +105 -0
  28. package/types/viewer.d.ts +15 -0
  29. package/types/wasm.d.ts +5 -0
  30. package/types/worker.d.ts +3 -0
  31. package/viewer/README.md +1 -1
  32. package/viewer/dist/Viewport.js +1774 -923
  33. package/viewer/dist/make_worker.js +6 -6
  34. package/viewer/dist/model_loaders.js +221 -109
  35. package/viewer/dist/toolpath_gpu.js +146 -147
  36. package/viewer/styles.css +27 -25
  37. package/data/config-schema-builddump.json +0 -12921
  38. package/viewer/src/Viewport.jsx +0 -1353
  39. package/viewer/src/assets/add.svg +0 -1
  40. package/viewer/src/assets/arrange.svg +0 -1
  41. package/viewer/src/assets/boolean.svg +0 -1
  42. package/viewer/src/assets/cut.svg +0 -1
  43. package/viewer/src/assets/delete.svg +0 -1
  44. package/viewer/src/assets/deleteall.svg +0 -1
  45. package/viewer/src/assets/duplicate.svg +0 -1
  46. package/viewer/src/assets/measure.svg +0 -1
  47. package/viewer/src/assets/mmu.svg +0 -1
  48. package/viewer/src/assets/move.svg +0 -1
  49. package/viewer/src/assets/negative.svg +0 -1
  50. package/viewer/src/assets/onbed.svg +0 -1
  51. package/viewer/src/assets/open.svg +0 -1
  52. package/viewer/src/assets/orient.svg +0 -1
  53. package/viewer/src/assets/paint.svg +0 -1
  54. package/viewer/src/assets/rotate.svg +0 -1
  55. package/viewer/src/assets/scale.svg +0 -1
  56. package/viewer/src/assets/seam.svg +0 -1
  57. package/viewer/src/assets/split.svg +0 -1
  58. package/viewer/src/assets/textmark.svg +0 -1
  59. package/viewer/src/assets/varlayer.svg +0 -1
  60. package/viewer/src/icons.js +0 -23
  61. package/viewer/src/make_worker.js +0 -9
  62. package/viewer/src/model_loaders.js +0 -178
  63. package/viewer/src/shadow_host.jsx +0 -30
  64. package/viewer/src/toolpath_gpu.js +0 -430
@@ -1,179 +1,178 @@
1
- import e, { useRef as R, useState as f, useLayoutEffect as F, useEffect as J, useMemo as z } from "react";
2
- import { createPortal as O } from "react-dom";
3
- import C from "three-slicer/data/config-schema.json";
4
- import M from "three-slicer/data/ui-tree.json";
5
- import { settingRaw as P } from "three-slicer/settings";
1
+ import e, { useRef as D, useState as h, useLayoutEffect as O, useEffect as P, useMemo as A } from "react";
2
+ import { createPortal as F } from "react-dom";
3
+ import { uiTree as S, schema as C } from "three-slicer/data";
4
+ import { settingRaw as J } from "three-slicer/settings";
6
5
  import { disabledKeys as I, makeCfg as B } from "three-slicer/toggle";
7
- function D({ css: t, className: r, children: n }) {
8
- const s = R(null), [l] = f(() => {
9
- const d = document.createElement("div");
10
- return d.style.display = "contents", d;
6
+ function L({ css: t, className: s, children: r }) {
7
+ const i = D(null), [c] = h(() => {
8
+ const l = document.createElement("div");
9
+ return l.style.display = "contents", l;
11
10
  });
12
- return F(() => {
13
- const d = s.current, o = d.shadowRoot || d.attachShadow({ mode: "open" });
14
- let i = o.querySelector("style[data-ts-css]");
15
- i || (i = document.createElement("style"), i.setAttribute("data-ts-css", ""), o.append(i)), i.textContent = t, o.contains(l) || o.append(l);
16
- }, [t, l]), /* @__PURE__ */ e.createElement("div", { ref: s, className: r }, O(n, l));
11
+ return O(() => {
12
+ const l = i.current, o = l.shadowRoot || l.attachShadow({ mode: "open" });
13
+ let p = o.querySelector("style[data-ts-css]");
14
+ p || (p = document.createElement("style"), p.setAttribute("data-ts-css", ""), o.append(p)), p.textContent = t, o.contains(c) || o.append(c);
15
+ }, [t, c]), /* @__PURE__ */ e.createElement("div", { ref: i, className: s }, F(r, c));
17
16
  }
18
- const L = ':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 #f0f0f0}.row .lbl{color:#222;text-decoration:none}.row .lbl:hover{color:#0a7d4f;text-decoration:underline}.row .key{font-size:11px;color:#999;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:#888;font-size:12px;white-space:nowrap}.pts{font-size:11px;color:#666}.badge{font-size:10px;padding:1px 6px;border-radius:8px;color:#fff;text-align:center}.mode-simple{background:#2e9e5b}.mode-advanced{background:#3673c3}.mode-expert{background:#d9822b}.mode-develop{background:#c0392b}.mode-none,.mode-undefined{background:#aaa}.muted{color:#999;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:#f2f2f2}.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:#e8873a;box-shadow:0 0 0 2px #fff3e6}.widget-cell{display:inline-flex;align-items:center;gap:6px;min-width:0}.reset-btn{flex:0 0 auto;border:1px solid #d8b48a;background:#fbf1e4;color:#b5701f;border-radius:5px;cursor:pointer;font-size:13px;line-height:1;padding:1px 5px}.reset-btn:hover{background:#f3e2cb}.settings-panel{flex:0 0 400px;width:400px;border-left:1px solid #e2e2e2;display:flex;flex-direction:column;min-height:0;background:#fff;color:#222;font:14px/1.5 -apple-system,Segoe UI,sans-serif}.settings-panel .sp-top{padding:10px 12px 8px;border-bottom:1px solid #eee}.sp-builder{display:flex;align-items:center;gap:8px;font-size:12px;color:#666}.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 #e0e0e0;background:#fafafa;border-radius:6px;padding:4px 8px;font-size:12px;cursor:pointer}.sp-pages button.on{background:#e6f5ee;color:#0a7d4f;border-color:#b7e2cd;font-weight:700}.sp-modes{display:flex;gap:4px;margin-top:8px}.sp-modes button{border:0;background:none;font-size:11px;color:#888;padding:2px 6px;border-radius:6px;cursor:pointer}.sp-modes button.on{background:#eef2f0;color:#0a7d4f;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 #d6d6d6;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 #e0e0e0;background:#fafafa;border-radius:6px;padding:2px 8px;font-size:11px;cursor:pointer}.bse-modes button.on{background:#e6f5ee;color:#0a7d4f;border-color:#b7e2cd;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:#666;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 #d6d6d6;border-radius:6px;box-sizing:border-box}.bse-json.err{border-color:#d0435a;background:#fdf3f4}', A = (t, r, n, s) => [[n, s], [t + n, s], [t + n, r + s], [n, r + s]], T = (t) => {
19
- const r = t / 2, n = 32;
20
- return Array.from({ length: n }, (s, l) => {
21
- const d = 2 * Math.PI * l / n;
22
- return [+(r + r * Math.cos(d)).toFixed(2), +(r + r * Math.sin(d)).toFixed(2)];
17
+ const T = ':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 #f0f0f0}.row .lbl{color:#222;text-decoration:none}.row .lbl:hover{color:#0a7d4f;text-decoration:underline}.row .key{font-size:11px;color:#999;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:#888;font-size:12px;white-space:nowrap}.pts{font-size:11px;color:#666}.badge{font-size:10px;padding:1px 6px;border-radius:8px;color:#fff;text-align:center}.mode-simple{background:#2e9e5b}.mode-advanced{background:#3673c3}.mode-expert{background:#d9822b}.mode-develop{background:#c0392b}.mode-none,.mode-undefined{background:#aaa}.muted{color:#999;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:#f2f2f2}.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:#e8873a;box-shadow:0 0 0 2px #fff3e6}.widget-cell{display:inline-flex;align-items:center;gap:6px;min-width:0}.reset-btn{flex:0 0 auto;border:1px solid #d8b48a;background:#fbf1e4;color:#b5701f;border-radius:5px;cursor:pointer;font-size:13px;line-height:1;padding:1px 5px}.reset-btn:hover{background:#f3e2cb}.settings-panel{flex:0 0 400px;width:400px;border-left:1px solid #e2e2e2;display:flex;flex-direction:column;min-height:0;background:#fff;color:#222;font:14px/1.5 -apple-system,Segoe UI,sans-serif}.settings-panel .sp-top{padding:10px 12px 8px;border-bottom:1px solid #eee}.sp-builder{display:flex;align-items:center;gap:8px;font-size:12px;color:#666}.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 #e0e0e0;background:#fafafa;border-radius:6px;padding:4px 8px;font-size:12px;cursor:pointer}.sp-pages button.on{background:#e6f5ee;color:#0a7d4f;border-color:#b7e2cd;font-weight:700}.sp-modes{display:flex;gap:4px;margin-top:8px}.sp-modes button{border:0;background:none;font-size:11px;color:#888;padding:2px 6px;border-radius:6px;cursor:pointer}.sp-modes button.on{background:#eef2f0;color:#0a7d4f;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 #d6d6d6;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 #e0e0e0;background:#fafafa;border-radius:6px;padding:2px 8px;font-size:11px;cursor:pointer}.bse-modes button.on{background:#e6f5ee;color:#0a7d4f;border-color:#b7e2cd;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:#666;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 #d6d6d6;border-radius:6px;box-sizing:border-box}.bse-json.err{border-color:#d0435a;background:#fdf3f4}', j = (t, s, r, i) => [[r, i], [t + r, i], [t + r, s + i], [r, s + i]], $ = (t) => {
18
+ const s = t / 2, r = 32;
19
+ return Array.from({ length: r }, (i, c) => {
20
+ const l = 2 * Math.PI * c / r;
21
+ return [+(s + s * Math.cos(l)).toFixed(2), +(s + s * Math.sin(l)).toFixed(2)];
23
22
  });
24
- }, S = (t) => {
25
- const r = t.map((s) => s[0]), n = t.map((s) => s[1]);
26
- return { x0: Math.min(...r), y0: Math.min(...n), w: Math.max(...r) - Math.min(...r), d: Math.max(...n) - Math.min(...n) };
23
+ }, R = (t) => {
24
+ const s = t.map((i) => i[0]), r = t.map((i) => i[1]);
25
+ return { x0: Math.min(...s), y0: Math.min(...r), w: Math.max(...s) - Math.min(...s), d: Math.max(...r) - Math.min(...r) };
27
26
  };
28
- function $({ value: t, onChange: r, disabled: n }) {
29
- const s = Array.isArray(t) && t.length >= 3 ? t : A(200, 200, 0, 0), l = S(s), [d, o] = f(s.length === 4 ? "rect" : s.length >= 12 ? "circle" : "custom"), [i, m] = f({ w: l.w, d: l.d, ox: l.x0, oy: l.y0 }), [h, u] = f(Math.round(l.w)), [c, x] = f(JSON.stringify(s)), [g, y] = f(!1);
30
- J(() => {
31
- const a = S(s);
32
- m({ w: a.w, d: a.d, ox: a.x0, oy: a.y0 }), u(Math.round(a.w)), x(JSON.stringify(s)), y(!1);
27
+ function U({ value: t, onChange: s, disabled: r }) {
28
+ const i = Array.isArray(t) && t.length >= 3 ? t : j(200, 200, 0, 0), c = R(i), [l, o] = h(i.length === 4 ? "rect" : i.length >= 12 ? "circle" : "custom"), [p, x] = h({ w: c.w, d: c.d, ox: c.x0, oy: c.y0 }), [w, u] = h(Math.round(c.w)), [d, f] = h(JSON.stringify(i)), [E, N] = h(!1);
29
+ P(() => {
30
+ const a = R(i);
31
+ x({ w: a.w, d: a.d, ox: a.x0, oy: a.y0 }), u(Math.round(a.w)), f(JSON.stringify(i)), N(!1);
33
32
  }, [JSON.stringify(t)]);
34
- const v = (a) => {
35
- m(a);
36
- const { w: p, d: b, ox: k, oy: _ } = a;
37
- p > 0 && b > 0 && r(A(p, b, k, _));
38
- }, w = (a) => {
39
- u(a), a > 0 && r(T(a));
40
- }, N = (a) => {
33
+ const y = (a) => {
41
34
  x(a);
35
+ const { w: m, d: n, ox: b, oy: v } = a;
36
+ m > 0 && n > 0 && s(j(m, n, b, v));
37
+ }, k = (a) => {
38
+ u(a), a > 0 && s($(a));
39
+ }, M = (a) => {
40
+ f(a);
42
41
  try {
43
- const p = JSON.parse(a);
44
- if (!Array.isArray(p) || p.length < 3 || !p.every((b) => Array.isArray(b) && b.length === 2 && b.every(Number.isFinite))) throw 0;
45
- y(!1), r(p);
42
+ const m = JSON.parse(a);
43
+ if (!Array.isArray(m) || m.length < 3 || !m.every((n) => Array.isArray(n) && n.length === 2 && n.every(Number.isFinite))) throw 0;
44
+ N(!1), s(m);
46
45
  } catch {
47
- y(!0);
46
+ N(!0);
48
47
  }
49
- }, E = (a) => {
50
- const p = parseFloat(a);
51
- return Number.isFinite(p) ? p : 0;
48
+ }, g = (a) => {
49
+ const m = parseFloat(a);
50
+ return Number.isFinite(m) ? m : 0;
52
51
  };
53
- return /* @__PURE__ */ e.createElement("div", { className: "bse", "data-testid": "bed-shape-editor" }, /* @__PURE__ */ e.createElement("div", { className: "bse-modes" }, [["rect", "사각형"], ["circle", "원형"], ["custom", "커스텀"]].map(([a, p]) => /* @__PURE__ */ e.createElement("button", { key: a, type: "button", className: d === a ? "on" : "", disabled: n, onClick: () => o(a) }, p))), d === "rect" && /* @__PURE__ */ e.createElement("div", { className: "bse-grid" }, /* @__PURE__ */ e.createElement("label", null, "폭 ", /* @__PURE__ */ e.createElement(
52
+ 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(([a, m]) => /* @__PURE__ */ e.createElement("button", { key: a, type: "button", className: l === a ? "on" : "", disabled: r, onClick: () => o(a) }, m))), l === "rect" && /* @__PURE__ */ e.createElement("div", { className: "bse-grid" }, /* @__PURE__ */ e.createElement("label", null, "Width ", /* @__PURE__ */ e.createElement(
54
53
  "input",
55
54
  {
56
55
  type: "number",
57
56
  min: "1",
58
- value: i.w,
59
- disabled: n,
57
+ value: p.w,
58
+ disabled: r,
60
59
  "data-testid": "bed-w",
61
- onChange: (a) => v({ ...i, w: E(a.target.value) })
60
+ onChange: (a) => y({ ...p, w: g(a.target.value) })
62
61
  }
63
- ), " mm"), /* @__PURE__ */ e.createElement("label", null, "깊이 ", /* @__PURE__ */ e.createElement(
62
+ ), " mm"), /* @__PURE__ */ e.createElement("label", null, "Depth ", /* @__PURE__ */ e.createElement(
64
63
  "input",
65
64
  {
66
65
  type: "number",
67
66
  min: "1",
68
- value: i.d,
69
- disabled: n,
67
+ value: p.d,
68
+ disabled: r,
70
69
  "data-testid": "bed-d",
71
- onChange: (a) => v({ ...i, d: E(a.target.value) })
70
+ onChange: (a) => y({ ...p, d: g(a.target.value) })
72
71
  }
73
- ), " mm"), /* @__PURE__ */ e.createElement("label", null, "원점 X ", /* @__PURE__ */ e.createElement(
72
+ ), " mm"), /* @__PURE__ */ e.createElement("label", null, "Origin X ", /* @__PURE__ */ e.createElement(
74
73
  "input",
75
74
  {
76
75
  type: "number",
77
- value: i.ox,
78
- disabled: n,
79
- onChange: (a) => v({ ...i, ox: E(a.target.value) })
76
+ value: p.ox,
77
+ disabled: r,
78
+ onChange: (a) => y({ ...p, ox: g(a.target.value) })
80
79
  }
81
- )), /* @__PURE__ */ e.createElement("label", null, "원점 Y ", /* @__PURE__ */ e.createElement(
80
+ )), /* @__PURE__ */ e.createElement("label", null, "Origin Y ", /* @__PURE__ */ e.createElement(
82
81
  "input",
83
82
  {
84
83
  type: "number",
85
- value: i.oy,
86
- disabled: n,
87
- onChange: (a) => v({ ...i, oy: E(a.target.value) })
84
+ value: p.oy,
85
+ disabled: r,
86
+ onChange: (a) => y({ ...p, oy: g(a.target.value) })
88
87
  }
89
- ))), d === "circle" && /* @__PURE__ */ e.createElement("div", { className: "bse-grid" }, /* @__PURE__ */ e.createElement("label", null, "지름 ", /* @__PURE__ */ e.createElement(
88
+ ))), l === "circle" && /* @__PURE__ */ e.createElement("div", { className: "bse-grid" }, /* @__PURE__ */ e.createElement("label", null, "Diameter ", /* @__PURE__ */ e.createElement(
90
89
  "input",
91
90
  {
92
91
  type: "number",
93
92
  min: "1",
94
- value: h,
95
- disabled: n,
93
+ value: w,
94
+ disabled: r,
96
95
  "data-testid": "bed-dia",
97
- onChange: (a) => w(E(a.target.value))
96
+ onChange: (a) => k(g(a.target.value))
98
97
  }
99
- ), " mm")), d === "custom" && /* @__PURE__ */ e.createElement(
98
+ ), " mm")), l === "custom" && /* @__PURE__ */ e.createElement(
100
99
  "textarea",
101
100
  {
102
- className: "bse-json" + (g ? " err" : ""),
101
+ className: "bse-json" + (E ? " err" : ""),
103
102
  rows: 3,
104
- value: c,
105
- disabled: n,
103
+ value: d,
104
+ disabled: r,
106
105
  "data-testid": "bed-json",
107
- onChange: (a) => N(a.target.value),
106
+ onChange: (a) => M(a.target.value),
108
107
  placeholder: "[[0,0],[200,0],[200,200],[0,200]]"
109
108
  }
110
109
  ));
111
110
  }
112
- const U = {
113
- printable_area: $
114
- }, j = Object.keys(M).filter((t) => M[t].length > 0), q = ["all", "simple", "advanced", "expert", "develop"], G = (t) => t.replace("::build", "").replace("Tab", "");
115
- function H(t, r) {
116
- var l;
117
- if (!t || !(r in t)) return !1;
118
- const n = (l = C[r]) == null ? void 0 : l.default, s = Array.isArray(n) ? n[0] : n;
119
- return String(t[r]) !== String(s ?? "");
111
+ const W = {
112
+ printable_area: U
113
+ }, _ = Object.keys(S).filter((t) => S[t].length > 0), q = ["all", "simple", "advanced", "expert", "develop"], G = (t) => t.replace("::build", "").replace("Tab", "");
114
+ function H(t, s) {
115
+ var c;
116
+ if (!t || !(s in t)) return !1;
117
+ const r = (c = C[s]) == null ? void 0 : c.default, i = Array.isArray(r) ? r[0] : r;
118
+ return String(t[s]) !== String(i ?? "");
120
119
  }
121
- function Q(t, r) {
122
- var s;
123
- if (r === "all" || t.startsWith("<")) return !0;
124
- const n = (s = C[t]) == null ? void 0 : s.mode;
125
- return r === "simple" ? n === "simple" : r === "advanced" ? n === "simple" || n === "advanced" : !0;
120
+ function Q(t, s) {
121
+ var i;
122
+ if (s === "all" || t.startsWith("<")) return !0;
123
+ const r = (i = C[t]) == null ? void 0 : i.mode;
124
+ return s === "simple" ? r === "simple" : s === "advanced" ? r === "simple" || r === "advanced" : !0;
126
125
  }
127
- function W(t) {
126
+ function X(t) {
128
127
  return t ? t.gui_type === "color" ? "color" : t.enum_values ? "select" : t.type === "coBool" || t.type === "coBools" ? "checkbox" : t.multiline || t.is_code ? "textarea" : t.type === "coPoints" || t.type === "coPoint" ? "points" : "input" : "unknown";
129
128
  }
130
- function X({ mode: t }) {
129
+ function Y({ mode: t }) {
131
130
  return /* @__PURE__ */ e.createElement("span", { className: `badge mode-${t ?? "none"}` }, t ?? "—");
132
131
  }
133
- function Y({ def: t, optKey: r, settings: n, setSettings: s, disabled: l, customWidgets: d }) {
134
- const o = P(n, r), i = (c) => s((x) => ({ ...x, [r]: c })), m = (d == null ? void 0 : d[r]) ?? U[r];
135
- if (m) return /* @__PURE__ */ e.createElement(m, { def: t, value: o, onChange: i, disabled: l });
136
- const h = W(t), u = Array.isArray(o) ? o[0] : o;
137
- switch (h) {
132
+ function V({ def: t, optKey: s, settings: r, setSettings: i, disabled: c, customWidgets: l }) {
133
+ const o = J(r, s), p = (d) => i((f) => ({ ...f, [s]: d })), x = (l == null ? void 0 : l[s]) ?? W[s];
134
+ if (x) return /* @__PURE__ */ e.createElement(x, { def: t, value: o, onChange: p, disabled: c });
135
+ const w = X(t), u = Array.isArray(o) ? o[0] : o;
136
+ switch (w) {
138
137
  case "select":
139
- return /* @__PURE__ */ e.createElement("select", { value: u ?? "", onChange: (c) => i(c.target.value), disabled: l }, t.enum_values.map((c, x) => {
140
- var g;
141
- return /* @__PURE__ */ e.createElement("option", { key: c, value: c }, ((g = t.enum_labels) == null ? void 0 : g[x]) ?? c);
138
+ return /* @__PURE__ */ e.createElement("select", { value: u ?? "", onChange: (d) => p(d.target.value), disabled: c }, t.enum_values.map((d, f) => {
139
+ var E;
140
+ return /* @__PURE__ */ e.createElement("option", { key: d, value: d }, ((E = t.enum_labels) == null ? void 0 : E[f]) ?? d);
142
141
  }));
143
142
  case "checkbox":
144
- return /* @__PURE__ */ e.createElement("input", { type: "checkbox", checked: Array.isArray(o) ? !!o[0] : !!o, onChange: (c) => i(c.target.checked), disabled: l });
143
+ return /* @__PURE__ */ e.createElement("input", { type: "checkbox", checked: Array.isArray(o) ? !!o[0] : !!o, onChange: (d) => p(d.target.checked), disabled: c });
145
144
  case "color":
146
- return /* @__PURE__ */ e.createElement("input", { type: "color", value: typeof u == "string" && u.startsWith("#") ? u : "#00ae42", onChange: (c) => i(c.target.value), disabled: l });
145
+ return /* @__PURE__ */ e.createElement("input", { type: "color", value: typeof u == "string" && u.startsWith("#") ? u : "#00ae42", onChange: (d) => p(d.target.value), disabled: c });
147
146
  case "textarea":
148
147
  return /* @__PURE__ */ e.createElement("textarea", { rows: 2, value: Array.isArray(o) ? o.join(`
149
- `) : o ?? "", onChange: (c) => i(c.target.value), disabled: l });
148
+ `) : o ?? "", onChange: (d) => p(d.target.value), disabled: c });
150
149
  case "points":
151
150
  return /* @__PURE__ */ e.createElement("code", { className: "pts" }, JSON.stringify(o ?? []));
152
151
  case "unknown":
153
- return /* @__PURE__ */ e.createElement("span", { className: "muted" }, "스키마 없음");
152
+ return /* @__PURE__ */ e.createElement("span", { className: "muted" }, "No schema");
154
153
  default:
155
- return /* @__PURE__ */ e.createElement("span", { className: "inputwrap" }, /* @__PURE__ */ e.createElement("input", { value: u ?? "", onChange: (c) => i(c.target.value), disabled: l }), t.sidetext && /* @__PURE__ */ e.createElement("span", { className: "unit" }, t.sidetext));
154
+ return /* @__PURE__ */ e.createElement("span", { className: "inputwrap" }, /* @__PURE__ */ e.createElement("input", { value: u ?? "", onChange: (d) => p(d.target.value), disabled: c }), t.sidetext && /* @__PURE__ */ e.createElement("span", { className: "unit" }, t.sidetext));
156
155
  }
157
156
  }
158
- function V({ optKey: t, settings: r, setSettings: n, disabled: s, onOptionOpen: l, customWidgets: d }) {
159
- if (t.startsWith("<")) return /* @__PURE__ */ e.createElement("div", { className: "row custom" }, /* @__PURE__ */ e.createElement("span", { className: "muted" }, "⚙ 커스텀 위젯 ", t));
160
- const o = C[t], i = s ? s[t] : void 0, m = i != null, h = H(r, t), u = () => n((x) => {
161
- const g = { ...x };
162
- return delete g[t], g;
163
- }), c = (o == null ? void 0 : o.label) || (o == null ? void 0 : o.full_label) || t;
164
- return /* @__PURE__ */ e.createElement("div", { className: "row" + (m ? " disabled" : ""), title: m ? `비활성 조건: ${i}` : (o == null ? void 0 : o.tooltip) ?? "", "data-testid": `row-${t}` }, /* @__PURE__ */ e.createElement("span", { className: "lbl-cell" }, h && /* @__PURE__ */ e.createElement("span", { className: "dirty-dot", title: "기본값에서 변경됨", "data-testid": `dirty-${t}` }), l ? /* @__PURE__ */ e.createElement("button", { type: "button", className: "lbl lbl-link", onClick: () => l(t), title: "상세" }, c) : /* @__PURE__ */ e.createElement("span", { className: "lbl" }, c)), /* @__PURE__ */ e.createElement("code", { className: "key" }, t), /* @__PURE__ */ e.createElement(X, { mode: o == null ? void 0 : o.mode }), /* @__PURE__ */ e.createElement("span", { className: "widget-cell" }, /* @__PURE__ */ e.createElement(Y, { def: o, optKey: t, settings: r, setSettings: n, disabled: m, customWidgets: d }), h && /* @__PURE__ */ e.createElement("button", { className: "reset-btn", onClick: u, title: "기본값으로 리셋", "data-testid": `reset-${t}` }, "↺")));
157
+ function Z({ optKey: t, settings: s, setSettings: r, disabled: i, onOptionOpen: c, customWidgets: l }) {
158
+ if (t.startsWith("<")) return /* @__PURE__ */ e.createElement("div", { className: "row custom" }, /* @__PURE__ */ e.createElement("span", { className: "muted" }, "⚙ Custom widget ", t));
159
+ const o = C[t], p = i ? i[t] : void 0, x = p != null, w = H(s, t), u = () => r((f) => {
160
+ const E = { ...f };
161
+ return delete E[t], E;
162
+ }), d = (o == null ? void 0 : o.label) || (o == null ? void 0 : o.full_label) || t;
163
+ return /* @__PURE__ */ e.createElement("div", { className: "row" + (x ? " disabled" : ""), title: x ? `Disabled when: ${p}` : (o == null ? void 0 : o.tooltip) ?? "", "data-testid": `row-${t}` }, /* @__PURE__ */ e.createElement("span", { className: "lbl-cell" }, w && /* @__PURE__ */ e.createElement("span", { className: "dirty-dot", title: "Changed from default", "data-testid": `dirty-${t}` }), c ? /* @__PURE__ */ e.createElement("button", { type: "button", className: "lbl lbl-link", onClick: () => c(t), title: "Details" }, d) : /* @__PURE__ */ e.createElement("span", { className: "lbl" }, d)), /* @__PURE__ */ e.createElement("code", { className: "key" }, t), /* @__PURE__ */ e.createElement(Y, { mode: o == null ? void 0 : o.mode }), /* @__PURE__ */ e.createElement("span", { className: "widget-cell" }, /* @__PURE__ */ e.createElement(V, { def: o, optKey: t, settings: s, setSettings: r, disabled: x, customWidgets: l }), w && /* @__PURE__ */ e.createElement("button", { className: "reset-btn", onClick: u, title: "Reset to default", "data-testid": `reset-${t}` }, "↺")));
165
164
  }
166
- function re({ settings: t, setSettings: r, onOptionOpen: n, embedded: s = !1, customWidgets: l }) {
167
- const [d, o] = f(j[0] ?? ""), [i, m] = f(0), [h, u] = f("all"), [c, x] = f(""), g = M[d] ?? [], y = g[Math.min(i, g.length - 1)], v = z(() => I(B(t)), [t]), w = c.trim().toLowerCase(), N = z(() => w ? Object.entries(C).filter(([a, p]) => {
168
- var b, k;
169
- return a.includes(w) || ((b = p.label) == null ? void 0 : b.toLowerCase().includes(w)) || ((k = p.full_label) == null ? void 0 : k.toLowerCase().includes(w));
170
- }).slice(0, 60).map(([a]) => a) : [], [w]), E = (a, p) => /* @__PURE__ */ e.createElement(V, { key: a + p, optKey: a, settings: t, setSettings: r, disabled: v, onOptionOpen: n, customWidgets: l });
171
- return /* @__PURE__ */ e.createElement(D, { css: L, className: s ? "sp-embedded" : void 0 }, /* @__PURE__ */ e.createElement("div", { className: "settings-panel" }, /* @__PURE__ */ e.createElement("div", { className: "sp-top" }, /* @__PURE__ */ e.createElement("input", { className: "sp-search", placeholder: "옵션 검색 (907개)…", value: c, onChange: (a) => x(a.target.value), "data-testid": "opt-search" }), !w && /* @__PURE__ */ e.createElement(e.Fragment, null, /* @__PURE__ */ e.createElement("label", { className: "sp-builder" }, /* @__PURE__ */ e.createElement("span", null, "설정 그룹"), /* @__PURE__ */ e.createElement("select", { value: d, onChange: (a) => {
172
- o(a.target.value), m(0);
173
- } }, j.map((a) => /* @__PURE__ */ e.createElement("option", { key: a, value: a }, G(a))))), /* @__PURE__ */ e.createElement("div", { className: "sp-pages" }, g.map((a, p) => /* @__PURE__ */ e.createElement("button", { key: a.page + p, className: p === Math.min(i, g.length - 1) ? "on" : "", onClick: () => m(p) }, a.page))), /* @__PURE__ */ e.createElement("div", { className: "sp-modes" }, q.map((a) => /* @__PURE__ */ e.createElement("button", { key: a, className: h === a ? "on" : "", onClick: () => u(a) }, a))))), /* @__PURE__ */ e.createElement("div", { className: "sp-body" }, w ? /* @__PURE__ */ e.createElement("section", { "data-testid": "search-results" }, /* @__PURE__ */ e.createElement("h3", null, "검색: “", c, "” — ", N.length, "건", N.length === 60 ? "+" : ""), N.map(E), !N.length && /* @__PURE__ */ e.createElement("p", { className: "muted" }, "일치 없음")) : y ? /* @__PURE__ */ e.createElement(e.Fragment, null, /* @__PURE__ */ e.createElement("h2", null, y.page, " ", /* @__PURE__ */ e.createElement("span", { className: "muted" }, "Tab.cpp:", y.line)), y.groups.map((a) => {
174
- const p = a.options.filter((b) => Q(b, h));
175
- return p.length ? /* @__PURE__ */ e.createElement("section", { key: a.group + a.line }, /* @__PURE__ */ e.createElement("h3", null, a.group || "(무제 그룹)", " ", /* @__PURE__ */ e.createElement("span", { className: "muted" }, ":", a.line)), p.map(E)) : null;
176
- })) : /* @__PURE__ */ e.createElement("p", { className: "muted" }, "빌더 없음"))));
165
+ function re({ settings: t, setSettings: s, onOptionOpen: r, embedded: i = !1, customWidgets: c, only: l }) {
166
+ const [o, p] = h((l == null ? void 0 : l.builder) ?? _[0] ?? ""), [x, w] = h(0), [u, d] = h("all"), [f, E] = h(""), N = S[(l == null ? void 0 : l.builder) ?? o] ?? [], y = l != null && l.page ? N.filter((n) => n.page === l.page) : N, k = y[Math.min(x, y.length - 1)], M = A(() => I(B(t)), [t]), g = f.trim().toLowerCase(), a = A(() => g ? Object.entries(C).filter(([n, b]) => {
167
+ var v, z;
168
+ return n.includes(g) || ((v = b.label) == null ? void 0 : v.toLowerCase().includes(g)) || ((z = b.full_label) == null ? void 0 : z.toLowerCase().includes(g));
169
+ }).slice(0, 60).map(([n]) => n) : [], [g]), m = (n, b) => /* @__PURE__ */ e.createElement(Z, { key: n + b, optKey: n, settings: t, setSettings: s, disabled: M, onOptionOpen: r, customWidgets: c });
170
+ return /* @__PURE__ */ e.createElement(L, { css: T, className: i ? "sp-embedded" : void 0 }, /* @__PURE__ */ e.createElement("div", { className: "settings-panel" }, !l && /* @__PURE__ */ e.createElement("div", { className: "sp-top" }, /* @__PURE__ */ e.createElement("input", { className: "sp-search", placeholder: "Search options (907)…", value: f, onChange: (n) => E(n.target.value), "data-testid": "opt-search" }), !g && /* @__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: o, onChange: (n) => {
171
+ p(n.target.value), w(0);
172
+ } }, _.map((n) => /* @__PURE__ */ e.createElement("option", { key: n, value: n }, G(n))))), /* @__PURE__ */ e.createElement("div", { className: "sp-pages" }, y.map((n, b) => /* @__PURE__ */ e.createElement("button", { key: n.page + b, className: b === Math.min(x, y.length - 1) ? "on" : "", onClick: () => w(b) }, n.page))), /* @__PURE__ */ e.createElement("div", { className: "sp-modes" }, q.map((n) => /* @__PURE__ */ e.createElement("button", { key: n, className: u === n ? "on" : "", onClick: () => d(n) }, n))))), /* @__PURE__ */ e.createElement("div", { className: "sp-body" }, g ? /* @__PURE__ */ e.createElement("section", { "data-testid": "search-results" }, /* @__PURE__ */ e.createElement("h3", null, "Search: “", f, "” — ", a.length, " hit", a.length === 1 ? "" : "s", a.length === 60 ? "+" : ""), a.map(m), !a.length && /* @__PURE__ */ e.createElement("p", { className: "muted" }, "No matches")) : k ? /* @__PURE__ */ e.createElement(e.Fragment, null, !l && /* @__PURE__ */ e.createElement("h2", null, k.page, " ", /* @__PURE__ */ e.createElement("span", { className: "muted" }, "Tab.cpp:", k.line)), k.groups.map((n) => {
173
+ const b = n.options.filter((v) => Q(v, u) && !(l && v.startsWith("<")));
174
+ return b.length ? /* @__PURE__ */ e.createElement("section", { key: n.group + n.line }, /* @__PURE__ */ e.createElement("h3", null, n.group || "(untitled group)", " ", /* @__PURE__ */ e.createElement("span", { className: "muted" }, ":", n.line)), b.map(m)) : null;
175
+ })) : /* @__PURE__ */ e.createElement("p", { className: "muted" }, "No builder"))));
177
176
  }
178
177
  export {
179
178
  re as default
@@ -1,10 +1,10 @@
1
- /* @three-slicer/components — SettingsPanel 스타일.
2
- Shadow DOM 격리(shadow_host.jsx): ?inline 으로 번들에 내장돼 패널의 shadow root 에
3
- 주입된다. 호스트 앱 CSS 와 양방향 무간섭 — 클래스명 충돌이 불가능하다. */
1
+ /* @three-slicer/components — SettingsPanel styles.
2
+ Shadow DOM isolation (shadow_host.jsx): inlined into the bundle via ?inline and injected into the
3
+ panel's shadow root. Fully isolated from host app CSS in both directions — class names cannot collide. */
4
4
  :host { display: contents; }
5
5
  * { box-sizing: border-box; }
6
6
 
7
- /* ---- 옵션 행 원자 ---- */
7
+ /* ---- Option row atoms ---- */
8
8
  .row { display: grid; grid-template-columns: 260px 220px 70px 1fr; gap: 8px;
9
9
  align-items: center; padding: 3px 0; border-bottom: 1px dashed #f0f0f0; }
10
10
  .row .lbl { color: #222; text-decoration: none; }
@@ -23,7 +23,7 @@
23
23
  .muted { color: #999; font-weight: 400; font-size: 12px; }
24
24
  .row.custom { grid-template-columns: 1fr; }
25
25
 
26
- /* 25단계 S5: toggle-rules 비활성 + dirty/리셋 */
26
+ /* Stage 25 S5: toggle-rules disabling + dirty/reset */
27
27
  .row.disabled { opacity: 0.42; }
28
28
  .row.disabled input, .row.disabled select, .row.disabled textarea { cursor: not-allowed; background: #f2f2f2; }
29
29
  .lbl-cell { display: inline-flex; align-items: center; gap: 5px; min-width: 0; }
@@ -34,7 +34,7 @@
34
34
  border-radius: 5px; cursor: pointer; font-size: 13px; line-height: 1; padding: 1px 5px; }
35
35
  .reset-btn:hover { background: #f3e2cb; }
36
36
 
37
- /* ---- 패널 셸 ---- */
37
+ /* ---- Panel shell ---- */
38
38
  .settings-panel { flex: 0 0 400px; width: 400px; border-left: 1px solid #e2e2e2;
39
39
  display: flex; flex-direction: column; min-height: 0; background: #fff; color: #222;
40
40
  font: 14px/1.5 -apple-system, "Segoe UI", sans-serif; }
@@ -52,7 +52,7 @@
52
52
  .settings-panel .sp-body { flex: 1 1 auto; overflow-y: auto; padding: 8px 14px 24px; }
53
53
  .settings-panel .sp-body h2 { font-size: 16px; }
54
54
 
55
- /* 사이드바 폭이 좁으므로 옵션 행을 세로 스택으로 재배치 */
55
+ /* The sidebar is narrow, so option rows are re-stacked vertically */
56
56
  .settings-panel .row { grid-template-columns: 1fr auto; grid-template-areas: "lbl badge" "key widget";
57
57
  row-gap: 2px; padding: 6px 0; }
58
58
  .settings-panel .row .lbl { grid-area: lbl; font-weight: 600; }
@@ -66,11 +66,11 @@
66
66
  .settings-panel .row.custom { grid-template-columns: 1fr; grid-template-areas: "lbl"; }
67
67
  .sp-search { width: 100%; box-sizing: border-box; padding: 6px 9px; border: 1px solid #d6d6d6; border-radius: 6px; font-size: 13px; margin-bottom: 8px; }
68
68
 
69
- /* ---- 임베드 변형 (:host 에 sp-embedded — viewer process-card 등 카드 안 전폭 배치) ---- */
69
+ /* ---- Embedded variant (sp-embedded on :host — full width inside cards such as the viewer process-card) ---- */
70
70
  :host(.sp-embedded) .settings-panel { position: static; width: auto; flex: none; border: 0; height: auto; }
71
71
  :host(.sp-embedded) .sp-body { padding: 4px 2px 8px; max-height: 460px; }
72
72
 
73
- /* ---- BedShapeEditor (printable_area 커스텀 위젯) ---- */
73
+ /* ---- BedShapeEditor (printable_area custom widget) ---- */
74
74
  .bse { display: flex; flex-direction: column; gap: 6px; min-width: 0; max-width: 220px; }
75
75
  .bse-modes { display: flex; gap: 4px; }
76
76
  .bse-modes button { border: 1px solid #e0e0e0; background: #fafafa; border-radius: 6px;
@@ -627,5 +627,5 @@
627
627
  "line": 1533
628
628
  }
629
629
  ],
630
- "note": "조건에 없는 opt_key(기본 분기)는 전체 무효화. steps는 분기 블록에서 참조된 단계 토큰."
630
+ "note": "An opt_key absent from the conditions (the default branch) invalidates everything. steps are the step tokens referenced in the branch block."
631
631
  }
@@ -1,7 +1,7 @@
1
1
  // three-slicer headless SDK example + independence proof: drive the engine from Node using ONLY the
2
2
  // public API (no UI, no viewer). Generates a 20mm cube STL, slices it, prints stats + G-code length.
3
3
  // run: node packages/engine/examples/headless.mjs
4
- import { createSlicer } from 'three-slicer' // 워크스페이스 심링크로 해석 — 외부 소비자와 동일한 경로
4
+ import { createSlicer } from 'three-slicer' // resolved via the workspace symlink — same path an external consumer takes
5
5
 
6
6
  // minimal binary-STL writer for a box (public API takes a binary-STL ArrayBuffer)
7
7
  function boxTris(ox, oy, oz, sx, sy, sz) {
@@ -27,7 +27,7 @@ const slicer = await createSlicer()
27
27
  const r = slicer.slice(cube, params)
28
28
  console.log(`batch : layers=${r.stats.layers} segments=${r.stats.path_segments} filament=${r.stats.filament_mm.toFixed(1)}mm gcode=${r.gcode.length} chars`)
29
29
 
30
- // (b) streaming slice via onLayer (30단계) — assemble G-code from per-layer chunks
30
+ // (b) streaming slice via onLayer (stage 30) — assemble G-code from per-layer chunks
31
31
  let chunks = 0, gbytes = 0
32
32
  const rs = slicer.slice(cube, params, { onLayer: ({ gcode }) => { chunks++; gbytes += gcode.length } })
33
33
  console.log(`stream: layers=${chunks} streamed=${rs.stats.streamed} assembled-gcode=${gbytes} chars`)
package/engine/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- // Type definitions for @three-slicer/engine
1
+ // Type definitions for three-slicer (engine entry point)
2
+ export type { SlicerSettings, SettingKey, Point } from '../types/settings-keys.d.ts'
3
+
2
4
  export interface SliceLayer { z: number; idx: number; gcode: string; paths: Float32Array; widths: Float32Array }
3
5
  export interface SliceCallbacks {
4
6
  onProgress?: (done: number, total: number) => void
package/engine/index.js CHANGED
@@ -7,7 +7,7 @@ import createModule from './src/slicer_core.js'
7
7
  const u8 = (b) => (b instanceof Uint8Array ? b : new Uint8Array(b))
8
8
 
9
9
  // Off-main-thread worker URL for the browser: `new Worker(engineWorkerURL(), { type: 'module' })`.
10
- // The worker speaks the 30단계 streaming protocol ({type:'layer'|'done'|'error'|'progress'}).
10
+ // The worker speaks the stage-30 streaming protocol ({type:'layer'|'done'|'error'|'progress'}).
11
11
  export const engineWorkerURL = () => new URL('./src/slicer.worker.js', import.meta.url)
12
12
 
13
13
  // createSlicer(): load the WASM kernel and return a handle. Works in Node and the browser main thread.
@@ -15,7 +15,7 @@ export const engineWorkerURL = () => new URL('./src/slicer.worker.js', import.me
15
15
  // - stl: ArrayBuffer|Uint8Array (binary STL)
16
16
  // - params: kernel-params object OR JSON string (see web/GUIDE.md; use deriveKernelParams to build from UI settings)
17
17
  // - onProgress(done,total): per-layer progress
18
- // - onLayer({z,idx,gcode,paths,widths}): 30단계 streaming — emits + frees each layer; when set, the
18
+ // - onLayer({z,idx,gcode,paths,widths}): stage-30 streaming — emits + frees each layer; when set, the
19
19
  // returned result carries stats only (assemble gcode/layers from the callbacks). When omitted, the
20
20
  // result carries the whole { gcode, stats, layers }.
21
21
  // paintPrepare/paint/paintClear/overlay: manual support enforcer/blocker painting (TriangleSelector).
@@ -0,0 +1,12 @@
1
+ // The single import site for the 4 extracted JSON files.
2
+ //
3
+ // Why funnel them through one module: JSON requires `with { type: 'json' }` in Node 22+ ESM, but
4
+ // Vite/esbuild strip that attribute from bundle output (neither build.target nor importAttributesKey
5
+ // prevents it — measured). So if the same JSON is pointed at by both "the original with the attribute
6
+ // (engine/src/*.js)" and "the bundle without it (components/dist)", the consumer's bundler warns with
7
+ // "inconsistent import attributes". Only this file holds the JSON; everything else imports this module
8
+ // as external -> no JSON import survives in the bundle, so the mismatch cannot arise.
9
+ export { default as schema } from 'three-slicer/data/config-schema.json' with { type: 'json' }
10
+ export { default as uiTree } from 'three-slicer/data/ui-tree.json' with { type: 'json' }
11
+ export { default as toggleRules } from 'three-slicer/data/toggle-rules.json' with { type: 'json' }
12
+ export { default as invalidationMap } from 'three-slicer/data/invalidation-map.json' with { type: 'json' }