three-slicer 0.1.1 → 0.1.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.
Files changed (39) hide show
  1. package/components/dist/SettingsPanel.js +112 -112
  2. package/components/styles.css +9 -9
  3. package/data/config-schema.json +999 -900
  4. package/data/invalidation-map.json +6 -5
  5. package/data/printers.json +1 -0
  6. package/data/processes.js +2 -0
  7. package/data/toggle-rules.json +44 -44
  8. package/data/ui-tree.json +170 -494
  9. package/engine/examples/headless.mjs +2 -2
  10. package/engine/index.js +2 -2
  11. package/engine/src/data.js +13 -7
  12. package/engine/src/settings.js +120 -35
  13. package/engine/src/slicer.worker.js +23 -21
  14. package/engine/src/slicer_core.js +0 -0
  15. package/engine/src/slicer_core.mt.js +0 -0
  16. package/engine/src/toggle_eval.js +11 -11
  17. package/package.json +4 -2
  18. package/types/components.d.ts +5 -5
  19. package/types/data/config-schema.json.d.ts +6 -6
  20. package/types/data/invalidation-map.json.d.ts +5 -5
  21. package/types/data/printers.json.d.ts +23 -0
  22. package/types/data/processes.js.d.ts +21 -0
  23. package/types/data/toggle-rules.json.d.ts +5 -5
  24. package/types/data/ui-tree.json.d.ts +3 -3
  25. package/types/data.d.ts +10 -7
  26. package/types/gen_settings_types.mjs +10 -10
  27. package/types/settings-keys.d.ts +18 -7
  28. package/types/settings.d.ts +6 -6
  29. package/types/toggle.d.ts +6 -6
  30. package/types/viewer-loaders.d.ts +8 -8
  31. package/types/viewer-toolpath.d.ts +17 -17
  32. package/types/viewer.d.ts +3 -3
  33. package/types/wasm.d.ts +3 -3
  34. package/types/worker.d.ts +2 -2
  35. package/viewer/dist/Viewport.js +1760 -966
  36. package/viewer/dist/make_worker.js +6 -6
  37. package/viewer/dist/model_loaders.js +35 -35
  38. package/viewer/dist/toolpath_gpu.js +146 -147
  39. package/viewer/styles.css +46 -25
