three-slicer 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +342 -24
- package/components/README.md +1 -1
- package/components/dist/SettingsPanel.js +3 -4
- package/engine/index.d.ts +3 -1
- package/engine/src/data.js +12 -0
- package/engine/src/settings.js +1 -1
- package/engine/src/slicer.worker.js +2 -1
- package/engine/src/slicer_core.js +0 -0
- package/engine/src/slicer_core.mt.js +0 -0
- package/engine/src/toggle_eval.js +1 -2
- package/package.json +54 -26
- package/types/components.d.ts +19 -0
- package/types/data/config-schema.json.d.ts +55 -0
- package/types/data/invalidation-map.json.d.ts +15 -0
- package/types/data/toggle-rules.json.d.ts +20 -0
- package/types/data/ui-tree.json.d.ts +19 -0
- package/types/data.d.ts +17 -0
- package/types/gen_settings_types.mjs +70 -0
- package/types/settings-keys.d.ts +1704 -0
- package/types/settings.d.ts +19 -0
- package/types/toggle.d.ts +22 -0
- package/types/viewer-loaders.d.ts +33 -0
- package/types/viewer-toolpath.d.ts +105 -0
- package/types/viewer.d.ts +15 -0
- package/types/wasm.d.ts +5 -0
- package/types/worker.d.ts +3 -0
- package/viewer/README.md +1 -1
- package/viewer/dist/Viewport.js +674 -617
- package/viewer/dist/model_loaders.js +220 -108
- package/viewer/styles.css +2 -0
- package/data/config-schema-builddump.json +0 -12921
- package/viewer/src/Viewport.jsx +0 -1353
- package/viewer/src/assets/add.svg +0 -1
- package/viewer/src/assets/arrange.svg +0 -1
- package/viewer/src/assets/boolean.svg +0 -1
- package/viewer/src/assets/cut.svg +0 -1
- package/viewer/src/assets/delete.svg +0 -1
- package/viewer/src/assets/deleteall.svg +0 -1
- package/viewer/src/assets/duplicate.svg +0 -1
- package/viewer/src/assets/measure.svg +0 -1
- package/viewer/src/assets/mmu.svg +0 -1
- package/viewer/src/assets/move.svg +0 -1
- package/viewer/src/assets/negative.svg +0 -1
- package/viewer/src/assets/onbed.svg +0 -1
- package/viewer/src/assets/open.svg +0 -1
- package/viewer/src/assets/orient.svg +0 -1
- package/viewer/src/assets/paint.svg +0 -1
- package/viewer/src/assets/rotate.svg +0 -1
- package/viewer/src/assets/scale.svg +0 -1
- package/viewer/src/assets/seam.svg +0 -1
- package/viewer/src/assets/split.svg +0 -1
- package/viewer/src/assets/textmark.svg +0 -1
- package/viewer/src/assets/varlayer.svg +0 -1
- package/viewer/src/icons.js +0 -23
- package/viewer/src/make_worker.js +0 -9
- package/viewer/src/model_loaders.js +0 -178
- package/viewer/src/shadow_host.jsx +0 -30
- package/viewer/src/toolpath_gpu.js +0 -430
package/viewer/dist/Viewport.js
CHANGED
|
@@ -1,408 +1,408 @@
|
|
|
1
|
-
import n, { useRef as g, useState as x, useLayoutEffect as
|
|
2
|
-
import * as
|
|
3
|
-
import { OrbitControls as
|
|
4
|
-
import { TransformControls as
|
|
5
|
-
import { deriveKernelParams as
|
|
6
|
-
import { makeSlicerWorker as
|
|
7
|
-
import { createPortal as
|
|
8
|
-
import { VIEW_TYPES as
|
|
9
|
-
import {
|
|
10
|
-
function
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
return
|
|
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;
|
|
14
14
|
});
|
|
15
|
-
return
|
|
16
|
-
const
|
|
17
|
-
let w =
|
|
18
|
-
w || (w = document.createElement("style"), w.setAttribute("data-ts-css", ""),
|
|
19
|
-
}, [
|
|
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));
|
|
20
20
|
}
|
|
21
|
-
const ia = ":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}.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}", 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="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>'), da = "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>'), pa = "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>'), ua = "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>'), 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="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>'), fa = "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>'), ba = "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>'), ha = "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>'), ga = "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>'), xa = "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>'), va = "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>'), 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="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>'), ya = "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>'), ka = "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>'), Ea = "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>'), Ma = "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>'), Ca = "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>'), 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="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>'), $n = "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>'), Na = "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>'), _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="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
|
|
23
|
-
const
|
|
24
|
-
for (let y = 0; y <
|
|
25
|
-
|
|
26
|
-
let
|
|
27
|
-
for (let y = 0; y <
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
for (let y = 0; y <
|
|
31
|
-
|
|
32
|
-
return { localPos:
|
|
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 } };
|
|
33
33
|
}
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
return
|
|
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
37
|
};
|
|
38
|
-
function
|
|
39
|
-
}, processPanel:
|
|
40
|
-
var
|
|
41
|
-
const
|
|
42
|
-
function
|
|
43
|
-
const t =
|
|
44
|
-
return { x: e % t *
|
|
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) };
|
|
45
45
|
}
|
|
46
|
-
function
|
|
47
|
-
const r =
|
|
48
|
-
return Math.max(0, Math.min(r - 1,
|
|
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));
|
|
49
49
|
}
|
|
50
|
-
const [
|
|
51
|
-
|
|
52
|
-
const e =
|
|
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
53
|
if (!e) return;
|
|
54
54
|
const t = document.createElement("canvas");
|
|
55
55
|
if (!(t.getContext("webgl2") || t.getContext("webgl"))) {
|
|
56
|
-
|
|
56
|
+
Qn(!1), ce("이 환경에서 WebGL 컨텍스트를 만들 수 없습니다 (헤드리스/GPU 미지원).");
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
59
|
let r = e.clientWidth || 800, a = e.clientHeight || 480;
|
|
60
|
-
const l = new
|
|
60
|
+
const l = new h.WebGLRenderer({ antialias: !0 });
|
|
61
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
|
|
63
|
-
s.position.set(210, 180, 260),
|
|
64
|
-
const b = new
|
|
65
|
-
b.position.set(120, 220, 160),
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
const
|
|
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
75
|
if (!o) return;
|
|
76
|
-
o.updateMatrixWorld(!0),
|
|
77
|
-
const i =
|
|
76
|
+
o.updateMatrixWorld(!0), B.setFromObject(o);
|
|
77
|
+
const i = B.min.y;
|
|
78
78
|
Number.isFinite(i) && Math.abs(i) > 1e-4 && (o.position.y -= i, o.updateMatrixWorld(!0));
|
|
79
79
|
};
|
|
80
|
-
|
|
81
|
-
var i,
|
|
82
|
-
|
|
83
|
-
}),
|
|
84
|
-
let
|
|
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
85
|
const M = () => {
|
|
86
|
-
|
|
86
|
+
V = !0;
|
|
87
87
|
};
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
let
|
|
91
|
-
const
|
|
92
|
-
for (const o of
|
|
93
|
-
},
|
|
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
94
|
const i = l.domElement.getBoundingClientRect();
|
|
95
|
-
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
const o =
|
|
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
99
|
return o.length ? o[0].object : null;
|
|
100
|
-
},
|
|
101
|
-
|
|
102
|
-
const o =
|
|
100
|
+
}, na = () => {
|
|
101
|
+
_.setFromCamera(T, s);
|
|
102
|
+
const o = _.intersectObjects(Wt(), !1);
|
|
103
103
|
return o.length ? o[0] : null;
|
|
104
|
-
},
|
|
105
|
-
var
|
|
106
|
-
const i =
|
|
104
|
+
}, On = (o) => {
|
|
105
|
+
var q;
|
|
106
|
+
const i = Et.current;
|
|
107
107
|
if (!i) return;
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
if (!
|
|
111
|
-
const E = (
|
|
112
|
-
(
|
|
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({
|
|
113
113
|
cmd: "paint",
|
|
114
|
-
facet:
|
|
115
|
-
hx:
|
|
116
|
-
hy:
|
|
117
|
-
hz:
|
|
114
|
+
facet: m.faceIndex,
|
|
115
|
+
hx: N[0],
|
|
116
|
+
hy: N[1],
|
|
117
|
+
hz: N[2],
|
|
118
118
|
cx: k[0],
|
|
119
119
|
cy: k[1],
|
|
120
120
|
cz: k[2],
|
|
121
|
-
radius:
|
|
122
|
-
enforcer:
|
|
121
|
+
radius: Qe.current,
|
|
122
|
+
enforcer: W.current === "enforcer"
|
|
123
123
|
});
|
|
124
|
-
},
|
|
124
|
+
}, Zn = () => {
|
|
125
125
|
const o = l.domElement;
|
|
126
|
-
o.style.cursor =
|
|
127
|
-
},
|
|
128
|
-
if (
|
|
129
|
-
if (
|
|
130
|
-
|
|
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);
|
|
131
131
|
return;
|
|
132
132
|
}
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
const i =
|
|
136
|
-
i !==
|
|
137
|
-
},
|
|
138
|
-
if (o.button !== 0 ||
|
|
139
|
-
if (
|
|
140
|
-
|
|
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);
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
const i =
|
|
146
|
-
i ? (
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
},
|
|
150
|
-
o.button !== 0 ||
|
|
151
|
-
},
|
|
152
|
-
if (
|
|
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
153
|
o.preventDefault(), o.stopPropagation();
|
|
154
|
-
const i = Math.min(15, Math.max(1,
|
|
155
|
-
|
|
154
|
+
const i = Math.min(15, Math.max(1, Qe.current + (o.deltaY < 0 ? 0.5 : -0.5)));
|
|
155
|
+
Qe.current = i, mn(i);
|
|
156
156
|
};
|
|
157
|
-
let
|
|
158
|
-
const
|
|
159
|
-
o.button === 2 && (
|
|
160
|
-
},
|
|
161
|
-
if (o.preventDefault(),
|
|
162
|
-
|
|
163
|
-
const i =
|
|
164
|
-
i ? (
|
|
165
|
-
const
|
|
166
|
-
D({ x: o.clientX -
|
|
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 });
|
|
167
167
|
};
|
|
168
|
-
l.domElement.addEventListener("pointermove",
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
},
|
|
172
|
-
const o = w.current, i = new
|
|
173
|
-
o.length ? o.forEach((k) => i.expandByObject(k.mesh)) : i.setFromCenterAndSize(new
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
},
|
|
177
|
-
const k = new
|
|
178
|
-
k.setAttribute("position", new
|
|
179
|
-
const
|
|
180
|
-
if (
|
|
181
|
-
|
|
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);
|
|
182
182
|
else {
|
|
183
|
-
const
|
|
184
|
-
w.current.length === 0 && (
|
|
185
|
-
const
|
|
186
|
-
|
|
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;
|
|
187
187
|
}
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
return w.current.push({ id:
|
|
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 };
|
|
191
191
|
};
|
|
192
|
-
|
|
193
|
-
setMode:
|
|
194
|
-
refreshCursor: () =>
|
|
192
|
+
d.current = {
|
|
193
|
+
setMode: ra,
|
|
194
|
+
refreshCursor: () => Zn(),
|
|
195
195
|
// 페인트 모드 전환 시 커서 힌트 동기화
|
|
196
196
|
detachTransform: () => {
|
|
197
|
-
|
|
197
|
+
z = null, f.detach(), ye();
|
|
198
198
|
},
|
|
199
199
|
// 20단계: 페인팅 진입 시 기즈모 해제
|
|
200
|
-
addObject: (o, i) =>
|
|
200
|
+
addObject: (o, i) => Fn(o, Ba(i).localPos),
|
|
201
201
|
// ---- 단축키 지원 (복제/넛지/회전/줌투) ----
|
|
202
202
|
getSnapshot: (o) => {
|
|
203
|
-
const i = w.current.find((
|
|
203
|
+
const i = w.current.find((m) => m.id === o);
|
|
204
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;
|
|
205
205
|
},
|
|
206
206
|
// keepPos=true 면 스냅샷의 원래 위치를 유지(분리). false(기본)면 배치 커서로 옆에 놓는다(복제/붙여넣기).
|
|
207
|
-
spawnSnapshot: (o, i = !1) => o ?
|
|
207
|
+
spawnSnapshot: (o, i = !1) => o ? Fn(o.name, o.localPos, o.rot, o.scale, i && o.pos || null) : null,
|
|
208
208
|
nudgeSelected: (o, i) => {
|
|
209
|
-
|
|
209
|
+
z && (z.position.x += o, z.position.z += i);
|
|
210
210
|
},
|
|
211
211
|
rotateSelectedY: (o) => {
|
|
212
|
-
|
|
212
|
+
z && (z.rotation.y += o);
|
|
213
213
|
},
|
|
214
|
-
frame: () =>
|
|
214
|
+
frame: () => Xt(),
|
|
215
215
|
// Z: 전체 오브젝트로 줌
|
|
216
216
|
frameBed: () => {
|
|
217
|
-
const o =
|
|
218
|
-
|
|
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();
|
|
219
219
|
},
|
|
220
220
|
removeObject: (o) => {
|
|
221
|
-
const i = w.current,
|
|
222
|
-
if (
|
|
223
|
-
const E = i[
|
|
224
|
-
|
|
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());
|
|
225
225
|
},
|
|
226
226
|
// MM: 익스트루더 오름차순으로 삼각형 정렬 병합 → 그룹0(ext1) 뒤 그룹1(ext2), split 반환.
|
|
227
|
-
platePos: (o) =>
|
|
227
|
+
platePos: (o) => _t(o),
|
|
228
228
|
// 플레이트 i 의 three (x,z) 오프셋
|
|
229
229
|
plateOfObject: (o) => {
|
|
230
230
|
o.mesh.updateMatrixWorld(!0);
|
|
231
|
-
const i = new
|
|
232
|
-
return
|
|
231
|
+
const i = new h.Vector3().setFromMatrixPosition(o.mesh.matrixWorld);
|
|
232
|
+
return on(i.x, i.z);
|
|
233
233
|
},
|
|
234
234
|
// plateIdx!=null 이면 그 플레이트 소속 오브젝트만 + 좌표를 플레이트 로컬(three-x -= PX)로 변환(28단계 계약 유지).
|
|
235
235
|
buildMergedSTL: (o = null) => {
|
|
236
236
|
let i = w.current.filter((v) => v.visible !== !1);
|
|
237
237
|
if (o != null && (i = i.filter((v) => {
|
|
238
238
|
v.mesh.updateMatrixWorld(!0);
|
|
239
|
-
const
|
|
240
|
-
return
|
|
239
|
+
const $ = new h.Vector3().setFromMatrixPosition(v.mesh.matrixWorld);
|
|
240
|
+
return on($.x, $.z) === o;
|
|
241
241
|
})), !i.length) return null;
|
|
242
|
-
const
|
|
243
|
-
let
|
|
244
|
-
for (const v of
|
|
245
|
-
(v.extruder || 1) >= 2 &&
|
|
246
|
-
const
|
|
247
|
-
for (let
|
|
248
|
-
|
|
249
|
-
|
|
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;
|
|
250
250
|
}
|
|
251
|
-
let
|
|
251
|
+
let le = 1e18, pe = 1e18, se = -1e18, je = -1e18;
|
|
252
252
|
for (let v = 0; v < k.length; v += 3)
|
|
253
|
-
k[v] <
|
|
254
|
-
const
|
|
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
255
|
for (let v = 0; v < k.length; v += 3)
|
|
256
|
-
k[v] -=
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
let
|
|
260
|
-
for (let v = 0; v <
|
|
261
|
-
|
|
262
|
-
for (let
|
|
263
|
-
|
|
264
|
-
|
|
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;
|
|
265
265
|
}
|
|
266
|
-
return { buf:
|
|
266
|
+
return { buf: R, split: I, extruders: E.size, offX: vt, offZ: Z };
|
|
267
267
|
},
|
|
268
268
|
setObjectExtruder: (o, i) => {
|
|
269
|
-
const
|
|
270
|
-
if (
|
|
271
|
-
|
|
272
|
-
const E =
|
|
273
|
-
E &&
|
|
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);
|
|
274
274
|
}
|
|
275
275
|
},
|
|
276
276
|
setObjectVisible: (o, i) => {
|
|
277
|
-
const
|
|
278
|
-
|
|
277
|
+
const m = w.current.find((E) => E.id === o);
|
|
278
|
+
m && (m.visible = i, m.mesh.visible = i);
|
|
279
279
|
},
|
|
280
280
|
// 27단계: 출력 토글(눈알)
|
|
281
281
|
recolorObjects: () => {
|
|
282
282
|
for (const o of w.current) {
|
|
283
|
-
const i =
|
|
283
|
+
const i = Re.current[(o.extruder || 1) - 1];
|
|
284
284
|
i && o.mesh.material.color.set(i);
|
|
285
285
|
}
|
|
286
286
|
},
|
|
287
287
|
// 필라멘트 색 변경 반영
|
|
288
288
|
selectedObjectId: () => {
|
|
289
289
|
var o;
|
|
290
|
-
return
|
|
290
|
+
return z ? ((o = w.current.find((i) => i.mesh === z)) == null ? void 0 : o.id) ?? null : null;
|
|
291
291
|
},
|
|
292
292
|
// 27단계: 뷰포트 툴바 "선택 삭제"
|
|
293
293
|
selectObject: (o) => {
|
|
294
|
-
const i = w.current.find((
|
|
295
|
-
i && (
|
|
294
|
+
const i = w.current.find((m) => m.id === o);
|
|
295
|
+
i && (z = i.mesh, f.attach(i.mesh), ye(), ce(ke()));
|
|
296
296
|
},
|
|
297
297
|
// 28단계 P1: 바닥 안착 — 로컬 지오메트리는 bakeLocal 이 minZ→0(로드 시 1회). 기즈모 Z이동 후 재안착.
|
|
298
298
|
// (원본 ensure_on_bed 의 싱킹 허용[allow_negative_z]은 범위 외 — 우리는 -min_z 안착만.)
|
|
299
299
|
placeOnBed: () => {
|
|
300
300
|
var o;
|
|
301
301
|
for (const i of w.current) i.mesh.position.y = 0;
|
|
302
|
-
|
|
302
|
+
z && ((o = f.update) == null || o.call(f));
|
|
303
303
|
},
|
|
304
304
|
onSliced: () => {
|
|
305
|
-
|
|
305
|
+
p.visible = !1, f.detach(), z = null, ye();
|
|
306
306
|
},
|
|
307
307
|
showObjects: () => {
|
|
308
|
-
|
|
308
|
+
p.visible = !0;
|
|
309
309
|
},
|
|
310
310
|
// 29단계-2: N 플레이트 렌더 — 각 플레이트 = 그리드+테두리, PX_i 오프셋, 선택 플레이트 테두리 하이라이트.
|
|
311
|
-
setPlates: (o, i,
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
for (const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const k =
|
|
318
|
-
for (let
|
|
319
|
-
for (let
|
|
320
|
-
const
|
|
321
|
-
const
|
|
322
|
-
return
|
|
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;
|
|
323
323
|
};
|
|
324
|
-
for (let
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
const v =
|
|
328
|
-
|
|
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: $ });
|
|
329
329
|
}
|
|
330
330
|
},
|
|
331
331
|
setBed: (o, i) => {
|
|
332
|
-
var
|
|
333
|
-
(
|
|
332
|
+
var m;
|
|
333
|
+
(m = d.current) == null || m.setPlates($e.current, o, i, re.current);
|
|
334
334
|
}
|
|
335
335
|
// 하위호환
|
|
336
336
|
};
|
|
337
|
-
const
|
|
337
|
+
const Gn = (o) => {
|
|
338
338
|
var i;
|
|
339
|
-
(i =
|
|
339
|
+
(i = Ye.current) == null || i.call(Ye, o), M();
|
|
340
340
|
};
|
|
341
|
-
window.addEventListener("keydown",
|
|
342
|
-
const
|
|
341
|
+
window.addEventListener("keydown", Gn);
|
|
342
|
+
const Dn = new ResizeObserver(() => {
|
|
343
343
|
r = e.clientWidth || r, a = e.clientHeight || a, s.aspect = r / a, s.updateProjectionMatrix(), l.setSize(r, a), M();
|
|
344
344
|
});
|
|
345
|
-
|
|
346
|
-
let
|
|
347
|
-
const
|
|
348
|
-
|
|
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));
|
|
349
349
|
};
|
|
350
|
-
return
|
|
351
|
-
cancelAnimationFrame(
|
|
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
352
|
o.geometry && o.geometry.dispose(), o.material && o.material.dispose();
|
|
353
353
|
}), l.dispose(), l.domElement.parentNode && l.domElement.parentNode.removeChild(l.domElement);
|
|
354
354
|
};
|
|
355
|
-
}, []),
|
|
356
|
-
|
|
357
|
-
}, []),
|
|
355
|
+
}, []), Be(() => () => {
|
|
356
|
+
S.current && (S.current.terminate(), S.current = null);
|
|
357
|
+
}, []), Be(() => {
|
|
358
358
|
try {
|
|
359
|
-
|
|
359
|
+
qe().postMessage({ cmd: "warmup" });
|
|
360
360
|
} catch {
|
|
361
361
|
}
|
|
362
362
|
}, []);
|
|
363
|
-
const
|
|
364
|
-
|
|
363
|
+
const J = Kt(P);
|
|
364
|
+
Be(() => {
|
|
365
365
|
var e;
|
|
366
|
-
(e =
|
|
367
|
-
}, [
|
|
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
368
|
var r;
|
|
369
|
-
|
|
370
|
-
const e =
|
|
369
|
+
fe.current = H;
|
|
370
|
+
const e = A.current;
|
|
371
371
|
if (!e.toolpathGroup) return;
|
|
372
|
-
const t =
|
|
373
|
-
e.toolpathGroup.visible = t, e.objectsGroup && (e.objectsGroup.visible = !t && w.current.length > 0), t && ((r =
|
|
374
|
-
}, [
|
|
375
|
-
function
|
|
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
376
|
var r;
|
|
377
377
|
const t = y.current[e];
|
|
378
|
-
t && (t.group.remove(t.ctl.mesh), t.group.remove(t.ctl.travLines), t.ctl.dispose(), (r =
|
|
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
379
|
}
|
|
380
|
-
function
|
|
381
|
-
for (const e of Object.keys(y.current))
|
|
382
|
-
|
|
380
|
+
function br() {
|
|
381
|
+
for (const e of Object.keys(y.current)) mt(Number(e));
|
|
382
|
+
G.current = null, ie.current = null;
|
|
383
383
|
}
|
|
384
|
-
function
|
|
385
|
-
const { toolpathGroup: r } =
|
|
386
|
-
if (!r || (
|
|
387
|
-
const a =
|
|
388
|
-
s.rotation.x = -Math.PI / 2, s.position.set(
|
|
389
|
-
const b =
|
|
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
390
|
l.setColors(b.color);
|
|
391
|
-
const
|
|
392
|
-
return y.current[e] =
|
|
391
|
+
const p = { group: s, ctl: l, seg: a, layers: t };
|
|
392
|
+
return y.current[e] = p, p;
|
|
393
393
|
}
|
|
394
|
-
function
|
|
395
|
-
for (const [e, t] of Object.entries(
|
|
394
|
+
function xr() {
|
|
395
|
+
for (const [e, t] of Object.entries(X.current)) {
|
|
396
396
|
const r = Number(e);
|
|
397
397
|
if (!t || t.error || !t.layers || !t.layers.length) continue;
|
|
398
398
|
const a = y.current[r];
|
|
399
|
-
(!a || a.layers !== t.layers) &&
|
|
399
|
+
(!a || a.layers !== t.layers) && Bt(r, t.layers);
|
|
400
400
|
}
|
|
401
401
|
}
|
|
402
|
-
function
|
|
402
|
+
function xn() {
|
|
403
403
|
const e = (r, a) => {
|
|
404
|
-
const l =
|
|
405
|
-
return Number.isFinite(
|
|
404
|
+
const l = wt(P, r), c = parseFloat(l);
|
|
405
|
+
return Number.isFinite(c) ? c : a;
|
|
406
406
|
}, t = e("outer_wall_speed", 60);
|
|
407
407
|
return {
|
|
408
408
|
speedByType: {
|
|
@@ -425,627 +425,684 @@ function Ia({ settings: L = {}, setSettings: $ = () => {
|
|
|
425
425
|
tempFirst: e("nozzle_temperature_initial_layer", e("nozzle_temperature", 210))
|
|
426
426
|
};
|
|
427
427
|
}
|
|
428
|
-
function
|
|
429
|
-
const e =
|
|
428
|
+
function It() {
|
|
429
|
+
const e = xn();
|
|
430
430
|
for (const t of Object.values(y.current)) {
|
|
431
|
-
const r =
|
|
432
|
-
t.ctl.setColors(r.color), t.ctl ===
|
|
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
433
|
}
|
|
434
434
|
}
|
|
435
|
-
function
|
|
436
|
-
const e =
|
|
435
|
+
function vr() {
|
|
436
|
+
const e = re.current, t = te.current || [];
|
|
437
437
|
if (!t.length) {
|
|
438
|
-
|
|
438
|
+
mt(e), Se(0), G.current = null, ie.current = null;
|
|
439
439
|
return;
|
|
440
440
|
}
|
|
441
|
-
const r =
|
|
441
|
+
const r = Bt(e, t);
|
|
442
442
|
if (!r) {
|
|
443
|
-
|
|
443
|
+
Se(0);
|
|
444
444
|
return;
|
|
445
445
|
}
|
|
446
|
-
|
|
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
447
|
}
|
|
448
|
-
function
|
|
448
|
+
function wr() {
|
|
449
449
|
var e;
|
|
450
|
-
(e =
|
|
450
|
+
(e = G.current) == null || e.setLayerRange(me.current, ne.current);
|
|
451
451
|
}
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
},
|
|
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)
|
|
457
|
+
try {
|
|
458
|
+
Atomics.store(e.cancel, 0, 1);
|
|
459
|
+
} catch {
|
|
460
|
+
e.cancel[0] = 1;
|
|
461
|
+
}
|
|
462
|
+
}, yr = (e, t) => {
|
|
455
463
|
const r = t > 2 ? (t - 2) / 2 : 0;
|
|
456
|
-
return r ? e <= r ? 0.
|
|
457
|
-
},
|
|
458
|
-
const t =
|
|
459
|
-
|
|
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(() => {
|
|
460
468
|
const r = t.arr[0];
|
|
461
|
-
|
|
469
|
+
_e(0.36 + 0.51 * Math.min(0.95, r / (3.5 * e)));
|
|
470
|
+
}, 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));
|
|
462
475
|
}, 150));
|
|
463
476
|
};
|
|
464
|
-
function
|
|
465
|
-
if (!
|
|
466
|
-
const e =
|
|
477
|
+
function qe() {
|
|
478
|
+
if (!S.current) {
|
|
479
|
+
const e = sa();
|
|
467
480
|
e.onmessage = (r) => {
|
|
468
|
-
var
|
|
469
|
-
const a = r.data, l =
|
|
481
|
+
var c, s, b, p, j;
|
|
482
|
+
const a = r.data, l = he.current;
|
|
470
483
|
if (a.type === "progress") {
|
|
471
|
-
const
|
|
472
|
-
|
|
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);
|
|
473
486
|
} else if (a.type === "supsab")
|
|
474
487
|
try {
|
|
475
|
-
|
|
476
|
-
} catch {
|
|
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);
|
|
477
491
|
}
|
|
478
492
|
else if (a.type === "layer") {
|
|
479
493
|
(b = l == null ? void 0 : l.kick) == null || b.call(l);
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
} else a.type === "done" ? (
|
|
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));
|
|
483
497
|
};
|
|
484
498
|
const t = (r) => {
|
|
485
499
|
var l;
|
|
486
|
-
|
|
487
|
-
|
|
500
|
+
if (S.current !== e) return;
|
|
501
|
+
we(), Le.current = null;
|
|
502
|
+
const a = he.current;
|
|
488
503
|
if (a) {
|
|
489
|
-
|
|
504
|
+
he.current = null, (l = a.stop) == null || l.call(a);
|
|
490
505
|
try {
|
|
491
506
|
e.terminate();
|
|
492
507
|
} catch {
|
|
493
508
|
}
|
|
494
|
-
|
|
509
|
+
S.current = null, a.reject(new Error(r));
|
|
495
510
|
} else
|
|
496
|
-
|
|
511
|
+
C(r), ge(!1);
|
|
497
512
|
};
|
|
498
|
-
e.onerror = (r) => t("Worker 종료(메모리 초과 추정): " + (r.message || "worker error")), e.onmessageerror = () => t("Worker 메시지 오류(구조화 복제 실패)"),
|
|
513
|
+
e.onerror = (r) => t("Worker 종료(메모리 초과 추정): " + (r.message || "worker error")), e.onmessageerror = () => t("Worker 메시지 오류(구조화 복제 실패)"), S.current = e, typeof window < "u" && (window.__vpWorker = e);
|
|
499
514
|
}
|
|
500
|
-
return
|
|
515
|
+
return S.current;
|
|
501
516
|
}
|
|
502
|
-
function
|
|
517
|
+
function wn(e) {
|
|
503
518
|
if (e && e.stats && e.stats.streamed) {
|
|
504
|
-
const t =
|
|
519
|
+
const t = Mt.current || { layers: [], gcode: [] };
|
|
505
520
|
return { stats: e.stats, layers: t.layers, gcode: t.gcode.join("") };
|
|
506
521
|
}
|
|
507
522
|
return e;
|
|
508
523
|
}
|
|
509
|
-
function
|
|
524
|
+
function Mr(e) {
|
|
510
525
|
var r;
|
|
511
526
|
if (e.error) {
|
|
512
|
-
|
|
527
|
+
C(String(e.error));
|
|
513
528
|
return;
|
|
514
529
|
}
|
|
515
|
-
|
|
530
|
+
te.current = e.layers;
|
|
516
531
|
const t = e.layers.length;
|
|
517
|
-
|
|
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" }))));
|
|
518
533
|
}
|
|
519
|
-
async function
|
|
520
|
-
var l,
|
|
521
|
-
const t = Array.from(e || []).filter((s) =>
|
|
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;
|
|
522
537
|
if (!t.length) {
|
|
523
|
-
r &&
|
|
538
|
+
r && C("지원 포맷: STL/OBJ/3MF/AMF/PLY");
|
|
524
539
|
return;
|
|
525
540
|
}
|
|
526
|
-
|
|
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();
|
|
527
542
|
let a = 0;
|
|
528
543
|
for (const s of t)
|
|
529
544
|
try {
|
|
530
|
-
const b = performance.now(),
|
|
531
|
-
for (const
|
|
532
|
-
(
|
|
533
|
-
console.info(`[vp-prof] load ${s.name}: read ${(
|
|
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`);
|
|
534
549
|
} catch (b) {
|
|
535
|
-
|
|
550
|
+
C(`로드 실패 ${s.name}: ${b && b.message || b}`);
|
|
536
551
|
}
|
|
537
|
-
|
|
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()}개 — 슬라이스가 오래 걸릴 수 있습니다`);
|
|
538
553
|
}
|
|
539
|
-
function
|
|
540
|
-
|
|
554
|
+
function Cr(e) {
|
|
555
|
+
yn(e.target.files), e.target.value = "";
|
|
541
556
|
}
|
|
542
|
-
function
|
|
557
|
+
function Vt(e) {
|
|
543
558
|
var t;
|
|
544
|
-
(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 })));
|
|
545
560
|
}
|
|
546
|
-
function
|
|
561
|
+
function jr(e) {
|
|
547
562
|
var t;
|
|
548
|
-
e.preventDefault(),
|
|
563
|
+
e.preventDefault(), Zt(!1), yn((t = e.dataTransfer) == null ? void 0 : t.files);
|
|
549
564
|
}
|
|
550
|
-
function
|
|
551
|
-
e.preventDefault(), e.dataTransfer && (e.dataTransfer.dropEffect = "copy"),
|
|
565
|
+
function Nr(e) {
|
|
566
|
+
e.preventDefault(), e.dataTransfer && (e.dataTransfer.dropEffect = "copy"), Ot || Zt(!0);
|
|
552
567
|
}
|
|
553
|
-
function
|
|
554
|
-
e.currentTarget.contains(e.relatedTarget) ||
|
|
568
|
+
function _r(e) {
|
|
569
|
+
e.currentTarget.contains(e.relatedTarget) || Zt(!1);
|
|
555
570
|
}
|
|
556
|
-
function
|
|
571
|
+
function Ar(e, t) {
|
|
557
572
|
var s, b;
|
|
558
|
-
const r =
|
|
573
|
+
const r = A.current;
|
|
559
574
|
if (!r.objectsGroup) return;
|
|
560
|
-
const a =
|
|
575
|
+
const a = Et.current || { cx: 0, cy: 0, minz: 0 }, l = et.current;
|
|
561
576
|
if (l)
|
|
562
|
-
for (const
|
|
563
|
-
const
|
|
564
|
-
if (!
|
|
565
|
-
const
|
|
566
|
-
for (let
|
|
567
|
-
const
|
|
568
|
-
|
|
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);
|
|
569
584
|
}
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
const
|
|
573
|
-
return
|
|
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;
|
|
574
589
|
};
|
|
575
|
-
|
|
590
|
+
et.current = { enf: c(e, 2845951), blk: c(t, 14826299) }, (b = (s = A.current).invalidate) == null || b.call(s);
|
|
576
591
|
}
|
|
577
|
-
function
|
|
578
|
-
const e =
|
|
592
|
+
function Sr() {
|
|
593
|
+
const e = A.current, t = et.current;
|
|
579
594
|
if (t && e.objectsGroup)
|
|
580
595
|
for (const r of ["enf", "blk"]) t[r] && (e.objectsGroup.remove(t[r]), t[r].geometry.dispose(), t[r].material.dispose());
|
|
581
|
-
|
|
596
|
+
et.current = null;
|
|
582
597
|
}
|
|
583
|
-
function
|
|
598
|
+
function oe(e) {
|
|
584
599
|
var t, r, a;
|
|
585
600
|
if (e !== "off" && w.current.length === 0) {
|
|
586
|
-
|
|
601
|
+
C("먼저 STL 을 업로드하세요");
|
|
587
602
|
return;
|
|
588
603
|
}
|
|
589
604
|
if (e !== "off") {
|
|
590
|
-
const l = (t =
|
|
605
|
+
const l = (t = d.current) == null ? void 0 : t.buildMergedSTL(re.current);
|
|
591
606
|
if (!l) return;
|
|
592
|
-
|
|
607
|
+
Et.current = { cx: l.offX, cy: -l.offZ, minz: 0 }, (r = d.current) == null || r.detachTransform(), qe().postMessage({ cmd: "prepare", stl: l.buf });
|
|
593
608
|
}
|
|
594
|
-
|
|
609
|
+
W.current = e, gr(e), (a = d.current) == null || a.refreshCursor();
|
|
595
610
|
}
|
|
596
|
-
function
|
|
597
|
-
|
|
611
|
+
function zr() {
|
|
612
|
+
qe().postMessage({ cmd: "clear" }), Sr(), hn({ enf: 0, blk: 0 });
|
|
598
613
|
}
|
|
599
|
-
const
|
|
600
|
-
function
|
|
614
|
+
const Rr = 6e4, Lr = 3e5;
|
|
615
|
+
function $t(e, t) {
|
|
601
616
|
return new Promise((r, a) => {
|
|
602
617
|
if (typeof window < "u" && window.__vpFail && window.__vpFail(window.__vpSliceN = (window.__vpSliceN || 0) + 1)) {
|
|
603
618
|
a(new Error("forced failure (test hook)"));
|
|
604
619
|
return;
|
|
605
620
|
}
|
|
606
|
-
const l = typeof window < "u" && window.__vpStallNext ? (window.__vpStallNext = !1, !0) : !1,
|
|
607
|
-
|
|
608
|
-
let s = 0, b = 0,
|
|
609
|
-
const
|
|
610
|
-
let
|
|
611
|
-
const
|
|
612
|
-
b = M,
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
},
|
|
616
|
-
const M =
|
|
617
|
-
return M && b >= M && b < M + 2 ?
|
|
618
|
-
},
|
|
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 = () => {
|
|
619
634
|
s && (clearTimeout(s), s = 0);
|
|
620
|
-
},
|
|
621
|
-
|
|
622
|
-
const M =
|
|
635
|
+
}, V = () => {
|
|
636
|
+
O();
|
|
637
|
+
const M = B();
|
|
623
638
|
s = setTimeout(() => {
|
|
624
|
-
var
|
|
625
|
-
|
|
639
|
+
var _;
|
|
640
|
+
he.current = null, we(), Le.current = null;
|
|
626
641
|
try {
|
|
627
|
-
(
|
|
642
|
+
(_ = S.current) == null || _.terminate();
|
|
628
643
|
} catch {
|
|
629
644
|
}
|
|
630
|
-
|
|
645
|
+
S.current = null, a(new Error(`watchdog: ${M}ms 무진행 — 메모리 압박으로 판단`));
|
|
631
646
|
}, M);
|
|
632
647
|
};
|
|
633
|
-
|
|
648
|
+
he.current = { resolve: r, reject: a, kick: V, stop: O, note: f }, V(), qe().postMessage({ stl: e, params: t, stall: l }), Er();
|
|
634
649
|
});
|
|
635
650
|
}
|
|
636
|
-
function
|
|
651
|
+
function kn() {
|
|
637
652
|
var e;
|
|
638
653
|
try {
|
|
639
|
-
(e =
|
|
654
|
+
(e = S.current) == null || e.terminate();
|
|
640
655
|
} catch {
|
|
641
656
|
}
|
|
642
|
-
|
|
657
|
+
S.current = null, qe();
|
|
658
|
+
}
|
|
659
|
+
async function En(e, t) {
|
|
660
|
+
const r = (a) => String((a == null ? void 0 : a.message) || a).includes("canceled");
|
|
661
|
+
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();
|
|
668
|
+
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;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return kn(), { r: await $t(e, JSON.stringify({ ...t, economy: !0, keep_stages: !1, reuse_stages: 0 })), economy: !0, recovered: !0 };
|
|
676
|
+
}
|
|
643
677
|
}
|
|
644
|
-
async function
|
|
678
|
+
async function Mn(e) {
|
|
645
679
|
try {
|
|
646
|
-
const
|
|
647
|
-
return
|
|
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("");
|
|
648
682
|
} catch {
|
|
649
|
-
return
|
|
683
|
+
return null;
|
|
650
684
|
}
|
|
651
685
|
}
|
|
652
|
-
function
|
|
653
|
-
|
|
654
|
-
|
|
686
|
+
function Cn(e, t) {
|
|
687
|
+
e.economy || (e.keep_stages = !0, e.reuse_stages = t && t === Ve.current ? 2 : 0);
|
|
688
|
+
}
|
|
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;
|
|
655
692
|
}
|
|
656
|
-
function
|
|
693
|
+
function Ut(e) {
|
|
657
694
|
var s;
|
|
658
|
-
|
|
659
|
-
const t =
|
|
695
|
+
xr();
|
|
696
|
+
const t = G.current;
|
|
660
697
|
t && t.setLayerRange(0, 1e9);
|
|
661
|
-
const r =
|
|
698
|
+
const r = X.current[e];
|
|
662
699
|
if (!r || r.error || !r.layers || !r.layers.length) {
|
|
663
|
-
|
|
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), ""));
|
|
664
701
|
return;
|
|
665
702
|
}
|
|
666
|
-
|
|
703
|
+
te.current = r.layers;
|
|
667
704
|
const a = r.layers.length;
|
|
668
|
-
|
|
669
|
-
const l = y.current[e],
|
|
670
|
-
|
|
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" }))));
|
|
671
708
|
}
|
|
672
|
-
function
|
|
709
|
+
function Pr(e, t) {
|
|
673
710
|
const r = URL.createObjectURL(new Blob([e], { type: "text/plain" })), a = document.createElement("a");
|
|
674
711
|
a.href = r, a.download = t, a.style.display = "none", document.body.appendChild(a), a.click(), setTimeout(() => {
|
|
675
712
|
a.remove(), URL.revokeObjectURL(r);
|
|
676
713
|
}, 4e3);
|
|
677
714
|
}
|
|
678
|
-
const
|
|
679
|
-
function
|
|
680
|
-
|
|
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);
|
|
681
718
|
}
|
|
682
|
-
async function
|
|
683
|
-
|
|
684
|
-
const e = Object.entries(
|
|
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]));
|
|
685
722
|
if (!e.length) {
|
|
686
|
-
|
|
723
|
+
C("내보낼 슬라이스 결과가 없습니다 — 먼저 슬라이스하세요");
|
|
687
724
|
return;
|
|
688
725
|
}
|
|
689
726
|
for (const [t, r] of e)
|
|
690
|
-
|
|
691
|
-
|
|
727
|
+
Pr(r.gcode, `plate_${Number(t) + 1}.gcode`), await Or(350);
|
|
728
|
+
Y(`${e.length}개 플레이트 G-code 를 내보냈습니다`);
|
|
692
729
|
}
|
|
693
|
-
async function
|
|
730
|
+
async function Pe(e = "current") {
|
|
694
731
|
var r, a;
|
|
695
|
-
|
|
696
|
-
const t =
|
|
697
|
-
if (
|
|
698
|
-
|
|
699
|
-
let l = 0,
|
|
700
|
-
const
|
|
701
|
-
for (let
|
|
702
|
-
const
|
|
703
|
-
if (
|
|
704
|
-
|
|
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 };
|
|
705
742
|
try {
|
|
706
|
-
const
|
|
707
|
-
|
|
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);
|
|
708
747
|
} catch {
|
|
709
|
-
|
|
748
|
+
Ve.current = null, b.push(p + 1);
|
|
710
749
|
}
|
|
711
750
|
}
|
|
712
751
|
}
|
|
713
|
-
if (
|
|
714
|
-
|
|
752
|
+
if (ge(!1), !l) {
|
|
753
|
+
Ce({ scope: "all" }), C("모든 플레이트 슬라이스 실패(절약 모드 포함) — 간소화 재시도를 시도하세요");
|
|
715
754
|
return;
|
|
716
755
|
}
|
|
717
|
-
|
|
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]);
|
|
718
757
|
} else {
|
|
719
|
-
const l = performance.now(),
|
|
720
|
-
if (!
|
|
721
|
-
|
|
758
|
+
const l = performance.now(), c = (a = d.current) == null ? void 0 : a.buildMergedSTL(t);
|
|
759
|
+
if (!c) {
|
|
760
|
+
C(`플레이트 ${t + 1} 에 오브젝트가 없습니다`);
|
|
722
761
|
return;
|
|
723
762
|
}
|
|
724
|
-
console.info(`[vp-prof] buildMergedSTL ${(performance.now() - l).toFixed(0)}ms (${(
|
|
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);
|
|
725
764
|
try {
|
|
726
|
-
const
|
|
727
|
-
|
|
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 정상)");
|
|
728
769
|
} catch (s) {
|
|
729
|
-
|
|
770
|
+
if (ge(!1), Ve.current = null, String((s == null ? void 0 : s.message) || s).includes("canceled")) {
|
|
771
|
+
Y("슬라이스 취소됨");
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
Ce({ scope: "current" }), C("슬라이스 실패(절약 모드도 실패): " + s.message);
|
|
730
775
|
}
|
|
731
776
|
}
|
|
732
777
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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;
|
|
737
794
|
try {
|
|
738
|
-
await
|
|
795
|
+
await Pe(e.scope);
|
|
739
796
|
} finally {
|
|
740
|
-
|
|
797
|
+
Ct.current = !1;
|
|
741
798
|
}
|
|
742
799
|
}
|
|
743
800
|
}
|
|
744
|
-
function
|
|
745
|
-
|
|
801
|
+
function Br() {
|
|
802
|
+
an((e) => Math.min(Jn, e + 1));
|
|
746
803
|
}
|
|
747
|
-
function
|
|
748
|
-
|
|
804
|
+
function Ir() {
|
|
805
|
+
an((e) => {
|
|
749
806
|
if (e <= 1) return e;
|
|
750
807
|
const t = e - 1;
|
|
751
|
-
return delete
|
|
808
|
+
return delete X.current[t], mt(t), ft(), re.current >= t && Nn(t - 1), t;
|
|
752
809
|
});
|
|
753
810
|
}
|
|
754
|
-
function
|
|
755
|
-
|
|
811
|
+
function Nn(e) {
|
|
812
|
+
re.current = e, or(e), Ue.current = 0, H === "preview" && Ut(e);
|
|
756
813
|
}
|
|
757
|
-
function
|
|
814
|
+
function _n(e, t) {
|
|
758
815
|
if (!(e > 0) || !(t > 0)) return;
|
|
759
|
-
const r =
|
|
760
|
-
|
|
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]] }));
|
|
761
818
|
}
|
|
762
|
-
function
|
|
819
|
+
function Vr(e, t) {
|
|
763
820
|
var r;
|
|
764
|
-
(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 })));
|
|
765
822
|
}
|
|
766
|
-
function
|
|
767
|
-
const r = Math.max(0,
|
|
823
|
+
function de(e, t) {
|
|
824
|
+
const r = Math.max(0, Ae - 1);
|
|
768
825
|
if (e = Math.max(0, Math.min(r, e)), t = Math.max(0, Math.min(r, t)), e > t) {
|
|
769
826
|
const a = e;
|
|
770
827
|
e = t, t = a;
|
|
771
828
|
}
|
|
772
|
-
|
|
829
|
+
me.current = e, ne.current = t, zt(e), Lt(t), wr();
|
|
773
830
|
}
|
|
774
|
-
function
|
|
831
|
+
function $r(e) {
|
|
775
832
|
const t = parseInt(e.target.value, 10);
|
|
776
|
-
|
|
833
|
+
Me ? de(t, t) : de(t, ne.current);
|
|
777
834
|
}
|
|
778
|
-
function
|
|
835
|
+
function Ur(e) {
|
|
779
836
|
const t = parseInt(e.target.value, 10);
|
|
780
|
-
|
|
837
|
+
de(Me ? t : me.current, t);
|
|
781
838
|
}
|
|
782
|
-
function
|
|
783
|
-
const e = !
|
|
784
|
-
|
|
839
|
+
function An() {
|
|
840
|
+
const e = !Me;
|
|
841
|
+
pr(e), e && de(ne.current, ne.current);
|
|
785
842
|
}
|
|
786
|
-
function
|
|
843
|
+
function Fr(e) {
|
|
787
844
|
const t = e.target.value;
|
|
788
|
-
|
|
845
|
+
dr(t), kt.current = t, It();
|
|
789
846
|
}
|
|
790
|
-
function
|
|
847
|
+
function Sn(e) {
|
|
791
848
|
const t = e.target.checked;
|
|
792
|
-
|
|
849
|
+
fr(t), yt.current = t;
|
|
793
850
|
for (const r of Object.values(y.current)) r.ctl.setTravelVisible(t);
|
|
794
851
|
}
|
|
795
|
-
function
|
|
852
|
+
function Gr(e) {
|
|
796
853
|
const t = e.target.checked;
|
|
797
|
-
|
|
854
|
+
F((r) => ({ ...r, enable_support: t }));
|
|
798
855
|
}
|
|
799
|
-
const
|
|
800
|
-
function
|
|
801
|
-
|
|
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 })));
|
|
802
859
|
}
|
|
803
|
-
function
|
|
804
|
-
|
|
860
|
+
function Hr(e, t) {
|
|
861
|
+
Tt((r) => {
|
|
805
862
|
var l;
|
|
806
863
|
const a = [...r];
|
|
807
|
-
return a[e] = t,
|
|
864
|
+
return a[e] = t, Re.current = a, (l = d.current) == null || l.recolorObjects(), a;
|
|
808
865
|
});
|
|
809
866
|
}
|
|
810
|
-
function
|
|
811
|
-
|
|
867
|
+
function qr() {
|
|
868
|
+
Tt((e) => {
|
|
812
869
|
if (e.length >= 4) return e;
|
|
813
870
|
const t = ["#e0473b", "#3bb0e0", "#7ad14a"], r = [...e, t[e.length - 1] || "#888888"];
|
|
814
|
-
return
|
|
871
|
+
return Re.current = r, r;
|
|
815
872
|
});
|
|
816
873
|
}
|
|
817
|
-
function
|
|
818
|
-
|
|
874
|
+
function Wr() {
|
|
875
|
+
Tt((e) => {
|
|
819
876
|
var r;
|
|
820
877
|
if (e.length <= 1) return e;
|
|
821
878
|
const t = e.slice(0, -1);
|
|
822
|
-
return
|
|
879
|
+
return Re.current = t, w.current.forEach((a) => {
|
|
823
880
|
var l;
|
|
824
|
-
(a.extruder || 1) > t.length && ((l =
|
|
825
|
-
}), (r =
|
|
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;
|
|
826
883
|
});
|
|
827
884
|
}
|
|
828
|
-
function
|
|
885
|
+
function Xr(e) {
|
|
829
886
|
var r;
|
|
830
887
|
const t = w.current.find((a) => a.id === e);
|
|
831
|
-
(r =
|
|
888
|
+
(r = d.current) == null || r.setObjectVisible(e, (t == null ? void 0 : t.visible) === !1), Oe();
|
|
832
889
|
}
|
|
833
|
-
function
|
|
834
|
-
|
|
890
|
+
function Kr() {
|
|
891
|
+
oe(ve === "off" ? "enforcer" : "off");
|
|
835
892
|
}
|
|
836
|
-
const
|
|
837
|
-
{ id: "add", icon:
|
|
893
|
+
const Yr = [
|
|
894
|
+
{ id: "add", icon: xa, label: "추가", tip: "모델 파일을 현재 플레이트에 추가 (기존 오브젝트 유지)", run: () => {
|
|
838
895
|
var e;
|
|
839
|
-
return (e =
|
|
896
|
+
return (e = Ge.current) == null ? void 0 : e.click();
|
|
840
897
|
} },
|
|
841
|
-
{ id: "delete", icon:
|
|
842
|
-
{ id: "delete-all", icon:
|
|
898
|
+
{ id: "delete", icon: ka, label: "삭제", tip: "선택한 오브젝트 삭제 (Del)", run: ht },
|
|
899
|
+
{ id: "delete-all", icon: Ea, label: "전체 삭제", tip: "플레이트의 모든 오브젝트 삭제", run: Jr, disabled: () => Ee.length === 0 },
|
|
843
900
|
{ sep: !0 },
|
|
844
|
-
{ id: "duplicate", icon:
|
|
845
|
-
{ id: "split", icon:
|
|
846
|
-
{ id: "onbed", icon:
|
|
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: () => {
|
|
847
904
|
var e;
|
|
848
|
-
return (e =
|
|
905
|
+
return (e = d.current) == null ? void 0 : e.placeOnBed();
|
|
849
906
|
} },
|
|
850
907
|
{ sep: !0 },
|
|
851
|
-
{ id: "arrange", icon:
|
|
852
|
-
{ id: "orient", icon:
|
|
908
|
+
{ id: "arrange", icon: va, label: "배치", tip: "자동 배치 — 오브젝트를 베드에 겹치지 않게 정렬. 미구현(libslic3r Arrange 이식 필요)" },
|
|
909
|
+
{ id: "orient", icon: za, label: "방향", tip: "자동 방향 — 서포트가 덜 필요한 방향으로 회전. 미구현(libslic3r Orient 이식 필요)" },
|
|
853
910
|
{ sep: !0 },
|
|
854
|
-
{ id: "cut", icon:
|
|
855
|
-
{ id: "boolean", icon:
|
|
856
|
-
{ id: "negative", icon:
|
|
911
|
+
{ id: "cut", icon: ya, label: "컷", tip: "컷 — 평면으로 모델을 잘라 두 조각으로 분리. 미구현(절단면 재봉합 필요)" },
|
|
912
|
+
{ id: "boolean", icon: wa, label: "불리언", tip: "메시 불리언 — 두 오브젝트의 합/차/교집합. 미구현(CGAL 불리언 이식 필요)" },
|
|
913
|
+
{ id: "negative", icon: _a, label: "음각 파트", tip: "음각·모디파이어 파트 추가 — 한 오브젝트 안에 성질이 다른 파트를 넣음. 미구현(파트 개념 부재)" },
|
|
857
914
|
{ sep: !0 },
|
|
858
|
-
{ id: "seam", icon:
|
|
859
|
-
{ id: "mmu", icon:
|
|
860
|
-
{ id: "text", icon:
|
|
861
|
-
{ id: "measure", icon:
|
|
862
|
-
{ id: "varlayer", icon:
|
|
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 미지원)" }
|
|
863
920
|
];
|
|
864
|
-
function
|
|
921
|
+
function Ft() {
|
|
865
922
|
var r, a, l;
|
|
866
|
-
const e = (r =
|
|
923
|
+
const e = (r = d.current) == null ? void 0 : r.selectedObjectId();
|
|
867
924
|
if (!e) {
|
|
868
|
-
|
|
925
|
+
C("먼저 복제할 오브젝트를 선택하세요");
|
|
869
926
|
return;
|
|
870
927
|
}
|
|
871
|
-
const t = (a =
|
|
872
|
-
t && ((l =
|
|
928
|
+
const t = (a = d.current) == null ? void 0 : a.getSnapshot(e);
|
|
929
|
+
t && ((l = d.current) == null || l.spawnSnapshot(t), Oe(), C(""));
|
|
873
930
|
}
|
|
874
|
-
function
|
|
931
|
+
function Gt() {
|
|
875
932
|
var t, r;
|
|
876
|
-
const e = (t =
|
|
933
|
+
const e = (t = d.current) == null ? void 0 : t.selectedObjectId();
|
|
877
934
|
if (!e) {
|
|
878
|
-
|
|
935
|
+
C("먼저 복사할 오브젝트를 선택하세요");
|
|
879
936
|
return;
|
|
880
937
|
}
|
|
881
|
-
|
|
938
|
+
Je.current = (r = d.current) == null ? void 0 : r.getSnapshot(e), C(""), Y("오브젝트를 복사했습니다 (Ctrl+V 로 붙여넣기)");
|
|
882
939
|
}
|
|
883
|
-
function
|
|
940
|
+
function zn() {
|
|
884
941
|
var e;
|
|
885
|
-
|
|
942
|
+
Je.current && ((e = d.current) == null || e.spawnSnapshot(Je.current), Oe());
|
|
886
943
|
}
|
|
887
|
-
function
|
|
944
|
+
function ht() {
|
|
888
945
|
var t;
|
|
889
|
-
const e = (t =
|
|
946
|
+
const e = (t = d.current) == null ? void 0 : t.selectedObjectId();
|
|
890
947
|
if (!e) {
|
|
891
|
-
|
|
948
|
+
C("먼저 삭제할 오브젝트를 선택하세요");
|
|
892
949
|
return;
|
|
893
950
|
}
|
|
894
|
-
|
|
951
|
+
Vt(e), C("");
|
|
895
952
|
}
|
|
896
|
-
function
|
|
953
|
+
function Jr() {
|
|
897
954
|
var t;
|
|
898
955
|
const e = w.current.map((r) => r.id);
|
|
899
956
|
if (e.length) {
|
|
900
|
-
for (const r of e) (t =
|
|
901
|
-
|
|
957
|
+
for (const r of e) (t = d.current) == null || t.removeObject(r);
|
|
958
|
+
Oe(), Y(`${e.length}개 오브젝트를 모두 삭제했습니다`);
|
|
902
959
|
}
|
|
903
960
|
}
|
|
904
|
-
function
|
|
905
|
-
var l,
|
|
906
|
-
const e = (l =
|
|
961
|
+
function Dt() {
|
|
962
|
+
var l, c;
|
|
963
|
+
const e = (l = d.current) == null ? void 0 : l.selectedObjectId();
|
|
907
964
|
if (!e) {
|
|
908
|
-
|
|
965
|
+
C("먼저 분리할 오브젝트를 선택하세요");
|
|
909
966
|
return;
|
|
910
967
|
}
|
|
911
|
-
const t = (
|
|
968
|
+
const t = (c = d.current) == null ? void 0 : c.getSnapshot(e);
|
|
912
969
|
if (!t) return;
|
|
913
970
|
let r;
|
|
914
971
|
try {
|
|
915
|
-
r =
|
|
972
|
+
r = ma(t.localPos);
|
|
916
973
|
} catch (s) {
|
|
917
|
-
|
|
974
|
+
C("분리 실패: " + ((s == null ? void 0 : s.message) || s));
|
|
918
975
|
return;
|
|
919
976
|
}
|
|
920
977
|
if (!r || r.length < 2) {
|
|
921
|
-
|
|
978
|
+
C("분리할 독립 부분이 없습니다 — 하나로 연결된 메시입니다");
|
|
922
979
|
return;
|
|
923
980
|
}
|
|
924
981
|
const a = String(t.name || "object").replace(/\.[^.]+$/, "");
|
|
925
|
-
|
|
926
|
-
var
|
|
927
|
-
return (
|
|
982
|
+
Vt(e), r.forEach((s, b) => {
|
|
983
|
+
var p;
|
|
984
|
+
return (p = d.current) == null ? void 0 : p.spawnSnapshot(
|
|
928
985
|
{ name: `${a}_${b + 1}`, localPos: s, rot: t.rot, scale: t.scale, pos: t.pos },
|
|
929
986
|
!0
|
|
930
987
|
);
|
|
931
|
-
}),
|
|
988
|
+
}), Oe(), C(""), Y(`${r.length}개 오브젝트로 분리했습니다`);
|
|
932
989
|
}
|
|
933
|
-
function
|
|
990
|
+
function Ht(e) {
|
|
934
991
|
var t;
|
|
935
|
-
|
|
992
|
+
W.current !== "off" && oe("off"), (t = d.current) == null || t.setMode(e);
|
|
936
993
|
}
|
|
937
|
-
|
|
938
|
-
var b,
|
|
994
|
+
Ye.current = (e) => {
|
|
995
|
+
var b, p, j, u, f, B, O, V, M;
|
|
939
996
|
const t = e.composedPath ? e.composedPath()[0] : e.target;
|
|
940
997
|
if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT" || t.isContentEditable)) return;
|
|
941
|
-
const r = e.key, a = typeof r == "string" ? r.toLowerCase() : "", l = e.ctrlKey || e.metaKey,
|
|
998
|
+
const r = e.key, a = typeof r == "string" ? r.toLowerCase() : "", l = e.ctrlKey || e.metaKey, c = fe.current === "preview", s = () => {
|
|
942
999
|
e.preventDefault(), e.stopPropagation();
|
|
943
1000
|
};
|
|
944
1001
|
if (l) {
|
|
945
|
-
a === "r" ? (s(),
|
|
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());
|
|
946
1003
|
return;
|
|
947
1004
|
}
|
|
948
|
-
if (
|
|
949
|
-
const
|
|
1005
|
+
if (c) {
|
|
1006
|
+
const _ = e.shiftKey ? 10 : 1;
|
|
950
1007
|
if (r === "ArrowUp") {
|
|
951
1008
|
s();
|
|
952
|
-
const
|
|
953
|
-
|
|
1009
|
+
const T = ne.current + _;
|
|
1010
|
+
de(Me ? T : me.current, T);
|
|
954
1011
|
} else if (r === "ArrowDown") {
|
|
955
1012
|
s();
|
|
956
|
-
const
|
|
957
|
-
|
|
958
|
-
} else a === "l" ? (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"));
|
|
959
1016
|
return;
|
|
960
1017
|
}
|
|
961
1018
|
if (a === "m" || a === "g")
|
|
962
|
-
s(),
|
|
1019
|
+
s(), Ht("translate");
|
|
963
1020
|
else if (a === "r")
|
|
964
|
-
s(),
|
|
1021
|
+
s(), Ht("rotate");
|
|
965
1022
|
else if (a === "s")
|
|
966
|
-
s(),
|
|
1023
|
+
s(), Ht("scale");
|
|
967
1024
|
else if (a === "z")
|
|
968
|
-
s(), (
|
|
1025
|
+
s(), (j = d.current) == null || j.frame();
|
|
969
1026
|
else if (a === "b")
|
|
970
|
-
s(), (
|
|
1027
|
+
s(), (u = d.current) == null || u.frameBed();
|
|
971
1028
|
else if (r === "Delete" || r === "Backspace")
|
|
972
|
-
s(),
|
|
1029
|
+
s(), ht();
|
|
973
1030
|
else if (r === "Escape")
|
|
974
|
-
s(),
|
|
1031
|
+
s(), W.current !== "off" && oe("off"), (f = d.current) == null || f.detachTransform();
|
|
975
1032
|
else if (r === "PageUp")
|
|
976
|
-
s(), (
|
|
1033
|
+
s(), (B = d.current) == null || B.rotateSelectedY(Math.PI / 4);
|
|
977
1034
|
else if (r === "PageDown")
|
|
978
|
-
s(), (
|
|
979
|
-
else if ((
|
|
1035
|
+
s(), (O = d.current) == null || O.rotateSelectedY(-Math.PI / 4);
|
|
1036
|
+
else if ((V = r.startsWith) != null && V.call(r, "Arrow")) {
|
|
980
1037
|
s();
|
|
981
|
-
const
|
|
982
|
-
(M =
|
|
983
|
-
r === "ArrowLeft" ? -
|
|
984
|
-
r === "ArrowUp" ? -
|
|
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
|
|
985
1042
|
);
|
|
986
|
-
} else r === "?" && (s(),
|
|
1043
|
+
} else r === "?" && (s(), ln((_) => !_));
|
|
987
1044
|
};
|
|
988
|
-
const
|
|
989
|
-
(
|
|
990
|
-
const
|
|
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 && (() => {
|
|
991
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`;
|
|
992
1049
|
return /* @__PURE__ */ n.createElement("div", { "data-testid": "print-time" }, "⏱ 예상 출력 ", /* @__PURE__ */ n.createElement("b", null, t));
|
|
993
|
-
})(),
|
|
994
|
-
return /* @__PURE__ */ n.createElement(
|
|
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: () => {
|
|
995
1052
|
var e;
|
|
996
|
-
return (e =
|
|
997
|
-
}, title: "모델 파일 열기 — 기존 오브젝트는 모두 지워짐", "data-testid": "open-file" }, /* @__PURE__ */ n.createElement("img", { src:
|
|
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: () => {
|
|
998
1055
|
var e;
|
|
999
|
-
|
|
1000
|
-
}, title: "오브젝트 이동 — 기즈모 축을 끌거나 방향키로 10mm(Shift 1mm) 이동 (M/G)", "data-testid": "gizmo-move" }, /* @__PURE__ */ n.createElement("img", { src:
|
|
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: () => {
|
|
1001
1058
|
var e;
|
|
1002
|
-
|
|
1003
|
-
}, title: "오브젝트 회전 — PageUp/PageDown 으로 45° 단위 회전 (R)", "data-testid": "gizmo-rotate" }, /* @__PURE__ */ n.createElement("img", { src:
|
|
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: () => {
|
|
1004
1061
|
var e;
|
|
1005
|
-
|
|
1006
|
-
}, title: "오브젝트 크기 조절 (S)", "data-testid": "gizmo-scale" }, /* @__PURE__ */ n.createElement("img", { src:
|
|
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(
|
|
1007
1064
|
"div",
|
|
1008
1065
|
{
|
|
1009
|
-
className: (
|
|
1010
|
-
ref:
|
|
1011
|
-
onDrop:
|
|
1012
|
-
onDragOver:
|
|
1013
|
-
onDragLeave:
|
|
1066
|
+
className: (K ? "vp-canvas" : "vp-canvas fail") + (Ot ? " drag-over" : ""),
|
|
1067
|
+
ref: ee,
|
|
1068
|
+
onDrop: jr,
|
|
1069
|
+
onDragOver: Nr,
|
|
1070
|
+
onDragLeave: _r,
|
|
1014
1071
|
"data-testid": "drop-zone"
|
|
1015
1072
|
},
|
|
1016
|
-
!
|
|
1017
|
-
|
|
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: () => {
|
|
1018
1075
|
var e;
|
|
1019
|
-
return (e =
|
|
1020
|
-
}, "data-testid": "empty-pick", title:
|
|
1021
|
-
|
|
1022
|
-
|
|
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) => {
|
|
1023
1080
|
e.preventDefault(), D(null);
|
|
1024
|
-
} }), /* @__PURE__ */ n.createElement("div", { className: "ctx-menu", style: { left:
|
|
1025
|
-
D(null), It();
|
|
1026
|
-
}, "data-testid": "ctx-duplicate", title: "선택 오브젝트를 같은 크기·회전으로 복제해 옆에 배치" }, "복제 ", /* @__PURE__ */ n.createElement("span", { className: "ctx-key" }, "Ctrl+K")), /* @__PURE__ */ n.createElement("button", { onClick: () => {
|
|
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: () => {
|
|
1027
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();
|
|
1028
1085
|
}, "data-testid": "ctx-copy", title: "선택 오브젝트를 버퍼에 복사 (붙여넣기로 추가)" }, "복사 ", /* @__PURE__ */ n.createElement("span", { className: "ctx-key" }, "Ctrl+C")), /* @__PURE__ */ n.createElement("button", { onClick: () => {
|
|
1029
|
-
D(null),
|
|
1086
|
+
D(null), Dt();
|
|
1030
1087
|
}, "data-testid": "ctx-split", title: "객체로 분리 — 서로 떨어진 부분(연결 성분)마다 독립 오브젝트로 나눔. 파트로 분리는 미구현(파트 개념 부재)" }, "분리"), /* @__PURE__ */ n.createElement("button", { onClick: () => {
|
|
1031
1088
|
var e;
|
|
1032
|
-
D(null), (e =
|
|
1089
|
+
D(null), (e = d.current) == null || e.placeOnBed();
|
|
1033
1090
|
}, "data-testid": "ctx-seat", title: "모든 오브젝트를 베드 바닥(Z=0)에 붙임" }, "바닥에 놓기"), /* @__PURE__ */ n.createElement("hr", null), /* @__PURE__ */ n.createElement("button", { className: "danger", onClick: () => {
|
|
1034
|
-
D(null),
|
|
1091
|
+
D(null), ht();
|
|
1035
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: () => {
|
|
1036
1093
|
var e;
|
|
1037
|
-
D(null), (e =
|
|
1038
|
-
}, "data-testid": "ctx-open", title: "모델 파일을 열어 현재 플레이트에 추가" }, "모델 열기…"), /* @__PURE__ */ n.createElement("button", { disabled: !
|
|
1039
|
-
D(null),
|
|
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();
|
|
1040
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: () => {
|
|
1041
1098
|
var e;
|
|
1042
|
-
D(null), (e =
|
|
1099
|
+
D(null), (e = d.current) == null || e.frame();
|
|
1043
1100
|
}, "data-testid": "ctx-zoom-all", title: "모든 오브젝트가 보이도록 카메라 맞춤" }, "전체 보기 ", /* @__PURE__ */ n.createElement("span", { className: "ctx-key" }, "Z")), /* @__PURE__ */ n.createElement("button", { onClick: () => {
|
|
1044
1101
|
var e;
|
|
1045
|
-
D(null), (e =
|
|
1102
|
+
D(null), (e = d.current) == null || e.frameBed();
|
|
1046
1103
|
}, "data-testid": "ctx-zoom-bed", title: "선택한 플레이트 전체가 보이도록 카메라 맞춤" }, "베드 보기 ", /* @__PURE__ */ n.createElement("span", { className: "ctx-key" }, "B"))))),
|
|
1047
|
-
|
|
1048
|
-
),
|
|
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) => {
|
|
1049
1106
|
var r;
|
|
1050
1107
|
return e.sep ? /* @__PURE__ */ n.createElement("span", { key: "sep" + t, className: "vtt-sep" }) : /* @__PURE__ */ n.createElement(
|
|
1051
1108
|
"button",
|
|
@@ -1058,28 +1115,28 @@ function Ia({ settings: L = {}, setSettings: $ = () => {
|
|
|
1058
1115
|
},
|
|
1059
1116
|
/* @__PURE__ */ n.createElement("img", { src: e.icon, alt: e.label })
|
|
1060
1117
|
);
|
|
1061
|
-
})),
|
|
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(
|
|
1062
1119
|
"input",
|
|
1063
1120
|
{
|
|
1064
1121
|
type: "range",
|
|
1065
1122
|
min: "1",
|
|
1066
1123
|
max: "15",
|
|
1067
1124
|
step: "0.5",
|
|
1068
|
-
value:
|
|
1125
|
+
value: un,
|
|
1069
1126
|
onChange: (e) => {
|
|
1070
1127
|
const t = parseFloat(e.target.value);
|
|
1071
|
-
|
|
1128
|
+
mn(t), Qe.current = t;
|
|
1072
1129
|
},
|
|
1073
1130
|
"data-testid": "brush-radius"
|
|
1074
1131
|
}
|
|
1075
|
-
)), /* @__PURE__ */ n.createElement("div", { className: "muted bp-counts", "data-testid": "paint-counts" }, "enforcer ",
|
|
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(
|
|
1076
1133
|
"input",
|
|
1077
1134
|
{
|
|
1078
1135
|
type: "number",
|
|
1079
1136
|
min: "1",
|
|
1080
|
-
key: `w${Math.round(
|
|
1081
|
-
defaultValue: Math.round(
|
|
1082
|
-
onBlur: (e) =>
|
|
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),
|
|
1083
1140
|
onKeyDown: (e) => {
|
|
1084
1141
|
e.key === "Enter" && e.target.blur();
|
|
1085
1142
|
},
|
|
@@ -1090,19 +1147,19 @@ function Ia({ settings: L = {}, setSettings: $ = () => {
|
|
|
1090
1147
|
{
|
|
1091
1148
|
type: "number",
|
|
1092
1149
|
min: "1",
|
|
1093
|
-
key: `d${Math.round(
|
|
1094
|
-
defaultValue: Math.round(
|
|
1095
|
-
onBlur: (e) =>
|
|
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),
|
|
1096
1153
|
onKeyDown: (e) => {
|
|
1097
1154
|
e.key === "Enter" && e.target.blur();
|
|
1098
1155
|
},
|
|
1099
1156
|
"data-testid": "bed-d-card"
|
|
1100
1157
|
}
|
|
1101
|
-
), "mm")), /* @__PURE__ */ n.createElement("div", { className: "sc-info" }, /* @__PURE__ */ n.createElement("span", null, "노즐 Ø"), /* @__PURE__ */ n.createElement("b", null,
|
|
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: () => {
|
|
1102
1159
|
var t;
|
|
1103
|
-
(t =
|
|
1104
|
-
}, title: "객체로 분리 — 서로 떨어진 부분(연결 성분)마다 독립 오브젝트로 나눔. 파트로 분리는 미구현(파트 개념 부재)", "data-testid": `split-${e.id}` }, /* @__PURE__ */ n.createElement("img", { src:
|
|
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 내보내기"))))));
|
|
1105
1162
|
}
|
|
1106
1163
|
export {
|
|
1107
|
-
|
|
1164
|
+
Wa as default
|
|
1108
1165
|
};
|