@@ -1,1165 +1,1959 @@
1
- import n, { useRef as g, useState as x, useLayoutEffect as aa, useEffect as Be } from "react";
2
- import * as h from "three";
3
- import { OrbitControls as oa } from "three/examples/jsm/controls/OrbitControls.js";
4
- import { TransformControls as la } from "three/examples/jsm/controls/TransformControls.js";
5
- import { deriveKernelParams as Kt, settingRaw as wt } from "three-slicer/settings";
6
- import { makeSlicerWorker as sa } from "./make_worker.js";
7
- import { createPortal as ia } from "react-dom";
8
- import { VIEW_TYPES as ca, DEFAULT_RANGES_COLORS as da, roleRatios as Xn, buildSegmentData as pa, makeToolpath as ua, computeColors as Kn } from "./toolpath_gpu.js";
9
- import { SUPPORTED_EXT as Yt, splitConnectedComponents as ma, fileExt as fa, loadModel as ha } from "./model_loaders.js";
10
- function ga({ css: P, className: F, children: L }) {
11
- const ee = g(null), [d] = x(() => {
12
- const A = document.createElement("div");
13
- return A.style.display = "contents", A;
1
+ import e, { useRef as H, useState as $, useLayoutEffect as Ln, useEffect as Ve, useMemo as Tt } from "react";
2
+ import { deriveKernelParams as xt, settingRaw as Je, printersByVendor as On, processPresets as Tn, printerDefaultPreset as Bn, printerKeys as lt, machineLimitKeys as Dn, printerSettings as Zn } from "three-slicer/settings";
3
+ import { schema as Bt } from "three-slicer/data";
4
+ import { createPortal as In } from "react-dom";
5
+ import { SUPPORTED_EXT as bt, fileExt as Vn, loadModel as Fn, splitConnectedComponents as Gn } from "./model_loaders.js";
6
+ import * as N from "three";
7
+ import { OrbitControls as Un } from "three/examples/jsm/controls/OrbitControls.js";
8
+ import { TransformControls as $n } from "three/examples/jsm/controls/TransformControls.js";
9
+ import { makeSlicerWorker as Hn } from "./make_worker.js";
10
+ import { roleRatios as It, computeColors as Dt, buildSegmentData as qn, makeToolpath as Wn, VIEW_TYPES as Xn, DEFAULT_RANGES_COLORS as Kn } from "./toolpath_gpu.js";
11
+ function Yn({ css: n, className: a, children: c }) {
12
+ const l = H(null), [r] = $(() => {
13
+ const m = document.createElement("div");
14
+ return m.style.display = "contents", m;
14
15
  });
15
- return aa(() => {
16
- const A = ee.current, S = A.shadowRoot || A.attachShadow({ mode: "open" });
17
- let w = S.querySelector("style[data-ts-css]");
18
- w || (w = document.createElement("style"), w.setAttribute("data-ts-css", ""), S.append(w)), w.textContent = P, S.contains(d) || S.append(d);
19
- }, [P, d]), /* @__PURE__ */ n.createElement("div", { ref: ee, className: F }, ia(L, d));
16
+ return Ln(() => {
17
+ const m = l.current, d = m.shadowRoot || m.attachShadow({ mode: "open" });
18
+ let w = d.querySelector("style[data-ts-css]");
19
+ w || (w = document.createElement("style"), w.setAttribute("data-ts-css", ""), d.append(w)), w.textContent = n, d.contains(r) || d.append(r);
20
+ }, [n, r]), /* @__PURE__ */ e.createElement("div", { ref: l, className: a }, In(c, r));
20
21
  }
21
- const ba = ":host{display:contents}*{box-sizing:border-box}.app-shell{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;background:#0e1216;color:#cdd6dd;font:14px/1.5 -apple-system,Segoe UI,sans-serif}.app-body{flex:1 1 auto;display:flex;min-height:0}.topbar{flex:0 0 44px;height:44px;display:flex;align-items:center;padding:0 12px;gap:10px;background:#161b21;border-bottom:1px solid #262d35}.topbar .tb-left,.topbar .tb-right{flex:1 1 0;display:flex;align-items:center;gap:8px}.topbar .tb-right{justify-content:flex-end}.tb-logo{font-size:15px;color:#e6edf3;letter-spacing:.01em}.tb-logo b{color:#00ae42}.tb-re{font-size:10px;background:#00ae42;color:#fff;padding:1px 5px;border-radius:4px;margin-left:2px}.tb-btn{display:inline-flex;align-items:center;gap:5px;background:#20272f;border:1px solid #2c353d;color:#cdd6dd;border-radius:6px;padding:4px 10px;cursor:pointer;font-size:12px}.tb-btn img{width:15px;height:15px}.tb-btn:hover{background:#28313a}.tb-tabs{display:flex;border:1px solid #2c353d;border-radius:8px;overflow:hidden;flex:0 0 auto}.tb-tabs button{padding:6px 24px;border:0;background:#1a2027;color:#9fb3c0;font-weight:600;font-size:13px;cursor:pointer}.tb-tabs button.on{background:#00ae42;color:#fff}.tb-tabs button:disabled{color:#5a6570;cursor:not-allowed}.tb-icon{width:28px;height:28px;border:1px solid #2c353d;background:#20272f;color:#7d8b96;border-radius:6px;font-size:15px;cursor:not-allowed}.left-rail{flex:0 0 48px;display:flex;flex-direction:column;align-items:center;gap:6px;padding:9px 0;background:#12171c;border-right:1px solid #232a31}.left-rail button{width:36px;height:36px;border:1px solid transparent;border-radius:8px;background:transparent;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0}.left-rail button img{width:21px;height:21px;opacity:.72}.left-rail button:hover{background:#1e252c}.left-rail button.on{background:#00ae42}.left-rail button.on img{opacity:1;filter:brightness(1.6)}.rail-sep{width:26px;height:1px;background:#2a323a;margin:3px 0}.viewport-col{position:relative;flex:1 1 auto;min-width:0;background:#161a1e}.vp-top-toolbar{position:absolute;top:12px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:3px;background:#12171cd9;padding:4px;border-radius:9px;border:1px solid #2a323a;z-index:3;max-width:calc(100% - 24px);overflow-x:auto;scrollbar-width:none}.vp-top-toolbar::-webkit-scrollbar{display:none}.vp-top-toolbar>*{flex:0 0 auto}.vp-top-toolbar button{width:32px;height:32px;border:0;background:transparent;border-radius:6px;cursor:pointer;display:flex;align-items:center;justify-content:center}.vp-top-toolbar button img{width:18px;height:18px;opacity:.82}.vp-top-toolbar button:hover:not(:disabled){background:#2a333c}.vp-top-toolbar button:disabled{opacity:.32;cursor:not-allowed}.vtt-sep{width:1px;align-self:stretch;background:#333c45;margin:3px}.plate-bar{position:absolute;right:14px;bottom:30px;display:flex;align-items:center;gap:4px;z-index:3;background:#12171cd9;border:1px solid #2a323a;border-radius:9px;padding:4px}.plate-bar button{min-width:26px;height:26px;border:1px solid #2c353d;background:#1a2027;color:#9fb3c0;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600}.plate-tab.on{background:#00ae42;color:#fff;border-color:#00ae42}.plate-add{color:#7ad19a!important}.plate-del{color:#e08a8a!important}.plate-bar button:disabled{opacity:.4;cursor:not-allowed}.slice-dd{position:relative;flex:1.4}.slice-dd .slice-btn{width:100%}.slice-menu{position:absolute;bottom:calc(100% + 6px);left:0;right:0;background:#fff;border:1px solid #d5d8dd;border-radius:8px;box-shadow:0 4px 16px #0000002e;overflow:hidden;z-index:10}.slice-menu button{display:block;width:100%;text-align:left;padding:9px 12px;border:0;background:#fff;color:#24282e;cursor:pointer;font-size:13px}.slice-menu button:hover{background:#eef7f1}.brush-panel{position:absolute;top:58px;left:14px;width:214px;background:#161b21f2;border:1px solid #2c353d;border-radius:10px;padding:10px;z-index:4;box-shadow:0 3px 14px #00000073}.bp-title{font-weight:600;margin-bottom:6px;color:#cdd6dd}.bp-modes{display:flex;gap:4px;flex-wrap:wrap}.bp-modes button{padding:3px 9px;border:1px solid #2c353d;background:#1a2027;color:#cdd6dd;border-radius:5px;cursor:pointer;font-size:12px}.bp-modes button.enf.on{background:#2b6cff;color:#fff;border-color:#2b6cff}.bp-modes button.blk.on{background:#e23b3b;color:#fff;border-color:#e23b3b}.bp-radius{display:block;font-size:12px;color:#aebcc6;margin-top:8px}.bp-radius input{width:100%}.bp-counts{margin-top:6px;font-size:11px}.stats-card{position:absolute;left:14px;bottom:32px;background:#161b21ed;border:1px solid #2c353d;border-radius:10px;padding:10px 14px;font-size:13px;line-height:1.7;z-index:2;color:#cdd6dd}.stats-card b{color:#9fe3bd}.sidebar{flex:0 0 384px;width:384px;display:flex;flex-direction:column;background:#eef0f2;color:#24282e;border-left:1px solid #d5d8dd;min-height:0}.sidebar-scroll{flex:1 1 auto;overflow-y:auto;padding:10px;display:flex;flex-direction:column;gap:10px}.side-card{background:#fff;border:1px solid #e2e4e8;border-radius:10px;padding:10px 12px}.sc-head{font-weight:700;font-size:13px;color:#2a2f36;display:flex;align-items:center;gap:6px;margin-bottom:7px}.sc-count{background:#00ae42;color:#fff;font-size:10px;border-radius:8px;padding:0 6px;font-weight:700}.sc-head-btns{margin-left:auto;display:flex;gap:4px}.sc-head-btns button{width:22px;height:22px;border:1px solid #d3d6db;background:#f6f7f8;border-radius:5px;cursor:pointer;font-size:14px;line-height:1;color:#333}.sc-head-btns button:disabled{opacity:.4;cursor:not-allowed}.sc-info{display:flex;justify-content:space-between;font-size:12px;color:#5a6069;padding:2px 0}.sc-info b{color:#24282e}.side-warn{margin:0 2px;font-size:12px}.filament-row{display:flex;align-items:center;gap:8px;padding:3px 0}.filament-row input[type=color]{width:28px;height:22px;border:1px solid #ccc;border-radius:5px;padding:0;cursor:pointer;background:none}.fil-t{font-weight:700;font-size:12px;width:20px}.fil-swatch{width:15px;height:15px;border-radius:4px;border:1px solid rgba(0,0,0,.15)}.fil-hex{font-size:11px;margin-left:auto}.obj-list2{list-style:none;margin:0 0 6px;padding:0}.obj-list2 li{display:flex;align-items:center;gap:6px;padding:3px 0;font-size:12px}.obj-list2 li.obj-hidden{opacity:.5}.obj-eye{border:0;background:none;cursor:pointer;font-size:13px;padding:0}.obj-list2 .obj-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#24282e}.obj-list2 .obj-ext{border:1px solid #d0d3d8;border-radius:5px;font-size:11px;padding:1px 3px;background:#fff}.obj-del{border:0;background:#fbe9ec;color:#d0435a;border-radius:5px;cursor:pointer;padding:0 6px}.side-card .slice-support{color:#444;font-size:12px;margin:5px 0 0}.process-card{padding:8px}.side-card .view-type-row{color:#444}.side-card .view-type-row select,.side-card .obj-ext{background:#fff;color:#222;border:1px solid #ccd0d5}.side-card .slice-layer label{color:#444;font-size:12px}.side-card .grad-title{color:#333}.side-card .grad-minmax{color:#666}.side-card .role-legend,.side-card .slice-travel{color:#555}.side-card .layer-ctl button{background:#f1f2f4;color:#333;border:1px solid #d3d6db}.side-card .layer-ctl button.on{background:#00ae42;color:#fff;border-color:#00ae42}.side-bottom{flex:0 0 auto;display:flex;gap:8px;padding:10px;border-top:1px solid #dcdfe3;background:#e6e9ec}.side-bottom .slice-btn{flex:1.4;margin:0}.auto-slice{display:flex;align-items:center;gap:5px;font-size:12px;color:#444;cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none}.auto-slice input{margin:0}.export-btn{flex:1;text-align:center;padding:9px;border-radius:8px;background:#dfe6ea;color:#1f6feb;border:1px solid #c7d2da;text-decoration:none;font-weight:600;cursor:pointer;font-size:13px}.export-btn:hover{background:#d3dee4}.export-btn:disabled{color:#9aa4ad;background:#e9ecef;cursor:not-allowed}.vp-canvas{position:absolute;top:0;right:0;bottom:0;left:0}.vp-canvas canvas{display:block}.vp-canvas.fail{display:flex;align-items:center;justify-content:center}.vp-fallback{color:#f0c9a8;padding:24px;text-align:center;max-width:560px;line-height:1.5}.vp-status{position:absolute;bottom:8px;left:12px;right:12px;z-index:1;font-size:11.5px;color:#9fe3bd;font-family:ui-monospace,monospace;text-shadow:0 1px 2px #000;pointer-events:none}.slice-warn{color:#f0c060;margin:4px 0}.slice-btn{width:100%;margin-top:6px;padding:7px;border:0;border-radius:7px;background:#00ae42;color:#fff;font-weight:700;cursor:pointer}.slice-btn:disabled{background:#3a4650;color:#8b98a2;cursor:not-allowed}.slice-err{color:#ff8b8b;margin-top:6px}.slice-stats{margin-top:8px;line-height:1.5}.slice-stats b{color:#9fe3bd}.slice-layer{margin-top:8px;display:flex;flex-direction:column;gap:4px}.slice-layer input[type=range]{width:100%}.slice-travel{color:#aebcc6;display:flex;align-items:center;gap:4px}.view-type-row{display:flex;align-items:center;justify-content:space-between;gap:6px;color:#aebcc6}.view-type-row select{flex:1;background:#10141a;color:#eee;border:1px solid #2c353d;border-radius:4px;padding:2px 4px}.dual-slider{position:relative;height:22px}.dual-slider input[type=range]{position:absolute;left:0;top:0;width:100%;margin:0;pointer-events:none;background:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.dual-slider input[type=range]::-webkit-slider-thumb{pointer-events:auto;-webkit-appearance:none;width:13px;height:13px;border-radius:50%;background:#00ae42;border:2px solid #0c1013;cursor:pointer}.dual-slider input[type=range]::-moz-range-thumb{pointer-events:auto;width:13px;height:13px;border-radius:50%;background:#00ae42;border:2px solid #0c1013;cursor:pointer}.dual-slider input[type=range]::-webkit-slider-runnable-track{height:4px;background:#2c353d;border-radius:2px}.dual-slider input[type=range]:first-child{z-index:2}.layer-ctl{display:flex;align-items:center;gap:8px}.layer-ctl button{padding:2px 8px;border:1px solid #2c353d;border-radius:5px;background:#1a2027;color:#cdd6dd;font-size:11px;cursor:pointer}.layer-ctl button.on{background:#00ae42;color:#fff;border-color:#00ae42}.grad-legend{margin-top:6px}.grad-title{font-size:11px;color:#cdd6dd;margin-bottom:3px}.grad-bar{height:10px;border-radius:3px;border:1px solid #2c353d}.grad-minmax{display:flex;justify-content:space-between;font-size:10px;color:#aebcc6;margin-top:2px}.role-legend{display:flex;flex-wrap:wrap;gap:4px 10px;margin-top:6px;font-size:11px;color:#aebcc6}.role-item{display:inline-flex;align-items:center;gap:4px}.role-item i{width:10px;height:6px;border-radius:2px;display:inline-block}.role-item b{color:#cdd6dd}.vp-canvas.drag-over{outline:3px dashed #00ae42;outline-offset:-6px}.empty-hint{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;pointer-events:none;color:#9fb3c0;text-align:center}.empty-hint .eh-icon{font-size:46px;opacity:.8}.empty-hint .eh-title{font-size:16px;font-weight:600;color:#cdd6dd}.empty-hint .eh-sub{font-size:12px;color:#7d8b96;letter-spacing:.04em}.empty-hint .eh-btn{pointer-events:auto;margin-top:8px;padding:7px 18px;border:0;border-radius:7px;background:#00ae42;color:#fff;font-weight:600;cursor:pointer}.empty-hint .eh-btn:hover{background:#06c04d}.drop-overlay{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;background:#00ae4224;color:#d7f7e3;font-size:18px;font-weight:700;pointer-events:none;border:3px dashed #00ae42;box-sizing:border-box}.obj-split{border:0;background:transparent;cursor:pointer;padding:0 3px;line-height:0}.obj-split img{width:13px;height:13px;opacity:.6}.obj-split:hover img{opacity:1}.ctx-scrim{position:absolute;top:0;right:0;bottom:0;left:0;z-index:24}.ctx-menu{position:absolute;z-index:25;min-width:168px;padding:4px;background:#1b2027;border:1px solid #2f3944;border-radius:8px;box-shadow:0 8px 24px #00000080}.ctx-menu button{display:flex;width:100%;align-items:center;justify-content:space-between;gap:16px;padding:6px 10px;border:0;border-radius:5px;background:transparent;color:#dfe6ee;font-size:12px;text-align:left;cursor:pointer}.ctx-menu button:hover:not(:disabled){background:#273140}.ctx-menu button:disabled{color:#64707d;cursor:not-allowed}.ctx-menu button.danger:hover:not(:disabled){background:#4a2229;color:#ffb3b3}.ctx-menu hr{margin:4px 6px;border:0;border-top:1px solid #2f3944}.ctx-key{color:#7d8b96;font-family:ui-monospace,monospace;font-size:11px}.help-overlay{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;background:#080c10b8;z-index:20}.help-card{background:#1b2027;color:#dfe6ee;border:1px solid #2f3944;border-radius:10px;padding:18px 22px;max-width:720px;box-shadow:0 12px 40px #00000080}.help-card h3{margin:0 0 12px;font-size:15px}.help-card h4{margin:0 0 6px;font-size:12px;color:#00ae42;text-transform:uppercase;letter-spacing:.04em}.help-cols{display:flex;gap:26px;flex-wrap:wrap}.help-card dl{display:grid;grid-template-columns:auto 1fr;gap:3px 12px;margin:0;font-size:12px}.help-card dt{color:#9fb0c2;font-family:ui-monospace,monospace;white-space:nowrap}.help-card dd{margin:0}.slice-support{display:flex;align-items:center;gap:5px;margin:6px 0;color:#cdd6dd;font-size:12px}.slice-support .muted{font-size:10px}.sc-bed{display:inline-flex;align-items:center;gap:4px;font-weight:700;color:#222}.sc-bed input{width:54px;padding:1px 4px;font-size:12px;font-weight:600;text-align:right;border:1px solid #ccd0d5;border-radius:5px;background:#fff;color:#222}", xa = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Zm3-6h3m2,0h3m-4-4v8" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), va = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M33.5,35H5.5A1.5,1.5,0,0,1,4,33.5V5.5A1.5,1.5,0,0,1,5.5,4h28A1.5,1.5,0,0,1,35,5.5v28A1.5,1.5,0,0,1,33.5,35ZM5.5,5a.5.5,0,0,0-.5.5v28a.5.5,0,0,0,.5.5h28a.5.5,0,0,0,.5-.5V5.5a.5.5,0,0,0-.5-.5Z" style="fill:#b6b6b6"/><path d="M30.5,32H8.5A1.5,1.5,0,0,1,7,30.5v-6A1.5,1.5,0,0,1,8.5,23h22A1.5,1.5,0,0,1,32,24.5v6A1.5,1.5,0,0,1,30.5,32Zm-22-8a.5.5,0,0,0-.5.5v6a.5.5,0,0,0,.5.5h22a.5.5,0,0,0,.5-.5v-6a.5.5,0,0,0-.5-.5Zm22-2h-6A1.5,1.5,0,0,1,23,20.5V8.5A1.5,1.5,0,0,1,24.5,7h6A1.5,1.5,0,0,1,32,8.5v12A1.5,1.5,0,0,1,30.5,22Zm-6-14a.5.5,0,0,0-.5.5v12a.5.5,0,0,0,.5.5h6a.5.5,0,0,0,.5-.5V8.5a.5.5,0,0,0-.5-.5Zm-6,6h-8a3.5,3.5,0,0,1,0-7h8a3.5,3.5,0,0,1,0,7Zm-8-6a2.5,2.5,0,0,0,0,5h8a2.5,2.5,0,0,0,0-5Zm8,14A3.5,3.5,0,1,1,22,18.5,3.5,3.5,0,0,1,18.5,22Zm0-6A2.5,2.5,0,1,0,21,18.5,2.5,2.5,0,0,0,18.5,16Zm-8,6A3.5,3.5,0,1,1,14,18.5,3.5,3.5,0,0,1,10.5,22Zm0-6A2.5,2.5,0,1,0,13,18.5,2.5,2.5,0,0,0,10.5,16Z" style="fill:#009688"/></svg>'), wa = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M6.2,8a2.6,2.6,0,1,0,5.2,0a2.6,2.6,0,1,0,-5.2,0" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M4.6,8a2.6,2.6,0,1,0,5.2,0a2.6,2.6,0,1,0,-5.2,0" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), ya = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M3.5,9h2m2,0h2m2,0h2" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M8,3.5l1.8,3h-3.6z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), ka = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Zm3-6h8" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), Ea = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M5,5l6,6" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M11,5l-6,6" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), Ma = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M4,4h5v5H4z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M7,7h5v5H7z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), Ca = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M2.5,8h11" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M2.5,6v4" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M13.5,6v4" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M6,7v2" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M10,7v2" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), ja = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M4,8a4,4,0,1,0,8,0a4,4,0,1,0,-8,0" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M8,4a4,4,0,0,1,0,8z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1;fill:#949494;stroke:none"/></svg>'), Na = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M34.854,19.146l-4-4a.5.5,0,0,0-.708.708L33.293,19H28.5a.5.5,0,0,0,0,1h4.793l-3.147,3.146a.5.5,0,0,0,.708.708l4-4A.5.5,0,0,0,34.854,19.146Z" style="fill:#009688"/><path d="M19.854,4.146a.5.5,0,0,0-.708,0l-4,4a.5.5,0,0,0,.708.708L19,5.707V10.5a.5.5,0,0,0,1,0V5.707l3.146,3.147a.5.5,0,0,0,.708-.708Z" style="fill:#009688"/><path d="M23.146,30.146,20,33.293V28.5a.5.5,0,0,0-1,0v4.793l-3.146-3.147a.5.5,0,0,0-.708.708l4,4a.5.5,0,0,0,.708,0l4-4a.5.5,0,0,0-.708-.708Z" style="fill:#009688"/><path d="M10.5,19H5.707l3.147-3.146a.5.5,0,0,0-.708-.708l-4,4a.5.5,0,0,0,0,.708l4,4a.5.5,0,0,0,.708-.708L5.707,20H10.5a.5.5,0,0,0,0-1Z" style="fill:#009688"/><path d="M24.5,26h-10A1.5,1.5,0,0,1,13,24.5v-10A1.5,1.5,0,0,1,14.5,13h10A1.5,1.5,0,0,1,26,14.5v10A1.5,1.5,0,0,1,24.5,26Zm-10-12a.5.5,0,0,0-.5.5v10a.5.5,0,0,0,.5.5h10a.5.5,0,0,0,.5-.5v-10a.5.5,0,0,0-.5-.5Z" style="fill:#b6b6b6"/></svg>'), _a = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M5,5h6v6H5z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1;stroke-dasharray:2 1.5"/></svg>'), Aa = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M8,3.5v5m-2-2 2,2 2-2" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M4,11.5h8" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), Sa = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M34.923,10.25a.392.392,0,0,0-.033-.05.5.5,0,0,0-.2-.162l0,0-15-6a.5.5,0,0,0-.372,0l-15,6,0,0a.5.5,0,0,0-.2.162.392.392,0,0,0-.033.05A.478.478,0,0,0,4,10.5v18a.464.464,0,0,0,.028.138.429.429,0,0,0,.011.054l.01.015a.51.51,0,0,0,.1.144l.008,0a.5.5,0,0,0,.156.1l0,0,14.989,6,0,0,.006,0a.5.5,0,0,0,.372,0l.006,0,0,0,1.989-.8a.5.5,0,0,0-.372-.928L20,33.762V16.839l14-5.6V21.5a.5.5,0,0,0,1,0v-11A.481.481,0,0,0,34.923,10.25ZM19,33.762l-14-5.6V11.239l14,5.6Zm.5-17.8L5.846,10.5,19.5,5.039,33.153,10.5Z" style="fill:#b6b6b6"/><path d="M33.5,24h-8A1.5,1.5,0,0,0,24,25.5v8A1.5,1.5,0,0,0,25.5,35h8A1.5,1.5,0,0,0,35,33.5v-8A1.5,1.5,0,0,0,33.5,24Zm.5,9.5a.5.5,0,0,1-.5.5h-8a.5.5,0,0,1-.5-.5v-8a.5.5,0,0,1,.5-.5h8a.5.5,0,0,1,.5.5Z" style="fill:#009688"/><path d="M32.5,29H30V26.5a.5.5,0,0,0-1,0V29H26.5a.5.5,0,0,0,0,1H29v2.5a.5.5,0,0,0,1,0V30h2.5a.5.5,0,0,0,0-1Z" style="fill:#009688"/></svg>'), za = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M25.5,22a.5.5,0,0,1-.5-.5V18H18v3.5a.5.5,0,0,1-1,0v-6a.5.5,0,0,1,.084-.277l2-3A.5.5,0,0,1,19.5,12h4a.5.5,0,0,1,.416.223l2,3A.5.5,0,0,1,26,15.5v6A.5.5,0,0,1,25.5,22ZM18,17h7V15.651L23.232,13H19.768L18,15.651Z" style="fill:#009688"/><path d="M11.964,29.314a.5.5,0,0,0-.65-.278L7.152,30.7a17.321,17.321,0,0,1,3.739-6.888.5.5,0,1,0-.782-.624A19.312,19.312,0,0,0,6.359,29.8L4.964,26.314a.5.5,0,1,0-.928.372l2,5c0,.01.016.015.021.025a.475.475,0,0,0,.15.185c.009.007.016.017.025.023a.5.5,0,0,0,.2.076A.367.367,0,0,0,6.5,32h0a.475.475,0,0,0,.181-.036h0l5-2A.5.5,0,0,0,11.964,29.314Z" style="fill:#009688"/><rect x="4" y="34" width="31" height="1" style="fill:#009688"/><path d="M34.854,17.146l-13-13a.5.5,0,0,0-.708,0l-13,13a.5.5,0,0,0,.708.708L21.5,5.207,33.793,17.5,21.146,30.146a.5.5,0,0,0,.708.708l13-13A.5.5,0,0,0,34.854,17.146Z" style="fill:#b6b6b6"/><path d="M21.5,31a.5.5,0,0,1-.354-.146l-13-13a.5.5,0,0,1,.708-.708l13,13A.5.5,0,0,1,21.5,31Z" style="fill:#009688"/></svg>'), Ra = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M27.878,34.926h-4.4c-.509,0-.845,0-.955-.394a.593.593,0,0,1,.36-.67,2.3,2.3,0,0,0,1.2-2.189,3.253,3.253,0,1,1,3.8,3.206Zm-3.538-1h2.988a2.253,2.253,0,1,0-2.253-2.253A3.445,3.445,0,0,1,24.34,33.926Z" style="fill:#009688"/><path d="M30.5,30.289l-.846-.535,4.364-6.9a.222.222,0,0,0-.36-.258l-5.4,6.644-.776-.631,5.4-6.644a1.222,1.222,0,0,1,1.982,1.423Z" style="fill:#009688"/><path d="M33.5,35h-1a.5.5,0,0,1,0-1h1a.5.5,0,0,0,.5-.5v-5a.5.5,0,0,1,1,0v5A1.5,1.5,0,0,1,33.5,35Z" style="fill:#b6b6b6"/><path d="M7.5,35a.5.5,0,0,1-.5-.5v-20a.5.5,0,0,1,1,0v20A.5.5,0,0,1,7.5,35Z" style="fill:#009688"/><path d="M10.5,35a.5.5,0,0,1-.5-.5v-20a.5.5,0,0,1,1,0v20A.5.5,0,0,1,10.5,35Z" style="fill:#009688"/><path d="M13.5,35h-9a.5.5,0,0,1-.5-.5v-20a.5.5,0,0,1,.5-.5h9a.5.5,0,0,1,.5.5v20A.5.5,0,0,1,13.5,35ZM5,34h8V15H5Z" style="fill:#009688"/><path d="M22.5,29a.5.5,0,0,1-.5-.5V15H4.5a.5.5,0,0,1-.5-.5v-9A1.5,1.5,0,0,1,5.5,4h28A1.5,1.5,0,0,1,35,5.5v13a.5.5,0,0,1-1,0V5.5a.5.5,0,0,0-.5-.5H5.5a.5.5,0,0,0-.5.5V14H22.5a.5.5,0,0,1,.5.5v14A.5.5,0,0,1,22.5,29Z" style="fill:#b6b6b6"/></svg>'), La = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M24.5,26h-10A1.5,1.5,0,0,1,13,24.5v-10A1.5,1.5,0,0,1,14.5,13h10A1.5,1.5,0,0,1,26,14.5v10A1.5,1.5,0,0,1,24.5,26Zm-10-12a.5.5,0,0,0-.5.5v10a.5.5,0,0,0,.5.5h10a.5.5,0,0,0,.5-.5v-10a.5.5,0,0,0-.5-.5Z" style="fill:#b6b6b6"/><path d="M35,19.5A15.517,15.517,0,0,1,19.5,35a15.767,15.767,0,0,1-2.063-.136.5.5,0,0,1-.43-.562.507.507,0,0,1,.562-.43A14.5,14.5,0,1,0,19.5,5,14.43,14.43,0,0,0,7.751,11H11.5a.5.5,0,0,1,0,1h-5a.5.5,0,0,1-.5-.5v-5a.5.5,0,0,1,1,0v3.836A15.5,15.5,0,0,1,35,19.5ZM5.943,24.66a.5.5,0,1,0-.933.356A15.426,15.426,0,0,0,7.354,29.13a.5.5,0,0,0,.783-.621A14.533,14.533,0,0,1,5.943,24.66Zm8.4,8.4a14.523,14.523,0,0,1-3.85-2.194.5.5,0,0,0-.621.784,15.455,15.455,0,0,0,4.115,2.343.487.487,0,0,0,.178.033.5.5,0,0,0,.178-.966ZM5.128,21.431A14.621,14.621,0,0,1,5,19.5a.5.5,0,0,0-1,0,15.729,15.729,0,0,0,.136,2.062.5.5,0,0,0,.992-.131Z" style="fill:#009688"/></svg>'), Pa = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M25.5,5h-2a.5.5,0,0,1,0-1h2a.5.5,0,0,1,0,1ZM21,4.5a.5.5,0,0,0-.5-.5h-2a.5.5,0,0,0,0,1h2A.5.5,0,0,0,21,4.5Zm-5,0a.5.5,0,0,0-.5-.5h-2a.5.5,0,0,0,0,1h2A.5.5,0,0,0,16,4.5Zm10,30a.5.5,0,0,0-.5-.5h-2a.5.5,0,0,0,0,1h2A.5.5,0,0,0,26,34.5Zm-5,0a.5.5,0,0,0-.5-.5h-2a.5.5,0,0,0,0,1h2A.5.5,0,0,0,21,34.5Zm-5,0a.5.5,0,0,0-.5-.5h-2a.5.5,0,0,0,0,1h2A.5.5,0,0,0,16,34.5ZM5,15.5v-2a.5.5,0,0,0-1,0v2a.5.5,0,0,0,1,0Zm0,5v-2a.5.5,0,0,0-1,0v2a.5.5,0,0,0,1,0Zm0,5v-2a.5.5,0,0,0-1,0v2a.5.5,0,0,0,1,0Zm30-10v-2a.5.5,0,0,0-1,0v2a.5.5,0,0,0,1,0Zm0,5v-2a.5.5,0,0,0-1,0v2a.5.5,0,0,0,1,0Zm0,5v-2a.5.5,0,0,0-1,0v2a.5.5,0,0,0,1,0Zm-9-1v-10A1.5,1.5,0,0,0,24.5,13h-10A1.5,1.5,0,0,0,13,14.5v10A1.5,1.5,0,0,0,14.5,26h10A1.5,1.5,0,0,0,26,24.5ZM24.5,14a.5.5,0,0,1,.5.5v10a.5.5,0,0,1-.5.5h-10a.5.5,0,0,1-.5-.5v-10a.5.5,0,0,1,.5-.5Z" style="fill:#b6b6b6"/><path d="M34.962,4.309A.5.5,0,0,0,34.5,4h-5a.5.5,0,0,0,0,1h3.793l-6.147,6.146a.5.5,0,0,0,.708.708L34,5.707V9.5a.5.5,0,0,0,1,0v-5A.5.5,0,0,0,34.962,4.309Z" style="fill:#009688"/><path d="M5.707,5H9.5a.5.5,0,0,0,0-1h-5a.5.5,0,0,0-.5.5v5a.5.5,0,0,0,1,0V5.707l6.146,6.147a.5.5,0,0,0,.708-.708Z" style="fill:#009688"/><path d="M34.5,29a.5.5,0,0,0-.5.5v3.793l-6.146-6.147a.5.5,0,0,0-.708.708L33.293,34H29.5a.5.5,0,0,0,0,1h5a.5.5,0,0,0,.5-.5v-5A.5.5,0,0,0,34.5,29Z" style="fill:#009688"/><path d="M11.146,27.146,5,33.293V29.5a.5.5,0,0,0-1,0v5a.5.5,0,0,0,.5.5h5a.5.5,0,0,0,0-1H5.707l6.147-6.146a.5.5,0,0,0-.708-.708Z" style="fill:#009688"/></svg>'), Oa = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M8,3.5v2m0,2v2m0,2v2" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), Yn = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M8,3.5v9" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M6,6.5,4,8l2,1.5" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M10,6.5,12,8l-2,1.5" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), Za = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M4.5,5h7" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M8,5v6.5" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), Ta = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M3,4.5h10" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M3,7h10" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M3,9h10" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M3,10.5h10" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M3,11.6h10" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>');
22
- function Ba(P) {
23
- const F = P.length, L = new Float32Array(F);
24
- for (let y = 0; y < F; y += 3)
25
- L[y] = P[y], L[y + 1] = P[y + 2], L[y + 2] = -P[y + 1];
26
- let ee = 1 / 0, d = 1 / 0, A = 1 / 0, S = -1 / 0, w = -1 / 0, te = -1 / 0;
27
- for (let y = 0; y < F; y += 3)
28
- ee = Math.min(ee, L[y]), S = Math.max(S, L[y]), d = Math.min(d, L[y + 1]), w = Math.max(w, L[y + 1]), A = Math.min(A, L[y + 2]), te = Math.max(te, L[y + 2]);
29
- const G = (ee + S) / 2, ie = (A + te) / 2;
30
- for (let y = 0; y < F; y += 3)
31
- L[y] -= G, L[y + 1] -= d, L[y + 2] -= ie;
32
- return { localPos: L, size: { w: S - ee, d: te - A, h: w - d } };
22
+ const Jn = ':host{display:contents}*{box-sizing:border-box}.app-shell{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;background:#0e1216;color:#cdd6dd;font:14px/1.5 -apple-system,Segoe UI,sans-serif}.app-body{flex:1 1 auto;display:flex;min-height:0}.topbar{flex:0 0 44px;height:44px;display:flex;align-items:center;padding:0 12px;gap:10px;background:#161b21;border-bottom:1px solid #262d35}.topbar .tb-left,.topbar .tb-right{flex:1 1 0;display:flex;align-items:center;gap:8px}.topbar .tb-right{justify-content:flex-end}.tb-logo{font-size:15px;color:#e6edf3;letter-spacing:.01em}.tb-logo b{color:#00ae42}.tb-re{font-size:10px;background:#00ae42;color:#fff;padding:1px 5px;border-radius:4px;margin-left:2px}.tb-btn{display:inline-flex;align-items:center;gap:5px;background:#20272f;border:1px solid #2c353d;color:#cdd6dd;border-radius:6px;padding:4px 10px;cursor:pointer;font-size:12px}.tb-btn img{width:15px;height:15px}.tb-btn:hover{background:#28313a}.tb-tabs{display:flex;border:1px solid #2c353d;border-radius:8px;overflow:hidden;flex:0 0 auto}.tb-tabs button{padding:6px 24px;border:0;background:#1a2027;color:#9fb3c0;font-weight:600;font-size:13px;cursor:pointer}.tb-tabs button.on{background:#00ae42;color:#fff}.tb-tabs button:disabled{color:#5a6570;cursor:not-allowed}.tb-icon{width:28px;height:28px;border:1px solid #2c353d;background:#20272f;color:#7d8b96;border-radius:6px;font-size:15px;cursor:not-allowed}.left-rail{flex:0 0 48px;display:flex;flex-direction:column;align-items:center;gap:6px;padding:9px 0;background:#12171c;border-right:1px solid #232a31}.left-rail button{width:36px;height:36px;border:1px solid transparent;border-radius:8px;background:transparent;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0}.left-rail button img{width:21px;height:21px;opacity:.72}.left-rail button:hover{background:#1e252c}.left-rail button.on{background:#00ae42}.left-rail button.on img{opacity:1;filter:brightness(1.6)}.rail-sep{width:26px;height:1px;background:#2a323a;margin:3px 0}.viewport-col{position:relative;flex:1 1 auto;min-width:0;background:#161a1e}.vp-top-toolbar{position:absolute;top:12px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:3px;background:#12171cd9;padding:4px;border-radius:9px;border:1px solid #2a323a;z-index:3;max-width:calc(100% - 24px);overflow-x:auto;scrollbar-width:none}.vp-top-toolbar::-webkit-scrollbar{display:none}.vp-top-toolbar>*{flex:0 0 auto}.vp-top-toolbar button{width:32px;height:32px;border:0;background:transparent;border-radius:6px;cursor:pointer;display:flex;align-items:center;justify-content:center}.vp-top-toolbar button img{width:18px;height:18px;opacity:.82}.vp-top-toolbar button:hover:not(:disabled){background:#2a333c}.vp-top-toolbar button:disabled{opacity:.32;cursor:not-allowed}.vtt-sep{width:1px;align-self:stretch;background:#333c45;margin:3px}.plate-bar{position:absolute;right:14px;bottom:30px;display:flex;align-items:center;gap:4px;z-index:3;background:#12171cd9;border:1px solid #2a323a;border-radius:9px;padding:4px}.plate-bar button{min-width:26px;height:26px;border:1px solid #2c353d;background:#1a2027;color:#9fb3c0;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600}.plate-tab.on{background:#00ae42;color:#fff;border-color:#00ae42}.plate-add{color:#7ad19a!important}.plate-del{color:#e08a8a!important}.plate-bar button:disabled{opacity:.4;cursor:not-allowed}.slice-dd{position:relative;flex:1.4}.slice-dd .slice-btn{width:100%}.slice-menu{position:absolute;bottom:calc(100% + 6px);left:0;right:0;background:#fff;border:1px solid #d5d8dd;border-radius:8px;box-shadow:0 4px 16px #0000002e;overflow:hidden;z-index:10}.slice-menu button{display:block;width:100%;text-align:left;padding:9px 12px;border:0;background:#fff;color:#24282e;cursor:pointer;font-size:13px}.slice-menu button:hover{background:#eef7f1}.brush-panel{position:absolute;top:58px;left:14px;width:214px;background:#161b21f2;border:1px solid #2c353d;border-radius:10px;padding:10px;z-index:4;box-shadow:0 3px 14px #00000073}.bp-title{font-weight:600;margin-bottom:6px;color:#cdd6dd}.bp-modes{display:flex;gap:4px;flex-wrap:wrap}.bp-modes button{padding:3px 9px;border:1px solid #2c353d;background:#1a2027;color:#cdd6dd;border-radius:5px;cursor:pointer;font-size:12px}.bp-modes button.enf.on{background:#2b6cff;color:#fff;border-color:#2b6cff}.bp-modes button.blk.on{background:#e23b3b;color:#fff;border-color:#e23b3b}.bp-radius{display:block;font-size:12px;color:#aebcc6;margin-top:8px}.bp-radius input{width:100%}.bp-counts{margin-top:6px;font-size:11px}.stats-card{position:absolute;left:14px;bottom:32px;background:#161b21ed;border:1px solid #2c353d;border-radius:10px;padding:10px 14px;font-size:13px;line-height:1.7;z-index:2;color:#cdd6dd}.stats-card b{color:#9fe3bd}.sidebar{flex:0 0 384px;width:384px;display:flex;flex-direction:column;background:#eef0f2;color:#24282e;border-left:1px solid #d5d8dd;min-height:0}.sidebar-scroll{flex:1 1 auto;overflow-y:auto;padding:10px;display:flex;flex-direction:column;gap:10px}.side-card{background:#fff;border:1px solid #e2e4e8;border-radius:10px;padding:10px 12px}.sc-head{font-weight:700;font-size:13px;color:#2a2f36;display:flex;align-items:center;gap:6px;margin-bottom:7px}.sc-count{background:#00ae42;color:#fff;font-size:10px;border-radius:8px;padding:0 6px;font-weight:700}.sc-head-btns{margin-left:auto;display:flex;gap:4px}.sc-head-btns button{width:22px;height:22px;border:1px solid #d3d6db;background:#f6f7f8;border-radius:5px;cursor:pointer;font-size:14px;line-height:1;color:#333}.sc-head-btns button:disabled{opacity:.4;cursor:not-allowed}.sc-info{display:flex;justify-content:space-between;font-size:12px;color:#5a6069;padding:2px 0}.sc-info b{color:#24282e}.side-warn{margin:0 2px;font-size:12px}.filament-row{display:flex;align-items:center;gap:8px;padding:3px 0}.filament-row input[type=color]{width:28px;height:22px;border:1px solid #ccc;border-radius:5px;padding:0;cursor:pointer;background:none}.fil-t{font-weight:700;font-size:12px;width:20px}.fil-swatch{width:15px;height:15px;border-radius:4px;border:1px solid rgba(0,0,0,.15)}.fil-hex{font-size:11px;margin-left:auto}.obj-list2{list-style:none;margin:0 0 6px;padding:0}.obj-list2 li{display:flex;align-items:center;gap:6px;padding:3px 0;font-size:12px}.obj-list2 li.obj-hidden{opacity:.5}.obj-eye{border:0;background:none;cursor:pointer;font-size:13px;padding:0}.obj-list2 .obj-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#24282e}.obj-list2 .obj-ext{border:1px solid #d0d3d8;border-radius:5px;font-size:11px;padding:1px 3px;background:#fff}.obj-del{border:0;background:#fbe9ec;color:#d0435a;border-radius:5px;cursor:pointer;padding:0 6px}.side-card .slice-support{color:#444;font-size:12px;margin:5px 0 0}.process-card{padding:8px}.side-card .view-type-row{color:#444}.side-card .view-type-row select,.side-card .obj-ext{background:#fff;color:#222;border:1px solid #ccd0d5}.side-card .slice-layer label{color:#444;font-size:12px}.side-card .grad-title{color:#333}.side-card .grad-minmax{color:#666}.side-card .role-legend,.side-card .slice-travel{color:#555}.side-card .layer-ctl button{background:#f1f2f4;color:#333;border:1px solid #d3d6db}.side-card .layer-ctl button.on{background:#00ae42;color:#fff;border-color:#00ae42}.side-bottom{flex:0 0 auto;display:flex;gap:8px;padding:10px;border-top:1px solid #dcdfe3;background:#e6e9ec}.side-bottom .slice-btn{flex:1.4;margin:0}.auto-slice{display:flex;align-items:center;gap:5px;font-size:12px;color:#444;cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none}.auto-slice input{margin:0}.export-btn{flex:1;text-align:center;padding:9px;border-radius:8px;background:#dfe6ea;color:#1f6feb;border:1px solid #c7d2da;text-decoration:none;font-weight:600;cursor:pointer;font-size:13px}.export-btn:hover{background:#d3dee4}.export-btn:disabled{color:#9aa4ad;background:#e9ecef;cursor:not-allowed}.vp-canvas{position:absolute;top:0;right:0;bottom:0;left:0}.vp-canvas canvas{display:block}.vp-canvas.fail{display:flex;align-items:center;justify-content:center}.vp-fallback{color:#f0c9a8;padding:24px;text-align:center;max-width:560px;line-height:1.5}.vp-status{position:absolute;bottom:8px;left:12px;right:12px;z-index:1;font-size:11.5px;color:#9fe3bd;font-family:ui-monospace,monospace;text-shadow:0 1px 2px #000;pointer-events:none}.slice-warn{color:#f0c060;margin:4px 0}.sc-sub{font-size:10.5px;opacity:.62;margin:1px 0 3px}.sc-fold>summary{display:flex;justify-content:space-between;align-items:center;font-size:12px;color:#5a6069;padding:2px 0;cursor:pointer;list-style:none}.sc-fold>summary::-webkit-details-marker{display:none}.sc-fold-lbl:before{content:"▸";display:inline-block;width:10px;font-size:9px;opacity:.5}.sc-fold[open] .sc-fold-lbl:before{content:"▾"}.sc-fold>summary b{color:#24282e}.sc-fold[open]>summary{border-bottom:1px solid #e3e6ea;margin-bottom:4px}.sc-fold-body{max-height:320px;overflow-y:auto}.sc-model{max-width:62%;font-size:11.5px;padding:1px 2px}.support-tools{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:2px 0 2px 16px;padding-left:6px;border-left:2px solid #e3e6ea}.sup-style{margin-left:16px;padding-left:6px;border-left:2px solid #e3e6ea}.sup-paint{font-size:11px;padding:2px 8px;border:1px solid #c9ced6;border-radius:5px;background:#fff;color:#24282e;cursor:pointer;white-space:nowrap}.sup-paint.on{background:#2b6cff;border-color:#2b6cff;color:#fff}.sup-paint:disabled{opacity:.45;cursor:not-allowed}.slice-btn{width:100%;margin-top:6px;padding:7px;border:0;border-radius:7px;background:#00ae42;color:#fff;font-weight:700;cursor:pointer}.slice-btn:disabled{background:#3a4650;color:#8b98a2;cursor:not-allowed}.slice-err{color:#ff8b8b;margin-top:6px}.slice-stats{margin-top:8px;line-height:1.5}.slice-stats b{color:#9fe3bd}.slice-layer{margin-top:8px;display:flex;flex-direction:column;gap:4px}.slice-layer input[type=range]{width:100%}.slice-travel{color:#aebcc6;display:flex;align-items:center;gap:4px}.view-type-row{display:flex;align-items:center;justify-content:space-between;gap:6px;color:#aebcc6}.view-type-row select{flex:1;background:#10141a;color:#eee;border:1px solid #2c353d;border-radius:4px;padding:2px 4px}.dual-slider{position:relative;height:22px}.dual-slider input[type=range]{position:absolute;left:0;top:0;width:100%;margin:0;pointer-events:none;background:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.dual-slider input[type=range]::-webkit-slider-thumb{pointer-events:auto;-webkit-appearance:none;width:13px;height:13px;border-radius:50%;background:#00ae42;border:2px solid #0c1013;cursor:pointer}.dual-slider input[type=range]::-moz-range-thumb{pointer-events:auto;width:13px;height:13px;border-radius:50%;background:#00ae42;border:2px solid #0c1013;cursor:pointer}.dual-slider input[type=range]::-webkit-slider-runnable-track{height:4px;background:#2c353d;border-radius:2px}.dual-slider input[type=range]:first-child{z-index:2}.layer-ctl{display:flex;align-items:center;gap:8px}.layer-ctl button{padding:2px 8px;border:1px solid #2c353d;border-radius:5px;background:#1a2027;color:#cdd6dd;font-size:11px;cursor:pointer}.layer-ctl button.on{background:#00ae42;color:#fff;border-color:#00ae42}.grad-legend{margin-top:6px}.grad-title{font-size:11px;color:#cdd6dd;margin-bottom:3px}.grad-bar{height:10px;border-radius:3px;border:1px solid #2c353d}.grad-minmax{display:flex;justify-content:space-between;font-size:10px;color:#aebcc6;margin-top:2px}.role-legend{display:flex;flex-wrap:wrap;gap:4px 10px;margin-top:6px;font-size:11px;color:#aebcc6}.role-item{display:inline-flex;align-items:center;gap:4px}.role-item i{width:10px;height:6px;border-radius:2px;display:inline-block}.role-item b{color:#cdd6dd}.vp-canvas.drag-over{outline:3px dashed #00ae42;outline-offset:-6px}.empty-hint{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;pointer-events:none;color:#9fb3c0;text-align:center}.empty-hint .eh-icon{font-size:46px;opacity:.8}.empty-hint .eh-title{font-size:16px;font-weight:600;color:#cdd6dd}.empty-hint .eh-sub{font-size:12px;color:#7d8b96;letter-spacing:.04em}.empty-hint .eh-btn{pointer-events:auto;margin-top:8px;padding:7px 18px;border:0;border-radius:7px;background:#00ae42;color:#fff;font-weight:600;cursor:pointer}.empty-hint .eh-btn:hover{background:#06c04d}.drop-overlay{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;background:#00ae4224;color:#d7f7e3;font-size:18px;font-weight:700;pointer-events:none;border:3px dashed #00ae42;box-sizing:border-box}.obj-split{border:0;background:transparent;cursor:pointer;padding:0 3px;line-height:0}.obj-split img{width:13px;height:13px;opacity:.6}.obj-split:hover img{opacity:1}.ctx-scrim{position:absolute;top:0;right:0;bottom:0;left:0;z-index:24}.ctx-menu{position:absolute;z-index:25;min-width:168px;padding:4px;background:#1b2027;border:1px solid #2f3944;border-radius:8px;box-shadow:0 8px 24px #00000080}.ctx-menu button{display:flex;width:100%;align-items:center;justify-content:space-between;gap:16px;padding:6px 10px;border:0;border-radius:5px;background:transparent;color:#dfe6ee;font-size:12px;text-align:left;cursor:pointer}.ctx-menu button:hover:not(:disabled){background:#273140}.ctx-menu button:disabled{color:#64707d;cursor:not-allowed}.ctx-menu button.danger:hover:not(:disabled){background:#4a2229;color:#ffb3b3}.ctx-menu hr{margin:4px 6px;border:0;border-top:1px solid #2f3944}.ctx-key{color:#7d8b96;font-family:ui-monospace,monospace;font-size:11px}.help-overlay{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;background:#080c10b8;z-index:20}.help-card{background:#1b2027;color:#dfe6ee;border:1px solid #2f3944;border-radius:10px;padding:18px 22px;max-width:720px;box-shadow:0 12px 40px #00000080}.help-card h3{margin:0 0 12px;font-size:15px}.help-card h4{margin:0 0 6px;font-size:12px;color:#00ae42;text-transform:uppercase;letter-spacing:.04em}.help-cols{display:flex;gap:26px;flex-wrap:wrap}.help-card dl{display:grid;grid-template-columns:auto 1fr;gap:3px 12px;margin:0;font-size:12px}.help-card dt{color:#9fb0c2;font-family:ui-monospace,monospace;white-space:nowrap}.help-card dd{margin:0}.slice-support{display:flex;align-items:center;gap:5px;margin:6px 0;color:#cdd6dd;font-size:12px}.slice-support .muted{font-size:10px}.sc-bed{display:inline-flex;align-items:center;gap:4px;font-weight:700;color:#222}.sc-bed input{width:54px;padding:1px 4px;font-size:12px;font-weight:600;text-align:right;border:1px solid #ccd0d5;border-radius:5px;background:#fff;color:#222}', Qn = 40, Ye = (n) => n + Qn, Vt = 9, gt = (n) => {
23
+ const a = Math.sqrt(n), c = Math.round(a);
24
+ return a > c ? c + 1 : c;
25
+ }, er = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Zm3-6h3m2,0h3m-4-4v8" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), tr = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M33.5,35H5.5A1.5,1.5,0,0,1,4,33.5V5.5A1.5,1.5,0,0,1,5.5,4h28A1.5,1.5,0,0,1,35,5.5v28A1.5,1.5,0,0,1,33.5,35ZM5.5,5a.5.5,0,0,0-.5.5v28a.5.5,0,0,0,.5.5h28a.5.5,0,0,0,.5-.5V5.5a.5.5,0,0,0-.5-.5Z" style="fill:#b6b6b6"/><path d="M30.5,32H8.5A1.5,1.5,0,0,1,7,30.5v-6A1.5,1.5,0,0,1,8.5,23h22A1.5,1.5,0,0,1,32,24.5v6A1.5,1.5,0,0,1,30.5,32Zm-22-8a.5.5,0,0,0-.5.5v6a.5.5,0,0,0,.5.5h22a.5.5,0,0,0,.5-.5v-6a.5.5,0,0,0-.5-.5Zm22-2h-6A1.5,1.5,0,0,1,23,20.5V8.5A1.5,1.5,0,0,1,24.5,7h6A1.5,1.5,0,0,1,32,8.5v12A1.5,1.5,0,0,1,30.5,22Zm-6-14a.5.5,0,0,0-.5.5v12a.5.5,0,0,0,.5.5h6a.5.5,0,0,0,.5-.5V8.5a.5.5,0,0,0-.5-.5Zm-6,6h-8a3.5,3.5,0,0,1,0-7h8a3.5,3.5,0,0,1,0,7Zm-8-6a2.5,2.5,0,0,0,0,5h8a2.5,2.5,0,0,0,0-5Zm8,14A3.5,3.5,0,1,1,22,18.5,3.5,3.5,0,0,1,18.5,22Zm0-6A2.5,2.5,0,1,0,21,18.5,2.5,2.5,0,0,0,18.5,16Zm-8,6A3.5,3.5,0,1,1,14,18.5,3.5,3.5,0,0,1,10.5,22Zm0-6A2.5,2.5,0,1,0,13,18.5,2.5,2.5,0,0,0,10.5,16Z" style="fill:#009688"/></svg>'), nr = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M6.2,8a2.6,2.6,0,1,0,5.2,0a2.6,2.6,0,1,0,-5.2,0" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M4.6,8a2.6,2.6,0,1,0,5.2,0a2.6,2.6,0,1,0,-5.2,0" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), rr = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M3.5,9h2m2,0h2m2,0h2" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M8,3.5l1.8,3h-3.6z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), Ft = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Zm3-6h8" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), or = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M5,5l6,6" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M11,5l-6,6" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), ar = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M4,4h5v5H4z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M7,7h5v5H7z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), lr = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M8,3a5.93,5.93,0,0,0-4.95,2.5A9.1,9.1,0,0,0,1,8a9.1,9.1,0,0,0,2.05,2.5A5.93,5.93,0,0,0,8,13a5.93,5.93,0,0,0,4.95-2.5A9.1,9.1,0,0,0,15,8a9.1,9.1,0,0,0-2.05-2.5A5.93,5.93,0,0,0,8,3m0,8a2,2,0,1,1,2-2A2,2,0,0,1,8,11" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), sr = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M1.5,2.5l13,11" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M8,3a5.93,5.93,0,0,0-4.95,2.5A9.1,9.1,0,0,0,1,8a9.1,9.1,0,0,0,2.05,2.5" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M8,3a5.93,5.93,0,0,1,4.95,2.5A9.1,9.1,0,0,1,15,8a9.1,9.1,0,0,1-2.05,2.5" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><circle cx="8" cy="8" r="2" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), ir = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M2.5,8h11" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M2.5,6v4" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M13.5,6v4" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M6,7v2" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M10,7v2" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), cr = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M4,8a4,4,0,1,0,8,0a4,4,0,1,0,-8,0" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M8,4a4,4,0,0,1,0,8z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1;fill:#949494;stroke:none"/></svg>'), dr = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M34.854,19.146l-4-4a.5.5,0,0,0-.708.708L33.293,19H28.5a.5.5,0,0,0,0,1h4.793l-3.147,3.146a.5.5,0,0,0,.708.708l4-4A.5.5,0,0,0,34.854,19.146Z" style="fill:#009688"/><path d="M19.854,4.146a.5.5,0,0,0-.708,0l-4,4a.5.5,0,0,0,.708.708L19,5.707V10.5a.5.5,0,0,0,1,0V5.707l3.146,3.147a.5.5,0,0,0,.708-.708Z" style="fill:#009688"/><path d="M23.146,30.146,20,33.293V28.5a.5.5,0,0,0-1,0v4.793l-3.146-3.147a.5.5,0,0,0-.708.708l4,4a.5.5,0,0,0,.708,0l4-4a.5.5,0,0,0-.708-.708Z" style="fill:#009688"/><path d="M10.5,19H5.707l3.147-3.146a.5.5,0,0,0-.708-.708l-4,4a.5.5,0,0,0,0,.708l4,4a.5.5,0,0,0,.708-.708L5.707,20H10.5a.5.5,0,0,0,0-1Z" style="fill:#009688"/><path d="M24.5,26h-10A1.5,1.5,0,0,1,13,24.5v-10A1.5,1.5,0,0,1,14.5,13h10A1.5,1.5,0,0,1,26,14.5v10A1.5,1.5,0,0,1,24.5,26Zm-10-12a.5.5,0,0,0-.5.5v10a.5.5,0,0,0,.5.5h10a.5.5,0,0,0,.5-.5v-10a.5.5,0,0,0-.5-.5Z" style="fill:#b6b6b6"/></svg>'), pr = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M5,5h6v6H5z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1;stroke-dasharray:2 1.5"/></svg>'), ur = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M8,3.5v5m-2-2 2,2 2-2" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M4,11.5h8" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), mr = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M34.923,10.25a.392.392,0,0,0-.033-.05.5.5,0,0,0-.2-.162l0,0-15-6a.5.5,0,0,0-.372,0l-15,6,0,0a.5.5,0,0,0-.2.162.392.392,0,0,0-.033.05A.478.478,0,0,0,4,10.5v18a.464.464,0,0,0,.028.138.429.429,0,0,0,.011.054l.01.015a.51.51,0,0,0,.1.144l.008,0a.5.5,0,0,0,.156.1l0,0,14.989,6,0,0,.006,0a.5.5,0,0,0,.372,0l.006,0,0,0,1.989-.8a.5.5,0,0,0-.372-.928L20,33.762V16.839l14-5.6V21.5a.5.5,0,0,0,1,0v-11A.481.481,0,0,0,34.923,10.25ZM19,33.762l-14-5.6V11.239l14,5.6Zm.5-17.8L5.846,10.5,19.5,5.039,33.153,10.5Z" style="fill:#b6b6b6"/><path d="M33.5,24h-8A1.5,1.5,0,0,0,24,25.5v8A1.5,1.5,0,0,0,25.5,35h8A1.5,1.5,0,0,0,35,33.5v-8A1.5,1.5,0,0,0,33.5,24Zm.5,9.5a.5.5,0,0,1-.5.5h-8a.5.5,0,0,1-.5-.5v-8a.5.5,0,0,1,.5-.5h8a.5.5,0,0,1,.5.5Z" style="fill:#009688"/><path d="M32.5,29H30V26.5a.5.5,0,0,0-1,0V29H26.5a.5.5,0,0,0,0,1H29v2.5a.5.5,0,0,0,1,0V30h2.5a.5.5,0,0,0,0-1Z" style="fill:#009688"/></svg>'), fr = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M25.5,22a.5.5,0,0,1-.5-.5V18H18v3.5a.5.5,0,0,1-1,0v-6a.5.5,0,0,1,.084-.277l2-3A.5.5,0,0,1,19.5,12h4a.5.5,0,0,1,.416.223l2,3A.5.5,0,0,1,26,15.5v6A.5.5,0,0,1,25.5,22ZM18,17h7V15.651L23.232,13H19.768L18,15.651Z" style="fill:#009688"/><path d="M11.964,29.314a.5.5,0,0,0-.65-.278L7.152,30.7a17.321,17.321,0,0,1,3.739-6.888.5.5,0,1,0-.782-.624A19.312,19.312,0,0,0,6.359,29.8L4.964,26.314a.5.5,0,1,0-.928.372l2,5c0,.01.016.015.021.025a.475.475,0,0,0,.15.185c.009.007.016.017.025.023a.5.5,0,0,0,.2.076A.367.367,0,0,0,6.5,32h0a.475.475,0,0,0,.181-.036h0l5-2A.5.5,0,0,0,11.964,29.314Z" style="fill:#009688"/><rect x="4" y="34" width="31" height="1" style="fill:#009688"/><path d="M34.854,17.146l-13-13a.5.5,0,0,0-.708,0l-13,13a.5.5,0,0,0,.708.708L21.5,5.207,33.793,17.5,21.146,30.146a.5.5,0,0,0,.708.708l13-13A.5.5,0,0,0,34.854,17.146Z" style="fill:#b6b6b6"/><path d="M21.5,31a.5.5,0,0,1-.354-.146l-13-13a.5.5,0,0,1,.708-.708l13,13A.5.5,0,0,1,21.5,31Z" style="fill:#009688"/></svg>'), hr = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M27.878,34.926h-4.4c-.509,0-.845,0-.955-.394a.593.593,0,0,1,.36-.67,2.3,2.3,0,0,0,1.2-2.189,3.253,3.253,0,1,1,3.8,3.206Zm-3.538-1h2.988a2.253,2.253,0,1,0-2.253-2.253A3.445,3.445,0,0,1,24.34,33.926Z" style="fill:#009688"/><path d="M30.5,30.289l-.846-.535,4.364-6.9a.222.222,0,0,0-.36-.258l-5.4,6.644-.776-.631,5.4-6.644a1.222,1.222,0,0,1,1.982,1.423Z" style="fill:#009688"/><path d="M33.5,35h-1a.5.5,0,0,1,0-1h1a.5.5,0,0,0,.5-.5v-5a.5.5,0,0,1,1,0v5A1.5,1.5,0,0,1,33.5,35Z" style="fill:#b6b6b6"/><path d="M7.5,35a.5.5,0,0,1-.5-.5v-20a.5.5,0,0,1,1,0v20A.5.5,0,0,1,7.5,35Z" style="fill:#009688"/><path d="M10.5,35a.5.5,0,0,1-.5-.5v-20a.5.5,0,0,1,1,0v20A.5.5,0,0,1,10.5,35Z" style="fill:#009688"/><path d="M13.5,35h-9a.5.5,0,0,1-.5-.5v-20a.5.5,0,0,1,.5-.5h9a.5.5,0,0,1,.5.5v20A.5.5,0,0,1,13.5,35ZM5,34h8V15H5Z" style="fill:#009688"/><path d="M22.5,29a.5.5,0,0,1-.5-.5V15H4.5a.5.5,0,0,1-.5-.5v-9A1.5,1.5,0,0,1,5.5,4h28A1.5,1.5,0,0,1,35,5.5v13a.5.5,0,0,1-1,0V5.5a.5.5,0,0,0-.5-.5H5.5a.5.5,0,0,0-.5.5V14H22.5a.5.5,0,0,1,.5.5v14A.5.5,0,0,1,22.5,29Z" style="fill:#b6b6b6"/></svg>'), gr = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M24.5,26h-10A1.5,1.5,0,0,1,13,24.5v-10A1.5,1.5,0,0,1,14.5,13h10A1.5,1.5,0,0,1,26,14.5v10A1.5,1.5,0,0,1,24.5,26Zm-10-12a.5.5,0,0,0-.5.5v10a.5.5,0,0,0,.5.5h10a.5.5,0,0,0,.5-.5v-10a.5.5,0,0,0-.5-.5Z" style="fill:#b6b6b6"/><path d="M35,19.5A15.517,15.517,0,0,1,19.5,35a15.767,15.767,0,0,1-2.063-.136.5.5,0,0,1-.43-.562.507.507,0,0,1,.562-.43A14.5,14.5,0,1,0,19.5,5,14.43,14.43,0,0,0,7.751,11H11.5a.5.5,0,0,1,0,1h-5a.5.5,0,0,1-.5-.5v-5a.5.5,0,0,1,1,0v3.836A15.5,15.5,0,0,1,35,19.5ZM5.943,24.66a.5.5,0,1,0-.933.356A15.426,15.426,0,0,0,7.354,29.13a.5.5,0,0,0,.783-.621A14.533,14.533,0,0,1,5.943,24.66Zm8.4,8.4a14.523,14.523,0,0,1-3.85-2.194.5.5,0,0,0-.621.784,15.455,15.455,0,0,0,4.115,2.343.487.487,0,0,0,.178.033.5.5,0,0,0,.178-.966ZM5.128,21.431A14.621,14.621,0,0,1,5,19.5a.5.5,0,0,0-1,0,15.729,15.729,0,0,0,.136,2.062.5.5,0,0,0,.992-.131Z" style="fill:#009688"/></svg>'), br = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M25.5,5h-2a.5.5,0,0,1,0-1h2a.5.5,0,0,1,0,1ZM21,4.5a.5.5,0,0,0-.5-.5h-2a.5.5,0,0,0,0,1h2A.5.5,0,0,0,21,4.5Zm-5,0a.5.5,0,0,0-.5-.5h-2a.5.5,0,0,0,0,1h2A.5.5,0,0,0,16,4.5Zm10,30a.5.5,0,0,0-.5-.5h-2a.5.5,0,0,0,0,1h2A.5.5,0,0,0,26,34.5Zm-5,0a.5.5,0,0,0-.5-.5h-2a.5.5,0,0,0,0,1h2A.5.5,0,0,0,21,34.5Zm-5,0a.5.5,0,0,0-.5-.5h-2a.5.5,0,0,0,0,1h2A.5.5,0,0,0,16,34.5ZM5,15.5v-2a.5.5,0,0,0-1,0v2a.5.5,0,0,0,1,0Zm0,5v-2a.5.5,0,0,0-1,0v2a.5.5,0,0,0,1,0Zm0,5v-2a.5.5,0,0,0-1,0v2a.5.5,0,0,0,1,0Zm30-10v-2a.5.5,0,0,0-1,0v2a.5.5,0,0,0,1,0Zm0,5v-2a.5.5,0,0,0-1,0v2a.5.5,0,0,0,1,0Zm0,5v-2a.5.5,0,0,0-1,0v2a.5.5,0,0,0,1,0Zm-9-1v-10A1.5,1.5,0,0,0,24.5,13h-10A1.5,1.5,0,0,0,13,14.5v10A1.5,1.5,0,0,0,14.5,26h10A1.5,1.5,0,0,0,26,24.5ZM24.5,14a.5.5,0,0,1,.5.5v10a.5.5,0,0,1-.5.5h-10a.5.5,0,0,1-.5-.5v-10a.5.5,0,0,1,.5-.5Z" style="fill:#b6b6b6"/><path d="M34.962,4.309A.5.5,0,0,0,34.5,4h-5a.5.5,0,0,0,0,1h3.793l-6.147,6.146a.5.5,0,0,0,.708.708L34,5.707V9.5a.5.5,0,0,0,1,0v-5A.5.5,0,0,0,34.962,4.309Z" style="fill:#009688"/><path d="M5.707,5H9.5a.5.5,0,0,0,0-1h-5a.5.5,0,0,0-.5.5v5a.5.5,0,0,0,1,0V5.707l6.146,6.147a.5.5,0,0,0,.708-.708Z" style="fill:#009688"/><path d="M34.5,29a.5.5,0,0,0-.5.5v3.793l-6.146-6.147a.5.5,0,0,0-.708.708L33.293,34H29.5a.5.5,0,0,0,0,1h5a.5.5,0,0,0,.5-.5v-5A.5.5,0,0,0,34.5,29Z" style="fill:#009688"/><path d="M11.146,27.146,5,33.293V29.5a.5.5,0,0,0-1,0v5a.5.5,0,0,0,.5.5h5a.5.5,0,0,0,0-1H5.707l6.147-6.146a.5.5,0,0,0-.708-.708Z" style="fill:#009688"/></svg>'), xr = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M8,3.5v2m0,2v2m0,2v2" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), Gt = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M8,3.5v9" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M6,6.5,4,8l2,1.5" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M10,6.5,12,8l-2,1.5" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), yr = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v12a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Z" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M4.5,5h7" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M8,5v6.5" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>'), vr = "data:image/svg+xml;utf8," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M3,4.5h10" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M3,7h10" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M3,9h10" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M3,10.5h10" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><path d="M3,11.6h10" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>');
26
+ function wr(n) {
27
+ return [
28
+ { id: "add", icon: er, label: "Add", tip: "Add a model file to the current plate (existing objects are kept)", run: n.add },
29
+ { id: "delete", icon: Ft, label: "Delete", tip: "Delete the selected object (Del)", run: n.remove },
30
+ { id: "delete-all", icon: or, label: "Delete all", tip: "Delete every object on the plate", run: n.removeAll, disabled: () => n.objectCount() === 0 },
31
+ { sep: !0 },
32
+ { id: "duplicate", icon: ar, label: "Duplicate", tip: "Duplicate the selected object (Ctrl+K)", run: n.duplicate },
33
+ { id: "split", icon: Gt, label: "Split", tip: "Split to objects — every disconnected part (connected component) becomes its own object. Split to parts is not implemented (no part concept)", run: n.split },
34
+ { id: "onbed", icon: ur, label: "Place on bed", tip: "Drop every object onto the bed (Z=0) — to re-seat after lifting with the gizmo", run: n.placeOnBed },
35
+ { sep: !0 },
36
+ { id: "arrange", icon: tr, label: "Arrange", tip: "Auto arrange — lay objects out on the bed without overlap. Not implemented (needs the libslic3r Arrange port)" },
37
+ { id: "orient", icon: fr, label: "Orient", tip: "Auto orient — rotate to the orientation needing the least support. Not implemented (needs the libslic3r Orient port)" },
38
+ { sep: !0 },
39
+ { id: "cut", icon: rr, label: "Cut", tip: "Cut — slice the model with a plane into two pieces. Not implemented (needs cut-surface re-stitching)" },
40
+ { id: "boolean", icon: nr, label: "Boolean", tip: "Mesh boolean — union/difference/intersection of two objects. Not implemented (needs the CGAL boolean port)" },
41
+ { id: "negative", icon: pr, label: "Negative part", tip: "Add a negative/modifier part — put a part with different properties inside one object. Not implemented (no part concept)" },
42
+ { sep: !0 },
43
+ { id: "seam", icon: xr, label: "Seam painting", tip: "Seam painting — brush where the layer seam goes. Not implemented (needs kernel seam wiring)" },
44
+ { id: "mmu", icon: cr, label: "Color painting", tip: "Color painting — assign multi-material colors per facet. Not implemented (needs the MMU paint codec wiring)" },
45
+ { id: "text", icon: yr, label: "Text", tip: "Text/SVG emboss — engrave letters or shapes onto the model surface. Not implemented (needs font rasterization)" },
46
+ { id: "measure", icon: ir, label: "Measure", tip: "Measure — distance and angle between two points or faces. Not implemented" },
47
+ { id: "varlayer", icon: vr, label: "Variable layers", tip: "Variable layer height — different layer heights per band. Not implemented (the kernel has no variable z)" }
48
+ ];
33
49
  }
34
- const Ia = 40, Ie = (P) => P + Ia, Jn = 9, Jt = (P) => {
35
- const F = Math.sqrt(P), L = Math.round(F);
36
- return F > L ? L + 1 : L;
37
- };
38
- function Wa({ settings: P = {}, setSettings: F = () => {
39
- }, processPanel: L = null }) {
40
- var Ln, Pn;
41
- const ee = g(null), d = g(null), A = g({}), S = g(null), w = g([]), te = g(null), G = g(null), ie = g(null), y = g({}), Ye = g(null), Je = g(null), yt = g(!1), kt = g("feature"), me = g(0), ne = g(0), fe = g("prepare"), Qt = g(0.42), W = g("off"), Qe = g(5), Et = g(null), et = g(null), tt = g(!1), X = g({}), nt = g({}), he = g(null), Mt = g(null), Ct = g(!1), Ve = g(null), re = g(0), $e = g(1), Ue = g(0), rt = g(200), at = g(200), [K, Qn] = x(!0), [jt, er] = x("translate"), [en, ce] = x("초기화 중…"), [Ee, ot] = x([]), [tn, nn] = x(""), [lt, ge] = x(!1), [Nt, tr] = x(!1), st = g(0), [nr, _e] = x(0), [rn, C] = x(""), [be, it] = x(null), [rr, ct] = x(!1), [Ae, dt] = x(0), [ar, Se] = x(0), [ae, an] = x(1);
42
- function _t(e) {
43
- const t = Jt($e.current);
44
- return { x: e % t * Ie(rt.current), z: Math.floor(e / t) * Ie(at.current) };
50
+ function kr(n) {
51
+ return (a) => {
52
+ var w;
53
+ const c = a.composedPath ? a.composedPath()[0] : a.target;
54
+ if (c && (c.tagName === "INPUT" || c.tagName === "TEXTAREA" || c.tagName === "SELECT" || c.isContentEditable)) return;
55
+ const l = a.key, r = typeof l == "string" ? l.toLowerCase() : "", m = a.ctrlKey || a.metaKey, d = () => {
56
+ a.preventDefault(), a.stopPropagation();
57
+ };
58
+ if (m) {
59
+ r === "r" ? (d(), n.slicing || n.slice("current")) : r === "c" ? (d(), n.copy()) : r === "v" ? (d(), n.paste()) : r === "x" ? (d(), n.copy(), n.remove()) : (r === "k" || r === "d") && (d(), n.duplicate());
60
+ return;
61
+ }
62
+ if (n.isPreview()) {
63
+ const b = a.shiftKey ? 10 : 1;
64
+ l === "ArrowUp" ? (d(), n.stepLayer(b)) : l === "ArrowDown" ? (d(), n.stepLayer(-b)) : r === "l" ? (d(), n.toggleSingleLayer()) : r === "t" ? (d(), n.toggleTravel()) : r === "z" ? (d(), n.zoomAll()) : r === "b" ? (d(), n.zoomBed()) : l === "Escape" && (d(), n.leavePreview());
65
+ return;
66
+ }
67
+ if (r === "m" || r === "g")
68
+ d(), n.setGizmo("translate");
69
+ else if (r === "r")
70
+ d(), n.setGizmo("rotate");
71
+ else if (r === "s")
72
+ d(), n.setGizmo("scale");
73
+ else if (r === "z")
74
+ d(), n.zoomAll();
75
+ else if (r === "b")
76
+ d(), n.zoomBed();
77
+ else if (l === "Delete" || l === "Backspace")
78
+ d(), n.remove();
79
+ else if (l === "Escape")
80
+ d(), n.cancelTool();
81
+ else if (l === "PageUp")
82
+ d(), n.rotateSelected(Math.PI / 4);
83
+ else if (l === "PageDown")
84
+ d(), n.rotateSelected(-Math.PI / 4);
85
+ else if ((w = l.startsWith) != null && w.call(l, "Arrow")) {
86
+ d();
87
+ const b = a.shiftKey ? 1 : 10;
88
+ n.nudgeSelected(
89
+ l === "ArrowLeft" ? -b : l === "ArrowRight" ? b : 0,
90
+ l === "ArrowUp" ? -b : l === "ArrowDown" ? b : 0
91
+ );
92
+ } else l === "?" && (d(), n.toggleHelp());
93
+ };
94
+ }
95
+ function Er(n, a) {
96
+ const c = n.geometry, l = c == null ? void 0 : c.getAttribute("position");
97
+ if (!l) return null;
98
+ const r = c.getIndex(), m = (r ? r.count : l.count) / 3, d = Math.cos(Math.max(0, Math.min(90, a)) * Math.PI / 180), w = new N.Matrix3().getNormalMatrix(n.matrixWorld), b = new N.Vector3(), T = new N.Vector3(), P = new N.Vector3(), M = new N.Vector3(), B = new N.Vector3(), ee = new N.Vector3(), L = [];
99
+ for (let I = 0; I < m; I++) {
100
+ const A = r ? r.getX(I * 3) : I * 3, u = r ? r.getX(I * 3 + 1) : I * 3 + 1, _ = r ? r.getX(I * 3 + 2) : I * 3 + 2;
101
+ b.fromBufferAttribute(l, A), T.fromBufferAttribute(l, u), P.fromBufferAttribute(l, _), ee.copy(M.subVectors(T, b)).cross(B.subVectors(P, b)), ee.lengthSq() !== 0 && (ee.applyMatrix3(w).normalize(), !(-ee.y <= d) && L.push(b.x, b.y, b.z, T.x, T.y, T.z, P.x, P.y, P.z));
45
102
  }
46
- function on(e, t) {
47
- const r = $e.current, a = Jt(r), l = Math.max(0, Math.min(a - 1, Math.round(e / Ie(rt.current)))), c = Math.max(0, Math.round(t / Ie(at.current)));
48
- return Math.max(0, Math.min(r - 1, c * a + l));
103
+ if (!L.length) return null;
104
+ const te = new N.BufferGeometry();
105
+ return te.setAttribute("position", new N.Float32BufferAttribute(L, 3)), te.computeVertexNormals(), te;
106
+ }
107
+ function Mr(n) {
108
+ const a = n.length, c = new Float32Array(a);
109
+ for (let M = 0; M < a; M += 3)
110
+ c[M] = n[M], c[M + 1] = n[M + 2], c[M + 2] = -n[M + 1];
111
+ let l = 1 / 0, r = 1 / 0, m = 1 / 0, d = -1 / 0, w = -1 / 0, b = -1 / 0;
112
+ for (let M = 0; M < a; M += 3)
113
+ l = Math.min(l, c[M]), d = Math.max(d, c[M]), r = Math.min(r, c[M + 1]), w = Math.max(w, c[M + 1]), m = Math.min(m, c[M + 2]), b = Math.max(b, c[M + 2]);
114
+ const T = (l + d) / 2, P = (m + b) / 2;
115
+ for (let M = 0; M < a; M += 3)
116
+ c[M] -= T, c[M + 1] -= r, c[M + 2] -= P;
117
+ return { localPos: c, size: { w: d - l, d: b - m, h: w - r } };
118
+ }
119
+ function jr(n) {
120
+ const {
121
+ apiRef: a,
122
+ objectsRef: c,
123
+ keyRef: l,
124
+ workerRef: r,
125
+ selectedPlateRef: m,
126
+ placeXRef: d,
127
+ plateCountRef: w,
128
+ canvasModeRef: b,
129
+ paintModeRef: T,
130
+ brushRadiusRef: P,
131
+ paintXformRef: M,
132
+ extruderColorsRef: B,
133
+ setOk: ee,
134
+ setStatus: L,
135
+ setGmode: te,
136
+ setCtxMenu: I,
137
+ setBrushRadius: A
138
+ } = n, u = H(null), _ = H({}), G = H(!1), K = H(200), h = H(200);
139
+ function E(j) {
140
+ const f = gt(w.current);
141
+ return { x: j % f * Ye(K.current), z: Math.floor(j / f) * Ye(h.current) };
142
+ }
143
+ function k(j, f) {
144
+ const g = w.current, y = gt(g), v = Math.max(0, Math.min(y - 1, Math.round(j / Ye(K.current)))), D = Math.max(0, Math.round(f / Ye(h.current)));
145
+ return Math.max(0, Math.min(g - 1, D * y + v));
49
146
  }
50
- const [Fe, or] = x(0), [lr, At] = x(!1), [sr, ln] = x(!1), [sn, ir] = x(0), [pt, D] = x(null), [cr, dr] = x("feature"), [xe, St] = x(null), [cn, zt] = x(0), [Rt, Lt] = x(0), [Me, pr] = x(!1), [ur, Pt] = x([]), [H, ze] = x("prepare"), [Ot, Zt] = x(!1), Ge = g(null), [De, Tt] = x(["#6aa0dc", "#e08a2b"]), Re = g(["#6aa0dc", "#e08a2b"]), [dn, ut] = x(""), [mr, fr] = x(!1), [pn, hr] = x(!1), [ve, gr] = x("off"), [un, mn] = x(5), [fn, hn] = x({ enf: 0, blk: 0 }), [gn, Y] = x(""), [bn, Ce] = x(null);
51
- Be(() => {
52
- const e = ee.current;
53
- if (!e) return;
54
- const t = document.createElement("canvas");
55
- if (!(t.getContext("webgl2") || t.getContext("webgl"))) {
56
- Qn(!1), ce("이 환경에서 WebGL 컨텍스트를 만들 수 없습니다 (헤드리스/GPU 미지원).");
147
+ return Ve(() => {
148
+ const j = u.current;
149
+ if (!j) return;
150
+ const f = document.createElement("canvas");
151
+ if (!(f.getContext("webgl2") || f.getContext("webgl"))) {
152
+ ee(!1), L("Cannot create a WebGL context in this environment (headless / no GPU).");
57
153
  return;
58
154
  }
59
- let r = e.clientWidth || 800, a = e.clientHeight || 480;
60
- const l = new h.WebGLRenderer({ antialias: !0 });
61
- l.setPixelRatio(Math.min(window.devicePixelRatio, 2)), l.setSize(r, a), l.setClearColor(1448478, 1), l.domElement.setAttribute("data-webgl", l.getContext() ? "ok" : "fail"), e.appendChild(l.domElement);
62
- const c = new h.Scene(), s = new h.PerspectiveCamera(50, r / a, 1, 3e3);
63
- s.position.set(210, 180, 260), c.add(new h.HemisphereLight(16777215, 2764598, 1));
64
- const b = new h.DirectionalLight(16777215, 1.1);
65
- b.position.set(120, 220, 160), c.add(b);
66
- const p = new h.Group();
67
- c.add(p);
68
- const j = new h.Group();
69
- c.add(j);
70
- const u = new oa(s, l.domElement);
71
- u.target.set(0, 22, 0), u.enableDamping = !1, u.update(), u.rotateSpeed = 1.6, u.panSpeed = 1.6;
72
- const f = new la(s, l.domElement);
73
- f.setMode("translate"), f.setSize(0.8);
74
- const B = new h.Box3(), O = (o) => {
75
- if (!o) return;
76
- o.updateMatrixWorld(!0), B.setFromObject(o);
77
- const i = B.min.y;
78
- Number.isFinite(i) && Math.abs(i) > 1e-4 && (o.position.y -= i, o.updateMatrixWorld(!0));
155
+ let g = j.clientWidth || 800, y = j.clientHeight || 480;
156
+ const v = new N.WebGLRenderer({ antialias: !0 });
157
+ v.setPixelRatio(Math.min(window.devicePixelRatio, 2)), v.setSize(g, y), v.setClearColor(1448478, 1), v.domElement.setAttribute("data-webgl", v.getContext() ? "ok" : "fail"), j.appendChild(v.domElement);
158
+ const D = new N.Scene(), W = new N.PerspectiveCamera(50, g / y, 1, 3e3);
159
+ W.position.set(210, 180, 260), D.add(new N.HemisphereLight(16777215, 2764598, 1));
160
+ const oe = new N.DirectionalLight(16777215, 1.1);
161
+ oe.position.set(120, 220, 160), D.add(oe);
162
+ const ge = new N.Group();
163
+ D.add(ge);
164
+ const de = new N.Group();
165
+ D.add(de);
166
+ const Y = new Un(W, v.domElement);
167
+ Y.target.set(0, 22, 0), Y.enableDamping = !1, Y.update(), Y.rotateSpeed = 1.6, Y.panSpeed = 1.6;
168
+ const C = new $n(W, v.domElement);
169
+ C.setMode("translate"), C.setSize(0.8);
170
+ const pe = new N.Box3(), je = (t) => {
171
+ if (!t) return;
172
+ t.updateMatrixWorld(!0), pe.setFromObject(t);
173
+ const s = pe.min.y;
174
+ Number.isFinite(s) && Math.abs(s) > 1e-4 && (t.position.y -= s, t.updateMatrixWorld(!0));
79
175
  };
80
- f.addEventListener("dragging-changed", (o) => {
81
- var i, m;
82
- u.enabled = !o.value, o.value || O(f.object), (m = (i = A.current).invalidate) == null || m.call(i);
83
- }), c.add(f), A.current = { scene: c, camera: s, renderer: l, orbit: u, transform: f, objectsGroup: p, toolpathGroup: j, plateBeds: [] }, typeof window < "u" && (window.__vpThree = A.current, window.__vpApi = () => d.current);
84
- let V = !0;
85
- const M = () => {
86
- V = !0;
176
+ C.addEventListener("dragging-changed", (t) => {
177
+ var s, R;
178
+ Y.enabled = !t.value, t.value || je(C.object), (R = (s = _.current).invalidate) == null || R.call(s);
179
+ }), D.add(C), _.current = { scene: D, camera: W, renderer: v, orbit: Y, transform: C, objectsGroup: ge, toolpathGroup: de, plateBeds: [] }, typeof window < "u" && (window.__vpThree = _.current, window.__vpApi = () => a.current);
180
+ let Ce = !0;
181
+ const ye = () => {
182
+ Ce = !0;
87
183
  };
88
- A.current.invalidate = M, u.addEventListener("change", M), f.addEventListener("change", M), l.domElement.addEventListener("pointermove", M);
89
- const _ = new h.Raycaster(), T = new h.Vector2();
90
- let U = null, z = null, ta = 0;
91
- const Wt = () => w.current.map((o) => o.mesh), ye = () => {
92
- for (const o of Wt()) o.material.emissive.setHex(o === z ? 44610 : o === U ? 2055220 : 0);
93
- }, ke = () => w.current.length ? `오브젝트 ${w.current.length}개 · 선택: ${z ? z.userData.name : "—"} | M/R/S · 좌드래그 회전 · ? 단축키` : `호버: ${U ? U.userData.name : "—"} · 선택: ${z ? z.userData.name : "—"} | 좌드래그 회전 · ? 단축키`, We = (o) => {
94
- const i = l.domElement.getBoundingClientRect();
95
- T.x = (o.clientX - i.left) / i.width * 2 - 1, T.y = -((o.clientY - i.top) / i.height) * 2 + 1;
96
- }, gt = () => {
97
- _.setFromCamera(T, s);
98
- const o = _.intersectObjects(Wt(), !1);
99
- return o.length ? o[0].object : null;
100
- }, na = () => {
101
- _.setFromCamera(T, s);
102
- const o = _.intersectObjects(Wt(), !1);
103
- return o.length ? o[0] : null;
104
- }, On = (o) => {
105
- var q;
106
- const i = Et.current;
107
- if (!i) return;
108
- We(o);
109
- const m = na();
110
- if (!m || m.faceIndex == null) return;
111
- const E = (I) => [I.x - i.cx, -I.z - i.cy, I.y - i.minz], N = E(m.point), k = E(s.position);
112
- (q = S.current) == null || q.postMessage({
184
+ _.current.invalidate = ye, Y.addEventListener("change", ye), C.addEventListener("change", ye), v.domElement.addEventListener("pointermove", ye);
185
+ const Re = new N.Raycaster(), be = new N.Vector2();
186
+ let Se = null, i = null, S = 0;
187
+ const V = () => c.current.map((t) => t.mesh), p = () => {
188
+ for (const t of V()) t.material.emissive.setHex(t === i ? 44610 : t === Se ? 2055220 : 0);
189
+ }, O = () => c.current.length ? `${c.current.length} object(s) · selected: ${i ? i.userData.name : "—"} | M/R/S · left-drag to orbit · ? for shortcuts` : `hover: ${Se ? Se.userData.name : "—"} · selected: ${i ? i.userData.name : "—"} | left-drag to orbit · ? for shortcuts`, ue = (t) => {
190
+ const s = v.domElement.getBoundingClientRect();
191
+ be.x = (t.clientX - s.left) / s.width * 2 - 1, be.y = -((t.clientY - s.top) / s.height) * 2 + 1;
192
+ }, ve = () => {
193
+ Re.setFromCamera(be, W);
194
+ const t = Re.intersectObjects(V(), !1);
195
+ return t.length ? t[0].object : null;
196
+ }, Ee = () => {
197
+ Re.setFromCamera(be, W);
198
+ const t = Re.intersectObjects(V(), !1);
199
+ return t.length ? t[0] : null;
200
+ }, Z = (t) => {
201
+ var me;
202
+ const s = M.current;
203
+ if (!s) return;
204
+ ue(t);
205
+ const R = Ee();
206
+ if (!R || R.faceIndex == null) return;
207
+ const F = (ne) => [ne.x - s.cx, -ne.z - s.cy, ne.y - s.minz], re = F(R.point), U = F(W.position);
208
+ (me = r.current) == null || me.postMessage({
113
209
  cmd: "paint",
114
- facet: m.faceIndex,
115
- hx: N[0],
116
- hy: N[1],
117
- hz: N[2],
118
- cx: k[0],
119
- cy: k[1],
120
- cz: k[2],
121
- radius: Qe.current,
122
- enforcer: W.current === "enforcer"
210
+ facet: R.faceIndex,
211
+ hx: re[0],
212
+ hy: re[1],
213
+ hz: re[2],
214
+ cx: U[0],
215
+ cy: U[1],
216
+ cz: U[2],
217
+ radius: P.current,
218
+ enforcer: T.current === "enforcer"
123
219
  });
124
- }, Zn = () => {
125
- const o = l.domElement;
126
- o.style.cursor = fe.current === "preview" ? "" : W.current !== "off" ? "crosshair" : U ? "pointer" : "";
127
- }, Tn = (o) => {
128
- if (fe.current === "preview") return;
129
- if (W.current !== "off") {
130
- tt.current && On(o);
220
+ }, q = () => {
221
+ const t = v.domElement;
222
+ t.style.cursor = b.current === "preview" ? "" : T.current !== "off" ? "crosshair" : Se ? "pointer" : "";
223
+ }, z = (t) => {
224
+ if (b.current === "preview") return;
225
+ if (T.current !== "off") {
226
+ G.current && Z(t);
131
227
  return;
132
228
  }
133
- if (f.dragging || f.axis) return;
134
- We(o);
135
- const i = gt();
136
- i !== U && (U = i, ye(), Zn(), ce(ke()));
137
- }, Bn = (o) => {
138
- if (o.button !== 0 || fe.current === "preview") return;
139
- if (W.current !== "off") {
140
- tt.current = !0, u.enabled = !1, On(o);
229
+ if (C.dragging || C.axis) return;
230
+ ue(t);
231
+ const s = ve();
232
+ s !== Se && (Se = s, p(), q(), L(O()));
233
+ }, ie = (t) => {
234
+ if (t.button !== 0 || b.current === "preview") return;
235
+ if (T.current !== "off") {
236
+ G.current = !0, Y.enabled = !1, Z(t);
141
237
  return;
142
238
  }
143
- if (f.dragging || f.axis) return;
144
- We(o);
145
- const i = gt();
146
- i ? (z = i, f.attach(i)) : (z = null, f.detach()), ye(), ce(ke());
147
- }, bt = () => {
148
- tt.current && (tt.current = !1, u.enabled = !0);
149
- }, In = (o) => {
150
- o.button !== 0 || fe.current === "preview" || W.current !== "off" || (We(o), gt() ? Xt() : (z = null, f.detach(), ye(), ce(ke())));
151
- }, Vn = (o) => {
152
- if (W.current === "off" || fe.current === "preview") return;
153
- o.preventDefault(), o.stopPropagation();
154
- const i = Math.min(15, Math.max(1, Qe.current + (o.deltaY < 0 ? 0.5 : -0.5)));
155
- Qe.current = i, mn(i);
239
+ if (C.dragging || C.axis) return;
240
+ ue(t);
241
+ const s = ve();
242
+ s ? (i = s, C.attach(s)) : (i = null, C.detach()), p(), L(O());
243
+ }, we = () => {
244
+ G.current && (G.current = !1, Y.enabled = !0);
245
+ }, se = (t) => {
246
+ t.button !== 0 || b.current === "preview" || T.current !== "off" || (ue(t), ve() ? Fe() : (i = null, C.detach(), p(), L(O())));
247
+ }, ae = (t) => {
248
+ if (T.current === "off" || b.current === "preview") return;
249
+ t.preventDefault(), t.stopPropagation();
250
+ const s = Math.min(15, Math.max(1, P.current + (t.deltaY < 0 ? 0.5 : -0.5)));
251
+ P.current = s, A(s);
156
252
  };
157
- let xt = null;
158
- const $n = (o) => {
159
- o.button === 2 && (xt = { x: o.clientX, y: o.clientY });
160
- }, Un = (o) => {
161
- if (o.preventDefault(), fe.current === "preview" || W.current !== "off" || xt && Math.hypot(o.clientX - xt.x, o.clientY - xt.y) > 4) return;
162
- We(o);
163
- const i = gt();
164
- i ? (z = i, f.attach(i)) : (z = null, f.detach()), ye(), ce(ke());
165
- const m = l.domElement.getBoundingClientRect();
166
- D({ x: o.clientX - m.left, y: o.clientY - m.top, onObject: !!i });
253
+ let J = null;
254
+ const ce = (t) => {
255
+ t.button === 2 && (J = { x: t.clientX, y: t.clientY });
256
+ }, ke = (t) => {
257
+ if (t.preventDefault(), b.current === "preview" || T.current !== "off" || J && Math.hypot(t.clientX - J.x, t.clientY - J.y) > 4) return;
258
+ ue(t);
259
+ const s = ve();
260
+ s ? (i = s, C.attach(s)) : (i = null, C.detach()), p(), L(O());
261
+ const R = v.domElement.getBoundingClientRect();
262
+ I({ x: t.clientX - R.left, y: t.clientY - R.top, onObject: !!s });
167
263
  };
168
- l.domElement.addEventListener("pointermove", Tn), l.domElement.addEventListener("pointerdown", Bn), l.domElement.addEventListener("pointerdown", $n), l.domElement.addEventListener("contextmenu", Un), l.domElement.addEventListener("dblclick", In), l.domElement.addEventListener("wheel", Vn, { passive: !1, capture: !0 }), window.addEventListener("pointerup", bt), window.addEventListener("pointercancel", bt);
169
- const ra = (o) => {
170
- f.setMode(o), er(o);
171
- }, Xt = () => {
172
- const o = w.current, i = new h.Box3();
173
- o.length ? o.forEach((k) => i.expandByObject(k.mesh)) : i.setFromCenterAndSize(new h.Vector3(0, 25, 0), new h.Vector3(100, 50, 100));
174
- const m = i.getCenter(new h.Vector3()), E = i.getSize(new h.Vector3()), N = Math.max(E.x, E.y, E.z, 20) * 1.9 + 40;
175
- u.target.copy(m), s.position.set(m.x + N * 0.7, m.y + N * 0.55 + E.y * 0.3, m.z + N), s.updateProjectionMatrix(), u.update();
176
- }, Fn = (o, i, m = null, E = null, N = null) => {
177
- const k = new h.BufferGeometry();
178
- k.setAttribute("position", new h.Float32BufferAttribute(i, 3)), k.computeVertexNormals(), k.computeBoundingBox();
179
- const q = Re.current[0] || "#6aa0dc", I = new h.Mesh(k, new h.MeshStandardMaterial({ color: new h.Color(q), roughness: 0.6, metalness: 0.05, side: h.DoubleSide }));
180
- if (m && I.rotation.copy(m), E && I.scale.copy(E), N)
181
- I.position.copy(N);
264
+ v.domElement.addEventListener("pointermove", z), v.domElement.addEventListener("pointerdown", ie), v.domElement.addEventListener("pointerdown", ce), v.domElement.addEventListener("contextmenu", ke), v.domElement.addEventListener("dblclick", se), v.domElement.addEventListener("wheel", ae, { passive: !1, capture: !0 }), window.addEventListener("pointerup", we), window.addEventListener("pointercancel", we);
265
+ const ze = (t) => {
266
+ C.setMode(t), te(t);
267
+ }, Fe = () => {
268
+ const t = c.current, s = new N.Box3();
269
+ t.length ? t.forEach((U) => s.expandByObject(U.mesh)) : s.setFromCenterAndSize(new N.Vector3(0, 25, 0), new N.Vector3(100, 50, 100));
270
+ const R = s.getCenter(new N.Vector3()), F = s.getSize(new N.Vector3()), re = Math.max(F.x, F.y, F.z, 20) * 1.9 + 40;
271
+ Y.target.copy(R), W.position.set(R.x + re * 0.7, R.y + re * 0.55 + F.y * 0.3, R.z + re), W.updateProjectionMatrix(), Y.update();
272
+ }, qe = (t, s, R = null, F = null, re = null) => {
273
+ const U = new N.BufferGeometry();
274
+ U.setAttribute("position", new N.Float32BufferAttribute(s, 3)), U.computeVertexNormals(), U.computeBoundingBox();
275
+ const me = B.current[0] || "#6aa0dc", ne = new N.Mesh(U, new N.MeshStandardMaterial({ color: new N.Color(me), roughness: 0.6, metalness: 0.05, side: N.DoubleSide }));
276
+ if (R && ne.rotation.copy(R), F && ne.scale.copy(F), re)
277
+ ne.position.copy(re);
182
278
  else {
183
- const pe = (k.boundingBox.max.x - k.boundingBox.min.x) * (E ? Math.abs(E.x) : 1);
184
- w.current.length === 0 && (Ue.current = 0);
185
- const se = _t(re.current);
186
- I.position.set(se.x + Ue.current + pe / 2, 0, se.z), Ue.current += pe + 8;
279
+ const Ne = (U.boundingBox.max.x - U.boundingBox.min.x) * (F ? Math.abs(F.x) : 1);
280
+ c.current.length === 0 && (d.current = 0);
281
+ const Pe = E(m.current);
282
+ ne.position.set(Pe.x + d.current + Ne / 2, 0, Pe.z), d.current += Ne + 8;
187
283
  }
188
- I.userData = { name: o }, p.add(I);
189
- const le = ++ta;
190
- return w.current.push({ id: le, name: o, mesh: I, localPos: i, extruder: 1, visible: !0 }), p.visible = !0, ce(ke()), Xt(), { id: le, name: o };
284
+ ne.userData = { name: t }, ge.add(ne);
285
+ const Me = ++S;
286
+ return c.current.push({ id: Me, name: t, mesh: ne, localPos: s, extruder: 1, visible: !0 }), ge.visible = !0, Ie != null && We(), L(O()), Fe(), { id: Me, name: t };
191
287
  };
192
- d.current = {
193
- setMode: ra,
194
- refreshCursor: () => Zn(),
195
- // 페인트 모드 전환 시 커서 힌트 동기화
288
+ let Ie = null;
289
+ function We() {
290
+ for (const t of c.current) {
291
+ const s = t.mesh.children.find((re) => {
292
+ var U;
293
+ return (U = re.userData) == null ? void 0 : U.overhang;
294
+ });
295
+ if (s && (t.mesh.remove(s), s.geometry.dispose(), s.material.dispose()), Ie == null) continue;
296
+ t.mesh.updateWorldMatrix(!0, !1);
297
+ const R = Er(t.mesh, Ie);
298
+ if (!R) continue;
299
+ const F = new N.Mesh(R, new N.MeshBasicMaterial({
300
+ color: 16729139,
301
+ transparent: !0,
302
+ opacity: 0.7,
303
+ side: N.DoubleSide,
304
+ depthWrite: !1,
305
+ polygonOffset: !0,
306
+ polygonOffsetFactor: -1,
307
+ polygonOffsetUnits: -1
308
+ }));
309
+ F.userData.overhang = !0, t.mesh.add(F);
310
+ }
311
+ ye();
312
+ }
313
+ C.addEventListener("dragging-changed", (t) => {
314
+ !t.value && Ie != null && We();
315
+ }), a.current = {
316
+ setMode: ze,
317
+ /** Highlight facets below `thresholdDeg` (the support threshold angle); null turns the shading off. */
318
+ setOverhang: (t) => {
319
+ Ie = t, We();
320
+ },
321
+ refreshCursor: () => q(),
322
+ // keeps the cursor hint in sync when the paint mode changes
196
323
  detachTransform: () => {
197
- z = null, f.detach(), ye();
324
+ i = null, C.detach(), p();
198
325
  },
199
- // 20단계: 페인팅 진입 시 기즈모 해제
200
- addObject: (o, i) => Fn(o, Ba(i).localPos),
201
- // ---- 단축키 지원 (복제/넛지/회전/줌투) ----
202
- getSnapshot: (o) => {
203
- const i = w.current.find((m) => m.id === o);
204
- return i ? { name: i.name, localPos: i.localPos, rot: i.mesh.rotation.clone(), scale: i.mesh.scale.clone(), pos: i.mesh.position.clone() } : null;
326
+ // stage 20: release the gizmo when entering painting
327
+ addObject: (t, s) => qe(t, Mr(s).localPos),
328
+ // ---- Shortcut support (duplicate/nudge/rotate/zoom-to) ----
329
+ getSnapshot: (t) => {
330
+ const s = c.current.find((R) => R.id === t);
331
+ return s ? { name: s.name, localPos: s.localPos, rot: s.mesh.rotation.clone(), scale: s.mesh.scale.clone(), pos: s.mesh.position.clone() } : null;
205
332
  },
206
- // keepPos=true 면 스냅샷의 원래 위치를 유지(분리). false(기본)면 배치 커서로 옆에 놓는다(복제/붙여넣기).
207
- spawnSnapshot: (o, i = !1) => o ? Fn(o.name, o.localPos, o.rot, o.scale, i && o.pos || null) : null,
208
- nudgeSelected: (o, i) => {
209
- z && (z.position.x += o, z.position.z += i);
333
+ // keepPos=true keeps the snapshot's original position (split). false (default) places it beside via the placement cursor (duplicate/paste).
334
+ spawnSnapshot: (t, s = !1) => t ? qe(t.name, t.localPos, t.rot, t.scale, s && t.pos || null) : null,
335
+ nudgeSelected: (t, s) => {
336
+ i && (i.position.x += t, i.position.z += s);
210
337
  },
211
- rotateSelectedY: (o) => {
212
- z && (z.rotation.y += o);
338
+ rotateSelectedY: (t) => {
339
+ i && (i.rotation.y += t);
213
340
  },
214
- frame: () => Xt(),
215
- // Z: 전체 오브젝트로 줌
341
+ frame: () => Fe(),
342
+ // Z: zoom to all objects
216
343
  frameBed: () => {
217
- const o = _t(re.current), i = Math.max(rt.current, at.current) * 1.1 + 40;
218
- u.target.set(o.x, 0, o.z), s.position.set(o.x + i * 0.55, i * 0.8, o.z + i), u.update();
344
+ const t = E(m.current), s = Math.max(K.current, h.current) * 1.1 + 40;
345
+ Y.target.set(t.x, 0, t.z), W.position.set(t.x + s * 0.55, s * 0.8, t.z + s), Y.update();
219
346
  },
220
- removeObject: (o) => {
221
- const i = w.current, m = i.findIndex((N) => N.id === o);
222
- if (m < 0) return;
223
- const E = i[m];
224
- z === E.mesh && (z = null, f.detach()), U === E.mesh && (U = null), p.remove(E.mesh), E.mesh.geometry.dispose(), E.mesh.material.dispose(), i.splice(m, 1), i.length === 0 && (Ue.current = 0), ye(), ce(ke());
347
+ removeObject: (t) => {
348
+ var re, U;
349
+ const s = c.current, R = s.findIndex((me) => me.id === t);
350
+ if (R < 0) return;
351
+ const F = s[R];
352
+ i === F.mesh && (i = null, C.detach()), Se === F.mesh && (Se = null);
353
+ for (const me of [...F.mesh.children])
354
+ F.mesh.remove(me), (re = me.geometry) == null || re.dispose(), (U = me.material) == null || U.dispose();
355
+ ge.remove(F.mesh), F.mesh.geometry.dispose(), F.mesh.material.dispose(), s.splice(R, 1), s.length === 0 && (d.current = 0), p(), L(O());
225
356
  },
226
- // MM: 익스트루더 오름차순으로 삼각형 정렬 병합 → 그룹0(ext1) 뒤 그룹1(ext2), split 반환.
227
- platePos: (o) => _t(o),
228
- // 플레이트 i 의 three (x,z) 오프셋
229
- plateOfObject: (o) => {
230
- o.mesh.updateMatrixWorld(!0);
231
- const i = new h.Vector3().setFromMatrixPosition(o.mesh.matrixWorld);
232
- return on(i.x, i.z);
357
+ // MM: merges triangles sorted by ascending extruder -> group0 (ext1) followed by group1 (ext2); returns split.
358
+ platePos: (t) => E(t),
359
+ // three (x,z) offset of plate i
360
+ plateOfObject: (t) => {
361
+ t.mesh.updateMatrixWorld(!0);
362
+ const s = new N.Vector3().setFromMatrixPosition(t.mesh.matrixWorld);
363
+ return k(s.x, s.z);
233
364
  },
234
- // plateIdx!=null 이면 그 플레이트 소속 오브젝트만 + 좌표를 플레이트 로컬(three-x -= PX)로 변환(28단계 계약 유지).
235
- buildMergedSTL: (o = null) => {
236
- let i = w.current.filter((v) => v.visible !== !1);
237
- if (o != null && (i = i.filter((v) => {
238
- v.mesh.updateMatrixWorld(!0);
239
- const $ = new h.Vector3().setFromMatrixPosition(v.mesh.matrixWorld);
240
- return on($.x, $.z) === o;
241
- })), !i.length) return null;
242
- const m = [...i].sort((v, $) => (v.extruder || 1) - ($.extruder || 1)), E = new Set(m.map((v) => v.extruder || 1)), N = new h.Vector3(), k = [];
243
- let q = 0, I = 0;
244
- for (const v of m) {
245
- (v.extruder || 1) >= 2 && I === 0 && (I = q), v.mesh.updateMatrixWorld(!0);
246
- const $ = v.mesh.matrixWorld, Xe = v.localPos;
247
- for (let Ke = 0; Ke < Xe.length; Ke += 3)
248
- N.set(Xe[Ke], Xe[Ke + 1], Xe[Ke + 2]).applyMatrix4($), k.push(N.x, -N.z, N.y);
249
- q += Xe.length / 9;
365
+ // When plateIdx != null, only objects on that plate are used and coordinates are converted to plate-local (three-x -= PX) (keeps the stage-28 contract).
366
+ buildMergedSTL: (t = null) => {
367
+ let s = c.current.filter((X) => X.visible !== !1);
368
+ if (t != null && (s = s.filter((X) => {
369
+ X.mesh.updateMatrixWorld(!0);
370
+ const xe = new N.Vector3().setFromMatrixPosition(X.mesh.matrixWorld);
371
+ return k(xe.x, xe.z) === t;
372
+ })), !s.length) return null;
373
+ const R = [...s].sort((X, xe) => (X.extruder || 1) - (xe.extruder || 1)), F = new Set(R.map((X) => X.extruder || 1)), re = new N.Vector3(), U = [];
374
+ let me = 0, ne = 0;
375
+ for (const X of R) {
376
+ (X.extruder || 1) >= 2 && ne === 0 && (ne = me), X.mesh.updateMatrixWorld(!0);
377
+ const xe = X.mesh.matrixWorld, Ge = X.localPos;
378
+ for (let Ue = 0; Ue < Ge.length; Ue += 3)
379
+ re.set(Ge[Ue], Ge[Ue + 1], Ge[Ue + 2]).applyMatrix4(xe), U.push(re.x, -re.z, re.y);
380
+ me += Ge.length / 9;
250
381
  }
251
- let le = 1e18, pe = 1e18, se = -1e18, je = -1e18;
252
- for (let v = 0; v < k.length; v += 3)
253
- k[v] < le && (le = k[v]), k[v] > se && (se = k[v]), k[v + 1] < pe && (pe = k[v + 1]), k[v + 1] > je && (je = k[v + 1]);
254
- const Ze = (le + se) / 2, Te = (pe + je) / 2;
255
- for (let v = 0; v < k.length; v += 3)
256
- k[v] -= Ze, k[v + 1] -= Te;
257
- const vt = Ze, Z = -Te, R = new ArrayBuffer(84 + q * 50), ue = new DataView(R);
258
- ue.setUint32(80, q, !0);
259
- let Q = 84, Ne = 0;
260
- for (let v = 0; v < q; v++) {
261
- Q += 12;
262
- for (let $ = 0; $ < 3; $++)
263
- ue.setFloat32(Q, k[Ne++], !0), ue.setFloat32(Q + 4, k[Ne++], !0), ue.setFloat32(Q + 8, k[Ne++], !0), Q += 12;
264
- ue.setUint16(Q, 0, !0), Q += 2;
382
+ let Me = 1e18, Ne = 1e18, Pe = -1e18, Ae = -1e18;
383
+ for (let X = 0; X < U.length; X += 3)
384
+ U[X] < Me && (Me = U[X]), U[X] > Pe && (Pe = U[X]), U[X + 1] < Ne && (Ne = U[X + 1]), U[X + 1] > Ae && (Ae = U[X + 1]);
385
+ const De = (Me + Pe) / 2, Oe = (Ne + Ae) / 2;
386
+ for (let X = 0; X < U.length; X += 3)
387
+ U[X] -= De, U[X + 1] -= Oe;
388
+ const Ze = De, fe = -Oe, le = new ArrayBuffer(84 + me * 50), Le = new DataView(le);
389
+ Le.setUint32(80, me, !0);
390
+ let _e = 84, Te = 0;
391
+ for (let X = 0; X < me; X++) {
392
+ _e += 12;
393
+ for (let xe = 0; xe < 3; xe++)
394
+ Le.setFloat32(_e, U[Te++], !0), Le.setFloat32(_e + 4, U[Te++], !0), Le.setFloat32(_e + 8, U[Te++], !0), _e += 12;
395
+ Le.setUint16(_e, 0, !0), _e += 2;
265
396
  }
266
- return { buf: R, split: I, extruders: E.size, offX: vt, offZ: Z };
397
+ return { buf: le, split: ne, extruders: F.size, offX: Ze, offZ: fe };
267
398
  },
268
- setObjectExtruder: (o, i) => {
269
- const m = w.current.find((E) => E.id === o);
270
- if (m) {
271
- m.extruder = i;
272
- const E = Re.current[i - 1];
273
- E && m.mesh.material.color.set(E);
399
+ setObjectExtruder: (t, s) => {
400
+ const R = c.current.find((F) => F.id === t);
401
+ if (R) {
402
+ R.extruder = s;
403
+ const F = B.current[s - 1];
404
+ F && R.mesh.material.color.set(F);
274
405
  }
275
406
  },
276
- setObjectVisible: (o, i) => {
277
- const m = w.current.find((E) => E.id === o);
278
- m && (m.visible = i, m.mesh.visible = i);
407
+ setObjectVisible: (t, s) => {
408
+ const R = c.current.find((F) => F.id === t);
409
+ R && (R.visible = s, R.mesh.visible = s);
279
410
  },
280
- // 27단계: 출력 토글(눈알)
411
+ // stage 27: print toggle (eye icon)
281
412
  recolorObjects: () => {
282
- for (const o of w.current) {
283
- const i = Re.current[(o.extruder || 1) - 1];
284
- i && o.mesh.material.color.set(i);
413
+ for (const t of c.current) {
414
+ const s = B.current[(t.extruder || 1) - 1];
415
+ s && t.mesh.material.color.set(s);
285
416
  }
286
417
  },
287
- // 필라멘트 색 변경 반영
418
+ // reflect filament color changes
288
419
  selectedObjectId: () => {
289
- var o;
290
- return z ? ((o = w.current.find((i) => i.mesh === z)) == null ? void 0 : o.id) ?? null : null;
420
+ var t;
421
+ return i ? ((t = c.current.find((s) => s.mesh === i)) == null ? void 0 : t.id) ?? null : null;
291
422
  },
292
- // 27단계: 뷰포트 툴바 "선택 삭제"
293
- selectObject: (o) => {
294
- const i = w.current.find((m) => m.id === o);
295
- i && (z = i.mesh, f.attach(i.mesh), ye(), ce(ke()));
423
+ // stage 27: viewport toolbar "delete selected"
424
+ selectObject: (t) => {
425
+ const s = c.current.find((R) => R.id === t);
426
+ s && (i = s.mesh, C.attach(s.mesh), p(), L(O()));
296
427
  },
297
- // 28단계 P1: 바닥 안착 — 로컬 지오메트리는 bakeLocal 이 minZ→0(로드 시 1회). 기즈모 Z이동 후 재안착.
298
- // (원본 ensure_on_bed 의 싱킹 허용[allow_negative_z]은 범위 외 — 우리는 -min_z 안착만.)
428
+ // Stage 28 P1: seat on the bed — local geometry is baked to minZ->0 by bakeLocal (once on load). Re-seat after a gizmo Z move.
429
+ // (The upstream ensure_on_bed sinking allowance [allow_negative_z] is out of scope — we only seat by -min_z.)
299
430
  placeOnBed: () => {
300
- var o;
301
- for (const i of w.current) i.mesh.position.y = 0;
302
- z && ((o = f.update) == null || o.call(f));
431
+ var t;
432
+ for (const s of c.current) s.mesh.position.y = 0;
433
+ i && ((t = C.update) == null || t.call(C));
303
434
  },
304
435
  onSliced: () => {
305
- p.visible = !1, f.detach(), z = null, ye();
436
+ ge.visible = !1, C.detach(), i = null, p();
306
437
  },
307
438
  showObjects: () => {
308
- p.visible = !0;
439
+ ge.visible = !0;
309
440
  },
310
- // 29단계-2: N 플레이트 렌더 — 각 플레이트 = 그리드+테두리, PX_i 오프셋, 선택 플레이트 테두리 하이라이트.
311
- setPlates: (o, i, m, E) => {
312
- const N = A.current;
313
- rt.current = i, at.current = m, $e.current = o, re.current = E;
314
- for (const Z of N.plateBeds || []) for (const R of [Z.gridThin, Z.gridBold, Z.border])
315
- N.scene.remove(R), R.geometry.dispose(), R.material.dispose();
316
- N.plateBeds = [];
317
- const k = Jt(o), q = Ie(i), I = Ie(m), le = Math.min(i, m), pe = le >= 6e3 ? 100 : le >= 1200 ? 50 : le >= 600 ? 20 : 10, se = [], je = [], Ze = -i / 2, Te = -m / 2;
318
- for (let Z = 0, R = Ze; R <= i / 2 + 1e-6; R = Ze + ++Z * pe) (Z % 5 ? se : je).push(R, 0, Te, R, 0, m / 2);
319
- for (let Z = 0, R = Te; R <= m / 2 + 1e-6; R = Te + ++Z * pe) (Z % 5 ? se : je).push(Ze, 0, R, i / 2, 0, R);
320
- const vt = (Z) => {
321
- const R = new h.BufferGeometry();
322
- return R.setAttribute("position", new h.Float32BufferAttribute(Z, 3)), R;
441
+ // Stage 29-2: render N plates — each plate = grid + border, offset by PX_i, with the selected plate's border highlighted.
442
+ setPlates: (t, s, R, F) => {
443
+ const re = _.current;
444
+ K.current = s, h.current = R, w.current = t, m.current = F;
445
+ for (const fe of re.plateBeds || []) for (const le of [fe.gridThin, fe.gridBold, fe.border])
446
+ re.scene.remove(le), le.geometry.dispose(), le.material.dispose();
447
+ re.plateBeds = [];
448
+ const U = gt(t), me = Ye(s), ne = Ye(R), Me = Math.min(s, R), Ne = Me >= 6e3 ? 100 : Me >= 1200 ? 50 : Me >= 600 ? 20 : 10, Pe = [], Ae = [], De = -s / 2, Oe = -R / 2;
449
+ for (let fe = 0, le = De; le <= s / 2 + 1e-6; le = De + ++fe * Ne) (fe % 5 ? Pe : Ae).push(le, 0, Oe, le, 0, R / 2);
450
+ for (let fe = 0, le = Oe; le <= R / 2 + 1e-6; le = Oe + ++fe * Ne) (fe % 5 ? Pe : Ae).push(De, 0, le, s / 2, 0, le);
451
+ const Ze = (fe) => {
452
+ const le = new N.BufferGeometry();
453
+ return le.setAttribute("position", new N.Float32BufferAttribute(fe, 3)), le;
323
454
  };
324
- for (let Z = 0; Z < o; Z++) {
325
- const R = Z % k * q, ue = Math.floor(Z / k) * I, Q = new h.LineSegments(vt(se), new h.LineBasicMaterial({ color: 2304561 })), Ne = new h.LineSegments(vt(je), new h.LineBasicMaterial({ color: 3752781 }));
326
- Q.position.set(R, 0, ue), Ne.position.set(R, 0, ue), N.scene.add(Q), N.scene.add(Ne);
327
- const v = Z === E, $ = new h.LineSegments(new h.EdgesGeometry(new h.PlaneGeometry(i, m)), new h.LineBasicMaterial({ color: v ? 44610 : 4871520, linewidth: v ? 2 : 1 }));
328
- $.rotation.x = -Math.PI / 2, $.position.set(R, 0, ue), N.scene.add($), N.plateBeds.push({ gridThin: Q, gridBold: Ne, border: $ });
455
+ for (let fe = 0; fe < t; fe++) {
456
+ const le = fe % U * me, Le = Math.floor(fe / U) * ne, _e = new N.LineSegments(Ze(Pe), new N.LineBasicMaterial({ color: 2304561 })), Te = new N.LineSegments(Ze(Ae), new N.LineBasicMaterial({ color: 3752781 }));
457
+ _e.position.set(le, 0, Le), Te.position.set(le, 0, Le), re.scene.add(_e), re.scene.add(Te);
458
+ const X = fe === F, xe = new N.LineSegments(new N.EdgesGeometry(new N.PlaneGeometry(s, R)), new N.LineBasicMaterial({ color: X ? 44610 : 4871520, linewidth: X ? 2 : 1 }));
459
+ xe.rotation.x = -Math.PI / 2, xe.position.set(le, 0, Le), re.scene.add(xe), re.plateBeds.push({ gridThin: _e, gridBold: Te, border: xe });
329
460
  }
330
461
  },
331
- setBed: (o, i) => {
332
- var m;
333
- (m = d.current) == null || m.setPlates($e.current, o, i, re.current);
462
+ setBed: (t, s) => {
463
+ var R;
464
+ (R = a.current) == null || R.setPlates(w.current, t, s, m.current);
334
465
  }
335
- // 하위호환
466
+ // backwards compatible
336
467
  };
337
- const Gn = (o) => {
338
- var i;
339
- (i = Ye.current) == null || i.call(Ye, o), M();
468
+ const et = (t) => {
469
+ var s;
470
+ (s = l.current) == null || s.call(l, t), ye();
340
471
  };
341
- window.addEventListener("keydown", Gn);
342
- const Dn = new ResizeObserver(() => {
343
- r = e.clientWidth || r, a = e.clientHeight || a, s.aspect = r / a, s.updateProjectionMatrix(), l.setSize(r, a), M();
472
+ window.addEventListener("keydown", et);
473
+ const tt = new ResizeObserver(() => {
474
+ g = j.clientWidth || g, y = j.clientHeight || y, W.aspect = g / y, W.updateProjectionMatrix(), v.setSize(g, y), ye();
344
475
  });
345
- Dn.observe(e), ce(ke());
346
- let Hn = 0, qn = 0;
347
- const Wn = (o = 0) => {
348
- Hn = requestAnimationFrame(Wn), u.update(), (V || o - qn > 500) && (V = !1, qn = o, l.render(c, s));
476
+ tt.observe(j), L(O());
477
+ let nt = 0, Xe = 0;
478
+ const rt = (t = 0) => {
479
+ nt = requestAnimationFrame(rt), Y.update(), (Ce || t - Xe > 500) && (Ce = !1, Xe = t, v.render(D, W));
349
480
  };
350
- return Wn(), () => {
351
- cancelAnimationFrame(Hn), Dn.disconnect(), window.removeEventListener("keydown", Gn), l.domElement.removeEventListener("pointermove", Tn), l.domElement.removeEventListener("pointerdown", Bn), l.domElement.removeEventListener("pointerdown", $n), l.domElement.removeEventListener("contextmenu", Un), l.domElement.removeEventListener("dblclick", In), l.domElement.removeEventListener("wheel", Vn, { capture: !0 }), window.removeEventListener("pointerup", bt), window.removeEventListener("pointercancel", bt), d.current = null, f.detach(), f.dispose(), u.dispose(), c.traverse((o) => {
352
- o.geometry && o.geometry.dispose(), o.material && o.material.dispose();
353
- }), l.dispose(), l.domElement.parentNode && l.domElement.parentNode.removeChild(l.domElement);
354
- };
355
- }, []), Be(() => () => {
356
- S.current && (S.current.terminate(), S.current = null);
357
- }, []), Be(() => {
358
- try {
359
- qe().postMessage({ cmd: "warmup" });
360
- } catch {
361
- }
362
- }, []);
363
- const J = Kt(P);
364
- Be(() => {
365
- var e;
366
- (e = d.current) == null || e.setPlates(ae, J.bed_width, J.bed_depth, Fe);
367
- }, [J.bed_width, J.bed_depth, ae, Fe]), Be(() => {
368
- var r;
369
- fe.current = H;
370
- const e = A.current;
371
- if (!e.toolpathGroup) return;
372
- const t = H === "preview";
373
- e.toolpathGroup.visible = t, e.objectsGroup && (e.objectsGroup.visible = !t && w.current.length > 0), t && ((r = d.current) == null || r.detachTransform(), W.current !== "off" && oe("off"));
374
- }, [H]);
375
- function mt(e) {
376
- var r;
377
- const t = y.current[e];
378
- t && (t.group.remove(t.ctl.mesh), t.group.remove(t.ctl.travLines), t.ctl.dispose(), (r = A.current.toolpathGroup) == null || r.remove(t.group), delete y.current[e], G.current === t.ctl && (G.current = null, ie.current = null));
379
- }
380
- function br() {
381
- for (const e of Object.keys(y.current)) mt(Number(e));
382
- G.current = null, ie.current = null;
383
- }
384
- function Bt(e, t) {
385
- const { toolpathGroup: r } = A.current;
386
- if (!r || (mt(e), !t || !t.length)) return null;
387
- const a = pa(t, Qt.current), l = ua(h, a), c = nt.current[e] || { offX: 0, offZ: 0 }, s = new h.Group();
388
- s.rotation.x = -Math.PI / 2, s.position.set(c.offX || 0, 0, c.offZ || 0), s.add(l.mesh), s.add(l.travLines), r.add(s), l.setTravelVisible(yt.current), l.setLayerRange(0, Math.max(0, t.length - 1));
389
- const b = Kn(a, kt.current, xn());
390
- l.setColors(b.color);
391
- const p = { group: s, ctl: l, seg: a, layers: t };
392
- return y.current[e] = p, p;
393
- }
394
- function xr() {
395
- for (const [e, t] of Object.entries(X.current)) {
396
- const r = Number(e);
397
- if (!t || t.error || !t.layers || !t.layers.length) continue;
398
- const a = y.current[r];
399
- (!a || a.layers !== t.layers) && Bt(r, t.layers);
400
- }
401
- }
402
- function xn() {
403
- const e = (r, a) => {
404
- const l = wt(P, r), c = parseFloat(l);
405
- return Number.isFinite(c) ? c : a;
406
- }, t = e("outer_wall_speed", 60);
407
- return {
408
- speedByType: {
409
- 1: t,
410
- 2: e("sparse_infill_speed", 40),
411
- 3: e("internal_solid_infill_speed", 45),
412
- 4: t,
413
- 5: e("support_speed", 35),
414
- 6: e("support_speed", 35),
415
- 7: e("gap_infill_speed", 30),
416
- 8: t,
417
- 9: e("bridge_speed", 25),
418
- 10: e("ironing_speed", 20),
419
- 11: t
420
- },
421
- firstLayerSpeed: e("initial_layer_speed", 30),
422
- closeFanLayers: e("close_fan_the_first_x_layers", 1),
423
- fanNormal: e("fan_max_speed", 100),
424
- tempNormal: e("nozzle_temperature", 210),
425
- tempFirst: e("nozzle_temperature_initial_layer", e("nozzle_temperature", 210))
481
+ return rt(), () => {
482
+ cancelAnimationFrame(nt), tt.disconnect(), window.removeEventListener("keydown", et), v.domElement.removeEventListener("pointermove", z), v.domElement.removeEventListener("pointerdown", ie), v.domElement.removeEventListener("pointerdown", ce), v.domElement.removeEventListener("contextmenu", ke), v.domElement.removeEventListener("dblclick", se), v.domElement.removeEventListener("wheel", ae, { capture: !0 }), window.removeEventListener("pointerup", we), window.removeEventListener("pointercancel", we), a.current = null, C.detach(), C.dispose(), Y.dispose(), D.traverse((t) => {
483
+ t.geometry && t.geometry.dispose(), t.material && t.material.dispose();
484
+ }), v.dispose(), v.domElement.parentNode && v.domElement.parentNode.removeChild(v.domElement);
426
485
  };
427
- }
428
- function It() {
429
- const e = xn();
430
- for (const t of Object.values(y.current)) {
431
- const r = Kn(t.seg, kt.current, e);
432
- t.ctl.setColors(r.color), t.ctl === G.current && St({ min: r.min, max: r.max, label: r.label, unit: r.unit, cont: r.cont });
433
- }
434
- }
435
- function vr() {
436
- const e = re.current, t = te.current || [];
437
- if (!t.length) {
438
- mt(e), Se(0), G.current = null, ie.current = null;
439
- return;
440
- }
441
- const r = Bt(e, t);
442
- if (!r) {
443
- Se(0);
444
- return;
445
- }
446
- G.current = r.ctl, ie.current = r.seg, r.ctl.setLayerRange(me.current, ne.current), It(), Se(r.seg.nSeg), Pt(Xn(r.seg.typeLengths));
447
- }
448
- function wr() {
449
- var e;
450
- (e = G.current) == null || e.setLayerRange(me.current, ne.current);
451
- }
452
- const Le = g(null), He = g(0), we = () => {
453
- He.current && (clearInterval(He.current), He.current = 0);
454
- }, vn = () => {
455
- const e = Le.current;
456
- if (console.info("[vp-cancel] cancelSlice, hasView=", !!(e != null && e.cancel)), e != null && e.cancel)
486
+ }, []), { mountRef: u, three: _ };
487
+ }
488
+ function Cr(n) {
489
+ const {
490
+ settings: a,
491
+ wipeTowerReal: c,
492
+ workerRef: l,
493
+ apiRef: r,
494
+ layersDataRef: m,
495
+ layerLoRef: d,
496
+ layerHiRef: w,
497
+ rebuildToolpaths: b,
498
+ rebuildPaintOverlay: T,
499
+ setProgress: P,
500
+ setSlicing: M,
501
+ setError: B,
502
+ setStats: ee,
503
+ setOverBed: L,
504
+ setLayerCount: te,
505
+ setLayerLo: I,
506
+ setLayerHi: A,
507
+ setGcodeUrl: u,
508
+ setCanvasMode: _,
509
+ setPaintCounts: G
510
+ } = n, K = H(null), h = H(null), E = H(!1), k = H(null), j = H(null), f = H(0), g = () => {
511
+ f.current && (clearInterval(f.current), f.current = 0);
512
+ }, y = () => {
513
+ const i = j.current;
514
+ if (console.info("[vp-cancel] cancelSlice, hasView=", !!(i != null && i.cancel)), i != null && i.cancel)
457
515
  try {
458
- Atomics.store(e.cancel, 0, 1);
516
+ Atomics.store(i.cancel, 0, 1);
459
517
  } catch {
460
- e.cancel[0] = 1;
518
+ i.cancel[0] = 1;
461
519
  }
462
- }, yr = (e, t) => {
463
- const r = t > 2 ? (t - 2) / 2 : 0;
464
- return r ? e <= r ? 0.35 * (e / r) : e === r + 1 ? 0.36 : e === r + 2 ? 0.87 : 0.87 + 0.13 * ((e - r - 2) / r) : t ? e / t : 0;
465
- }, kr = (e) => {
466
- const t = Le.current;
467
- t && (we(), He.current = setInterval(() => {
468
- const r = t.arr[0];
469
- _e(0.36 + 0.51 * Math.min(0.95, r / (3.5 * e)));
520
+ }, v = (i, S) => {
521
+ const V = S > 2 ? (S - 2) / 2 : 0;
522
+ return V ? i <= V ? 0.35 * (i / V) : i === V + 1 ? 0.36 : i === V + 2 ? 0.87 : 0.87 + 0.13 * ((i - V - 2) / V) : S ? i / S : 0;
523
+ }, D = (i) => {
524
+ const S = j.current;
525
+ S && (g(), f.current = setInterval(() => {
526
+ const V = S.arr[0];
527
+ P(0.36 + 0.51 * Math.min(0.95, V / (3.5 * i)));
470
528
  }, 150));
471
- }, Er = () => {
472
- const e = Le.current;
473
- e && (we(), He.current = setInterval(() => {
474
- _e(0.35 * Math.min(1, e.arr[0] / 1e3));
529
+ }, W = () => {
530
+ const i = j.current;
531
+ i && (g(), f.current = setInterval(() => {
532
+ P(0.35 * Math.min(1, i.arr[0] / 1e3));
475
533
  }, 150));
476
534
  };
477
- function qe() {
478
- if (!S.current) {
479
- const e = sa();
480
- e.onmessage = (r) => {
481
- var c, s, b, p, j;
482
- const a = r.data, l = he.current;
483
- if (a.type === "progress") {
484
- const u = a.total > 2 ? (a.total - 2) / 2 : 0;
485
- u && a.done <= u && we(), u && a.done === u + 1 && kr(u), u && a.done >= u + 2 && we(), _e(yr(a.done, a.total)), (c = l == null ? void 0 : l.note) == null || c.call(l, a.done, a.total), (s = l == null ? void 0 : l.kick) == null || s.call(l);
486
- } else if (a.type === "supsab")
535
+ function oe() {
536
+ if (!l.current) {
537
+ const i = Hn();
538
+ i.onmessage = (V) => {
539
+ var ue, ve, Ee, Z, q;
540
+ const p = V.data, O = K.current;
541
+ if (p.type === "progress") {
542
+ const z = p.total > 2 ? (p.total - 2) / 2 : 0;
543
+ z && p.done <= z && g(), z && p.done === z + 1 && D(z), z && p.done >= z + 2 && g(), P(v(p.done, p.total)), (ue = O == null ? void 0 : O.note) == null || ue.call(O, p.done, p.total), (ve = O == null ? void 0 : O.kick) == null || ve.call(O);
544
+ } else if (p.type === "supsab")
487
545
  try {
488
- Le.current = { arr: new Uint32Array(a.buf, a.ptr, 1), cancel: a.cancelPtr ? new Uint32Array(a.buf, a.cancelPtr, 1) : null }, console.info("[vp-cancel] supsab cancelPtr=", a.cancelPtr);
489
- } catch (u) {
490
- console.info("[vp-cancel] supsab view fail", u);
546
+ j.current = { arr: new Uint32Array(p.buf, p.ptr, 1), cancel: p.cancelPtr ? new Uint32Array(p.buf, p.cancelPtr, 1) : null }, console.info("[vp-cancel] supsab cancelPtr=", p.cancelPtr);
547
+ } catch (z) {
548
+ console.info("[vp-cancel] supsab view fail", z);
491
549
  }
492
- else if (a.type === "layer") {
493
- (b = l == null ? void 0 : l.kick) == null || b.call(l);
494
- const u = Mt.current;
495
- u && (u.layers.push({ z: a.z, paths: a.paths, widths: a.widths }), a.gcode && u.gcode.push(a.gcode));
496
- } else a.type === "done" ? (we(), l ? (he.current = null, (p = l.stop) == null || p.call(l), l.resolve(wn(a.result))) : (Mr(wn(a.result)), ge(!1))) : a.type === "error" ? (we(), l ? (he.current = null, (j = l.stop) == null || j.call(l), l.reject(new Error(a.error))) : (C("슬라이스 실패: " + a.error), ge(!1))) : a.type === "prepared" || (a.type === "painted" ? (hn({ enf: a.enf, blk: a.blk }), e.postMessage({ cmd: "overlay" })) : a.type === "overlay" && Ar(a.enf, a.blk));
550
+ else if (p.type === "layer") {
551
+ (Ee = O == null ? void 0 : O.kick) == null || Ee.call(O);
552
+ const z = h.current;
553
+ z && (z.layers.push({ z: p.z, paths: p.paths, widths: p.widths }), p.gcode && z.gcode.push(p.gcode));
554
+ } else p.type === "done" ? (g(), O ? (K.current = null, (Z = O.stop) == null || Z.call(O), O.resolve(ge(p.result))) : (de(ge(p.result)), M(!1))) : p.type === "error" ? (g(), O ? (K.current = null, (q = O.stop) == null || q.call(O), O.reject(new Error(p.error))) : (B("Slice failed: " + p.error), M(!1))) : p.type === "prepared" || (p.type === "painted" ? (G({ enf: p.enf, blk: p.blk }), i.postMessage({ cmd: "overlay" })) : p.type === "overlay" && T(p.enf, p.blk));
497
555
  };
498
- const t = (r) => {
499
- var l;
500
- if (S.current !== e) return;
501
- we(), Le.current = null;
502
- const a = he.current;
503
- if (a) {
504
- he.current = null, (l = a.stop) == null || l.call(a);
556
+ const S = (V) => {
557
+ var O;
558
+ if (l.current !== i) return;
559
+ g(), j.current = null;
560
+ const p = K.current;
561
+ if (p) {
562
+ K.current = null, (O = p.stop) == null || O.call(p);
505
563
  try {
506
- e.terminate();
564
+ i.terminate();
507
565
  } catch {
508
566
  }
509
- S.current = null, a.reject(new Error(r));
567
+ l.current = null, p.reject(new Error(V));
510
568
  } else
511
- C(r), ge(!1);
569
+ B(V), M(!1);
512
570
  };
513
- e.onerror = (r) => t("Worker 종료(메모리 초과 추정): " + (r.message || "worker error")), e.onmessageerror = () => t("Worker 메시지 오류(구조화 복제 실패)"), S.current = e, typeof window < "u" && (window.__vpWorker = e);
514
- }
515
- return S.current;
516
- }
517
- function wn(e) {
518
- if (e && e.stats && e.stats.streamed) {
519
- const t = Mt.current || { layers: [], gcode: [] };
520
- return { stats: e.stats, layers: t.layers, gcode: t.gcode.join("") };
571
+ i.onerror = (V) => S("Worker terminated (likely out of memory): " + (V.message || "worker error")), i.onmessageerror = () => S("Worker message error (structured clone failed)"), l.current = i, typeof window < "u" && (window.__vpWorker = i);
521
572
  }
522
- return e;
573
+ return l.current;
523
574
  }
524
- function Mr(e) {
525
- var r;
526
- if (e.error) {
527
- C(String(e.error));
528
- return;
575
+ function ge(i) {
576
+ if (i && i.stats && i.stats.streamed) {
577
+ const S = h.current || { layers: [], gcode: [] };
578
+ return { stats: i.stats, layers: S.layers, gcode: S.gcode.join("") };
529
579
  }
530
- te.current = e.layers;
531
- const t = e.layers.length;
532
- me.current = 0, ne.current = t - 1, zt(0), Lt(t - 1), vr(), (r = d.current) == null || r.onSliced(), ze("preview"), it({ layers: e.stats.layers, segments: e.stats.path_segments, filament: e.stats.filament_mm, timeSec: e.stats.time_estimate }), ct(!!e.stats.over_bed), dt(t), ut((a) => (a && URL.revokeObjectURL(a), URL.createObjectURL(new Blob([e.gcode], { type: "text/plain" }))));
580
+ return i;
533
581
  }
534
- async function yn(e) {
535
- var l, c;
536
- const t = Array.from(e || []).filter((s) => Yt.includes(fa(s.name))), r = Array.from(e || []).length - t.length;
537
- if (!t.length) {
538
- r && C("지원 포맷: STL/OBJ/3MF/AMF/PLY");
582
+ function de(i) {
583
+ var V;
584
+ if (i.error) {
585
+ B(String(i.error));
539
586
  return;
540
587
  }
541
- C(""), nn(""), _e(0), te.current = null, ie.current = null, X.current = {}, nt.current = {}, br(), ft(), it(null), ct(!1), dt(0), Se(0), St(null), Y(""), Ce(null), ut((s) => (s && URL.revokeObjectURL(s), "")), ze("prepare"), (l = d.current) == null || l.showObjects();
542
- let a = 0;
543
- for (const s of t)
544
- try {
545
- const b = performance.now(), p = await s.arrayBuffer(), j = performance.now(), u = await ha(s.name, p), f = performance.now();
546
- for (const B of u)
547
- (c = d.current) == null || c.addObject(B.name, B.modelPos), a += B.modelPos.length / 9;
548
- console.info(`[vp-prof] load ${s.name}: read ${(j - b).toFixed(0)}ms, parse ${(f - j).toFixed(0)}ms, scene ${(performance.now() - f).toFixed(0)}ms`);
549
- } catch (b) {
550
- C(`로드 실패 ${s.name}: ${b && b.message || b}`);
551
- }
552
- ot(w.current.map((s) => ({ id: s.id, name: s.name, extruder: s.extruder, visible: s.visible !== !1 }))), a > 1e5 && nn(`삼각형 ${Math.round(a).toLocaleString()}개 — 슬라이스가 오래 걸릴 수 있습니다`);
553
- }
554
- function Cr(e) {
555
- yn(e.target.files), e.target.value = "";
556
- }
557
- function Vt(e) {
558
- var t;
559
- (t = d.current) == null || t.removeObject(e), ot(w.current.map((r) => ({ id: r.id, name: r.name, extruder: r.extruder, visible: r.visible !== !1 })));
560
- }
561
- function jr(e) {
562
- var t;
563
- e.preventDefault(), Zt(!1), yn((t = e.dataTransfer) == null ? void 0 : t.files);
564
- }
565
- function Nr(e) {
566
- e.preventDefault(), e.dataTransfer && (e.dataTransfer.dropEffect = "copy"), Ot || Zt(!0);
567
- }
568
- function _r(e) {
569
- e.currentTarget.contains(e.relatedTarget) || Zt(!1);
570
- }
571
- function Ar(e, t) {
572
- var s, b;
573
- const r = A.current;
574
- if (!r.objectsGroup) return;
575
- const a = Et.current || { cx: 0, cy: 0, minz: 0 }, l = et.current;
576
- if (l)
577
- for (const p of ["enf", "blk"]) l[p] && (r.objectsGroup.remove(l[p]), l[p].geometry.dispose(), l[p].material.dispose());
578
- const c = (p, j) => {
579
- if (!p || p.length < 9) return null;
580
- const u = new Float32Array(p.length);
581
- for (let O = 0; O < p.length; O += 3) {
582
- const V = p[O], M = p[O + 1], _ = p[O + 2];
583
- u[O] = V + a.cx, u[O + 1] = _ + a.minz, u[O + 2] = -(M + a.cy);
584
- }
585
- const f = new h.BufferGeometry();
586
- f.setAttribute("position", new h.Float32BufferAttribute(u, 3)), f.computeVertexNormals();
587
- const B = new h.Mesh(f, new h.MeshBasicMaterial({ color: j, transparent: !0, opacity: 0.55, side: h.DoubleSide, depthTest: !1 }));
588
- return B.renderOrder = 999, r.objectsGroup.add(B), B;
589
- };
590
- et.current = { enf: c(e, 2845951), blk: c(t, 14826299) }, (b = (s = A.current).invalidate) == null || b.call(s);
588
+ m.current = i.layers;
589
+ const S = i.layers.length;
590
+ d.current = 0, w.current = S - 1, I(0), A(S - 1), b(), (V = r.current) == null || V.onSliced(), _("preview"), ee({
591
+ layers: i.stats.layers,
592
+ segments: i.stats.path_segments,
593
+ filament: i.stats.filament_mm,
594
+ timeSec: i.stats.time_estimate,
595
+ engine: i.stats.time_engine,
596
+ limits: i.stats.machine_limits
597
+ }), L(!!i.stats.over_bed), te(S), u((p) => (p && URL.revokeObjectURL(p), URL.createObjectURL(new Blob([i.gcode], { type: "text/plain" }))));
591
598
  }
592
- function Sr() {
593
- const e = A.current, t = et.current;
594
- if (t && e.objectsGroup)
595
- for (const r of ["enf", "blk"]) t[r] && (e.objectsGroup.remove(t[r]), t[r].geometry.dispose(), t[r].material.dispose());
596
- et.current = null;
597
- }
598
- function oe(e) {
599
- var t, r, a;
600
- if (e !== "off" && w.current.length === 0) {
601
- C("먼저 STL 을 업로드하세요");
602
- return;
603
- }
604
- if (e !== "off") {
605
- const l = (t = d.current) == null ? void 0 : t.buildMergedSTL(re.current);
606
- if (!l) return;
607
- Et.current = { cx: l.offX, cy: -l.offZ, minz: 0 }, (r = d.current) == null || r.detachTransform(), qe().postMessage({ cmd: "prepare", stl: l.buf });
599
+ Ve(() => () => {
600
+ l.current && (l.current.terminate(), l.current = null);
601
+ }, []), Ve(() => {
602
+ try {
603
+ oe().postMessage({ cmd: "warmup" });
604
+ } catch {
608
605
  }
609
- W.current = e, gr(e), (a = d.current) == null || a.refreshCursor();
610
- }
611
- function zr() {
612
- qe().postMessage({ cmd: "clear" }), Sr(), hn({ enf: 0, blk: 0 });
613
- }
614
- const Rr = 6e4, Lr = 3e5;
615
- function $t(e, t) {
616
- return new Promise((r, a) => {
606
+ }, []);
607
+ const Y = 6e4, C = 3e5;
608
+ function pe(i, S) {
609
+ return new Promise((V, p) => {
617
610
  if (typeof window < "u" && window.__vpFail && window.__vpFail(window.__vpSliceN = (window.__vpSliceN || 0) + 1)) {
618
- a(new Error("forced failure (test hook)"));
611
+ p(new Error("forced failure (test hook)"));
619
612
  return;
620
613
  }
621
- const l = typeof window < "u" && window.__vpStallNext ? (window.__vpStallNext = !1, !0) : !1, c = typeof window < "u" && window.__vpWatchdogOnce ? ((M) => (window.__vpWatchdogOnce = 0, M))(window.__vpWatchdogOnce) : Rr;
622
- Mt.current = { layers: [], gcode: [] };
623
- let s = 0, b = 0, p = 0;
624
- const j = performance.now();
625
- let u = "";
626
- const f = (M, _) => {
627
- b = M, p = _;
628
- const T = _ > 2 ? (_ - 2) / 2 : 0, U = T ? M < T ? "PASS1…" : M === T ? "PASS1-done" : M === T + 1 ? "surf-done" : M === T + 2 ? "support-done" : M === _ ? "emit-done" : "emit…" : "";
629
- U && U !== u && (console.info(`[vp-prof] ${U} +${((performance.now() - j) / 1e3).toFixed(1)}s`), u = U);
630
- }, B = () => {
631
- const M = p > 2 ? (p - 2) / 2 : 0;
632
- return M && b >= M && b < M + 2 ? Lr : c;
633
- }, O = () => {
634
- s && (clearTimeout(s), s = 0);
635
- }, V = () => {
636
- O();
637
- const M = B();
638
- s = setTimeout(() => {
639
- var _;
640
- he.current = null, we(), Le.current = null;
614
+ const O = typeof window < "u" && window.__vpStallNext ? (window.__vpStallNext = !1, !0) : !1, ue = typeof window < "u" && window.__vpWatchdogOnce ? ((J) => (window.__vpWatchdogOnce = 0, J))(window.__vpWatchdogOnce) : Y;
615
+ h.current = { layers: [], gcode: [] };
616
+ let ve = 0, Ee = 0, Z = 0;
617
+ const q = performance.now();
618
+ let z = "";
619
+ const ie = (J, ce) => {
620
+ Ee = J, Z = ce;
621
+ const ke = ce > 2 ? (ce - 2) / 2 : 0, ze = ke ? J < ke ? "PASS1…" : J === ke ? "PASS1-done" : J === ke + 1 ? "surf-done" : J === ke + 2 ? "support-done" : J === ce ? "emit-done" : "emit…" : "";
622
+ ze && ze !== z && (console.info(`[vp-prof] ${ze} +${((performance.now() - q) / 1e3).toFixed(1)}s`), z = ze);
623
+ }, we = () => {
624
+ const J = Z > 2 ? (Z - 2) / 2 : 0;
625
+ return J && Ee >= J && Ee < J + 2 ? C : ue;
626
+ }, se = () => {
627
+ ve && (clearTimeout(ve), ve = 0);
628
+ }, ae = () => {
629
+ se();
630
+ const J = we();
631
+ ve = setTimeout(() => {
632
+ var ce;
633
+ K.current = null, g(), j.current = null;
641
634
  try {
642
- (_ = S.current) == null || _.terminate();
635
+ (ce = l.current) == null || ce.terminate();
643
636
  } catch {
644
637
  }
645
- S.current = null, a(new Error(`watchdog: ${M}ms 무진행 — 메모리 압박으로 판단`));
646
- }, M);
638
+ l.current = null, p(new Error(`watchdog: no progress for ${J}ms — assuming memory pressure`));
639
+ }, J);
647
640
  };
648
- he.current = { resolve: r, reject: a, kick: V, stop: O, note: f }, V(), qe().postMessage({ stl: e, params: t, stall: l }), Er();
641
+ K.current = { resolve: V, reject: p, kick: ae, stop: se, note: ie }, ae(), oe().postMessage({ stl: i, params: S, stall: O }), W();
649
642
  });
650
643
  }
651
- function kn() {
652
- var e;
644
+ function je() {
645
+ var i;
653
646
  try {
654
- (e = S.current) == null || e.terminate();
647
+ (i = l.current) == null || i.terminate();
655
648
  } catch {
656
649
  }
657
- S.current = null, qe();
650
+ l.current = null, oe();
658
651
  }
659
- async function En(e, t) {
660
- const r = (a) => String((a == null ? void 0 : a.message) || a).includes("canceled");
652
+ async function Ce(i, S) {
653
+ const V = (p) => String((p == null ? void 0 : p.message) || p).includes("canceled");
661
654
  try {
662
- const a = await $t(e, JSON.stringify(t));
663
- return { r: a, economy: !!(a.stats && a.stats.economy) };
664
- } catch (a) {
665
- if (r(a)) throw a;
666
- if (t.wall_generator !== "classic") {
667
- kn();
655
+ const p = await pe(i, JSON.stringify(S));
656
+ return { r: p, economy: !!(p.stats && p.stats.economy) };
657
+ } catch (p) {
658
+ if (V(p)) throw p;
659
+ if (S.wall_generator !== "classic") {
660
+ je();
668
661
  try {
669
- const c = await $t(e, JSON.stringify({ ...t, wall_generator: "classic", keep_stages: !1, reuse_stages: 0 }));
670
- return { r: c, economy: !!(c.stats && c.stats.economy), classicWalls: !0 };
671
- } catch (c) {
672
- if (r(c)) throw c;
662
+ const ue = await pe(i, JSON.stringify({ ...S, wall_generator: "classic", keep_stages: !1, reuse_stages: 0 }));
663
+ return { r: ue, economy: !!(ue.stats && ue.stats.economy), classicWalls: !0 };
664
+ } catch (ue) {
665
+ if (V(ue)) throw ue;
673
666
  }
674
667
  }
675
- return kn(), { r: await $t(e, JSON.stringify({ ...t, economy: !0, keep_stages: !1, reuse_stages: 0 })), economy: !0, recovered: !0 };
668
+ return je(), { r: await pe(i, JSON.stringify({ ...S, economy: !0, keep_stages: !1, reuse_stages: 0 })), economy: !0, recovered: !0 };
676
669
  }
677
670
  }
678
- async function Mn(e) {
671
+ async function ye(i) {
679
672
  try {
680
- const t = await crypto.subtle.digest("SHA-256", e);
681
- return Array.from(new Uint8Array(t, 0, 8)).map((r) => r.toString(16).padStart(2, "0")).join("");
673
+ const S = await crypto.subtle.digest("SHA-256", i);
674
+ return Array.from(new Uint8Array(S, 0, 8)).map((V) => V.toString(16).padStart(2, "0")).join("");
682
675
  } catch {
683
676
  return null;
684
677
  }
685
678
  }
686
- function Cn(e, t) {
687
- e.economy || (e.keep_stages = !0, e.reuse_stages = t && t === Ve.current ? 2 : 0);
679
+ function Re(i, S) {
680
+ i.economy || (i.keep_stages = !0, i.reuse_stages = S && S === k.current ? 2 : 0);
681
+ }
682
+ function be(i) {
683
+ const S = xt(a);
684
+ return i.extruders >= 2 && i.split > 0 && (S.extruder_count = i.extruders, S.mm_group_split = i.split, S.wipe_tower_real = c), E.current && (S.sparse_infill_pattern = "rectilinear", S.infill_density = Math.min(S.infill_density ?? 0.15, 0.08), S.economy = !0), typeof window < "u" && window.__vpForceTree && (S.enable_support = !0, S.support_style = "tree", S.support_threshold_angle = 40), S;
685
+ }
686
+ async function Se(i) {
687
+ const S = be(i), V = await ye(i.buf);
688
+ Re(S, V);
689
+ try {
690
+ const p = await Ce(i.buf, S);
691
+ return k.current = p.economy || p.classicWalls ? null : V, { ...p, params: S };
692
+ } catch (p) {
693
+ throw k.current = null, p;
694
+ }
695
+ }
696
+ return { getWorker: oe, cancelSlice: y, runSlice: Se, pendingSliceRef: K, downgradeRef: E };
697
+ }
698
+ function Sr(n) {
699
+ const {
700
+ three: a,
701
+ plateTpRef: c,
702
+ toolpathRef: l,
703
+ segDataRef: r,
704
+ layersDataRef: m,
705
+ plateResultsRef: d,
706
+ plateOffsetsRef: w,
707
+ lineWidthRef: b,
708
+ showTravelRef: T,
709
+ viewTypeRef: P,
710
+ layerLoRef: M,
711
+ layerHiRef: B,
712
+ selectedPlateRef: ee,
713
+ settings: L,
714
+ setSegCount: te,
715
+ setColorRange: I,
716
+ setRoleLegend: A
717
+ } = n;
718
+ function u(f) {
719
+ var y;
720
+ const g = c.current[f];
721
+ g && (g.group.remove(g.ctl.mesh), g.group.remove(g.ctl.travLines), g.ctl.dispose(), (y = a.current.toolpathGroup) == null || y.remove(g.group), delete c.current[f], l.current === g.ctl && (l.current = null, r.current = null));
722
+ }
723
+ function _() {
724
+ for (const f of Object.keys(c.current)) u(Number(f));
725
+ l.current = null, r.current = null;
726
+ }
727
+ function G(f, g) {
728
+ const { toolpathGroup: y } = a.current;
729
+ if (!y || (u(f), !g || !g.length)) return null;
730
+ const v = qn(g, b.current), D = Wn(N, v), W = w.current[f] || { offX: 0, offZ: 0 }, oe = new N.Group();
731
+ oe.rotation.x = -Math.PI / 2, oe.position.set(W.offX || 0, 0, W.offZ || 0), oe.add(D.mesh), oe.add(D.travLines), y.add(oe), D.setTravelVisible(T.current), D.setLayerRange(0, Math.max(0, g.length - 1));
732
+ const ge = Dt(v, P.current, h());
733
+ D.setColors(ge.color);
734
+ const de = { group: oe, ctl: D, seg: v, layers: g };
735
+ return c.current[f] = de, de;
736
+ }
737
+ function K() {
738
+ for (const [f, g] of Object.entries(d.current)) {
739
+ const y = Number(f);
740
+ if (!g || g.error || !g.layers || !g.layers.length) continue;
741
+ const v = c.current[y];
742
+ (!v || v.layers !== g.layers) && G(y, g.layers);
743
+ }
744
+ }
745
+ function h() {
746
+ const f = (y, v) => {
747
+ const D = Je(L, y), W = parseFloat(D);
748
+ return Number.isFinite(W) ? W : v;
749
+ }, g = f("outer_wall_speed", 60);
750
+ return {
751
+ speedByType: {
752
+ 1: g,
753
+ 2: f("sparse_infill_speed", 40),
754
+ 3: f("internal_solid_infill_speed", 45),
755
+ 4: g,
756
+ 5: f("support_speed", 35),
757
+ 6: f("support_speed", 35),
758
+ 7: f("gap_infill_speed", 30),
759
+ 8: g,
760
+ 9: f("bridge_speed", 25),
761
+ 10: f("ironing_speed", 20),
762
+ 11: g
763
+ },
764
+ firstLayerSpeed: f("initial_layer_speed", 30),
765
+ closeFanLayers: f("close_fan_the_first_x_layers", 1),
766
+ fanNormal: f("fan_max_speed", 100),
767
+ tempNormal: f("nozzle_temperature", 210),
768
+ tempFirst: f("nozzle_temperature_initial_layer", f("nozzle_temperature", 210))
769
+ };
770
+ }
771
+ function E() {
772
+ const f = h();
773
+ for (const g of Object.values(c.current)) {
774
+ const y = Dt(g.seg, P.current, f);
775
+ g.ctl.setColors(y.color), g.ctl === l.current && I({ min: y.min, max: y.max, label: y.label, unit: y.unit, cont: y.cont });
776
+ }
777
+ }
778
+ function k() {
779
+ const f = ee.current, g = m.current || [];
780
+ if (!g.length) {
781
+ u(f), te(0), l.current = null, r.current = null;
782
+ return;
783
+ }
784
+ const y = G(f, g);
785
+ if (!y) {
786
+ te(0);
787
+ return;
788
+ }
789
+ l.current = y.ctl, r.current = y.seg, y.ctl.setLayerRange(M.current, B.current), E(), te(y.seg.nSeg), A(It(y.seg.typeLengths));
688
790
  }
689
- function jn(e) {
690
- const t = Kt(P);
691
- return e.extruders >= 2 && e.split > 0 && (t.extruder_count = e.extruders, t.mm_group_split = e.split, t.wipe_tower_real = pn), Ct.current && (t.sparse_infill_pattern = "rectilinear", t.infill_density = Math.min(t.infill_density ?? 0.15, 0.08), t.economy = !0), typeof window < "u" && window.__vpForceTree && (t.enable_support = !0, t.support_style = "tree", t.support_threshold_angle = 40), t;
791
+ function j() {
792
+ var f;
793
+ (f = l.current) == null || f.setLayerRange(M.current, B.current);
692
794
  }
693
- function Ut(e) {
694
- var s;
695
- xr();
696
- const t = G.current;
697
- t && t.setLayerRange(0, 1e9);
698
- const r = X.current[e];
699
- if (!r || r.error || !r.layers || !r.layers.length) {
700
- te.current = null, G.current = null, ie.current = null, it(null), ct(!1), dt(0), Se(0), St(null), Pt([]), ut((b) => (b && URL.revokeObjectURL(b), ""));
795
+ return { disposePlateToolpath: u, clearToolpaths: _, buildPlateToolpath: G, ensurePlateToolpaths: K, viewCtx: h, applyViewColors: E, rebuildToolpaths: k, applyLayerRange: j };
796
+ }
797
+ function Rr(n) {
798
+ const {
799
+ apiRef: a,
800
+ selectedPlateRef: c,
801
+ plateCountRef: l,
802
+ placeXRef: r,
803
+ plateResultsRef: m,
804
+ plateOffsetsRef: d,
805
+ plateTpRef: w,
806
+ layersDataRef: b,
807
+ toolpathRef: T,
808
+ segDataRef: P,
809
+ layerLoRef: M,
810
+ layerHiRef: B,
811
+ lineWidthRef: ee,
812
+ downgradeRef: L,
813
+ settings: te,
814
+ canvasMode: I,
815
+ downgradeOffer: A,
816
+ runSlice: u,
817
+ ensurePlateToolpaths: _,
818
+ buildPlateToolpath: G,
819
+ applyViewColors: K,
820
+ disposePlateToolpath: h,
821
+ setStats: E,
822
+ setOverBed: k,
823
+ setLayerCount: j,
824
+ setSegCount: f,
825
+ setColorRange: g,
826
+ setRoleLegend: y,
827
+ setGcodeUrl: v,
828
+ setLayerLo: D,
829
+ setLayerHi: W,
830
+ setCanvasMode: oe,
831
+ setSlicedPlateCount: ge,
832
+ setSliceMenu: de,
833
+ setError: Y,
834
+ setSliceNotice: C,
835
+ setDowngradeOffer: pe,
836
+ setSlicing: je,
837
+ setProgress: Ce,
838
+ setPlateCount: ye,
839
+ setSelectedPlate: Re
840
+ } = n;
841
+ function be(Z) {
842
+ var ae;
843
+ _();
844
+ const q = T.current;
845
+ q && q.setLayerRange(0, 1e9);
846
+ const z = m.current[Z];
847
+ if (!z || z.error || !z.layers || !z.layers.length) {
848
+ b.current = null, T.current = null, P.current = null, E(null), k(!1), j(0), f(0), g(null), y([]), v((J) => (J && URL.revokeObjectURL(J), ""));
701
849
  return;
702
850
  }
703
- te.current = r.layers;
704
- const a = r.layers.length;
705
- me.current = 0, ne.current = a - 1, zt(0), Lt(a - 1);
706
- const l = y.current[e], c = l && l.layers === r.layers ? l : Bt(e, r.layers);
707
- c && (G.current = c.ctl, ie.current = c.seg, c.ctl.setLayerRange(0, a - 1), It(), Se(c.seg.nSeg), Pt(Xn(c.seg.typeLengths))), (s = d.current) == null || s.onSliced(), ze("preview"), it({ layers: r.stats.layers, segments: r.stats.path_segments, filament: r.stats.filament_mm, timeSec: r.stats.time_estimate }), ct(!!r.stats.over_bed), dt(a), ut((b) => (b && URL.revokeObjectURL(b), URL.createObjectURL(new Blob([r.gcode], { type: "text/plain" }))));
851
+ b.current = z.layers;
852
+ const ie = z.layers.length;
853
+ M.current = 0, B.current = ie - 1, D(0), W(ie - 1);
854
+ const we = w.current[Z], se = we && we.layers === z.layers ? we : G(Z, z.layers);
855
+ se && (T.current = se.ctl, P.current = se.seg, se.ctl.setLayerRange(0, ie - 1), K(), f(se.seg.nSeg), y(It(se.seg.typeLengths))), (ae = a.current) == null || ae.onSliced(), oe("preview"), E({
856
+ layers: z.stats.layers,
857
+ segments: z.stats.path_segments,
858
+ filament: z.stats.filament_mm,
859
+ timeSec: z.stats.time_estimate,
860
+ engine: z.stats.time_engine,
861
+ limits: z.stats.machine_limits
862
+ }), k(!!z.stats.over_bed), j(ie), v((J) => (J && URL.revokeObjectURL(J), URL.createObjectURL(new Blob([z.gcode], { type: "text/plain" }))));
708
863
  }
709
- function Pr(e, t) {
710
- const r = URL.createObjectURL(new Blob([e], { type: "text/plain" })), a = document.createElement("a");
711
- a.href = r, a.download = t, a.style.display = "none", document.body.appendChild(a), a.click(), setTimeout(() => {
712
- a.remove(), URL.revokeObjectURL(r);
864
+ function Se(Z, q) {
865
+ const z = URL.createObjectURL(new Blob([Z], { type: "text/plain" })), ie = document.createElement("a");
866
+ ie.href = z, ie.download = q, ie.style.display = "none", document.body.appendChild(ie), ie.click(), setTimeout(() => {
867
+ ie.remove(), URL.revokeObjectURL(z);
713
868
  }, 4e3);
714
869
  }
715
- const Or = (e) => new Promise((t) => setTimeout(t, e));
716
- function ft() {
717
- ir(Object.values(X.current).filter((e) => e && !e.error && e.gcode).length);
870
+ const i = (Z) => new Promise((q) => setTimeout(q, Z));
871
+ function S() {
872
+ ge(Object.values(m.current).filter((Z) => Z && !Z.error && Z.gcode).length);
718
873
  }
719
- async function Zr() {
720
- At(!1);
721
- const e = Object.entries(X.current).filter(([, t]) => t && !t.error && t.gcode).sort((t, r) => Number(t[0]) - Number(r[0]));
722
- if (!e.length) {
723
- C("내보낼 슬라이스 결과가 없습니다 — 먼저 슬라이스하세요");
874
+ async function V() {
875
+ de(!1);
876
+ const Z = Object.entries(m.current).filter(([, q]) => q && !q.error && q.gcode).sort((q, z) => Number(q[0]) - Number(z[0]));
877
+ if (!Z.length) {
878
+ Y("No slice results to export — slice first");
724
879
  return;
725
880
  }
726
- for (const [t, r] of e)
727
- Pr(r.gcode, `plate_${Number(t) + 1}.gcode`), await Or(350);
728
- Y(`${e.length}개 플레이트 G-code 를 내보냈습니다`);
881
+ for (const [q, z] of Z)
882
+ Se(z.gcode, `plate_${Number(q) + 1}.gcode`), await i(350);
883
+ C(`Exported G-code for ${Z.length} plate(s)`);
729
884
  }
730
- async function Pe(e = "current") {
731
- var r, a;
732
- At(!1), C(""), Y(""), Ce(null);
733
- const t = re.current;
734
- if (Qt.current = Kt(P).line_width, e === "all") {
735
- ge(!0), _e(0);
736
- let l = 0, c = !1, s = !1;
737
- const b = [];
738
- for (let p = 0; p < $e.current; p++) {
739
- const j = (r = d.current) == null ? void 0 : r.buildMergedSTL(p);
740
- if (j) {
741
- nt.current[p] = { offX: j.offX, offZ: j.offZ };
885
+ async function p(Z = "current") {
886
+ var z, ie;
887
+ de(!1), Y(""), C(""), pe(null);
888
+ const q = c.current;
889
+ if (ee.current = xt(te).line_width, Z === "all") {
890
+ je(!0), Ce(0);
891
+ let we = 0, se = !1, ae = !1;
892
+ const J = [];
893
+ for (let ce = 0; ce < l.current; ce++) {
894
+ const ke = (z = a.current) == null ? void 0 : z.buildMergedSTL(ce);
895
+ if (ke) {
896
+ d.current[ce] = { offX: ke.offX, offZ: ke.offZ };
742
897
  try {
743
- const u = jn(j), f = await Mn(j.buf);
744
- Cn(u, f);
745
- const { r: B, economy: O, classicWalls: V } = await En(j.buf, u);
746
- Ve.current = O || V ? null : f, X.current[p] = B, ft(), l++, O && (c = !0), V && (s = !0);
898
+ const { r: ze, economy: Fe, classicWalls: qe } = await u(ke);
899
+ m.current[ce] = ze, S(), we++, Fe && (se = !0), qe && (ae = !0);
747
900
  } catch {
748
- Ve.current = null, b.push(p + 1);
901
+ J.push(ce + 1);
749
902
  }
750
903
  }
751
904
  }
752
- if (ge(!1), !l) {
753
- Ce({ scope: "all" }), C("모든 플레이트 슬라이스 실패(절약 모드 포함) — 간소화 재시도를 시도하세요");
905
+ if (je(!1), !we) {
906
+ pe({ scope: "all" }), Y("All plates failed to slice (economy mode included) — try the simplified retry");
754
907
  return;
755
908
  }
756
- b.length ? C(`플레이트 ${b.join(", ")} 실패 — 완주한 ${l}개 결과는 유지됨(탭에서 조회·내보내기)`) : (C(""), Ce(null)), c ? Y("메모리 압박 — 일부 플레이트를 절약 모드로 완주(프리뷰 없음, G-code 정상)") : s && Y("Arachne 벽 생성 실패(퇴화 지오메트리) — classic 벽으로 완주(G-code 정상)"), Ut(X.current[t] ? t : Object.keys(X.current).map(Number)[0]);
909
+ J.length ? Y(`Plate ${J.join(", ")} failed — the ${we} finished result(s) are kept (inspect/export from the tabs)`) : (Y(""), pe(null)), se ? C("Memory pressure — some plates finished in economy mode (no preview, G-code is fine)") : ae && C("Arachne wall generation failed (degenerate geometry) — finished with classic walls (G-code is fine)"), be(m.current[q] ? q : Object.keys(m.current).map(Number)[0]);
757
910
  } else {
758
- const l = performance.now(), c = (a = d.current) == null ? void 0 : a.buildMergedSTL(t);
759
- if (!c) {
760
- C(`플레이트 ${t + 1} 에 오브젝트가 없습니다`);
911
+ const we = performance.now(), se = (ie = a.current) == null ? void 0 : ie.buildMergedSTL(q);
912
+ if (!se) {
913
+ Y(`Plate ${q + 1} has no objects`);
761
914
  return;
762
915
  }
763
- console.info(`[vp-prof] buildMergedSTL ${(performance.now() - l).toFixed(0)}ms (${(c.buf.byteLength / 1048576).toFixed(1)}MB)`), nt.current[t] = { offX: c.offX, offZ: c.offZ }, ge(!0), _e(0);
916
+ console.info(`[vp-prof] buildMergedSTL ${(performance.now() - we).toFixed(0)}ms (${(se.buf.byteLength / 1048576).toFixed(1)}MB)`), d.current[q] = { offX: se.offX, offZ: se.offZ }, je(!0), Ce(0);
764
917
  try {
765
- const s = jn(c), b = await Mn(c.buf);
766
- Cn(s, b);
767
- const { r: p, economy: j, classicWalls: u } = await En(c.buf, s);
768
- Ve.current = j || u ? null : b, p != null && p.stats && console.info(`[vp-prof] kernel stages p1=${(p.stats.t_pass1_ms / 1e3).toFixed(1)}s surf=${(p.stats.t_surface_ms / 1e3).toFixed(1)}s sup=${(p.stats.t_support_ms / 1e3).toFixed(1)}s emit=${(p.stats.t_emit_ms / 1e3).toFixed(1)}s reuse=${s.reuse_stages}`), X.current[t] = p, ft(), ge(!1), Ut(t), C(""), Ce(null), j ? Y("메모리 압박 — 절약 모드로 완주(프리뷰 없음, G-code 는 다운로드 가능)") : u && Y("Arachne 벽 생성 실패(퇴화 지오메트리) — classic 벽으로 완주(G-code 정상)");
769
- } catch (s) {
770
- if (ge(!1), Ve.current = null, String((s == null ? void 0 : s.message) || s).includes("canceled")) {
771
- Y("슬라이스 취소됨");
918
+ const { r: ae, economy: J, classicWalls: ce, params: ke } = await u(se);
919
+ ae != null && ae.stats && console.info(`[vp-prof] kernel stages p1=${(ae.stats.t_pass1_ms / 1e3).toFixed(1)}s surf=${(ae.stats.t_surface_ms / 1e3).toFixed(1)}s sup=${(ae.stats.t_support_ms / 1e3).toFixed(1)}s emit=${(ae.stats.t_emit_ms / 1e3).toFixed(1)}s reuse=${ke.reuse_stages}`), m.current[q] = ae, S(), je(!1), be(q), Y(""), pe(null), J ? C("Memory pressure — finished in economy mode (no preview, G-code can still be downloaded)") : ce && C("Arachne wall generation failed (degenerate geometry) — finished with classic walls (G-code is fine)");
920
+ } catch (ae) {
921
+ if (je(!1), String((ae == null ? void 0 : ae.message) || ae).includes("canceled")) {
922
+ C("Slice canceled");
772
923
  return;
773
924
  }
774
- Ce({ scope: "current" }), C("슬라이스 실패(절약 모드도 실패): " + s.message);
925
+ pe({ scope: "current" }), Y("Slice failed (economy mode failed too): " + ae.message);
775
926
  }
776
927
  }
777
928
  }
778
- Be(() => {
779
- if (!Nt || !Ee.length || !X.current[re.current]) return;
780
- clearTimeout(st.current);
781
- const e = () => {
782
- if (he.current) {
783
- vn(), st.current = setTimeout(e, 300);
784
- return;
785
- }
786
- Pe("current");
787
- };
788
- return st.current = setTimeout(e, 800), () => clearTimeout(st.current);
789
- }, [P, Nt]);
790
- async function Tr() {
791
- const e = bn;
792
- if (Ce(null), !!e) {
793
- Ct.current = !0;
929
+ async function O() {
930
+ const Z = A;
931
+ if (pe(null), !!Z) {
932
+ L.current = !0;
794
933
  try {
795
- await Pe(e.scope);
934
+ await p(Z.scope);
796
935
  } finally {
797
- Ct.current = !1;
936
+ L.current = !1;
798
937
  }
799
938
  }
800
939
  }
801
- function Br() {
802
- an((e) => Math.min(Jn, e + 1));
940
+ function ue() {
941
+ ye((Z) => Math.min(Vt, Z + 1));
803
942
  }
804
- function Ir() {
805
- an((e) => {
806
- if (e <= 1) return e;
807
- const t = e - 1;
808
- return delete X.current[t], mt(t), ft(), re.current >= t && Nn(t - 1), t;
943
+ function ve() {
944
+ ye((Z) => {
945
+ if (Z <= 1) return Z;
946
+ const q = Z - 1;
947
+ return delete m.current[q], h(q), S(), c.current >= q && Ee(q - 1), q;
809
948
  });
810
949
  }
811
- function Nn(e) {
812
- re.current = e, or(e), Ue.current = 0, H === "preview" && Ut(e);
813
- }
814
- function _n(e, t) {
815
- if (!(e > 0) || !(t > 0)) return;
816
- const r = wt(P, "printable_area"), a = Array.isArray(r) && r.length >= 3, l = a ? Math.min(...r.map((s) => s[0])) : 0, c = a ? Math.min(...r.map((s) => s[1])) : 0;
817
- F((s) => ({ ...s, printable_area: [[l, c], [l + e, c], [l + e, c + t], [l, c + t]] }));
950
+ function Ee(Z) {
951
+ c.current = Z, Re(Z), r.current = 0, I === "preview" && be(Z);
818
952
  }
819
- function Vr(e, t) {
820
- var r;
821
- (r = d.current) == null || r.setObjectExtruder(e, t), ot(w.current.map((a) => ({ id: a.id, name: a.name, extruder: a.extruder, visible: a.visible !== !1 })));
822
- }
823
- function de(e, t) {
824
- const r = Math.max(0, Ae - 1);
825
- if (e = Math.max(0, Math.min(r, e)), t = Math.max(0, Math.min(r, t)), e > t) {
826
- const a = e;
827
- e = t, t = a;
953
+ return { showPlateResult: be, refreshSlicedCount: S, exportAllGcode: V, onSlice: p, retryDowngrade: O, addPlate: ue, deletePlate: ve, selectPlate: Ee };
954
+ }
955
+ function Nr(n) {
956
+ const {
957
+ apiRef: a,
958
+ objectsRef: c,
959
+ layersDataRef: l,
960
+ segDataRef: r,
961
+ plateResultsRef: m,
962
+ plateOffsetsRef: d,
963
+ clearToolpaths: w,
964
+ refreshSlicedCount: b,
965
+ dragOver: T,
966
+ setError: P,
967
+ setTriWarn: M,
968
+ setProgress: B,
969
+ setStats: ee,
970
+ setOverBed: L,
971
+ setLayerCount: te,
972
+ setSegCount: I,
973
+ setColorRange: A,
974
+ setSliceNotice: u,
975
+ setDowngradeOffer: _,
976
+ setGcodeUrl: G,
977
+ setCanvasMode: K,
978
+ setObjects: h,
979
+ setDragOver: E
980
+ } = n;
981
+ async function k(D) {
982
+ var de, Y;
983
+ const W = Array.from(D || []).filter((C) => bt.includes(Vn(C.name))), oe = Array.from(D || []).length - W.length;
984
+ if (!W.length) {
985
+ oe && P("Supported formats: STL/OBJ/3MF/AMF/PLY");
986
+ return;
828
987
  }
829
- me.current = e, ne.current = t, zt(e), Lt(t), wr();
830
- }
831
- function $r(e) {
832
- const t = parseInt(e.target.value, 10);
833
- Me ? de(t, t) : de(t, ne.current);
834
- }
835
- function Ur(e) {
836
- const t = parseInt(e.target.value, 10);
837
- de(Me ? t : me.current, t);
838
- }
839
- function An() {
840
- const e = !Me;
841
- pr(e), e && de(ne.current, ne.current);
988
+ P(""), M(""), B(0), l.current = null, r.current = null, m.current = {}, d.current = {}, w(), b(), ee(null), L(!1), te(0), I(0), A(null), u(""), _(null), G((C) => (C && URL.revokeObjectURL(C), "")), K("prepare"), (de = a.current) == null || de.showObjects();
989
+ let ge = 0;
990
+ for (const C of W)
991
+ try {
992
+ const pe = performance.now(), je = await C.arrayBuffer(), Ce = performance.now(), ye = await Fn(C.name, je), Re = performance.now();
993
+ for (const be of ye)
994
+ (Y = a.current) == null || Y.addObject(be.name, be.modelPos), ge += be.modelPos.length / 9;
995
+ console.info(`[vp-prof] load ${C.name}: read ${(Ce - pe).toFixed(0)}ms, parse ${(Re - Ce).toFixed(0)}ms, scene ${(performance.now() - Re).toFixed(0)}ms`);
996
+ } catch (pe) {
997
+ P(`Failed to load ${C.name}: ${pe && pe.message || pe}`);
998
+ }
999
+ h(c.current.map((C) => ({ id: C.id, name: C.name, extruder: C.extruder, visible: C.visible !== !1 }))), ge > 1e5 && M(`${Math.round(ge).toLocaleString()} triangles — slicing may take a while`);
842
1000
  }
843
- function Fr(e) {
844
- const t = e.target.value;
845
- dr(t), kt.current = t, It();
1001
+ function j(D) {
1002
+ k(D.target.files), D.target.value = "";
846
1003
  }
847
- function Sn(e) {
848
- const t = e.target.checked;
849
- fr(t), yt.current = t;
850
- for (const r of Object.values(y.current)) r.ctl.setTravelVisible(t);
1004
+ function f(D) {
1005
+ var W;
1006
+ (W = a.current) == null || W.removeObject(D), h(c.current.map((oe) => ({ id: oe.id, name: oe.name, extruder: oe.extruder, visible: oe.visible !== !1 })));
851
1007
  }
852
- function Gr(e) {
853
- const t = e.target.checked;
854
- F((r) => ({ ...r, enable_support: t }));
1008
+ function g(D) {
1009
+ var W;
1010
+ D.preventDefault(), E(!1), k((W = D.dataTransfer) == null ? void 0 : W.files);
855
1011
  }
856
- const Dr = !!wt(P, "enable_support");
857
- function Oe() {
858
- ot(w.current.map((e) => ({ id: e.id, name: e.name, extruder: e.extruder, visible: e.visible !== !1 })));
1012
+ function y(D) {
1013
+ D.preventDefault(), D.dataTransfer && (D.dataTransfer.dropEffect = "copy"), T || E(!0);
859
1014
  }
860
- function Hr(e, t) {
861
- Tt((r) => {
862
- var l;
863
- const a = [...r];
864
- return a[e] = t, Re.current = a, (l = d.current) == null || l.recolorObjects(), a;
865
- });
1015
+ function v(D) {
1016
+ D.currentTarget.contains(D.relatedTarget) || E(!1);
866
1017
  }
867
- function qr() {
868
- Tt((e) => {
869
- if (e.length >= 4) return e;
870
- const t = ["#e0473b", "#3bb0e0", "#7ad14a"], r = [...e, t[e.length - 1] || "#888888"];
871
- return Re.current = r, r;
872
- });
1018
+ return { loadFiles: k, onFiles: j, removeObject: f, onDrop: g, onDragOver: y, onDragLeave: v };
1019
+ }
1020
+ function Ar(n) {
1021
+ const {
1022
+ three: a,
1023
+ objectsRef: c,
1024
+ apiRef: l,
1025
+ getWorker: r,
1026
+ selectedPlateRef: m,
1027
+ paintXformRef: d,
1028
+ paintOverlayRef: w,
1029
+ paintModeRef: b,
1030
+ setError: T,
1031
+ setPaintModeState: P,
1032
+ setPaintCounts: M
1033
+ } = n;
1034
+ function B(I, A) {
1035
+ var h, E;
1036
+ const u = a.current;
1037
+ if (!u.objectsGroup) return;
1038
+ const _ = d.current || { cx: 0, cy: 0, minz: 0 }, G = w.current;
1039
+ if (G)
1040
+ for (const k of ["enf", "blk"]) G[k] && (u.objectsGroup.remove(G[k]), G[k].geometry.dispose(), G[k].material.dispose());
1041
+ const K = (k, j) => {
1042
+ if (!k || k.length < 9) return null;
1043
+ const f = new Float32Array(k.length);
1044
+ for (let v = 0; v < k.length; v += 3) {
1045
+ const D = k[v], W = k[v + 1], oe = k[v + 2];
1046
+ f[v] = D + _.cx, f[v + 1] = oe + _.minz, f[v + 2] = -(W + _.cy);
1047
+ }
1048
+ const g = new N.BufferGeometry();
1049
+ g.setAttribute("position", new N.Float32BufferAttribute(f, 3)), g.computeVertexNormals();
1050
+ const y = new N.Mesh(g, new N.MeshBasicMaterial({ color: j, transparent: !0, opacity: 0.55, side: N.DoubleSide, depthTest: !1 }));
1051
+ return y.renderOrder = 999, u.objectsGroup.add(y), y;
1052
+ };
1053
+ w.current = { enf: K(I, 2845951), blk: K(A, 14826299) }, (E = (h = a.current).invalidate) == null || E.call(h);
873
1054
  }
874
- function Wr() {
875
- Tt((e) => {
876
- var r;
877
- if (e.length <= 1) return e;
878
- const t = e.slice(0, -1);
879
- return Re.current = t, w.current.forEach((a) => {
880
- var l;
881
- (a.extruder || 1) > t.length && ((l = d.current) == null || l.setObjectExtruder(a.id, t.length));
882
- }), (r = d.current) == null || r.recolorObjects(), Oe(), t;
883
- });
1055
+ function ee() {
1056
+ const I = a.current, A = w.current;
1057
+ if (A && I.objectsGroup) for (const u of ["enf", "blk"]) A[u] && (I.objectsGroup.remove(A[u]), A[u].geometry.dispose(), A[u].material.dispose());
1058
+ w.current = null;
884
1059
  }
885
- function Xr(e) {
886
- var r;
887
- const t = w.current.find((a) => a.id === e);
888
- (r = d.current) == null || r.setObjectVisible(e, (t == null ? void 0 : t.visible) === !1), Oe();
1060
+ function L(I) {
1061
+ var A, u, _;
1062
+ if (I !== "off" && c.current.length === 0) {
1063
+ T("Upload an STL first");
1064
+ return;
1065
+ }
1066
+ if (I !== "off") {
1067
+ const G = (A = l.current) == null ? void 0 : A.buildMergedSTL(m.current);
1068
+ if (!G) return;
1069
+ d.current = { cx: G.offX, cy: -G.offZ, minz: 0 }, (u = l.current) == null || u.detachTransform(), r().postMessage({ cmd: "prepare", stl: G.buf });
1070
+ }
1071
+ b.current = I, P(I), (_ = l.current) == null || _.refreshCursor();
889
1072
  }
890
- function Kr() {
891
- oe(ve === "off" ? "enforcer" : "off");
1073
+ function te() {
1074
+ r().postMessage({ cmd: "clear" }), ee(), M({ enf: 0, blk: 0 });
892
1075
  }
893
- const Yr = [
894
- { id: "add", icon: xa, label: "추가", tip: "모델 파일을 현재 플레이트에 추가 (기존 오브젝트 유지)", run: () => {
895
- var e;
896
- return (e = Ge.current) == null ? void 0 : e.click();
897
- } },
898
- { id: "delete", icon: ka, label: "삭제", tip: "선택한 오브젝트 삭제 (Del)", run: ht },
899
- { id: "delete-all", icon: Ea, label: "전체 삭제", tip: "플레이트의 모든 오브젝트 삭제", run: Jr, disabled: () => Ee.length === 0 },
900
- { sep: !0 },
901
- { id: "duplicate", icon: Ma, label: "복제", tip: "선택 오브젝트 복제 (Ctrl+K)", run: Ft },
902
- { id: "split", icon: Yn, label: "분리", tip: "객체로 분리 — 서로 떨어진 부분(연결 성분)마다 독립 오브젝트로 나눔. 파트로 분리는 미구현(파트 개념 부재)", run: Dt },
903
- { id: "onbed", icon: Aa, label: "바닥에 놓기", tip: "모든 오브젝트를 베드 바닥(Z=0)에 붙임 — 기즈모로 띄운 뒤 재안착용", run: () => {
904
- var e;
905
- return (e = d.current) == null ? void 0 : e.placeOnBed();
906
- } },
907
- { sep: !0 },
908
- { id: "arrange", icon: va, label: "배치", tip: "자동 배치 — 오브젝트를 베드에 겹치지 않게 정렬. 미구현(libslic3r Arrange 이식 필요)" },
909
- { id: "orient", icon: za, label: "방향", tip: "자동 방향 — 서포트가 덜 필요한 방향으로 회전. 미구현(libslic3r Orient 이식 필요)" },
910
- { sep: !0 },
911
- { id: "cut", icon: ya, label: "컷", tip: "컷 — 평면으로 모델을 잘라 두 조각으로 분리. 미구현(절단면 재봉합 필요)" },
912
- { id: "boolean", icon: wa, label: "불리언", tip: "메시 불리언 — 두 오브젝트의 합/차/교집합. 미구현(CGAL 불리언 이식 필요)" },
913
- { id: "negative", icon: _a, label: "음각 파트", tip: "음각·모디파이어 파트 추가 — 한 오브젝트 안에 성질이 다른 파트를 넣음. 미구현(파트 개념 부재)" },
914
- { sep: !0 },
915
- { id: "seam", icon: Oa, label: "심 페인팅", tip: "심 페인팅 — 레이어 이음매 위치를 브러시로 지정. 미구현(커널 심 배선 필요)" },
916
- { id: "mmu", icon: ja, label: "컬러 페인팅", tip: "컬러 페인팅 — 멀티머티리얼 색을 면 단위로 지정. 미구현(MMU 페인트 코덱 배선 필요)" },
917
- { id: "text", icon: Za, label: "텍스트", tip: "텍스트·SVG 각인 — 모델 표면에 문자/도형을 새김. 미구현(폰트 래스터화 필요)" },
918
- { id: "measure", icon: Ca, label: "측정", tip: "측정 — 두 지점/면 사이 거리·각도 측정. 미구현" },
919
- { id: "varlayer", icon: Ta, label: "가변 레이어", tip: "가변 레이어 높이 — 구간별로 레이어 높이를 다르게. 미구현(커널 가변 z 미지원)" }
920
- ];
921
- function Ft() {
922
- var r, a, l;
923
- const e = (r = d.current) == null ? void 0 : r.selectedObjectId();
924
- if (!e) {
925
- C("먼저 복제할 오브젝트를 선택하세요");
1076
+ return { rebuildPaintOverlay: B, clearPaintOverlay: ee, setPaintMode: L, clearPaint: te };
1077
+ }
1078
+ function _r(n) {
1079
+ const {
1080
+ apiRef: a,
1081
+ objectsRef: c,
1082
+ clipboardRef: l,
1083
+ paintModeRef: r,
1084
+ setPaintMode: m,
1085
+ removeObject: d,
1086
+ refreshObjects: w,
1087
+ setError: b,
1088
+ setSliceNotice: T
1089
+ } = n;
1090
+ function P() {
1091
+ var _, G, K;
1092
+ const A = (_ = a.current) == null ? void 0 : _.selectedObjectId();
1093
+ if (!A) {
1094
+ b("Select an object to duplicate first");
926
1095
  return;
927
1096
  }
928
- const t = (a = d.current) == null ? void 0 : a.getSnapshot(e);
929
- t && ((l = d.current) == null || l.spawnSnapshot(t), Oe(), C(""));
1097
+ const u = (G = a.current) == null ? void 0 : G.getSnapshot(A);
1098
+ u && ((K = a.current) == null || K.spawnSnapshot(u), w(), b(""));
930
1099
  }
931
- function Gt() {
932
- var t, r;
933
- const e = (t = d.current) == null ? void 0 : t.selectedObjectId();
934
- if (!e) {
935
- C("먼저 복사할 오브젝트를 선택하세요");
1100
+ function M() {
1101
+ var u, _;
1102
+ const A = (u = a.current) == null ? void 0 : u.selectedObjectId();
1103
+ if (!A) {
1104
+ b("Select an object to copy first");
936
1105
  return;
937
1106
  }
938
- Je.current = (r = d.current) == null ? void 0 : r.getSnapshot(e), C(""), Y("오브젝트를 복사했습니다 (Ctrl+V 로 붙여넣기)");
1107
+ l.current = (_ = a.current) == null ? void 0 : _.getSnapshot(A), b(""), T("Object copied (paste with Ctrl+V)");
939
1108
  }
940
- function zn() {
941
- var e;
942
- Je.current && ((e = d.current) == null || e.spawnSnapshot(Je.current), Oe());
1109
+ function B() {
1110
+ var A;
1111
+ l.current && ((A = a.current) == null || A.spawnSnapshot(l.current), w());
943
1112
  }
944
- function ht() {
945
- var t;
946
- const e = (t = d.current) == null ? void 0 : t.selectedObjectId();
947
- if (!e) {
948
- C("먼저 삭제할 오브젝트를 선택하세요");
1113
+ function ee() {
1114
+ var u;
1115
+ const A = (u = a.current) == null ? void 0 : u.selectedObjectId();
1116
+ if (!A) {
1117
+ b("Select an object to delete first");
949
1118
  return;
950
1119
  }
951
- Vt(e), C("");
1120
+ d(A), b("");
952
1121
  }
953
- function Jr() {
954
- var t;
955
- const e = w.current.map((r) => r.id);
956
- if (e.length) {
957
- for (const r of e) (t = d.current) == null || t.removeObject(r);
958
- Oe(), Y(`${e.length}개 오브젝트를 모두 삭제했습니다`);
1122
+ function L() {
1123
+ var u;
1124
+ const A = c.current.map((_) => _.id);
1125
+ if (A.length) {
1126
+ for (const _ of A) (u = a.current) == null || u.removeObject(_);
1127
+ w(), T(`Deleted all ${A.length} object(s)`);
959
1128
  }
960
1129
  }
961
- function Dt() {
962
- var l, c;
963
- const e = (l = d.current) == null ? void 0 : l.selectedObjectId();
964
- if (!e) {
965
- C("먼저 분리할 오브젝트를 선택하세요");
1130
+ function te() {
1131
+ var K, h;
1132
+ const A = (K = a.current) == null ? void 0 : K.selectedObjectId();
1133
+ if (!A) {
1134
+ b("Select an object to split first");
966
1135
  return;
967
1136
  }
968
- const t = (c = d.current) == null ? void 0 : c.getSnapshot(e);
969
- if (!t) return;
970
- let r;
1137
+ const u = (h = a.current) == null ? void 0 : h.getSnapshot(A);
1138
+ if (!u) return;
1139
+ let _;
971
1140
  try {
972
- r = ma(t.localPos);
973
- } catch (s) {
974
- C("분리 실패: " + ((s == null ? void 0 : s.message) || s));
1141
+ _ = Gn(u.localPos);
1142
+ } catch (E) {
1143
+ b("Split failed: " + ((E == null ? void 0 : E.message) || E));
975
1144
  return;
976
1145
  }
977
- if (!r || r.length < 2) {
978
- C("분리할 독립 부분이 없습니다 — 하나로 연결된 메시입니다");
1146
+ if (!_ || _.length < 2) {
1147
+ b("No separate parts to split — this is a single connected mesh");
979
1148
  return;
980
1149
  }
981
- const a = String(t.name || "object").replace(/\.[^.]+$/, "");
982
- Vt(e), r.forEach((s, b) => {
983
- var p;
984
- return (p = d.current) == null ? void 0 : p.spawnSnapshot(
985
- { name: `${a}_${b + 1}`, localPos: s, rot: t.rot, scale: t.scale, pos: t.pos },
1150
+ const G = String(u.name || "object").replace(/\.[^.]+$/, "");
1151
+ d(A), _.forEach((E, k) => {
1152
+ var j;
1153
+ return (j = a.current) == null ? void 0 : j.spawnSnapshot(
1154
+ { name: `${G}_${k + 1}`, localPos: E, rot: u.rot, scale: u.scale, pos: u.pos },
986
1155
  !0
987
1156
  );
988
- }), Oe(), C(""), Y(`${r.length}개 오브젝트로 분리했습니다`);
1157
+ }), w(), b(""), T(`Split into ${_.length} objects`);
989
1158
  }
990
- function Ht(e) {
991
- var t;
992
- W.current !== "off" && oe("off"), (t = d.current) == null || t.setMode(e);
1159
+ function I(A) {
1160
+ var u;
1161
+ r.current !== "off" && m("off"), (u = a.current) == null || u.setMode(A);
993
1162
  }
994
- Ye.current = (e) => {
995
- var b, p, j, u, f, B, O, V, M;
996
- const t = e.composedPath ? e.composedPath()[0] : e.target;
997
- if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT" || t.isContentEditable)) return;
998
- const r = e.key, a = typeof r == "string" ? r.toLowerCase() : "", l = e.ctrlKey || e.metaKey, c = fe.current === "preview", s = () => {
999
- e.preventDefault(), e.stopPropagation();
1000
- };
1001
- if (l) {
1002
- a === "r" ? (s(), lt || Pe("current")) : a === "c" ? (s(), Gt()) : a === "v" ? (s(), zn()) : a === "x" ? (s(), Gt(), ht()) : (a === "k" || a === "d") && (s(), Ft());
1003
- return;
1004
- }
1005
- if (c) {
1006
- const _ = e.shiftKey ? 10 : 1;
1007
- if (r === "ArrowUp") {
1008
- s();
1009
- const T = ne.current + _;
1010
- de(Me ? T : me.current, T);
1011
- } else if (r === "ArrowDown") {
1012
- s();
1013
- const T = ne.current - _;
1014
- de(Me ? T : me.current, T);
1015
- } else a === "l" ? (s(), An()) : a === "t" ? (s(), Sn({ target: { checked: !yt.current } })) : a === "z" ? (s(), (b = d.current) == null || b.frame()) : a === "b" ? (s(), (p = d.current) == null || p.frameBed()) : r === "Escape" && (s(), ze("prepare"));
1016
- return;
1017
- }
1018
- if (a === "m" || a === "g")
1019
- s(), Ht("translate");
1020
- else if (a === "r")
1021
- s(), Ht("rotate");
1022
- else if (a === "s")
1023
- s(), Ht("scale");
1024
- else if (a === "z")
1025
- s(), (j = d.current) == null || j.frame();
1026
- else if (a === "b")
1027
- s(), (u = d.current) == null || u.frameBed();
1028
- else if (r === "Delete" || r === "Backspace")
1029
- s(), ht();
1030
- else if (r === "Escape")
1031
- s(), W.current !== "off" && oe("off"), (f = d.current) == null || f.detachTransform();
1032
- else if (r === "PageUp")
1033
- s(), (B = d.current) == null || B.rotateSelectedY(Math.PI / 4);
1034
- else if (r === "PageDown")
1035
- s(), (O = d.current) == null || O.rotateSelectedY(-Math.PI / 4);
1036
- else if ((V = r.startsWith) != null && V.call(r, "Arrow")) {
1037
- s();
1038
- const _ = e.shiftKey ? 1 : 10;
1039
- (M = d.current) == null || M.nudgeSelected(
1040
- r === "ArrowLeft" ? -_ : r === "ArrowRight" ? _ : 0,
1041
- r === "ArrowUp" ? -_ : r === "ArrowDown" ? _ : 0
1042
- );
1043
- } else r === "?" && (s(), ln((_) => !_));
1044
- };
1045
- const Qr = J.nozzle_diameter || wt(P, "nozzle_diameter") || "0.4";
1046
- (Pn = (Ln = A.current).invalidate) == null || Pn.call(Ln);
1047
- const Rn = Ae > 0 && /* @__PURE__ */ n.createElement("div", { className: "slice-layer", "data-testid": "preview-controls" }, /* @__PURE__ */ n.createElement("label", { className: "view-type-row" }, "뷰 타입", /* @__PURE__ */ n.createElement("select", { value: cr, onChange: Fr, "data-testid": "view-type-select", title: "툴패스 색을 무엇으로 칠할지 — 피처 종류·속도·레이어 높이·폭·팬·온도" }, ca.map((e) => /* @__PURE__ */ n.createElement("option", { key: e.key, value: e.key }, e.label)))), /* @__PURE__ */ n.createElement("label", null, "레이어 ", /* @__PURE__ */ n.createElement("b", { "data-testid": "layer-range" }, Me ? Rt + 1 : `${cn + 1}..${Rt + 1}`), " / ", Ae, /* @__PURE__ */ n.createElement("span", { className: "muted" }, " (", ar.toLocaleString(), " 세그먼트)")), /* @__PURE__ */ n.createElement("div", { className: "dual-slider" }, /* @__PURE__ */ n.createElement("input", { type: "range", min: "0", max: Math.max(0, Ae - 1), value: cn, onChange: $r, "data-testid": "layer-lo", title: "표시할 최하단 레이어 (↓/↑ 로도 조절)" }), /* @__PURE__ */ n.createElement("input", { type: "range", min: "0", max: Math.max(0, Ae - 1), value: Rt, onChange: Ur, "data-testid": "layer-hi", title: "표시할 최상단 레이어 (↓/↑, Shift 로 10단계)" })), /* @__PURE__ */ n.createElement("div", { className: "layer-ctl" }, /* @__PURE__ */ n.createElement("button", { className: Me ? "on" : "", onClick: An, "data-testid": "single-layer-btn", title: "선택한 한 층만 보기 (L)" }, "단일 레이어"), /* @__PURE__ */ n.createElement("label", { className: "slice-travel" }, /* @__PURE__ */ n.createElement("input", { type: "checkbox", checked: mr, onChange: Sn, "data-testid": "travel-toggle", title: "압출 없이 이동하는 경로를 회색 선으로 표시 (T)" }), " 트래블")), xe && xe.cont ? /* @__PURE__ */ n.createElement("div", { className: "grad-legend", "data-testid": "view-legend" }, /* @__PURE__ */ n.createElement("div", { className: "grad-title" }, xe.label, " ", /* @__PURE__ */ n.createElement("span", { className: "muted" }, xe.unit)), /* @__PURE__ */ n.createElement("div", { className: "grad-bar", "data-testid": "gradient-bar", style: { background: `linear-gradient(to right, ${da.map((e) => `rgb(${Math.round(e[0] * 255)},${Math.round(e[1] * 255)},${Math.round(e[2] * 255)})`).join(",")})` } }), /* @__PURE__ */ n.createElement("div", { className: "grad-minmax" }, /* @__PURE__ */ n.createElement("span", { "data-testid": "grad-min" }, xe.min.toFixed(xe.unit === "mm" ? 2 : 0)), /* @__PURE__ */ n.createElement("span", { "data-testid": "grad-max" }, xe.max.toFixed(xe.unit === "mm" ? 2 : 0)))) : /* @__PURE__ */ n.createElement("div", { className: "role-legend", "data-testid": "view-legend" }, ur.map((e) => /* @__PURE__ */ n.createElement("span", { key: e.type, className: "role-item" }, /* @__PURE__ */ n.createElement("i", { style: { background: `rgb(${Math.round(e.color[0] * 255)},${Math.round(e.color[1] * 255)},${Math.round(e.color[2] * 255)})` } }), e.label, " ", /* @__PURE__ */ n.createElement("b", null, e.pct.toFixed(0), "%"))))), ea = be && /* @__PURE__ */ n.createElement(n.Fragment, null, /* @__PURE__ */ n.createElement("div", null, /* @__PURE__ */ n.createElement("b", null, be.layers), " 레이어 · ", /* @__PURE__ */ n.createElement("b", null, be.segments), " 세그먼트"), /* @__PURE__ */ n.createElement("div", null, "필라멘트 ", /* @__PURE__ */ n.createElement("b", null, be.filament.toFixed(1)), " mm"), typeof be.timeSec == "number" && be.timeSec > 0 && (() => {
1048
- const e = Math.round(be.timeSec), t = `${Math.floor(e / 3600)}h ${String(Math.floor(e % 3600 / 60)).padStart(2, "0")}m ${String(e % 60).padStart(2, "0")}s`;
1049
- return /* @__PURE__ */ n.createElement("div", { "data-testid": "print-time" }, "⏱ 예상 출력 ", /* @__PURE__ */ n.createElement("b", null, t));
1050
- })(), rr && /* @__PURE__ */ n.createElement("div", { className: "slice-warn", "data-testid": "over-bed" }, "⚠ 모델이 베드를 벗어남")), qt = Yt.map((e) => e.toUpperCase()).join(" · ");
1051
- return /* @__PURE__ */ n.createElement(ga, { css: ba }, /* @__PURE__ */ n.createElement("div", { className: "app-shell" }, /* @__PURE__ */ n.createElement("input", { ref: Ge, type: "file", accept: Yt.map((e) => "." + e).join(","), multiple: !0, onChange: Cr, title: `${qt} (여러 개 선택 가능)`, "data-testid": "stl-input", style: { display: "none" } }), /* @__PURE__ */ n.createElement("header", { className: "topbar" }, /* @__PURE__ */ n.createElement("div", { className: "tb-left" }, /* @__PURE__ */ n.createElement("span", { className: "tb-logo" }, /* @__PURE__ */ n.createElement("b", null, "Three"), "Slicer ", /* @__PURE__ */ n.createElement("span", { className: "tb-re" }, "RE")), /* @__PURE__ */ n.createElement("button", { className: "tb-btn", onClick: () => {
1052
- var e;
1053
- return (e = Ge.current) == null ? void 0 : e.click();
1054
- }, title: "모델 파일 열기 — 기존 오브젝트는 모두 지워짐", "data-testid": "open-file" }, /* @__PURE__ */ n.createElement("img", { src: Sa, alt: "" }), /* @__PURE__ */ n.createElement("span", null, "열기"))), K && /* @__PURE__ */ n.createElement("div", { className: "tb-tabs", role: "tablist", "aria-label": "캔버스 모드" }, /* @__PURE__ */ n.createElement("button", { role: "tab", className: H === "prepare" ? "on" : "", onClick: () => ze("prepare"), "data-testid": "mode-prepare", title: "모델 배치·변형·서포트 페인팅" }, "Prepare"), /* @__PURE__ */ n.createElement("button", { role: "tab", className: H === "preview" ? "on" : "", onClick: () => ze("preview"), disabled: Ae === 0, "data-testid": "mode-preview", title: "슬라이스된 툴패스 미리보기 (슬라이스 후 활성화)" }, "Preview")), /* @__PURE__ */ n.createElement("div", { className: "tb-right" }, /* @__PURE__ */ n.createElement("button", { className: "tb-icon", disabled: !0, title: "실행 취소 — 미구현(작업 이력 스택 필요)" }, "↶"), /* @__PURE__ */ n.createElement("button", { className: "tb-icon", disabled: !0, title: "다시 실행 — 미구현(작업 이력 스택 필요)" }, "↷"))), /* @__PURE__ */ n.createElement("div", { className: "app-body" }, K && H === "prepare" && /* @__PURE__ */ n.createElement("nav", { className: "left-rail", role: "toolbar", "aria-label": "기즈모 도구" }, /* @__PURE__ */ n.createElement("button", { className: jt === "translate" && ve === "off" ? "on" : "", onClick: () => {
1055
- var e;
1056
- oe("off"), (e = d.current) == null || e.setMode("translate");
1057
- }, title: "오브젝트 이동 — 기즈모 축을 끌거나 방향키로 10mm(Shift 1mm) 이동 (M/G)", "data-testid": "gizmo-move" }, /* @__PURE__ */ n.createElement("img", { src: Na, alt: "이동" })), /* @__PURE__ */ n.createElement("button", { className: jt === "rotate" && ve === "off" ? "on" : "", onClick: () => {
1058
- var e;
1059
- oe("off"), (e = d.current) == null || e.setMode("rotate");
1060
- }, title: "오브젝트 회전 — PageUp/PageDown 으로 45° 단위 회전 (R)", "data-testid": "gizmo-rotate" }, /* @__PURE__ */ n.createElement("img", { src: La, alt: "회전" })), /* @__PURE__ */ n.createElement("button", { className: jt === "scale" && ve === "off" ? "on" : "", onClick: () => {
1061
- var e;
1062
- oe("off"), (e = d.current) == null || e.setMode("scale");
1063
- }, title: "오브젝트 크기 조절 (S)", "data-testid": "gizmo-scale" }, /* @__PURE__ */ n.createElement("img", { src: Pa, alt: "스케일" })), /* @__PURE__ */ n.createElement("div", { className: "rail-sep" }), /* @__PURE__ */ n.createElement("button", { className: ve !== "off" ? "on" : "", onClick: Kr, title: "브러시로 면을 칠해 서포트를 강제하거나 차단 — 휠로 브러시 크기 조절", "data-testid": "gizmo-paint" }, /* @__PURE__ */ n.createElement("img", { src: Ra, alt: "서포트 페인팅" }))), /* @__PURE__ */ n.createElement("div", { className: "viewport-col" }, /* @__PURE__ */ n.createElement(
1064
- "div",
1065
- {
1066
- className: (K ? "vp-canvas" : "vp-canvas fail") + (Ot ? " drag-over" : ""),
1067
- ref: ee,
1068
- onDrop: jr,
1069
- onDragOver: Nr,
1070
- onDragLeave: _r,
1071
- "data-testid": "drop-zone"
1072
- },
1073
- !K && /* @__PURE__ */ n.createElement("div", { className: "vp-fallback" }, "⚠ ", en),
1074
- K && Ee.length === 0 && H !== "preview" && /* @__PURE__ */ n.createElement("div", { className: "empty-hint", "data-testid": "empty-hint" }, /* @__PURE__ */ n.createElement("div", { className: "eh-icon" }, "📦"), /* @__PURE__ */ n.createElement("div", { className: "eh-title" }, "파일을 드래그하거나 선택하세요"), /* @__PURE__ */ n.createElement("div", { className: "eh-sub" }, qt), /* @__PURE__ */ n.createElement("button", { className: "eh-btn", onClick: () => {
1075
- var e;
1076
- return (e = Ge.current) == null ? void 0 : e.click();
1077
- }, "data-testid": "empty-pick", title: `${qt} 파일 선택 (여러 개 가능)` }, "파일 선택")),
1078
- Ot && /* @__PURE__ */ n.createElement("div", { className: "drop-overlay", "data-testid": "drop-overlay" }, "여기에 놓기 (STL/OBJ/3MF/AMF/PLY)"),
1079
- pt && /* @__PURE__ */ n.createElement(n.Fragment, null, /* @__PURE__ */ n.createElement("div", { className: "ctx-scrim", onPointerDown: () => D(null), onContextMenu: (e) => {
1080
- e.preventDefault(), D(null);
1081
- } }), /* @__PURE__ */ n.createElement("div", { className: "ctx-menu", style: { left: pt.x, top: pt.y }, "data-testid": "ctx-menu" }, pt.onObject ? /* @__PURE__ */ n.createElement(n.Fragment, null, /* @__PURE__ */ n.createElement("button", { onClick: () => {
1082
- D(null), Ft();
1083
- }, "data-testid": "ctx-duplicate", title: "선택 오브젝트를 같은 크기·회전으로 복제해 옆에 배치" }, "복제 ", /* @__PURE__ */ n.createElement("span", { className: "ctx-key" }, "Ctrl+K")), /* @__PURE__ */ n.createElement("button", { onClick: () => {
1084
- D(null), Gt();
1085
- }, "data-testid": "ctx-copy", title: "선택 오브젝트를 버퍼에 복사 (붙여넣기로 추가)" }, "복사 ", /* @__PURE__ */ n.createElement("span", { className: "ctx-key" }, "Ctrl+C")), /* @__PURE__ */ n.createElement("button", { onClick: () => {
1086
- D(null), Dt();
1087
- }, "data-testid": "ctx-split", title: "객체로 분리 — 서로 떨어진 부분(연결 성분)마다 독립 오브젝트로 나눔. 파트로 분리는 미구현(파트 개념 부재)" }, "분리"), /* @__PURE__ */ n.createElement("button", { onClick: () => {
1088
- var e;
1089
- D(null), (e = d.current) == null || e.placeOnBed();
1090
- }, "data-testid": "ctx-seat", title: "모든 오브젝트를 베드 바닥(Z=0)에 붙임" }, "바닥에 놓기"), /* @__PURE__ */ n.createElement("hr", null), /* @__PURE__ */ n.createElement("button", { className: "danger", onClick: () => {
1091
- D(null), ht();
1092
- }, "data-testid": "ctx-delete", title: "선택 오브젝트를 씬에서 제거" }, "삭제 ", /* @__PURE__ */ n.createElement("span", { className: "ctx-key" }, "Del"))) : /* @__PURE__ */ n.createElement(n.Fragment, null, /* @__PURE__ */ n.createElement("button", { onClick: () => {
1093
- var e;
1094
- D(null), (e = Ge.current) == null || e.click();
1095
- }, "data-testid": "ctx-open", title: "모델 파일을 열어 현재 플레이트에 추가" }, "모델 열기…"), /* @__PURE__ */ n.createElement("button", { disabled: !Je.current, onClick: () => {
1096
- D(null), zn();
1097
- }, "data-testid": "ctx-paste", title: "복사해 둔 오브젝트를 현재 플레이트에 추가" }, "붙여넣기 ", /* @__PURE__ */ n.createElement("span", { className: "ctx-key" }, "Ctrl+V")), /* @__PURE__ */ n.createElement("hr", null), /* @__PURE__ */ n.createElement("button", { onClick: () => {
1098
- var e;
1099
- D(null), (e = d.current) == null || e.frame();
1100
- }, "data-testid": "ctx-zoom-all", title: "모든 오브젝트가 보이도록 카메라 맞춤" }, "전체 보기 ", /* @__PURE__ */ n.createElement("span", { className: "ctx-key" }, "Z")), /* @__PURE__ */ n.createElement("button", { onClick: () => {
1101
- var e;
1102
- D(null), (e = d.current) == null || e.frameBed();
1103
- }, "data-testid": "ctx-zoom-bed", title: "선택한 플레이트 전체가 보이도록 카메라 맞춤" }, "베드 보기 ", /* @__PURE__ */ n.createElement("span", { className: "ctx-key" }, "B"))))),
1104
- sr && /* @__PURE__ */ n.createElement("div", { className: "help-overlay", "data-testid": "help-overlay", onClick: () => ln(!1) }, /* @__PURE__ */ n.createElement("div", { className: "help-card", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ n.createElement("h3", null, "단축키 ", /* @__PURE__ */ n.createElement("span", { className: "muted" }, "(? 로 닫기)")), /* @__PURE__ */ n.createElement("div", { className: "help-cols" }, /* @__PURE__ */ n.createElement("section", null, /* @__PURE__ */ n.createElement("h4", null, "Prepare"), /* @__PURE__ */ n.createElement("dl", null, /* @__PURE__ */ n.createElement("dt", null, "M / G"), /* @__PURE__ */ n.createElement("dd", null, "이동"), /* @__PURE__ */ n.createElement("dt", null, "R"), /* @__PURE__ */ n.createElement("dd", null, "회전"), /* @__PURE__ */ n.createElement("dt", null, "S"), /* @__PURE__ */ n.createElement("dd", null, "스케일"), /* @__PURE__ */ n.createElement("dt", null, "방향키"), /* @__PURE__ */ n.createElement("dd", null, "10mm 이동 (Shift 1mm)"), /* @__PURE__ */ n.createElement("dt", null, "PageUp/Down"), /* @__PURE__ */ n.createElement("dd", null, "45° 회전"), /* @__PURE__ */ n.createElement("dt", null, "Del"), /* @__PURE__ */ n.createElement("dd", null, "선택 삭제"), /* @__PURE__ */ n.createElement("dt", null, "Esc"), /* @__PURE__ */ n.createElement("dd", null, "선택/페인트 해제"))), /* @__PURE__ */ n.createElement("section", null, /* @__PURE__ */ n.createElement("h4", null, "Preview"), /* @__PURE__ */ n.createElement("dl", null, /* @__PURE__ */ n.createElement("dt", null, "↑ / ↓"), /* @__PURE__ */ n.createElement("dd", null, "레이어 (Shift 10단계)"), /* @__PURE__ */ n.createElement("dt", null, "L"), /* @__PURE__ */ n.createElement("dd", null, "단일 레이어"), /* @__PURE__ */ n.createElement("dt", null, "T"), /* @__PURE__ */ n.createElement("dd", null, "트래블"), /* @__PURE__ */ n.createElement("dt", null, "Esc"), /* @__PURE__ */ n.createElement("dd", null, "Prepare 로"))), /* @__PURE__ */ n.createElement("section", null, /* @__PURE__ */ n.createElement("h4", null, "공통"), /* @__PURE__ */ n.createElement("dl", null, /* @__PURE__ */ n.createElement("dt", null, "Ctrl+R"), /* @__PURE__ */ n.createElement("dd", null, "슬라이스"), /* @__PURE__ */ n.createElement("dt", null, "Ctrl+K / ⌘D"), /* @__PURE__ */ n.createElement("dd", null, "복제"), /* @__PURE__ */ n.createElement("dt", null, "Ctrl+C/V/X"), /* @__PURE__ */ n.createElement("dd", null, "복사/붙여넣기/잘라내기"), /* @__PURE__ */ n.createElement("dt", null, "Z / B"), /* @__PURE__ */ n.createElement("dd", null, "전체 / 베드로 줌"))))))
1105
- ), K && H === "prepare" && /* @__PURE__ */ n.createElement("div", { className: "vp-top-toolbar", role: "toolbar", "aria-label": "오브젝트 도구" }, Yr.map((e, t) => {
1106
- var r;
1107
- return e.sep ? /* @__PURE__ */ n.createElement("span", { key: "sep" + t, className: "vtt-sep" }) : /* @__PURE__ */ n.createElement(
1163
+ return { duplicateSelected: P, copySelected: M, pasteClipboard: B, deleteSelected: ee, deleteAllObjects: L, splitSelected: te, setGizmo: I };
1164
+ }
1165
+ function zr({ showTabs: n, canvasMode: a, onCanvasMode: c, previewEnabled: l, onOpen: r }) {
1166
+ return /* @__PURE__ */ e.createElement("header", { className: "topbar" }, /* @__PURE__ */ e.createElement("div", { className: "tb-left" }, /* @__PURE__ */ e.createElement("a", { href: "/", className: "tb-logo" }, /* @__PURE__ */ e.createElement("b", null, "Three"), "Slicer ", /* @__PURE__ */ e.createElement("span", { className: "tb-re" }, "RE")), /* @__PURE__ */ e.createElement("button", { className: "tb-btn", onClick: r, title: "Open a model file — every existing object is cleared", "data-testid": "open-file" }, /* @__PURE__ */ e.createElement("img", { src: mr, alt: "" }), /* @__PURE__ */ e.createElement("span", null, "Open"))), n && /* @__PURE__ */ e.createElement("div", { className: "tb-tabs", role: "tablist", "aria-label": "Canvas mode" }, /* @__PURE__ */ e.createElement("button", { role: "tab", className: a === "prepare" ? "on" : "", onClick: () => c("prepare"), "data-testid": "mode-prepare", title: "Arrange, transform and paint supports" }, "Prepare"), /* @__PURE__ */ e.createElement("button", { role: "tab", className: a === "preview" ? "on" : "", onClick: () => c("preview"), disabled: !l, "data-testid": "mode-preview", title: "Preview the sliced toolpaths (enabled after slicing)" }, "Preview")), /* @__PURE__ */ e.createElement("div", { className: "tb-right" }, /* @__PURE__ */ e.createElement("button", { className: "tb-icon", disabled: !0, title: "Undo — not implemented (needs an action history stack)" }, "↶"), /* @__PURE__ */ e.createElement("button", { className: "tb-icon", disabled: !0, title: "Redo — not implemented (needs an action history stack)" }, "↷")));
1167
+ }
1168
+ function Pr({ gizmoMode: n, paintMode: a, onGizmo: c, onTogglePaint: l }) {
1169
+ const r = (m) => n === m && a === "off" ? "on" : "";
1170
+ return /* @__PURE__ */ e.createElement("nav", { className: "left-rail", role: "toolbar", "aria-label": "Gizmo tools" }, /* @__PURE__ */ e.createElement("button", { className: r("translate"), onClick: () => c("translate"), title: "Move the object — drag a gizmo axis or use arrow keys for 10mm (Shift 1mm) (M/G)", "data-testid": "gizmo-move" }, /* @__PURE__ */ e.createElement("img", { src: dr, alt: "Move" })), /* @__PURE__ */ e.createElement("button", { className: r("rotate"), onClick: () => c("rotate"), title: "Rotate the object — PageUp/PageDown rotates in 45° steps (R)", "data-testid": "gizmo-rotate" }, /* @__PURE__ */ e.createElement("img", { src: gr, alt: "Rotate" })), /* @__PURE__ */ e.createElement("button", { className: r("scale"), onClick: () => c("scale"), title: "Scale the object (S)", "data-testid": "gizmo-scale" }, /* @__PURE__ */ e.createElement("img", { src: br, alt: "Scale" })), /* @__PURE__ */ e.createElement("div", { className: "rail-sep" }), /* @__PURE__ */ e.createElement("button", { className: a !== "off" ? "on" : "", onClick: l, title: "Brush facets to enforce or block support — the wheel changes the brush size", "data-testid": "gizmo-paint" }, /* @__PURE__ */ e.createElement("img", { src: hr, alt: "Support painting" })));
1171
+ }
1172
+ function Lr({ tools: n }) {
1173
+ return /* @__PURE__ */ e.createElement("div", { className: "vp-top-toolbar", role: "toolbar", "aria-label": "Object tools" }, n.map((a, c) => {
1174
+ var l;
1175
+ return a.sep ? /* @__PURE__ */ e.createElement("span", { key: "sep" + c, className: "vtt-sep" }) : /* @__PURE__ */ e.createElement(
1108
1176
  "button",
1109
1177
  {
1110
- key: e.id,
1111
- onClick: e.run,
1112
- disabled: ((r = e.disabled) == null ? void 0 : r.call(e)) ?? !e.run,
1113
- title: e.tip,
1114
- "data-testid": `tool-${e.id}`
1178
+ key: a.id,
1179
+ onClick: a.run,
1180
+ disabled: ((l = a.disabled) == null ? void 0 : l.call(a)) ?? !a.run,
1181
+ title: a.tip,
1182
+ "data-testid": `tool-${a.id}`
1115
1183
  },
1116
- /* @__PURE__ */ n.createElement("img", { src: e.icon, alt: e.label })
1184
+ /* @__PURE__ */ e.createElement("img", { src: a.icon, alt: a.label })
1117
1185
  );
1118
- })), K && H === "prepare" && ve !== "off" && /* @__PURE__ */ n.createElement("div", { className: "brush-panel", "data-testid": "paint-tools" }, /* @__PURE__ */ n.createElement("div", { className: "bp-title" }, "서포트 페인팅"), /* @__PURE__ */ n.createElement("div", { className: "bp-modes" }, /* @__PURE__ */ n.createElement("button", { className: ve === "enforcer" ? "on enf" : "enf", onClick: () => oe("enforcer"), title: "칠한 면 아래에 서포트를 강제로 생성", "data-testid": "paint-enforcer" }, "enforcer"), /* @__PURE__ */ n.createElement("button", { className: ve === "blocker" ? "on blk" : "blk", onClick: () => oe("blocker"), title: "칠한 면 아래에 서포트가 생기지 않게 차단", "data-testid": "paint-blocker" }, "blocker"), /* @__PURE__ */ n.createElement("button", { onClick: zr, "data-testid": "paint-clear", title: "칠한 강제/차단 영역을 모두 지움" }, "지우기"), /* @__PURE__ */ n.createElement("button", { onClick: () => oe("off"), "data-testid": "paint-off", title: "페인팅 모드 종료 (Esc)" }, "닫기")), /* @__PURE__ */ n.createElement("label", { className: "bp-radius" }, "브러시 반경 ", un, "mm", /* @__PURE__ */ n.createElement(
1186
+ }));
1187
+ }
1188
+ function Or({ menu: n, onClose: a, canPaste: c, actions: l }) {
1189
+ const r = (m) => () => {
1190
+ a(), m == null || m();
1191
+ };
1192
+ return /* @__PURE__ */ e.createElement(e.Fragment, null, /* @__PURE__ */ e.createElement("div", { className: "ctx-scrim", onPointerDown: a, onContextMenu: (m) => {
1193
+ m.preventDefault(), a();
1194
+ } }), /* @__PURE__ */ e.createElement("div", { className: "ctx-menu", style: { left: n.x, top: n.y }, "data-testid": "ctx-menu" }, n.onObject ? /* @__PURE__ */ e.createElement(e.Fragment, null, /* @__PURE__ */ e.createElement("button", { onClick: r(l.duplicate), "data-testid": "ctx-duplicate", title: "Duplicate the selected object at the same scale and rotation, placed beside it" }, "Duplicate ", /* @__PURE__ */ e.createElement("span", { className: "ctx-key" }, "Ctrl+K")), /* @__PURE__ */ e.createElement("button", { onClick: r(l.copy), "data-testid": "ctx-copy", title: "Copy the selected object to the buffer (paste to add it)" }, "Copy ", /* @__PURE__ */ e.createElement("span", { className: "ctx-key" }, "Ctrl+C")), /* @__PURE__ */ e.createElement("button", { onClick: r(l.split), "data-testid": "ctx-split", title: "Split to objects — every disconnected part (connected component) becomes its own object. Split to parts is not implemented (no part concept)" }, "Split"), /* @__PURE__ */ e.createElement("button", { onClick: r(l.placeOnBed), "data-testid": "ctx-seat", title: "Drop every object onto the bed (Z=0)" }, "Place on bed"), /* @__PURE__ */ e.createElement("hr", null), /* @__PURE__ */ e.createElement("button", { className: "danger", onClick: r(l.remove), "data-testid": "ctx-delete", title: "Remove the selected object from the scene" }, "Delete ", /* @__PURE__ */ e.createElement("span", { className: "ctx-key" }, "Del"))) : /* @__PURE__ */ e.createElement(e.Fragment, null, /* @__PURE__ */ e.createElement("button", { onClick: r(l.openFile), "data-testid": "ctx-open", title: "Open a model file and add it to the current plate" }, "Open model…"), /* @__PURE__ */ e.createElement("button", { disabled: !c, onClick: r(l.paste), "data-testid": "ctx-paste", title: "Add the copied object to the current plate" }, "Paste ", /* @__PURE__ */ e.createElement("span", { className: "ctx-key" }, "Ctrl+V")), /* @__PURE__ */ e.createElement("hr", null), /* @__PURE__ */ e.createElement("button", { onClick: r(l.zoomAll), "data-testid": "ctx-zoom-all", title: "Fit the camera so every object is visible" }, "Zoom all ", /* @__PURE__ */ e.createElement("span", { className: "ctx-key" }, "Z")), /* @__PURE__ */ e.createElement("button", { onClick: r(l.zoomBed), "data-testid": "ctx-zoom-bed", title: "Fit the camera to the whole selected plate" }, "Zoom bed ", /* @__PURE__ */ e.createElement("span", { className: "ctx-key" }, "B")))));
1195
+ }
1196
+ function Tr({ onClose: n }) {
1197
+ return /* @__PURE__ */ e.createElement("div", { className: "help-overlay", "data-testid": "help-overlay", onClick: n }, /* @__PURE__ */ e.createElement("div", { className: "help-card", onClick: (a) => a.stopPropagation() }, /* @__PURE__ */ e.createElement("h3", null, "Shortcuts ", /* @__PURE__ */ e.createElement("span", { className: "muted" }, "(press ? to close)")), /* @__PURE__ */ e.createElement("div", { className: "help-cols" }, /* @__PURE__ */ e.createElement("section", null, /* @__PURE__ */ e.createElement("h4", null, "Prepare"), /* @__PURE__ */ e.createElement("dl", null, /* @__PURE__ */ e.createElement("dt", null, "M / G"), /* @__PURE__ */ e.createElement("dd", null, "Move"), /* @__PURE__ */ e.createElement("dt", null, "R"), /* @__PURE__ */ e.createElement("dd", null, "Rotate"), /* @__PURE__ */ e.createElement("dt", null, "S"), /* @__PURE__ */ e.createElement("dd", null, "Scale"), /* @__PURE__ */ e.createElement("dt", null, "Arrow keys"), /* @__PURE__ */ e.createElement("dd", null, "Move 10mm (Shift 1mm)"), /* @__PURE__ */ e.createElement("dt", null, "PageUp/Down"), /* @__PURE__ */ e.createElement("dd", null, "Rotate 45°"), /* @__PURE__ */ e.createElement("dt", null, "Del"), /* @__PURE__ */ e.createElement("dd", null, "Delete selection"), /* @__PURE__ */ e.createElement("dt", null, "Esc"), /* @__PURE__ */ e.createElement("dd", null, "Clear selection / leave paint"))), /* @__PURE__ */ e.createElement("section", null, /* @__PURE__ */ e.createElement("h4", null, "Preview"), /* @__PURE__ */ e.createElement("dl", null, /* @__PURE__ */ e.createElement("dt", null, "↑ / ↓"), /* @__PURE__ */ e.createElement("dd", null, "Layer (Shift steps of 10)"), /* @__PURE__ */ e.createElement("dt", null, "L"), /* @__PURE__ */ e.createElement("dd", null, "Single layer"), /* @__PURE__ */ e.createElement("dt", null, "T"), /* @__PURE__ */ e.createElement("dd", null, "Travel"), /* @__PURE__ */ e.createElement("dt", null, "Esc"), /* @__PURE__ */ e.createElement("dd", null, "Back to Prepare"))), /* @__PURE__ */ e.createElement("section", null, /* @__PURE__ */ e.createElement("h4", null, "Common"), /* @__PURE__ */ e.createElement("dl", null, /* @__PURE__ */ e.createElement("dt", null, "Ctrl+R"), /* @__PURE__ */ e.createElement("dd", null, "Slice"), /* @__PURE__ */ e.createElement("dt", null, "Ctrl+K / ⌘D"), /* @__PURE__ */ e.createElement("dd", null, "Duplicate"), /* @__PURE__ */ e.createElement("dt", null, "Ctrl+C/V/X"), /* @__PURE__ */ e.createElement("dd", null, "Copy / paste / cut"), /* @__PURE__ */ e.createElement("dt", null, "Z / B"), /* @__PURE__ */ e.createElement("dd", null, "Zoom all / zoom bed"))))));
1198
+ }
1199
+ function Br({ paintMode: n, onPaintMode: a, onClear: c, brushRadius: l, onBrushRadius: r, paintCounts: m }) {
1200
+ return /* @__PURE__ */ e.createElement("div", { className: "brush-panel", "data-testid": "paint-tools" }, /* @__PURE__ */ e.createElement("div", { className: "bp-title" }, "Support painting"), /* @__PURE__ */ e.createElement("div", { className: "bp-modes" }, /* @__PURE__ */ e.createElement("button", { className: n === "enforcer" ? "on enf" : "enf", onClick: () => a("enforcer"), title: "Force support under the painted facets", "data-testid": "paint-enforcer" }, "enforcer"), /* @__PURE__ */ e.createElement("button", { className: n === "blocker" ? "on blk" : "blk", onClick: () => a("blocker"), title: "Block support under the painted facets", "data-testid": "paint-blocker" }, "blocker"), /* @__PURE__ */ e.createElement("button", { onClick: c, "data-testid": "paint-clear", title: "Clear every painted enforcer/blocker area" }, "Clear"), /* @__PURE__ */ e.createElement("button", { onClick: () => a("off"), "data-testid": "paint-off", title: "Leave painting mode (Esc)" }, "Close")), /* @__PURE__ */ e.createElement("label", { className: "bp-radius" }, "Brush radius ", l, "mm", /* @__PURE__ */ e.createElement(
1119
1201
  "input",
1120
1202
  {
1121
1203
  type: "range",
1122
1204
  min: "1",
1123
1205
  max: "15",
1124
1206
  step: "0.5",
1125
- value: un,
1126
- onChange: (e) => {
1127
- const t = parseFloat(e.target.value);
1128
- mn(t), Qe.current = t;
1129
- },
1207
+ value: l,
1208
+ onChange: (d) => r(parseFloat(d.target.value)),
1130
1209
  "data-testid": "brush-radius"
1131
1210
  }
1132
- )), /* @__PURE__ */ n.createElement("div", { className: "muted bp-counts", "data-testid": "paint-counts" }, "enforcer ", fn.enf, " · blocker ", fn.blk, " · 모델 위 드래그로 페인트")), K && H === "preview" && be && /* @__PURE__ */ n.createElement("div", { className: "stats-card", "data-testid": "slice-stats" }, ea), K && /* @__PURE__ */ n.createElement("div", { className: "plate-bar", "data-testid": "plate-bar", role: "tablist", "aria-label": "플레이트" }, Array.from({ length: ae }, (e, t) => /* @__PURE__ */ n.createElement("button", { key: t, role: "tab", className: "plate-tab" + (t === Fe ? " on" : ""), onClick: () => Nn(t), title: `플레이트 ${t + 1} 선택 — Preview 에선 이 플레이트 결과로 전환`, "data-testid": `plate-${t}` }, t + 1)), /* @__PURE__ */ n.createElement("button", { className: "plate-add", onClick: Br, disabled: ae >= Jn, title: "빈 플레이트 추가 (최대 9개) — 플레이트별로 따로 슬라이스·내보내기", "data-testid": "plate-add" }, "+"), ae > 1 && /* @__PURE__ */ n.createElement("button", { className: "plate-del", onClick: Ir, title: "마지막 플레이트와 그 슬라이스 결과 삭제", "data-testid": "plate-del" }, "−")), K && /* @__PURE__ */ n.createElement("div", { className: "vp-status", "data-testid": "vp-status" }, en)), K && /* @__PURE__ */ n.createElement("aside", { className: "sidebar" }, /* @__PURE__ */ n.createElement("div", { className: "sidebar-scroll" }, /* @__PURE__ */ n.createElement("section", { className: "side-card" }, /* @__PURE__ */ n.createElement("div", { className: "sc-head" }, "🖨 프린터"), /* @__PURE__ */ n.createElement("div", { className: "sc-info" }, /* @__PURE__ */ n.createElement("span", null, "베드"), /* @__PURE__ */ n.createElement("span", { className: "sc-bed", title: "플레이트 크기 — Enter 또는 포커스 아웃으로 적용. 원형/커스텀 형상은 printable_area 옵션에서" }, /* @__PURE__ */ n.createElement(
1211
+ )), /* @__PURE__ */ e.createElement("div", { className: "muted bp-counts", "data-testid": "paint-counts" }, "enforcer ", m.enf, " · blocker ", m.blk, " · drag over the model to paint"));
1212
+ }
1213
+ function Dr({ plateCount: n, selectedPlate: a, maxPlates: c, onSelect: l, onAdd: r, onDelete: m }) {
1214
+ return /* @__PURE__ */ e.createElement("div", { className: "plate-bar", "data-testid": "plate-bar", role: "tablist", "aria-label": "Plates" }, Array.from({ length: n }, (d, w) => /* @__PURE__ */ e.createElement("button", { key: w, role: "tab", className: "plate-tab" + (w === a ? " on" : ""), onClick: () => l(w), title: `Select plate ${w + 1} — in Preview this switches to that plate's result`, "data-testid": `plate-${w}` }, w + 1)), /* @__PURE__ */ e.createElement("button", { className: "plate-add", onClick: r, disabled: n >= c, title: "Add an empty plate (max 9) — each plate slices and exports separately", "data-testid": "plate-add" }, "+"), n > 1 && /* @__PURE__ */ e.createElement("button", { className: "plate-del", onClick: m, title: "Delete the last plate and its slice result", "data-testid": "plate-del" }, "−"));
1215
+ }
1216
+ const Zt = (n) => `rgb(${Math.round(n[0] * 255)},${Math.round(n[1] * 255)},${Math.round(n[2] * 255)})`;
1217
+ function Zr({
1218
+ viewType: n,
1219
+ onViewType: a,
1220
+ layerCount: c,
1221
+ layerLo: l,
1222
+ layerHi: r,
1223
+ segCount: m,
1224
+ singleLayer: d,
1225
+ onLayerLo: w,
1226
+ onLayerHi: b,
1227
+ onToggleSingle: T,
1228
+ showTravel: P,
1229
+ onToggleTravel: M,
1230
+ colorRange: B,
1231
+ roleLegend: ee
1232
+ }) {
1233
+ return /* @__PURE__ */ e.createElement("div", { className: "slice-layer", "data-testid": "preview-controls" }, /* @__PURE__ */ e.createElement("label", { className: "view-type-row" }, "View type", /* @__PURE__ */ e.createElement("select", { value: n, onChange: a, "data-testid": "view-type-select", title: "What the toolpath colors represent — feature type, speed, layer height, width, fan, temperature" }, Xn.map((L) => /* @__PURE__ */ e.createElement("option", { key: L.key, value: L.key }, L.label)))), /* @__PURE__ */ e.createElement("label", null, "Layer ", /* @__PURE__ */ e.createElement("b", { "data-testid": "layer-range" }, d ? r + 1 : `${l + 1}..${r + 1}`), " / ", c, /* @__PURE__ */ e.createElement("span", { className: "muted" }, " (", m.toLocaleString(), " segments)")), /* @__PURE__ */ e.createElement("div", { className: "dual-slider" }, /* @__PURE__ */ e.createElement("input", { type: "range", min: "0", max: Math.max(0, c - 1), value: l, onChange: w, "data-testid": "layer-lo", title: "Lowest layer to show (also adjustable with ↓/↑)" }), /* @__PURE__ */ e.createElement("input", { type: "range", min: "0", max: Math.max(0, c - 1), value: r, onChange: b, "data-testid": "layer-hi", title: "Highest layer to show (↓/↑, hold Shift for steps of 10)" })), /* @__PURE__ */ e.createElement("div", { className: "layer-ctl" }, /* @__PURE__ */ e.createElement("button", { className: d ? "on" : "", onClick: T, "data-testid": "single-layer-btn", title: "Show only the selected layer (L)" }, "Single layer"), /* @__PURE__ */ e.createElement("label", { className: "slice-travel" }, /* @__PURE__ */ e.createElement("input", { type: "checkbox", checked: P, onChange: M, "data-testid": "travel-toggle", title: "Show non-extruding moves as gray lines (T)" }), " Travel")), B && B.cont ? /* @__PURE__ */ e.createElement("div", { className: "grad-legend", "data-testid": "view-legend" }, /* @__PURE__ */ e.createElement("div", { className: "grad-title" }, B.label, " ", /* @__PURE__ */ e.createElement("span", { className: "muted" }, B.unit)), /* @__PURE__ */ e.createElement("div", { className: "grad-bar", "data-testid": "gradient-bar", style: { background: `linear-gradient(to right, ${Kn.map(Zt).join(",")})` } }), /* @__PURE__ */ e.createElement("div", { className: "grad-minmax" }, /* @__PURE__ */ e.createElement("span", { "data-testid": "grad-min" }, B.min.toFixed(B.unit === "mm" ? 2 : 0)), /* @__PURE__ */ e.createElement("span", { "data-testid": "grad-max" }, B.max.toFixed(B.unit === "mm" ? 2 : 0)))) : /* @__PURE__ */ e.createElement("div", { className: "role-legend", "data-testid": "view-legend" }, ee.map((L) => /* @__PURE__ */ e.createElement("span", { key: L.type, className: "role-item" }, /* @__PURE__ */ e.createElement("i", { style: { background: Zt(L.color) } }), L.label, " ", /* @__PURE__ */ e.createElement("b", null, L.pct.toFixed(0), "%")))));
1234
+ }
1235
+ const Ir = (n) => {
1236
+ const a = Math.round(n);
1237
+ return `${Math.floor(a / 3600)}h ${String(Math.floor(a % 3600 / 60)).padStart(2, "0")}m ${String(a % 60).padStart(2, "0")}s`;
1238
+ }, Vr = { full: "GCodeProcessor", transcribed: "transcribed planner", fallback: "fallback" };
1239
+ function Fr({ stats: n, overBed: a }) {
1240
+ return n ? /* @__PURE__ */ e.createElement(e.Fragment, null, /* @__PURE__ */ e.createElement("div", null, /* @__PURE__ */ e.createElement("b", null, n.layers), " layers · ", /* @__PURE__ */ e.createElement("b", null, n.segments), " segments"), /* @__PURE__ */ e.createElement("div", null, "Filament ", /* @__PURE__ */ e.createElement("b", null, n.filament.toFixed(1)), " mm"), typeof n.timeSec == "number" && n.timeSec > 0 && /* @__PURE__ */ e.createElement(e.Fragment, null, /* @__PURE__ */ e.createElement("div", { "data-testid": "print-time" }, "⏱ Estimated print ", /* @__PURE__ */ e.createElement("b", null, Ir(n.timeSec))), n.limits && // What the estimator actually ran with, echoed back by the kernel — not re-derived from the settings,
1241
+ // so an unwired limit shows up as a mismatch here instead of being silently reported as applied.
1242
+ /* @__PURE__ */ e.createElement(
1243
+ "div",
1244
+ {
1245
+ className: "sc-sub",
1246
+ "data-testid": "print-time-basis",
1247
+ title: Object.entries(n.limits).map(([c, l]) => `${c}: ${l}`).join(`
1248
+ `)
1249
+ },
1250
+ Vr[n.engine] ?? n.engine,
1251
+ " · ",
1252
+ n.limits.max_speed_xy,
1253
+ " mm/s · ",
1254
+ n.limits.accel_print,
1255
+ " mm/s² · jerk ",
1256
+ n.limits.jerk_xy
1257
+ )), a && /* @__PURE__ */ e.createElement("div", { className: "slice-warn", "data-testid": "over-bed" }, "⚠ Model extends beyond the bed")) : null;
1258
+ }
1259
+ function Gr({ bedWidth: n, bedDepth: a, nozzleDia: c, onBedSize: l, settings: r, setSettings: m, motionPanel: d }) {
1260
+ const w = Tt(() => Object.entries(On).sort(([h], [E]) => h.localeCompare(E)), []), b = (r == null ? void 0 : r.printer_settings_id) ?? "", T = Tt(() => {
1261
+ const h = /* @__PURE__ */ new Map();
1262
+ for (const [E, k] of w)
1263
+ for (const [j, [f, , g]] of Object.entries(k)) {
1264
+ const y = `${E}\0${g}`;
1265
+ h.has(y) || h.set(y, { vendor: E, model: g, variants: [] }), h.get(y).variants.push({ name: j, nozzle: f });
1266
+ }
1267
+ for (const E of h.values()) E.variants.sort((k, j) => parseFloat(k.nozzle) - parseFloat(j.nozzle));
1268
+ return h;
1269
+ }, [w]), P = [...T.entries()].find(([, h]) => h.variants.some((E) => E.name === b)), M = (P == null ? void 0 : P[1].variants) ?? [], [B, ee] = $(null), L = (h) => m == null ? void 0 : m((E) => {
1270
+ const k = { ...E };
1271
+ for (const f of lt) delete k[f];
1272
+ for (const f of (B == null ? void 0 : B.keys) ?? []) delete k[f];
1273
+ delete k.printer_settings_id, delete k.print_settings_id;
1274
+ const j = h ? Zn(h) : null;
1275
+ return j ? { ...k, ...j, printer_settings_id: h } : k;
1276
+ });
1277
+ Ve(() => {
1278
+ if (!b) return;
1279
+ let h = !0;
1280
+ return Tn().then((E) => {
1281
+ if (!h) return;
1282
+ ee(E);
1283
+ const k = Bn(b);
1284
+ m == null || m((j) => {
1285
+ if (j.printer_settings_id !== b || j.print_settings_id) return j;
1286
+ const f = k && E.listFor(b).includes(k) ? E.settingsFor(k) : null;
1287
+ if (!f) return j;
1288
+ for (const g of lt) delete f[g];
1289
+ return { ...j, ...f, print_settings_id: k };
1290
+ });
1291
+ }), () => {
1292
+ h = !1;
1293
+ };
1294
+ }, [b]);
1295
+ const te = B && b ? B.listFor(b) : [], I = (h) => m == null ? void 0 : m((E) => {
1296
+ const k = { ...E };
1297
+ for (const f of B.keys)
1298
+ lt.includes(f) || delete k[f];
1299
+ delete k.print_settings_id;
1300
+ const j = h ? B.settingsFor(h) : null;
1301
+ if (!j) return k;
1302
+ for (const f of lt) delete j[f];
1303
+ return { ...k, ...j, print_settings_id: h };
1304
+ }), A = (h) => {
1305
+ const E = T.get(h);
1306
+ if (!E) return L("");
1307
+ const k = E.variants.find((j) => {
1308
+ var f;
1309
+ return j.nozzle === ((f = M.find((g) => g.name === b)) == null ? void 0 : f.nozzle);
1310
+ });
1311
+ L((k ?? E.variants[Math.min(1, E.variants.length - 1)]).name);
1312
+ }, u = Dn.filter((h) => r && h in r), _ = Math.round(n), G = Math.round(a), K = (h) => {
1313
+ h.key === "Enter" && h.target.blur();
1314
+ };
1315
+ return /* @__PURE__ */ e.createElement("section", { className: "side-card" }, /* @__PURE__ */ e.createElement("div", { className: "sc-head" }, "🖨 Printer"), m && /* @__PURE__ */ e.createElement(e.Fragment, null, /* @__PURE__ */ e.createElement("div", { className: "sc-info" }, /* @__PURE__ */ e.createElement("span", null, "Model"), /* @__PURE__ */ e.createElement(
1316
+ "select",
1317
+ {
1318
+ className: "sc-model",
1319
+ value: (P == null ? void 0 : P[0]) ?? "",
1320
+ onChange: (h) => A(h.target.value),
1321
+ "data-testid": "printer-model",
1322
+ title: "Loads this printer's profile (motion limits, bed, nozzle) from the upstream vendor data"
1323
+ },
1324
+ /* @__PURE__ */ e.createElement("option", { value: "" }, "Custom (defaults)"),
1325
+ w.map(([h]) => /* @__PURE__ */ e.createElement("optgroup", { key: h, label: h }, [...T.entries()].filter(([, E]) => E.vendor === h).map(([E, k]) => /* @__PURE__ */ e.createElement("option", { key: E, value: E }, k.model))))
1326
+ )), M.length > 1 && /* @__PURE__ */ e.createElement("div", { className: "sc-info" }, /* @__PURE__ */ e.createElement("span", null, "Nozzle"), /* @__PURE__ */ e.createElement("select", { className: "sc-model", value: b, onChange: (h) => L(h.target.value), "data-testid": "printer-nozzle" }, M.map((h) => /* @__PURE__ */ e.createElement("option", { key: h.name, value: h.name }, h.nozzle, " mm")))), te.length > 0 && /* @__PURE__ */ e.createElement("div", { className: "sc-info" }, /* @__PURE__ */ e.createElement("span", null, "Quality"), /* @__PURE__ */ e.createElement(
1327
+ "select",
1328
+ {
1329
+ className: "sc-model",
1330
+ value: (r == null ? void 0 : r.print_settings_id) ?? "",
1331
+ onChange: (h) => I(h.target.value),
1332
+ "data-testid": "process-preset",
1333
+ title: "Print preset from the vendor profile — the speeds and accelerations the printer is actually driven at"
1334
+ },
1335
+ /* @__PURE__ */ e.createElement("option", { value: "" }, "Custom (defaults)"),
1336
+ te.map((h) => /* @__PURE__ */ e.createElement("option", { key: h, value: h }, h.replace(/\s*@.*$/, "")))
1337
+ ))), /* @__PURE__ */ e.createElement("div", { className: "sc-info" }, /* @__PURE__ */ e.createElement("span", null, "Bed"), /* @__PURE__ */ e.createElement("span", { className: "sc-bed", title: "Plate size — applied on Enter or blur. Circular/custom shapes come from the printable_area option" }, /* @__PURE__ */ e.createElement(
1133
1338
  "input",
1134
1339
  {
1135
1340
  type: "number",
1136
1341
  min: "1",
1137
- key: `w${Math.round(J.bed_width)}`,
1138
- defaultValue: Math.round(J.bed_width),
1139
- onBlur: (e) => _n(+e.target.value, J.bed_depth),
1140
- onKeyDown: (e) => {
1141
- e.key === "Enter" && e.target.blur();
1142
- },
1342
+ key: `w${_}`,
1343
+ defaultValue: _,
1344
+ onBlur: (h) => l(+h.target.value, a),
1345
+ onKeyDown: K,
1143
1346
  "data-testid": "bed-w-card"
1144
1347
  }
1145
- ), "×", /* @__PURE__ */ n.createElement(
1348
+ ), "×", /* @__PURE__ */ e.createElement(
1146
1349
  "input",
1147
1350
  {
1148
1351
  type: "number",
1149
1352
  min: "1",
1150
- key: `d${Math.round(J.bed_depth)}`,
1151
- defaultValue: Math.round(J.bed_depth),
1152
- onBlur: (e) => _n(J.bed_width, +e.target.value),
1153
- onKeyDown: (e) => {
1154
- e.key === "Enter" && e.target.blur();
1155
- },
1353
+ key: `d${G}`,
1354
+ defaultValue: G,
1355
+ onBlur: (h) => l(n, +h.target.value),
1356
+ onKeyDown: K,
1156
1357
  "data-testid": "bed-d-card"
1157
1358
  }
1158
- ), "mm")), /* @__PURE__ */ n.createElement("div", { className: "sc-info" }, /* @__PURE__ */ n.createElement("span", null, "노즐 Ø"), /* @__PURE__ */ n.createElement("b", null, Qr, " mm"))), /* @__PURE__ */ n.createElement("section", { className: "side-card", "data-testid": "filament-section" }, /* @__PURE__ */ n.createElement("div", { className: "sc-head" }, "🧵 필라멘트 ", /* @__PURE__ */ n.createElement("span", { className: "sc-count" }, De.length), /* @__PURE__ */ n.createElement("span", { className: "sc-head-btns" }, /* @__PURE__ */ n.createElement("button", { onClick: qr, disabled: De.length >= 4, title: "필라멘트(익스트루더) 추가 — 최대 4개, 오브젝트별로 지정 가능", "data-testid": "filament-add" }, "+"), /* @__PURE__ */ n.createElement("button", { onClick: Wr, disabled: De.length <= 1, title: "마지막 필라멘트 제거 — 이를 쓰던 오브젝트는 남은 번호로 재배정", "data-testid": "filament-del" }, "−"))), De.map((e, t) => /* @__PURE__ */ n.createElement("div", { className: "filament-row", key: t }, /* @__PURE__ */ n.createElement("input", { type: "color", value: e, onChange: (r) => Hr(t, r.target.value), title: `T${t + 1} 필라멘트 색`, "data-testid": `filament-color-${t}` }), /* @__PURE__ */ n.createElement("span", { className: "fil-t" }, "T", t + 1), /* @__PURE__ */ n.createElement("span", { className: "fil-swatch", style: { background: e } }), /* @__PURE__ */ n.createElement("span", { className: "fil-hex muted" }, e)))), Ee.length > 0 && /* @__PURE__ */ n.createElement("section", { className: "side-card", "data-testid": "object-section" }, /* @__PURE__ */ n.createElement("div", { className: "sc-head" }, "📦 오브젝트 ", /* @__PURE__ */ n.createElement("span", { className: "sc-count" }, Ee.length)), /* @__PURE__ */ n.createElement("ul", { className: "obj-list2", "data-testid": "obj-list" }, Ee.map((e) => /* @__PURE__ */ n.createElement("li", { key: e.id, className: e.visible === !1 ? "obj-hidden" : "" }, /* @__PURE__ */ n.createElement("button", { className: "obj-eye", onClick: () => Xr(e.id), title: "이 오브젝트를 출력 대상에서 제외/포함 — 제외해도 씬에는 남음", "data-testid": `eye-${e.id}` }, e.visible === !1 ? "🚫" : "👁"), /* @__PURE__ */ n.createElement("span", { className: "obj-name", title: e.name }, e.name), /* @__PURE__ */ n.createElement("select", { className: "obj-ext", value: e.extruder ?? 1, onChange: (t) => Vr(e.id, +t.target.value), title: "이 오브젝트를 출력할 필라멘트(익스트루더) 번호", "data-testid": `ext-${e.id}` }, De.map((t, r) => /* @__PURE__ */ n.createElement("option", { key: r, value: r + 1 }, "T", r + 1))), /* @__PURE__ */ n.createElement("button", { className: "obj-split", onClick: () => {
1159
- var t;
1160
- (t = d.current) == null || t.selectObject(e.id), Dt();
1161
- }, title: "객체로 분리 — 서로 떨어진 부분(연결 성분)마다 독립 오브젝트로 나눔. 파트로 분리는 미구현(파트 개념 부재)", "data-testid": `split-${e.id}` }, /* @__PURE__ */ n.createElement("img", { src: Yn, alt: "분리" })), /* @__PURE__ */ n.createElement("button", { className: "obj-del", onClick: () => Vt(e.id), title: "이 오브젝트를 씬에서 제거" }, "✕")))), /* @__PURE__ */ n.createElement("label", { className: "slice-support" }, /* @__PURE__ */ n.createElement("input", { type: "checkbox", checked: Dr, onChange: Gr, title: "오버행 아래에 지지 구조를 생성 (설정 패널의 enable_support 와 동일)", "data-testid": "support-toggle" }), " 서포트 생성"), /* @__PURE__ */ n.createElement("label", { className: "slice-support" }, /* @__PURE__ */ n.createElement("input", { type: "checkbox", checked: pn, onChange: (e) => hr(e.target.checked), title: "멀티머티리얼 툴체인지 시 원본 WipeTower 로 퍼지량을 계산 (끄면 단순 사각 링)", "data-testid": "wipe-tower-real-toggle" }), " 실 와이프타워 ", /* @__PURE__ */ n.createElement("span", { className: "muted" }, "(MM)"))), tn && /* @__PURE__ */ n.createElement("div", { className: "slice-warn side-warn" }, "⚠ ", tn), gn && /* @__PURE__ */ n.createElement("div", { className: "slice-warn side-warn", "data-testid": "slice-notice" }, "ℹ ", gn), rn && /* @__PURE__ */ n.createElement("div", { className: "slice-err side-warn", "data-testid": "slice-err" }, rn), bn && /* @__PURE__ */ n.createElement("button", { className: "slice-btn", "data-testid": "downgrade-retry", onClick: Tr, title: "인필을 단순화하고 밀도를 낮춰 메모리 부담을 줄인 뒤 재시도" }, "간소화 재시도(인필 단순화·절약 모드)"), H === "preview" && Rn && /* @__PURE__ */ n.createElement("section", { className: "side-card" }, /* @__PURE__ */ n.createElement("div", { className: "sc-head" }, "🎚 미리보기"), Rn), /* @__PURE__ */ n.createElement("section", { className: "side-card process-card", "data-testid": "process-section" }, /* @__PURE__ */ n.createElement("div", { className: "sc-head" }, "⚙ 프로세스"), L)), /* @__PURE__ */ n.createElement("div", { className: "side-bottom" }, /* @__PURE__ */ n.createElement("label", { className: "auto-slice", "data-testid": "auto-slice", title: "설정을 바꾸면 0.8초 뒤 자동으로 재슬라이스합니다 (첫 슬라이스는 수동, 진행 중이면 취소 후 재시작)" }, /* @__PURE__ */ n.createElement("input", { type: "checkbox", checked: Nt, onChange: (e) => tr(e.target.checked) }), " 자동 슬라이스"), /* @__PURE__ */ n.createElement("div", { className: "slice-dd" }, /* @__PURE__ */ n.createElement("button", { className: "slice-btn", title: lt ? "클릭하면 슬라이스를 취소합니다" : ae > 1 ? "슬라이스 대상 선택 (Ctrl+R = 현재 플레이트)" : "현재 플레이트를 슬라이스 (Ctrl+R)", onClick: () => lt ? vn() : ae > 1 ? At((e) => !e) : Pe("current"), disabled: Ee.length === 0, "data-testid": "slice-btn" }, lt ? `슬라이싱… ${Math.round(nr * 100)}%` : ae > 1 ? "슬라이스 ▾" : "슬라이스"), lr && ae > 1 && /* @__PURE__ */ n.createElement("div", { className: "slice-menu", "data-testid": "slice-menu" }, /* @__PURE__ */ n.createElement("button", { onClick: () => Pe("current"), "data-testid": "slice-current", title: "선택한 플레이트만 슬라이스 (Ctrl+R)" }, "현재 플레이트 (P", Fe + 1, ")"), /* @__PURE__ */ n.createElement("button", { onClick: () => Pe("all"), "data-testid": "slice-all", title: "모든 플레이트를 차례로 슬라이스 — 결과는 탭 전환으로 조회" }, "전체 플레이트 (", ae, ")"), sn > 0 && /* @__PURE__ */ n.createElement("button", { onClick: Zr, "data-testid": "export-all", title: "슬라이스된 모든 플레이트의 G-code를 각각 파일로 저장" }, "전체 G-code 내보내기 (", sn, ")"))), dn ? /* @__PURE__ */ n.createElement("a", { className: "export-btn", href: dn, download: `plate_${Fe + 1}.gcode`, title: "현재 보고 있는 플레이트의 G-code 파일 저장", "data-testid": "gcode-dl" }, "G-code 내보내기") : /* @__PURE__ */ n.createElement("button", { className: "export-btn", disabled: !0, title: "G-code 내보내기 — 슬라이스 후 활성화됩니다" }, "G-code 내보내기"))))));
1359
+ ), "mm")), /* @__PURE__ */ e.createElement("div", { className: "sc-info" }, /* @__PURE__ */ e.createElement("span", null, "Nozzle Ø"), /* @__PURE__ */ e.createElement("b", null, c, " mm")), d ? /* @__PURE__ */ e.createElement("details", { className: "sc-fold", "data-testid": "motion-fold" }, /* @__PURE__ */ e.createElement("summary", null, /* @__PURE__ */ e.createElement("span", { className: "sc-fold-lbl" }, "Motion"), /* @__PURE__ */ e.createElement("b", { "data-testid": "motion-source" }, u.length ? `Custom (${u.length})` : "Default")), /* @__PURE__ */ e.createElement("div", { className: "sc-fold-body" }, d)) : /* @__PURE__ */ e.createElement("div", { className: "sc-info" }, /* @__PURE__ */ e.createElement("span", null, "Motion"), /* @__PURE__ */ e.createElement(
1360
+ "b",
1361
+ {
1362
+ "data-testid": "motion-source",
1363
+ title: u.length ? `Edited: ${u.join(", ")}` : "Machine limits are at their defaults"
1364
+ },
1365
+ u.length ? `Custom (${u.length})` : "Default"
1366
+ )));
1367
+ }
1368
+ function Ur({ colors: n, onColor: a, onAdd: c, onRemove: l }) {
1369
+ return /* @__PURE__ */ e.createElement("section", { className: "side-card", "data-testid": "filament-section" }, /* @__PURE__ */ e.createElement("div", { className: "sc-head" }, "🧵 Filament ", /* @__PURE__ */ e.createElement("span", { className: "sc-count" }, n.length), /* @__PURE__ */ e.createElement("span", { className: "sc-head-btns" }, /* @__PURE__ */ e.createElement("button", { onClick: c, disabled: n.length >= 4, title: "Add a filament (extruder) — up to 4, assignable per object", "data-testid": "filament-add" }, "+"), /* @__PURE__ */ e.createElement("button", { onClick: l, disabled: n.length <= 1, title: "Remove the last filament — objects using it are reassigned to a remaining number", "data-testid": "filament-del" }, "−"))), n.map((r, m) => /* @__PURE__ */ e.createElement("div", { className: "filament-row", key: m }, /* @__PURE__ */ e.createElement("input", { type: "color", value: r, onChange: (d) => a(m, d.target.value), title: `T${m + 1} filament color`, "data-testid": `filament-color-${m}` }), /* @__PURE__ */ e.createElement("span", { className: "fil-t" }, "T", m + 1), /* @__PURE__ */ e.createElement("span", { className: "fil-swatch", style: { background: r } }), /* @__PURE__ */ e.createElement("span", { className: "fil-hex muted" }, r))));
1370
+ }
1371
+ function $r({
1372
+ objects: n,
1373
+ extruderColors: a,
1374
+ onToggleVisible: c,
1375
+ onExtruder: l,
1376
+ onSplit: r,
1377
+ onRemove: m,
1378
+ supportOn: d,
1379
+ onToggleSupport: w,
1380
+ wipeTowerReal: b,
1381
+ onToggleWipeTower: T,
1382
+ overhangOn: P,
1383
+ onToggleOverhang: M,
1384
+ overhangAngle: B,
1385
+ paintMode: ee,
1386
+ onTogglePaint: L,
1387
+ supportStyle: te,
1388
+ supportStyles: I,
1389
+ onSupportStyle: A
1390
+ }) {
1391
+ return /* @__PURE__ */ e.createElement("section", { className: "side-card", "data-testid": "object-section" }, /* @__PURE__ */ e.createElement("div", { className: "sc-head" }, "📦 Objects ", /* @__PURE__ */ e.createElement("span", { className: "sc-count" }, n.length)), /* @__PURE__ */ e.createElement("ul", { className: "obj-list2", "data-testid": "obj-list" }, n.map((u) => /* @__PURE__ */ e.createElement("li", { key: u.id, className: u.visible === !1 ? "obj-hidden" : "" }, /* @__PURE__ */ e.createElement("button", { className: "obj-eye", onClick: () => c(u.id), title: "Include/exclude this object from printing — excluding it keeps it in the scene", "data-testid": `eye-${u.id}` }, /* @__PURE__ */ e.createElement("img", { src: u.visible === !1 ? sr : lr, alt: u.visible === !1 ? "Hidden" : "Visible" })), /* @__PURE__ */ e.createElement("span", { className: "obj-name", title: u.name }, u.name), /* @__PURE__ */ e.createElement("select", { className: "obj-ext", value: u.extruder ?? 1, onChange: (_) => l(u.id, +_.target.value), title: "Which filament (extruder) prints this object", "data-testid": `ext-${u.id}` }, a.map((_, G) => /* @__PURE__ */ e.createElement("option", { key: G, value: G + 1 }, "T", G + 1))), /* @__PURE__ */ e.createElement("button", { className: "obj-split", onClick: () => r(u.id), title: "Split to objects — every disconnected part (connected component) becomes its own object. Split to parts is not implemented (no part concept)", "data-testid": `split-${u.id}` }, /* @__PURE__ */ e.createElement("img", { src: Gt, alt: "Split" })), /* @__PURE__ */ e.createElement("button", { className: "obj-del", onClick: () => m(u.id), title: "Remove this object from the scene" }, /* @__PURE__ */ e.createElement("img", { src: Ft, alt: "Delete" }))))), /* @__PURE__ */ e.createElement("label", { className: "slice-support" }, /* @__PURE__ */ e.createElement("input", { type: "checkbox", checked: d, onChange: w, title: "Generate support structures under overhangs (same as enable_support in the settings panel)", "data-testid": "support-toggle" }), " Generate support"), d && /* @__PURE__ */ e.createElement("div", { className: "sc-info sup-style" }, /* @__PURE__ */ e.createElement("span", null, "Style"), /* @__PURE__ */ e.createElement(
1392
+ "select",
1393
+ {
1394
+ className: "sc-model",
1395
+ value: te,
1396
+ onChange: (u) => A(u.target.value),
1397
+ "data-testid": "support-style",
1398
+ title: "Support style (support_style). The kernel builds the tree/organic family as real tree support; the rest as grid."
1399
+ },
1400
+ I.map((u) => /* @__PURE__ */ e.createElement("option", { key: u.value, value: u.value }, u.label))
1401
+ )), d && /* @__PURE__ */ e.createElement("div", { className: "support-tools" }, /* @__PURE__ */ e.createElement("label", { className: "slice-support" }, /* @__PURE__ */ e.createElement(
1402
+ "input",
1403
+ {
1404
+ type: "checkbox",
1405
+ checked: !!P,
1406
+ onChange: M,
1407
+ title: `Shade the facets that will need support (slope below ${B}°, the support threshold angle)`,
1408
+ "data-testid": "overhang-toggle"
1409
+ }
1410
+ ), " Show overhangs ", /* @__PURE__ */ e.createElement("span", { className: "muted" }, "<", B, "°")), /* @__PURE__ */ e.createElement(
1411
+ "button",
1412
+ {
1413
+ className: ee !== "off" ? "sup-paint on" : "sup-paint",
1414
+ onClick: L,
1415
+ disabled: !n.length,
1416
+ "data-testid": "support-paint-btn",
1417
+ title: "Brush facets to force or block support — the same tool as the paint icon on the left rail"
1418
+ },
1419
+ ee !== "off" ? "Painting…" : "Paint support"
1420
+ )), /* @__PURE__ */ e.createElement("label", { className: "slice-support" }, /* @__PURE__ */ e.createElement("input", { type: "checkbox", checked: b, onChange: T, title: "Use the upstream WipeTower to compute purge volumes on multi-material tool changes (off = a simple square ring)", "data-testid": "wipe-tower-real-toggle" }), " Real wipe tower ", /* @__PURE__ */ e.createElement("span", { className: "muted" }, "(MM)")));
1421
+ }
1422
+ function Hr({
1423
+ autoSlice: n,
1424
+ onAutoSlice: a,
1425
+ slicing: c,
1426
+ progress: l,
1427
+ plateCount: r,
1428
+ selectedPlate: m,
1429
+ sliceMenuOpen: d,
1430
+ onSliceMenu: w,
1431
+ slicedPlateCount: b,
1432
+ canSlice: T,
1433
+ onSlice: P,
1434
+ onCancel: M,
1435
+ onExportAll: B,
1436
+ gcodeUrl: ee
1437
+ }) {
1438
+ const L = c ? "Click to cancel the slice" : r > 1 ? "Choose what to slice (Ctrl+R = current plate)" : "Slice the current plate (Ctrl+R)";
1439
+ return /* @__PURE__ */ e.createElement("div", { className: "side-bottom" }, /* @__PURE__ */ e.createElement("label", { className: "auto-slice", "data-testid": "auto-slice", title: "Re-slice automatically 0.8s after a settings change (the first slice is manual; a running slice is canceled and restarted)" }, /* @__PURE__ */ e.createElement("input", { type: "checkbox", checked: n, onChange: (te) => a(te.target.checked) }), " Auto slice"), /* @__PURE__ */ e.createElement("div", { className: "slice-dd" }, /* @__PURE__ */ e.createElement(
1440
+ "button",
1441
+ {
1442
+ className: "slice-btn",
1443
+ title: L,
1444
+ onClick: () => c ? M() : r > 1 ? w() : P("current"),
1445
+ disabled: !T,
1446
+ "data-testid": "slice-btn"
1447
+ },
1448
+ c ? `Slicing… ${Math.round(l * 100)}%` : r > 1 ? "Slice ▾" : "Slice"
1449
+ ), d && r > 1 && /* @__PURE__ */ e.createElement("div", { className: "slice-menu", "data-testid": "slice-menu" }, /* @__PURE__ */ e.createElement("button", { onClick: () => P("current"), "data-testid": "slice-current", title: "Slice only the selected plate (Ctrl+R)" }, "Current plate (P", m + 1, ")"), /* @__PURE__ */ e.createElement("button", { onClick: () => P("all"), "data-testid": "slice-all", title: "Slice every plate in turn — switch tabs to inspect the results" }, "All plates (", r, ")"), b > 0 && /* @__PURE__ */ e.createElement("button", { onClick: B, "data-testid": "export-all", title: "Save the G-code of every sliced plate as its own file" }, "Export all G-code (", b, ")"))), ee ? /* @__PURE__ */ e.createElement("a", { className: "export-btn", href: ee, download: `plate_${m + 1}.gcode`, title: "Save the G-code of the plate you are viewing", "data-testid": "gcode-dl" }, "Export G-code") : /* @__PURE__ */ e.createElement("button", { className: "export-btn", disabled: !0, title: "Export G-code — enabled after slicing" }, "Export G-code"));
1450
+ }
1451
+ function no({ settings: n = {}, setSettings: a = () => {
1452
+ }, processPanel: c = null, motionPanel: l = null }) {
1453
+ var Pt, Lt, Ot;
1454
+ const r = H(null), m = H(null), d = H([]), w = H(null), b = H(null), T = H(null), P = H({}), M = H(null), B = H(null), ee = H(!1), L = H("feature"), te = H(0), I = H(0), A = H("prepare"), u = H(0.42), _ = H("off"), G = H(5), K = H(null), h = H(null), E = H({}), k = H({}), j = H(0), f = H(1), g = H(0), [y, v] = $(!0), [D, W] = $("translate"), [oe, ge] = $("Initializing…"), [de, Y] = $([]), [C, pe] = $(""), [je, Ce] = $(!1), [ye, Re] = $(!1), be = H(0), [Se, i] = $(0), [S, V] = $(""), [p, O] = $(null), [ue, ve] = $(!1), [Ee, Z] = $(0), [q, z] = $(0), [ie, we] = $(1), [se, ae] = $(0), [J, ce] = $(!1), [ke, ze] = $(!1), [Fe, qe] = $(0), [Ie, We] = $(null), [et, tt] = $("feature"), [nt, Xe] = $(null), [rt, t] = $(0), [s, R] = $(0), [F, re] = $(!1), [U, me] = $([]), [ne, Me] = $("prepare"), [Ne, Pe] = $(!1), Ae = H(null), [De, Oe] = $(["#6aa0dc", "#e08a2b"]), Ze = H(["#6aa0dc", "#e08a2b"]), [fe, le] = $(""), [Le, _e] = $(!1), [Te, X] = $(!1), [xe, Ge] = $("off"), [Ue, yt] = $(5), [Ut, vt] = $({ enf: 0, blk: 0 }), [wt, st] = $(""), [kt, Et] = $(null), { mountRef: $t, three: ot } = jr({
1455
+ apiRef: r,
1456
+ objectsRef: d,
1457
+ keyRef: M,
1458
+ workerRef: m,
1459
+ selectedPlateRef: j,
1460
+ placeXRef: g,
1461
+ plateCountRef: f,
1462
+ canvasModeRef: A,
1463
+ paintModeRef: _,
1464
+ brushRadiusRef: G,
1465
+ paintXformRef: K,
1466
+ extruderColorsRef: Ze,
1467
+ setOk: v,
1468
+ setStatus: ge,
1469
+ setGmode: W,
1470
+ setCtxMenu: We,
1471
+ setBrushRadius: yt
1472
+ }), $e = xt(n);
1473
+ Ve(() => {
1474
+ var o;
1475
+ (o = r.current) == null || o.setPlates(ie, $e.bed_width, $e.bed_depth, se);
1476
+ }, [$e.bed_width, $e.bed_depth, ie, se]), Ve(() => {
1477
+ var Q;
1478
+ A.current = ne;
1479
+ const o = ot.current;
1480
+ if (!o.toolpathGroup) return;
1481
+ const x = ne === "preview";
1482
+ o.toolpathGroup.visible = x, o.objectsGroup && (o.objectsGroup.visible = !x && d.current.length > 0), x && ((Q = r.current) == null || Q.detachTransform(), _.current !== "off" && Ke("off"));
1483
+ }, [ne]);
1484
+ const {
1485
+ disposePlateToolpath: Ht,
1486
+ clearToolpaths: qt,
1487
+ buildPlateToolpath: Wt,
1488
+ ensurePlateToolpaths: Xt,
1489
+ applyViewColors: Mt,
1490
+ rebuildToolpaths: Kt,
1491
+ applyLayerRange: Yt
1492
+ } = Sr({
1493
+ three: ot,
1494
+ plateTpRef: P,
1495
+ toolpathRef: b,
1496
+ segDataRef: T,
1497
+ layersDataRef: w,
1498
+ plateResultsRef: E,
1499
+ plateOffsetsRef: k,
1500
+ lineWidthRef: u,
1501
+ showTravelRef: ee,
1502
+ viewTypeRef: L,
1503
+ layerLoRef: te,
1504
+ layerHiRef: I,
1505
+ selectedPlateRef: j,
1506
+ settings: n,
1507
+ setSegCount: z,
1508
+ setColorRange: Xe,
1509
+ setRoleLegend: me
1510
+ }), { getWorker: Jt, cancelSlice: jt, runSlice: Qt, pendingSliceRef: en, downgradeRef: tn } = Cr({
1511
+ settings: n,
1512
+ wipeTowerReal: Te,
1513
+ workerRef: m,
1514
+ apiRef: r,
1515
+ layersDataRef: w,
1516
+ layerLoRef: te,
1517
+ layerHiRef: I,
1518
+ rebuildToolpaths: Kt,
1519
+ rebuildPaintOverlay: (o, x) => nn(o, x),
1520
+ setProgress: i,
1521
+ setSlicing: Ce,
1522
+ setError: V,
1523
+ setStats: O,
1524
+ setOverBed: ve,
1525
+ setLayerCount: Z,
1526
+ setLayerLo: t,
1527
+ setLayerHi: R,
1528
+ setGcodeUrl: le,
1529
+ setCanvasMode: Me,
1530
+ setPaintCounts: vt
1531
+ }), { rebuildPaintOverlay: nn, setPaintMode: Ke, clearPaint: rn } = Ar({
1532
+ three: ot,
1533
+ objectsRef: d,
1534
+ apiRef: r,
1535
+ getWorker: Jt,
1536
+ selectedPlateRef: j,
1537
+ paintXformRef: K,
1538
+ paintOverlayRef: h,
1539
+ paintModeRef: _,
1540
+ setError: V,
1541
+ setPaintModeState: Ge,
1542
+ setPaintCounts: vt
1543
+ }), {
1544
+ refreshSlicedCount: on,
1545
+ exportAllGcode: an,
1546
+ onSlice: it,
1547
+ retryDowngrade: ln,
1548
+ addPlate: sn,
1549
+ deletePlate: cn,
1550
+ selectPlate: dn
1551
+ } = Rr({
1552
+ apiRef: r,
1553
+ selectedPlateRef: j,
1554
+ plateCountRef: f,
1555
+ placeXRef: g,
1556
+ plateResultsRef: E,
1557
+ plateOffsetsRef: k,
1558
+ plateTpRef: P,
1559
+ layersDataRef: w,
1560
+ toolpathRef: b,
1561
+ segDataRef: T,
1562
+ layerLoRef: te,
1563
+ layerHiRef: I,
1564
+ lineWidthRef: u,
1565
+ downgradeRef: tn,
1566
+ settings: n,
1567
+ canvasMode: ne,
1568
+ downgradeOffer: kt,
1569
+ runSlice: Qt,
1570
+ ensurePlateToolpaths: Xt,
1571
+ buildPlateToolpath: Wt,
1572
+ applyViewColors: Mt,
1573
+ disposePlateToolpath: Ht,
1574
+ setStats: O,
1575
+ setOverBed: ve,
1576
+ setLayerCount: Z,
1577
+ setSegCount: z,
1578
+ setColorRange: Xe,
1579
+ setRoleLegend: me,
1580
+ setGcodeUrl: le,
1581
+ setLayerLo: t,
1582
+ setLayerHi: R,
1583
+ setCanvasMode: Me,
1584
+ setSlicedPlateCount: qe,
1585
+ setSliceMenu: ce,
1586
+ setError: V,
1587
+ setSliceNotice: st,
1588
+ setDowngradeOffer: Et,
1589
+ setSlicing: Ce,
1590
+ setProgress: i,
1591
+ setPlateCount: we,
1592
+ setSelectedPlate: ae
1593
+ }), { onFiles: pn, removeObject: Ct, onDrop: un, onDragOver: mn, onDragLeave: fn } = Nr({
1594
+ apiRef: r,
1595
+ objectsRef: d,
1596
+ layersDataRef: w,
1597
+ segDataRef: T,
1598
+ plateResultsRef: E,
1599
+ plateOffsetsRef: k,
1600
+ clearToolpaths: qt,
1601
+ refreshSlicedCount: on,
1602
+ dragOver: Ne,
1603
+ setError: V,
1604
+ setTriWarn: pe,
1605
+ setProgress: i,
1606
+ setStats: O,
1607
+ setOverBed: ve,
1608
+ setLayerCount: Z,
1609
+ setSegCount: z,
1610
+ setColorRange: Xe,
1611
+ setSliceNotice: st,
1612
+ setDowngradeOffer: Et,
1613
+ setGcodeUrl: le,
1614
+ setCanvasMode: Me,
1615
+ setObjects: Y,
1616
+ setDragOver: Pe
1617
+ });
1618
+ Ve(() => {
1619
+ if (!ye || !de.length || !E.current[j.current]) return;
1620
+ clearTimeout(be.current);
1621
+ const o = () => {
1622
+ if (en.current) {
1623
+ jt(), be.current = setTimeout(o, 300);
1624
+ return;
1625
+ }
1626
+ it("current");
1627
+ };
1628
+ return be.current = setTimeout(o, 800), () => clearTimeout(be.current);
1629
+ }, [n, ye]);
1630
+ function hn(o, x) {
1631
+ if (!(o > 0) || !(x > 0)) return;
1632
+ const Q = Je(n, "printable_area"), he = Array.isArray(Q) && Q.length >= 3, Be = he ? Math.min(...Q.map((Qe) => Qe[0])) : 0, at = he ? Math.min(...Q.map((Qe) => Qe[1])) : 0;
1633
+ a((Qe) => ({ ...Qe, printable_area: [[Be, at], [Be + o, at], [Be + o, at + x], [Be, at + x]] }));
1634
+ }
1635
+ function gn(o, x) {
1636
+ var Q;
1637
+ (Q = r.current) == null || Q.setObjectExtruder(o, x), Y(d.current.map((he) => ({ id: he.id, name: he.name, extruder: he.extruder, visible: he.visible !== !1 })));
1638
+ }
1639
+ function He(o, x) {
1640
+ const Q = Math.max(0, Ee - 1);
1641
+ if (o = Math.max(0, Math.min(Q, o)), x = Math.max(0, Math.min(Q, x)), o > x) {
1642
+ const he = o;
1643
+ o = x, x = he;
1644
+ }
1645
+ te.current = o, I.current = x, t(o), R(x), Yt();
1646
+ }
1647
+ function bn(o) {
1648
+ const x = parseInt(o.target.value, 10);
1649
+ F ? He(x, x) : He(x, I.current);
1650
+ }
1651
+ function xn(o) {
1652
+ const x = parseInt(o.target.value, 10);
1653
+ He(F ? x : te.current, x);
1654
+ }
1655
+ function St() {
1656
+ const o = !F;
1657
+ re(o), o && He(I.current, I.current);
1658
+ }
1659
+ function yn(o) {
1660
+ const x = o.target.value;
1661
+ tt(x), L.current = x, Mt();
1662
+ }
1663
+ function Rt(o) {
1664
+ const x = o.target.checked;
1665
+ _e(x), ee.current = x;
1666
+ for (const Q of Object.values(P.current)) Q.ctl.setTravelVisible(x);
1667
+ }
1668
+ function vn(o) {
1669
+ const x = o.target.checked;
1670
+ a((Q) => ({ ...Q, enable_support: x }));
1671
+ }
1672
+ const wn = !!Je(n, "enable_support"), kn = (((Pt = Bt.support_style) == null ? void 0 : Pt.enum_values) ?? []).map((o, x) => {
1673
+ var Q, he;
1674
+ return { value: o, label: ((he = (Q = Bt.support_style) == null ? void 0 : Q.enum_labels) == null ? void 0 : he[x]) ?? o };
1675
+ }), En = String(Je(n, "support_style") ?? "default"), [ct, Mn] = $(!1), dt = Number(Je(n, "support_threshold_angle")) || 30;
1676
+ Ve(() => {
1677
+ var o;
1678
+ (o = r.current) == null || o.setOverhang(ct && ne === "prepare" ? dt : null);
1679
+ }, [ct, dt, ne, de.length]);
1680
+ function pt() {
1681
+ Y(d.current.map((o) => ({ id: o.id, name: o.name, extruder: o.extruder, visible: o.visible !== !1 })));
1682
+ }
1683
+ function jn(o, x) {
1684
+ Oe((Q) => {
1685
+ var Be;
1686
+ const he = [...Q];
1687
+ return he[o] = x, Ze.current = he, (Be = r.current) == null || Be.recolorObjects(), he;
1688
+ });
1689
+ }
1690
+ function Cn() {
1691
+ Oe((o) => {
1692
+ if (o.length >= 4) return o;
1693
+ const x = ["#e0473b", "#3bb0e0", "#7ad14a"], Q = [...o, x[o.length - 1] || "#888888"];
1694
+ return Ze.current = Q, Q;
1695
+ });
1696
+ }
1697
+ function Sn() {
1698
+ Oe((o) => {
1699
+ var Q;
1700
+ if (o.length <= 1) return o;
1701
+ const x = o.slice(0, -1);
1702
+ return Ze.current = x, d.current.forEach((he) => {
1703
+ var Be;
1704
+ (he.extruder || 1) > x.length && ((Be = r.current) == null || Be.setObjectExtruder(he.id, x.length));
1705
+ }), (Q = r.current) == null || Q.recolorObjects(), pt(), x;
1706
+ });
1707
+ }
1708
+ function Rn(o) {
1709
+ var Q;
1710
+ const x = d.current.find((he) => he.id === o);
1711
+ (Q = r.current) == null || Q.setObjectVisible(o, (x == null ? void 0 : x.visible) === !1), pt();
1712
+ }
1713
+ function Nt() {
1714
+ Ke(xe === "off" ? "enforcer" : "off");
1715
+ }
1716
+ const {
1717
+ duplicateSelected: ut,
1718
+ copySelected: At,
1719
+ pasteClipboard: _t,
1720
+ deleteSelected: mt,
1721
+ deleteAllObjects: Nn,
1722
+ splitSelected: ft,
1723
+ setGizmo: An
1724
+ } = _r({
1725
+ apiRef: r,
1726
+ objectsRef: d,
1727
+ clipboardRef: B,
1728
+ paintModeRef: _,
1729
+ setPaintMode: Ke,
1730
+ removeObject: Ct,
1731
+ refreshObjects: pt,
1732
+ setError: V,
1733
+ setSliceNotice: st
1734
+ }), _n = wr({
1735
+ add: () => {
1736
+ var o;
1737
+ return (o = Ae.current) == null ? void 0 : o.click();
1738
+ },
1739
+ remove: mt,
1740
+ removeAll: Nn,
1741
+ duplicate: ut,
1742
+ split: ft,
1743
+ placeOnBed: () => {
1744
+ var o;
1745
+ return (o = r.current) == null ? void 0 : o.placeOnBed();
1746
+ },
1747
+ objectCount: () => de.length
1748
+ });
1749
+ M.current = kr({
1750
+ slicing: je,
1751
+ isPreview: () => A.current === "preview",
1752
+ slice: it,
1753
+ copy: At,
1754
+ paste: _t,
1755
+ remove: mt,
1756
+ duplicate: ut,
1757
+ stepLayer: (o) => {
1758
+ const x = I.current + o;
1759
+ He(F ? x : te.current, x);
1760
+ },
1761
+ toggleSingleLayer: St,
1762
+ toggleTravel: () => Rt({ target: { checked: !ee.current } }),
1763
+ zoomAll: () => {
1764
+ var o;
1765
+ return (o = r.current) == null ? void 0 : o.frame();
1766
+ },
1767
+ zoomBed: () => {
1768
+ var o;
1769
+ return (o = r.current) == null ? void 0 : o.frameBed();
1770
+ },
1771
+ leavePreview: () => Me("prepare"),
1772
+ setGizmo: An,
1773
+ cancelTool: () => {
1774
+ var o;
1775
+ _.current !== "off" && Ke("off"), (o = r.current) == null || o.detachTransform();
1776
+ },
1777
+ rotateSelected: (o) => {
1778
+ var x;
1779
+ return (x = r.current) == null ? void 0 : x.rotateSelectedY(o);
1780
+ },
1781
+ nudgeSelected: (o, x) => {
1782
+ var Q;
1783
+ return (Q = r.current) == null ? void 0 : Q.nudgeSelected(o, x);
1784
+ },
1785
+ toggleHelp: () => ze((o) => !o)
1786
+ });
1787
+ const zn = $e.nozzle_diameter || Je(n, "nozzle_diameter") || "0.4";
1788
+ (Ot = (Lt = ot.current).invalidate) == null || Ot.call(Lt);
1789
+ const zt = Ee > 0 && /* @__PURE__ */ e.createElement(
1790
+ Zr,
1791
+ {
1792
+ viewType: et,
1793
+ onViewType: yn,
1794
+ layerCount: Ee,
1795
+ layerLo: rt,
1796
+ layerHi: s,
1797
+ segCount: q,
1798
+ singleLayer: F,
1799
+ onLayerLo: bn,
1800
+ onLayerHi: xn,
1801
+ onToggleSingle: St,
1802
+ showTravel: Le,
1803
+ onToggleTravel: Rt,
1804
+ colorRange: nt,
1805
+ roleLegend: U
1806
+ }
1807
+ ), Pn = /* @__PURE__ */ e.createElement(Fr, { stats: p, overBed: ue }), ht = bt.map((o) => o.toUpperCase()).join(" · ");
1808
+ return /* @__PURE__ */ e.createElement(Yn, { css: Jn }, /* @__PURE__ */ e.createElement("div", { className: "app-shell" }, /* @__PURE__ */ e.createElement("input", { ref: Ae, type: "file", accept: bt.map((o) => "." + o).join(","), multiple: !0, onChange: pn, title: `${ht} (multiple files allowed)`, "data-testid": "stl-input", style: { display: "none" } }), /* @__PURE__ */ e.createElement(
1809
+ zr,
1810
+ {
1811
+ showTabs: y,
1812
+ canvasMode: ne,
1813
+ onCanvasMode: Me,
1814
+ previewEnabled: Ee > 0,
1815
+ onOpen: () => {
1816
+ var o;
1817
+ return (o = Ae.current) == null ? void 0 : o.click();
1818
+ }
1819
+ }
1820
+ ), /* @__PURE__ */ e.createElement("div", { className: "app-body" }, y && ne === "prepare" && /* @__PURE__ */ e.createElement(
1821
+ Pr,
1822
+ {
1823
+ gizmoMode: D,
1824
+ paintMode: xe,
1825
+ onGizmo: (o) => {
1826
+ var x;
1827
+ Ke("off"), (x = r.current) == null || x.setMode(o);
1828
+ },
1829
+ onTogglePaint: Nt
1830
+ }
1831
+ ), /* @__PURE__ */ e.createElement("div", { className: "viewport-col" }, /* @__PURE__ */ e.createElement(
1832
+ "div",
1833
+ {
1834
+ className: (y ? "vp-canvas" : "vp-canvas fail") + (Ne ? " drag-over" : ""),
1835
+ ref: $t,
1836
+ onDrop: un,
1837
+ onDragOver: mn,
1838
+ onDragLeave: fn,
1839
+ "data-testid": "drop-zone"
1840
+ },
1841
+ !y && /* @__PURE__ */ e.createElement("div", { className: "vp-fallback" }, "⚠ ", oe),
1842
+ y && de.length === 0 && ne !== "preview" && /* @__PURE__ */ e.createElement("div", { className: "empty-hint", "data-testid": "empty-hint" }, /* @__PURE__ */ e.createElement("div", { className: "eh-icon" }, "📦"), /* @__PURE__ */ e.createElement("div", { className: "eh-title" }, "Drag in a file or pick one"), /* @__PURE__ */ e.createElement("div", { className: "eh-sub" }, ht), /* @__PURE__ */ e.createElement("button", { className: "eh-btn", onClick: () => {
1843
+ var o;
1844
+ return (o = Ae.current) == null ? void 0 : o.click();
1845
+ }, "data-testid": "empty-pick", title: `Pick a ${ht} file (multiple allowed)` }, "Choose file")),
1846
+ Ne && /* @__PURE__ */ e.createElement("div", { className: "drop-overlay", "data-testid": "drop-overlay" }, "Drop here (STL/OBJ/3MF/AMF/PLY)"),
1847
+ Ie && /* @__PURE__ */ e.createElement(
1848
+ Or,
1849
+ {
1850
+ menu: Ie,
1851
+ onClose: () => We(null),
1852
+ canPaste: !!B.current,
1853
+ actions: {
1854
+ duplicate: ut,
1855
+ copy: At,
1856
+ split: ft,
1857
+ placeOnBed: () => {
1858
+ var o;
1859
+ return (o = r.current) == null ? void 0 : o.placeOnBed();
1860
+ },
1861
+ remove: mt,
1862
+ openFile: () => {
1863
+ var o;
1864
+ return (o = Ae.current) == null ? void 0 : o.click();
1865
+ },
1866
+ paste: _t,
1867
+ zoomAll: () => {
1868
+ var o;
1869
+ return (o = r.current) == null ? void 0 : o.frame();
1870
+ },
1871
+ zoomBed: () => {
1872
+ var o;
1873
+ return (o = r.current) == null ? void 0 : o.frameBed();
1874
+ }
1875
+ }
1876
+ }
1877
+ ),
1878
+ ke && /* @__PURE__ */ e.createElement(Tr, { onClose: () => ze(!1) })
1879
+ ), y && ne === "prepare" && /* @__PURE__ */ e.createElement(Lr, { tools: _n }), y && ne === "prepare" && xe !== "off" && /* @__PURE__ */ e.createElement(
1880
+ Br,
1881
+ {
1882
+ paintMode: xe,
1883
+ onPaintMode: Ke,
1884
+ onClear: rn,
1885
+ brushRadius: Ue,
1886
+ paintCounts: Ut,
1887
+ onBrushRadius: (o) => {
1888
+ yt(o), G.current = o;
1889
+ }
1890
+ }
1891
+ ), y && ne === "preview" && p && /* @__PURE__ */ e.createElement("div", { className: "stats-card", "data-testid": "slice-stats" }, Pn), y && /* @__PURE__ */ e.createElement(
1892
+ Dr,
1893
+ {
1894
+ plateCount: ie,
1895
+ selectedPlate: se,
1896
+ maxPlates: Vt,
1897
+ onSelect: dn,
1898
+ onAdd: sn,
1899
+ onDelete: cn
1900
+ }
1901
+ ), y && /* @__PURE__ */ e.createElement("div", { className: "vp-status", "data-testid": "vp-status" }, oe)), y && /* @__PURE__ */ e.createElement("aside", { className: "sidebar" }, /* @__PURE__ */ e.createElement("div", { className: "sidebar-scroll" }, /* @__PURE__ */ e.createElement(
1902
+ Gr,
1903
+ {
1904
+ bedWidth: $e.bed_width,
1905
+ bedDepth: $e.bed_depth,
1906
+ nozzleDia: zn,
1907
+ onBedSize: hn,
1908
+ settings: n,
1909
+ setSettings: a,
1910
+ motionPanel: l
1911
+ }
1912
+ ), /* @__PURE__ */ e.createElement(Ur, { colors: De, onColor: jn, onAdd: Cn, onRemove: Sn }), de.length > 0 && /* @__PURE__ */ e.createElement(
1913
+ $r,
1914
+ {
1915
+ objects: de,
1916
+ extruderColors: De,
1917
+ onToggleVisible: Rn,
1918
+ onExtruder: gn,
1919
+ onSplit: (o) => {
1920
+ var x;
1921
+ (x = r.current) == null || x.selectObject(o), ft();
1922
+ },
1923
+ onRemove: Ct,
1924
+ supportOn: wn,
1925
+ onToggleSupport: vn,
1926
+ overhangOn: ct,
1927
+ onToggleOverhang: (o) => Mn(o.target.checked),
1928
+ overhangAngle: dt,
1929
+ paintMode: xe,
1930
+ onTogglePaint: Nt,
1931
+ supportStyle: En,
1932
+ supportStyles: kn,
1933
+ onSupportStyle: (o) => a((x) => ({ ...x, support_style: o })),
1934
+ wipeTowerReal: Te,
1935
+ onToggleWipeTower: (o) => X(o.target.checked)
1936
+ }
1937
+ ), C && /* @__PURE__ */ e.createElement("div", { className: "slice-warn side-warn" }, "⚠ ", C), wt && /* @__PURE__ */ e.createElement("div", { className: "slice-warn side-warn", "data-testid": "slice-notice" }, "ℹ ", wt), S && /* @__PURE__ */ e.createElement("div", { className: "slice-err side-warn", "data-testid": "slice-err" }, S), kt && /* @__PURE__ */ e.createElement("button", { className: "slice-btn", "data-testid": "downgrade-retry", onClick: ln, title: "Simplify the infill and lower its density to reduce memory pressure, then retry" }, "Simplified retry (simple infill, economy mode)"), ne === "preview" && zt && /* @__PURE__ */ e.createElement("section", { className: "side-card" }, /* @__PURE__ */ e.createElement("div", { className: "sc-head" }, "🎚 Preview"), zt), /* @__PURE__ */ e.createElement("section", { className: "side-card process-card", "data-testid": "process-section" }, /* @__PURE__ */ e.createElement("div", { className: "sc-head" }, "⚙ Process"), c)), /* @__PURE__ */ e.createElement(
1938
+ Hr,
1939
+ {
1940
+ autoSlice: ye,
1941
+ onAutoSlice: Re,
1942
+ slicing: je,
1943
+ progress: Se,
1944
+ plateCount: ie,
1945
+ selectedPlate: se,
1946
+ sliceMenuOpen: J,
1947
+ onSliceMenu: () => ce((o) => !o),
1948
+ slicedPlateCount: Fe,
1949
+ canSlice: de.length > 0,
1950
+ onSlice: it,
1951
+ onCancel: jt,
1952
+ onExportAll: an,
1953
+ gcodeUrl: fe
1954
+ }
1955
+ )))));
1162
1956
  }
1163
1957
  export {
1164
- Wa as default
1958
+ no as default
1165
1959
  };