three-slicer 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +342 -24
  2. package/components/README.md +1 -1
  3. package/components/dist/SettingsPanel.js +111 -112
  4. package/components/styles.css +9 -9
  5. package/data/invalidation-map.json +1 -1
  6. package/engine/examples/headless.mjs +2 -2
  7. package/engine/index.d.ts +3 -1
  8. package/engine/index.js +2 -2
  9. package/engine/src/data.js +12 -0
  10. package/engine/src/settings.js +35 -35
  11. package/engine/src/slicer.worker.js +25 -22
  12. package/engine/src/slicer_core.js +0 -0
  13. package/engine/src/slicer_core.mt.js +0 -0
  14. package/engine/src/toggle_eval.js +12 -13
  15. package/package.json +54 -26
  16. package/types/components.d.ts +19 -0
  17. package/types/data/config-schema.json.d.ts +55 -0
  18. package/types/data/invalidation-map.json.d.ts +15 -0
  19. package/types/data/toggle-rules.json.d.ts +20 -0
  20. package/types/data/ui-tree.json.d.ts +19 -0
  21. package/types/data.d.ts +17 -0
  22. package/types/gen_settings_types.mjs +70 -0
  23. package/types/settings-keys.d.ts +1704 -0
  24. package/types/settings.d.ts +19 -0
  25. package/types/toggle.d.ts +22 -0
  26. package/types/viewer-loaders.d.ts +33 -0
  27. package/types/viewer-toolpath.d.ts +105 -0
  28. package/types/viewer.d.ts +15 -0
  29. package/types/wasm.d.ts +5 -0
  30. package/types/worker.d.ts +3 -0
  31. package/viewer/README.md +1 -1
  32. package/viewer/dist/Viewport.js +1774 -923
  33. package/viewer/dist/make_worker.js +6 -6
  34. package/viewer/dist/model_loaders.js +221 -109
  35. package/viewer/dist/toolpath_gpu.js +146 -147
  36. package/viewer/styles.css +27 -25
  37. package/data/config-schema-builddump.json +0 -12921
  38. package/viewer/src/Viewport.jsx +0 -1353
  39. package/viewer/src/assets/add.svg +0 -1
  40. package/viewer/src/assets/arrange.svg +0 -1
  41. package/viewer/src/assets/boolean.svg +0 -1
  42. package/viewer/src/assets/cut.svg +0 -1
  43. package/viewer/src/assets/delete.svg +0 -1
  44. package/viewer/src/assets/deleteall.svg +0 -1
  45. package/viewer/src/assets/duplicate.svg +0 -1
  46. package/viewer/src/assets/measure.svg +0 -1
  47. package/viewer/src/assets/mmu.svg +0 -1
  48. package/viewer/src/assets/move.svg +0 -1
  49. package/viewer/src/assets/negative.svg +0 -1
  50. package/viewer/src/assets/onbed.svg +0 -1
  51. package/viewer/src/assets/open.svg +0 -1
  52. package/viewer/src/assets/orient.svg +0 -1
  53. package/viewer/src/assets/paint.svg +0 -1
  54. package/viewer/src/assets/rotate.svg +0 -1
  55. package/viewer/src/assets/scale.svg +0 -1
  56. package/viewer/src/assets/seam.svg +0 -1
  57. package/viewer/src/assets/split.svg +0 -1
  58. package/viewer/src/assets/textmark.svg +0 -1
  59. package/viewer/src/assets/varlayer.svg +0 -1
  60. package/viewer/src/icons.js +0 -23
  61. package/viewer/src/make_worker.js +0 -9
  62. package/viewer/src/model_loaders.js +0 -178
  63. package/viewer/src/shadow_host.jsx +0 -30
  64. package/viewer/src/toolpath_gpu.js +0 -430
@@ -1,33 +1,4 @@
1
- const le = [
2
- 0,
3
- 1,
4
- 2,
5
- 0,
6
- 2,
7
- 3,
8
- // front spike
9
- 0,
10
- 3,
11
- 4,
12
- 0,
13
- 4,
14
- 5,
15
- // right/bottom body
16
- 0,
17
- 5,
18
- 6,
19
- 0,
20
- 6,
21
- 1,
22
- // left/top body
23
- 5,
24
- 4,
25
- 7,
26
- 5,
27
- 7,
28
- 6
29
- // back spike
30
- ], U = {
1
+ const U = {
31
2
  0: [0.42, 0.45, 0.5],
32
3
  1: [0.85, 0.51, 0.17],
33
4
  2: [0.21, 0.45, 0.76],
@@ -45,42 +16,61 @@ function ie(t) {
45
16
  const i = Math.round(t[0] * 255), s = Math.round(t[1] * 255), n = Math.round(t[2] * 255);
46
17
  return i << 16 | s << 8 | n;
47
18
  }
48
- function ue(t, i) {
19
+ const le = { 1: "Wall", 2: "Sparse", 3: "Solid", 4: "Skirt", 5: "Support", 6: "Raft", 7: "Gap fill", 8: "Thin wall", 9: "Bridge", 10: "Ironing", 11: "Prime" }, _e = [
20
+ [11, 44, 122],
21
+ [19, 89, 133],
22
+ [28, 136, 145],
23
+ [4, 214, 15],
24
+ [170, 242, 0],
25
+ [252, 249, 3],
26
+ [245, 206, 10],
27
+ [227, 136, 32],
28
+ [209, 104, 48],
29
+ [194, 82, 60],
30
+ [148, 38, 22]
31
+ ].map((t) => [t[0] / 255, t[1] / 255, t[2] / 255]);
32
+ function ce(t, i, s, n) {
33
+ const l = n.length;
34
+ if (!(s > i)) return n[0];
35
+ const a = (s - i) / (l - 1), h = (t - i) / a, _ = Math.max(0, Math.min(l - 1, Math.floor(h))), y = Math.max(0, Math.min(l - 1, _ + 1)), v = h - _, r = n[_], x = n[y];
36
+ return [r[0] + (x[0] - r[0]) * v, r[1] + (x[1] - r[1]) * v, r[2] + (x[2] - r[2]) * v];
37
+ }
38
+ function pe(t, i) {
49
39
  const s = t.length, n = i > 0 ? i : 0.42, l = new Array(s);
50
40
  for (let e = 0; e < s; e++) {
51
41
  const o = t[e].z;
52
42
  l[e] = Math.max(0.02, e === 0 ? o : o - t[e - 1].z);
53
43
  }
54
- const a = [], h = [], _ = [], y = [], v = [], r = [], x = [], b = [], A = [], M = [], L = new Float64Array(16), P = [], k = [];
44
+ const a = [], h = [], _ = [], y = [], v = [], r = [], x = [], b = [], A = [], M = [], L = new Float64Array(16), P = [], D = [];
55
45
  let W = -1, $ = 0, R = 0, H = 0, E = -1, c = 0;
56
- const d = 1e-4, u = (e, o, g, m, p, w) => (a.push(e), h.push(o), _.push(g), r.push(m), y.push(p), v.push(w), x.push(c), b.push(!1), a.length - 1);
46
+ const d = 1e-4, p = (e, o, g, m, u, w) => (a.push(e), h.push(o), _.push(g), r.push(m), y.push(u), v.push(w), x.push(c), b.push(!1), a.length - 1);
57
47
  for (let e = 0; e < s; e++) {
58
48
  const o = t[e].paths, g = t[e].widths, m = l[e];
59
49
  if (c = e, !!o)
60
- for (let p = 0; p < o.length; p += 8) {
61
- const w = o[p + 3], F = o[p], I = o[p + 1], G = o[p + 2], S = o[p + 4], V = o[p + 5], B = o[p + 6];
50
+ for (let u = 0; u < o.length; u += 8) {
51
+ const w = o[u + 3], F = o[u], S = o[u + 1], G = o[u + 2], I = o[u + 4], V = o[u + 5], B = o[u + 6];
62
52
  if (w === 0) {
63
- P.push(F, I, G, S, V, B), k.push(e);
53
+ P.push(F, S, G, I, V, B), D.push(e);
64
54
  continue;
65
55
  }
66
- const O = g && g[p / 8] > 0 ? g[p / 8] : n;
67
- w < 16 && (L[w] += Math.hypot(S - F, V - I));
68
- let D;
69
- W >= 0 && E === w && Math.abs($ - F) < d && Math.abs(R - I) < d && Math.abs(H - G) < d ? (D = W, u(S, V, B, w, m, O)) : (D = u(F, I, G, w, m, O), u(S, V, B, w, m, O)), b[D] = !0, W = D + 1, $ = S, R = V, H = B, E = w, A.push(D), M.push(e);
56
+ const O = g && g[u / 8] > 0 ? g[u / 8] : n;
57
+ w < 16 && (L[w] += Math.hypot(I - F, V - S));
58
+ let k;
59
+ W >= 0 && E === w && Math.abs($ - F) < d && Math.abs(R - S) < d && Math.abs(H - G) < d ? (k = W, p(I, V, B, w, m, O)) : (k = p(F, S, G, w, m, O), p(I, V, B, w, m, O)), b[k] = !0, W = k + 1, $ = I, R = V, H = B, E = w, A.push(k), M.push(e);
70
60
  }
71
61
  }
72
62
  const f = a.length, C = A.length, z = new Float32Array(f * 4), Y = new Float32Array(f * 4);
73
- let ee = 0, ne = !1, j = 1 / 0, T = 1 / 0, X = 1 / 0, Z = -1 / 0, q = -1 / 0, J = -1 / 0;
63
+ let ee = 0, ne = !1, T = 1 / 0, j = 1 / 0, X = 1 / 0, Z = -1 / 0, q = -1 / 0, J = -1 / 0;
74
64
  for (let e = 0; e < f; e++) {
75
- const o = y[e], g = a[e], m = h[e], p = _[e] - 0.5 * o;
76
- z[e * 4] = g, z[e * 4 + 1] = m, z[e * 4 + 2] = p;
65
+ const o = y[e], g = a[e], m = h[e], u = _[e] - 0.5 * o;
66
+ z[e * 4] = g, z[e * 4 + 1] = m, z[e * 4 + 2] = u;
77
67
  const w = e > 0 && b[e - 1], F = b[e];
78
- let I = 0;
68
+ let S = 0;
79
69
  if (w || F) {
80
- const G = w ? a[e] - a[e - 1] : 0, S = w ? h[e] - h[e - 1] : 0, V = w ? _[e] - _[e - 1] : 0, B = F ? a[e + 1] - a[e] : 0, O = F ? h[e + 1] - h[e] : 0, D = F ? _[e + 1] - _[e] : 0;
81
- I = Math.atan2(G * O - S * B, G * B + S * O + V * D);
70
+ const G = w ? a[e] - a[e - 1] : 0, I = w ? h[e] - h[e - 1] : 0, V = w ? _[e] - _[e - 1] : 0, B = F ? a[e + 1] - a[e] : 0, O = F ? h[e + 1] - h[e] : 0, k = F ? _[e + 1] - _[e] : 0;
71
+ S = Math.atan2(G * O - I * B, G * B + I * O + V * k);
82
72
  }
83
- Y[e * 4] = o, Y[e * 4 + 1] = v[e], Y[e * 4 + 2] = I, Y[e * 4 + 3] = ie(U[r[e]] || U[1]), (!Number.isFinite(g) || !Number.isFinite(m) || !Number.isFinite(p) || !Number.isFinite(I)) && (ne = !0), ee = Math.max(ee, Math.abs(g), Math.abs(m), Math.abs(p)), g < j && (j = g), g > Z && (Z = g), m < T && (T = m), m > q && (q = m), p < X && (X = p), p > J && (J = p);
73
+ Y[e * 4] = o, Y[e * 4 + 1] = v[e], Y[e * 4 + 2] = S, Y[e * 4 + 3] = ie(U[r[e]] || U[1]), (!Number.isFinite(g) || !Number.isFinite(m) || !Number.isFinite(u) || !Number.isFinite(S)) && (ne = !0), ee = Math.max(ee, Math.abs(g), Math.abs(m), Math.abs(u)), g < T && (T = g), g > Z && (Z = g), m < j && (j = m), m > q && (q = m), u < X && (X = u), u > J && (J = u);
84
74
  }
85
75
  const te = new Uint32Array(C * 4);
86
76
  for (let e = 0; e < C; e++)
@@ -96,24 +86,23 @@ function ue(t, i) {
96
86
  oe[o + 1] = e;
97
87
  }
98
88
  }
99
- const Q = k.length, N = new Float32Array(Q * 6);
89
+ const Q = D.length, N = new Float32Array(Q * 6);
100
90
  for (let e = 0; e < N.length; e++) N[e] = P[e];
101
91
  for (let e = 0; e < N.length; e += 3) {
102
92
  const o = N[e], g = N[e + 1], m = N[e + 2];
103
- o < j && (j = o), o > Z && (Z = o), g < T && (T = g), g > q && (q = g), m < X && (X = m), m > J && (J = m);
93
+ o < T && (T = o), o > Z && (Z = o), g < j && (j = g), g > q && (q = g), m < X && (X = m), m > J && (J = m);
104
94
  }
105
95
  const re = new Int32Array(s + 1);
106
96
  {
107
97
  let e = 0;
108
98
  for (let o = 0; o < s; o++) {
109
- for (; e < Q && k[e] === o; ) e++;
99
+ for (; e < Q && D[e] === o; ) e++;
110
100
  re[o + 1] = e;
111
101
  }
112
102
  }
113
- const se = f + Q > 0 ? { min: [j, T, X], max: [Z, q, J] } : null;
103
+ const se = f + Q > 0 ? { min: [T, j, X], max: [Z, q, J] } : null;
114
104
  return { position: z, hwa: Y, segIndex: te, nV: f, nSeg: C, layerSegPrefix: oe, travelPos: N, travelPrefix: re, nTrav: Q, layerCount: s, maxAbs: ee, hasNaN: ne, meta: K, typeLengths: L, bbox: se };
115
105
  }
116
- const _e = { 1: "벽", 2: "스파스", 3: "솔리드", 4: "스커트", 5: "서포트", 6: "래프트", 7: "갭필", 8: "씬월", 9: "브리지", 10: "아이어닝", 11: "프라임" };
117
106
  function ve(t) {
118
107
  let i = 0;
119
108
  for (let n = 1; n < 16; n++) i += t[n] || 0;
@@ -121,74 +110,11 @@ function ve(t) {
121
110
  if (i <= 0) return s;
122
111
  for (let n = 1; n < 16; n++) {
123
112
  const l = t[n] || 0;
124
- l > 0 && s.push({ type: n, label: _e[n] || "t" + n, pct: 100 * l / i, color: U[n] || U[1] });
113
+ l > 0 && s.push({ type: n, label: le[n] || "t" + n, pct: 100 * l / i, color: U[n] || U[1] });
125
114
  }
126
115
  return s.sort((n, l) => l.pct - n.pct);
127
116
  }
128
- const ce = [
129
- [11, 44, 122],
130
- [19, 89, 133],
131
- [28, 136, 145],
132
- [4, 214, 15],
133
- [170, 242, 0],
134
- [252, 249, 3],
135
- [245, 206, 10],
136
- [227, 136, 32],
137
- [209, 104, 48],
138
- [194, 82, 60],
139
- [148, 38, 22]
140
- ].map((t) => [t[0] / 255, t[1] / 255, t[2] / 255]);
141
- function he(t, i, s, n) {
142
- const l = n.length;
143
- if (!(s > i)) return n[0];
144
- const a = (s - i) / (l - 1), h = (t - i) / a, _ = Math.max(0, Math.min(l - 1, Math.floor(h))), y = Math.max(0, Math.min(l - 1, _ + 1)), v = h - _, r = n[_], x = n[y];
145
- return [r[0] + (x[0] - r[0]) * v, r[1] + (x[1] - r[1]) * v, r[2] + (x[2] - r[2]) * v];
146
- }
147
- const ae = [
148
- { key: "feature", label: "Feature type", cont: !1, unit: "" },
149
- { key: "speed", label: "Speed", cont: !0, unit: "mm/s" },
150
- { key: "height", label: "Layer Height", cont: !0, unit: "mm" },
151
- { key: "width", label: "Line Width", cont: !0, unit: "mm" },
152
- { key: "fan", label: "Fan Speed", cont: !0, unit: "%" },
153
- { key: "temp", label: "Temperature", cont: !0, unit: "°C" }
154
- ];
155
- function de(t, i, s, n) {
156
- const l = i.vType[s], a = i.vLayer[s], h = a === 0;
157
- switch (t) {
158
- case "height":
159
- return i.vHeight[s];
160
- case "width":
161
- return i.vWidth[s];
162
- case "speed":
163
- return h ? n.firstLayerSpeed : n.speedByType[l] ?? n.speedByType[1];
164
- case "fan":
165
- return a < n.closeFanLayers ? 0 : l === 9 ? 100 : n.fanNormal;
166
- case "temp":
167
- return h ? n.tempFirst : n.tempNormal;
168
- default:
169
- return 0;
170
- }
171
- }
172
- function ge(t, i, s) {
173
- const { meta: n, nV: l } = t, a = new Float32Array(l * 4), h = ae.find((r) => r.key === i) || ae[0];
174
- if (!h.cont) {
175
- for (let r = 0; r < l; r++) a[r * 4] = ie(U[n.vType[r]] || U[1]);
176
- return { color: a, min: 0, max: 0, viewType: i, label: h.label, unit: h.unit, cont: !1 };
177
- }
178
- let _ = 1 / 0, y = -1 / 0;
179
- const v = new Float32Array(l);
180
- for (let r = 0; r < l; r++) {
181
- const x = de(i, n, r, s);
182
- v[r] = x, x < _ && (_ = x), x > y && (y = x);
183
- }
184
- Number.isFinite(_) || (_ = 0, y = 1);
185
- for (let r = 0; r < l; r++) {
186
- const x = he(v[r], _, y, ce);
187
- a[r * 4] = ie(x);
188
- }
189
- return { color: a, min: _, max: y, viewType: i, label: h.label, unit: h.unit, cont: !0 };
190
- }
191
- const fe = `
117
+ const he = `
192
118
  precision highp float;
193
119
  precision highp int;
194
120
  precision highp sampler2D;
@@ -209,11 +135,11 @@ uniform mat4 projection_matrix;
209
135
  uniform vec3 camera_position;
210
136
  uniform sampler2D position_tex;
211
137
  uniform sampler2D height_width_angle_tex;
212
- uniform int layer_lo; // 25단계: 이중 슬라이더 하한(레이어). 범위 밖 세그먼트는 셰이더가 O(1) 클립.
213
- uniform int layer_hi; // 상한은 instanceCount 로 컷(레이어 순 정렬).
138
+ uniform int layer_lo; // stage 25: dual slider lower bound (layer). The shader clips out-of-range segments in O(1).
139
+ uniform int layer_hi; // the upper bound is cut via instanceCount (sorted by layer).
214
140
  in float vertex_id_float;
215
- in uint seg_id_a_u; // 인스턴스 어트리뷰트(구 segment_index_tex.r) — 어트리뷰트 fetch 가 texelFetch 보다 쌈
216
- in uint seg_layer_u; // 인스턴스 어트리뷰트(구 segment_index_tex.g)
141
+ in uint seg_id_a_u; // instance attribute (formerly segment_index_tex.r) — attribute fetch is cheaper than texelFetch
142
+ in uint seg_layer_u; // instance attribute (formerly segment_index_tex.g)
217
143
  out vec3 color;
218
144
  vec3 decode_color(float col) {
219
145
  int c = int(round(col));
@@ -236,7 +162,7 @@ ivec2 tex_coord(sampler2D sampler, int id) {
236
162
  void main() {
237
163
  int vertex_id = int(vertex_id_float);
238
164
  int seg_layer = int(seg_layer_u);
239
- if (seg_layer < layer_lo || seg_layer > layer_hi) { gl_Position = vec4(2.0, 2.0, 2.0, 1.0); return; } // 범위 밖 → 클립
165
+ if (seg_layer < layer_lo || seg_layer > layer_hi) { gl_Position = vec4(2.0, 2.0, 2.0, 1.0); return; } // out of range -> clip
240
166
  int id_a = int(seg_id_a_u);
241
167
  int id_b = id_a + 1;
242
168
  vec3 pos_a = texelFetch(position_tex, tex_coord(position_tex, id_a), 0).xyz;
@@ -308,22 +234,51 @@ void main() {
308
234
  color = color_base * lighting(eye_position, eye_normal);
309
235
  gl_Position = projection_matrix * vec4(eye_position, 1.0);
310
236
  }
311
- `, pe = `
237
+ `, de = `
312
238
  precision highp float;
313
239
  in vec3 color;
314
240
  out vec4 fragment_color;
315
241
  void main() {
316
242
  fragment_color = vec4(color, 1.0);
317
243
  }
318
- `;
319
- function me(t, i) {
244
+ `, fe = [
245
+ 0,
246
+ 1,
247
+ 2,
248
+ 0,
249
+ 2,
250
+ 3,
251
+ // front spike
252
+ 0,
253
+ 3,
254
+ 4,
255
+ 0,
256
+ 4,
257
+ 5,
258
+ // right/bottom body
259
+ 0,
260
+ 5,
261
+ 6,
262
+ 0,
263
+ 6,
264
+ 1,
265
+ // left/top body
266
+ 5,
267
+ 4,
268
+ 7,
269
+ 5,
270
+ 7,
271
+ 6
272
+ // back spike
273
+ ];
274
+ function ge(t, i) {
320
275
  const s = (c, d) => {
321
- const u = Math.min(2048, Math.max(1, d)), f = Math.max(1, Math.ceil(d / u)), C = new Float32Array(u * f * 4);
322
- C.set(c.subarray(0, Math.min(c.length, u * f * 4)));
323
- const z = new t.DataTexture(C, u, f, t.RGBAFormat, t.FloatType);
276
+ const p = Math.min(2048, Math.max(1, d)), f = Math.max(1, Math.ceil(d / p)), C = new Float32Array(p * f * 4);
277
+ C.set(c.subarray(0, Math.min(c.length, p * f * 4)));
278
+ const z = new t.DataTexture(C, p, f, t.RGBAFormat, t.FloatType);
324
279
  return z.minFilter = z.magFilter = t.NearestFilter, z.generateMipmaps = !1, z.needsUpdate = !0, z;
325
280
  }, n = s(i.position, i.nV), l = s(i.hwa, i.nV), a = new t.InstancedBufferGeometry();
326
- a.setIndex(le), a.setAttribute("position", new t.BufferAttribute(new Float32Array(8 * 3), 3)), a.setAttribute("vertex_id_float", new t.BufferAttribute(new Float32Array([0, 1, 2, 3, 4, 5, 6, 7]), 1));
281
+ a.setIndex(fe), a.setAttribute("position", new t.BufferAttribute(new Float32Array(8 * 3), 3)), a.setAttribute("vertex_id_float", new t.BufferAttribute(new Float32Array([0, 1, 2, 3, 4, 5, 6, 7]), 1));
327
282
  const h = new t.InstancedInterleavedBuffer(i.segIndex, 4);
328
283
  a.setAttribute("seg_id_a_u", new t.InterleavedBufferAttribute(h, 1, 0)), a.setAttribute("seg_layer_u", new t.InterleavedBufferAttribute(h, 1, 1)), a.instanceCount = 0;
329
284
  const _ = new t.RawShaderMaterial({
@@ -337,50 +292,94 @@ function me(t, i) {
337
292
  layer_lo: { value: 0 },
338
293
  layer_hi: { value: i.layerCount }
339
294
  },
340
- vertexShader: fe,
341
- fragmentShader: pe,
295
+ vertexShader: he,
296
+ fragmentShader: de,
342
297
  side: t.DoubleSide
343
298
  }), y = new t.Mesh(a, _);
344
299
  let v = null;
345
300
  if (i.bbox) {
346
- const { min: c, max: d } = i.bbox, u = new t.Vector3((c[0] + d[0]) / 2, (c[1] + d[1]) / 2, (c[2] + d[2]) / 2), f = Math.hypot(d[0] - c[0], d[1] - c[1], d[2] - c[2]) / 2 + 2;
347
- v = new t.Sphere(u, f);
301
+ const { min: c, max: d } = i.bbox, p = new t.Vector3((c[0] + d[0]) / 2, (c[1] + d[1]) / 2, (c[2] + d[2]) / 2), f = Math.hypot(d[0] - c[0], d[1] - c[1], d[2] - c[2]) / 2 + 2;
302
+ v = new t.Sphere(p, f);
348
303
  }
349
304
  y.frustumCulled = !!v, v && (a.boundingSphere = v);
350
305
  const r = new t.Matrix4(), x = new t.Vector3();
351
- y.onBeforeRender = (c, d, u) => {
352
- _.uniforms.projection_matrix.value.copy(u.projectionMatrix), _.uniforms.view_matrix.value.multiplyMatrices(u.matrixWorldInverse, y.matrixWorld), r.copy(y.matrixWorld).invert(), u.getWorldPosition(x).applyMatrix4(r), _.uniforms.camera_position.value.copy(x);
306
+ y.onBeforeRender = (c, d, p) => {
307
+ _.uniforms.projection_matrix.value.copy(p.projectionMatrix), _.uniforms.view_matrix.value.multiplyMatrices(p.matrixWorldInverse, y.matrixWorld), r.copy(y.matrixWorld).invert(), p.getWorldPosition(x).applyMatrix4(r), _.uniforms.camera_position.value.copy(x);
353
308
  };
354
309
  const b = new t.BufferGeometry();
355
310
  b.setAttribute("position", new t.BufferAttribute(i.travelPos, 3)), b.setDrawRange(0, 0);
356
311
  const A = new t.LineSegments(b, new t.LineBasicMaterial({ color: 7041658 }));
357
312
  A.frustumCulled = !!v, A.visible = !1, v && (b.boundingSphere = v);
358
313
  let M = !1, L = 0, P = i.layerCount - 1;
359
- const k = () => {
314
+ const D = () => {
360
315
  const c = i.travelPrefix[L], d = i.travelPrefix[P + 1];
361
316
  b.setDrawRange(M ? c * 2 : 0, M ? (d - c) * 2 : 0);
362
317
  }, W = (c, d) => {
363
- const u = i.layerCount;
364
- L = Math.max(0, Math.min(u - 1, c | 0)), P = Math.max(L, Math.min(u - 1, d | 0)), _.uniforms.layer_lo.value = L, _.uniforms.layer_hi.value = P, a.instanceCount = i.layerSegPrefix[P + 1], k();
318
+ const p = i.layerCount;
319
+ L = Math.max(0, Math.min(p - 1, c | 0)), P = Math.max(L, Math.min(p - 1, d | 0)), _.uniforms.layer_lo.value = L, _.uniforms.layer_hi.value = P, a.instanceCount = i.layerSegPrefix[P + 1], D();
365
320
  };
366
321
  return { mesh: y, travLines: A, setVisibleLayers: (c) => W(0, (c | 0) - 1), setLayerRange: W, setTravelVisible: (c) => {
367
- M = !!c, A.visible = M, k();
322
+ M = !!c, A.visible = M, D();
368
323
  }, setColors: (c) => {
369
- const d = l.image.data, u = Math.min(c.length, d.length) / 4;
370
- for (let f = 0; f < u; f++) d[f * 4 + 3] = c[f * 4];
324
+ const d = l.image.data, p = Math.min(c.length, d.length) / 4;
325
+ for (let f = 0; f < p; f++) d[f * 4 + 3] = c[f * 4];
371
326
  l.needsUpdate = !0;
372
327
  }, dispose: () => {
373
328
  a.dispose(), _.dispose(), n.dispose(), l.dispose(), b.dispose(), A.material.dispose();
374
329
  }, nSeg: i.nSeg, layerCount: i.layerCount };
375
330
  }
331
+ const ae = [
332
+ { key: "feature", label: "Feature type", cont: !1, unit: "" },
333
+ { key: "speed", label: "Speed", cont: !0, unit: "mm/s" },
334
+ { key: "height", label: "Layer Height", cont: !0, unit: "mm" },
335
+ { key: "width", label: "Line Width", cont: !0, unit: "mm" },
336
+ { key: "fan", label: "Fan Speed", cont: !0, unit: "%" },
337
+ { key: "temp", label: "Temperature", cont: !0, unit: "°C" }
338
+ ];
339
+ function ue(t, i, s, n) {
340
+ const l = i.vType[s], a = i.vLayer[s], h = a === 0;
341
+ switch (t) {
342
+ case "height":
343
+ return i.vHeight[s];
344
+ case "width":
345
+ return i.vWidth[s];
346
+ case "speed":
347
+ return h ? n.firstLayerSpeed : n.speedByType[l] ?? n.speedByType[1];
348
+ case "fan":
349
+ return a < n.closeFanLayers ? 0 : l === 9 ? 100 : n.fanNormal;
350
+ case "temp":
351
+ return h ? n.tempFirst : n.tempNormal;
352
+ default:
353
+ return 0;
354
+ }
355
+ }
356
+ function me(t, i, s) {
357
+ const { meta: n, nV: l } = t, a = new Float32Array(l * 4), h = ae.find((r) => r.key === i) || ae[0];
358
+ if (!h.cont) {
359
+ for (let r = 0; r < l; r++) a[r * 4] = ie(U[n.vType[r]] || U[1]);
360
+ return { color: a, min: 0, max: 0, viewType: i, label: h.label, unit: h.unit, cont: !1 };
361
+ }
362
+ let _ = 1 / 0, y = -1 / 0;
363
+ const v = new Float32Array(l);
364
+ for (let r = 0; r < l; r++) {
365
+ const x = ue(i, n, r, s);
366
+ v[r] = x, x < _ && (_ = x), x > y && (y = x);
367
+ }
368
+ Number.isFinite(_) || (_ = 0, y = 1);
369
+ for (let r = 0; r < l; r++) {
370
+ const x = ce(v[r], _, y, _e);
371
+ a[r * 4] = ie(x);
372
+ }
373
+ return { color: a, min: _, max: y, viewType: i, label: h.label, unit: h.unit, cont: !0 };
374
+ }
376
375
  export {
377
- ce as DEFAULT_RANGES_COLORS,
376
+ _e as DEFAULT_RANGES_COLORS,
378
377
  U as TYPE_COLOR,
379
- _e as TYPE_LABEL,
380
- le as VERTEX_DATA,
378
+ le as TYPE_LABEL,
379
+ fe as VERTEX_DATA,
381
380
  ae as VIEW_TYPES,
382
- ue as buildSegmentData,
383
- ge as computeColors,
384
- me as makeToolpath,
381
+ pe as buildSegmentData,
382
+ me as computeColors,
383
+ ge as makeToolpath,
385
384
  ve as roleRatios
386
385
  };
package/viewer/styles.css CHANGED
@@ -1,14 +1,14 @@
1
- /* Shadow DOM 격리(shadow_host.jsx) — 이 파일은 ?inline 으로 번들에 문자열로 내장돼
2
- shadow root 에 주입된다. 외부 리셋(*{box-sizing} 등)이 닿지 않으므로 여기서 명시한다. */
1
+ /* Shadow DOM isolation (shadow_host.jsx) — this file is inlined into the bundle as a string via ?inline
2
+ and injected into the shadow root. Outside resets (*{box-sizing}, …) cannot reach it, so they are spelled out here. */
3
3
  :host { display: contents; }
4
4
  * { box-sizing: border-box; }
5
5
 
6
- /* ===== 27단계: 데스크톱형 셸 (상단바 · 좌측 레일 · 중앙 뷰포트 · 우측 사이드바) ===== */
6
+ /* ===== Stage 27: desktop-style shell (top bar · left rail · center viewport · right sidebar) ===== */
7
7
  .app-shell { position: absolute; inset: 0; display: flex; flex-direction: column; background: #0e1216; color: #cdd6dd;
8
8
  font: 14px/1.5 -apple-system, "Segoe UI", sans-serif; }
9
9
  .app-body { flex: 1 1 auto; display: flex; min-height: 0; }
10
10
 
11
- /* S1 상단바 */
11
+ /* S1 top bar */
12
12
  .topbar { flex: 0 0 44px; height: 44px; display: flex; align-items: center; padding: 0 12px; gap: 10px;
13
13
  background: #161b21; border-bottom: 1px solid #262d35; }
14
14
  .topbar .tb-left, .topbar .tb-right { flex: 1 1 0; display: flex; align-items: center; gap: 8px; }
@@ -25,7 +25,7 @@
25
25
  .tb-tabs button:disabled { color: #5a6570; cursor: not-allowed; }
26
26
  .tb-icon { width: 28px; height: 28px; border: 1px solid #2c353d; background: #20272f; color: #7d8b96; border-radius: 6px; font-size: 15px; cursor: not-allowed; }
27
27
 
28
- /* S3 좌측 기즈모 레일 */
28
+ /* S3 left gizmo rail */
29
29
  .left-rail { flex: 0 0 48px; display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 9px 0;
30
30
  background: #12171c; border-right: 1px solid #232a31; }
31
31
  .left-rail button { width: 36px; height: 36px; border: 1px solid transparent; border-radius: 8px; background: transparent;
@@ -36,12 +36,12 @@
36
36
  .left-rail button.on img { opacity: 1; filter: brightness(1.6); }
37
37
  .rail-sep { width: 26px; height: 1px; background: #2a323a; margin: 3px 0; }
38
38
 
39
- /* 중앙 뷰포트 칼럼 */
39
+ /* Center viewport column */
40
40
  .viewport-col { position: relative; flex: 1 1 auto; min-width: 0; background: #161a1e; }
41
41
  .vp-top-toolbar { position: absolute; top: 12px; left: 50%; transform: translateX(-50%); display: flex; align-items: center;
42
42
  gap: 3px; background: rgba(18,23,28,.85); padding: 4px; border-radius: 9px; border: 1px solid #2a323a; z-index: 3;
43
- /* 33단계: 버튼이 늘어남. flex-wrap 은 절대배치 박스의 shrink-to-fit 폭을 붕괴시켜(실측 848→424px)
44
- 내용이 다 들어가는데도 2줄로 접힌다. 한 줄을 유지하고, 좁은 뷰포트에서만 가로 스크롤로 흘린다. */
43
+ /* Stage 33: more buttons. flex-wrap collapses the shrink-to-fit width of an absolutely positioned box (measured 848 -> 424px),
44
+ folding into two rows even when everything fits. Keep one row and let narrow viewports scroll horizontally instead. */
45
45
  max-width: calc(100% - 24px); overflow-x: auto; scrollbar-width: none; }
46
46
  .vp-top-toolbar::-webkit-scrollbar { display: none; }
47
47
  .vp-top-toolbar > * { flex: 0 0 auto; }
@@ -52,7 +52,7 @@
52
52
  .vp-top-toolbar button:disabled { opacity: .32; cursor: not-allowed; }
53
53
  .vtt-sep { width: 1px; align-self: stretch; background: #333c45; margin: 3px 3px; }
54
54
 
55
- /* 29단계-2: 플레이트 탭바 + 슬라이스 드롭다운 */
55
+ /* Stage 29-2: plate tab bar + slice dropdown */
56
56
  .plate-bar { position: absolute; right: 14px; bottom: 30px; display: flex; align-items: center; gap: 4px; z-index: 3;
57
57
  background: rgba(18,23,28,.85); border: 1px solid #2a323a; border-radius: 9px; padding: 4px; }
58
58
  .plate-bar button { min-width: 26px; height: 26px; border: 1px solid #2c353d; background: #1a2027; color: #9fb3c0;
@@ -68,7 +68,7 @@
68
68
  color: #24282e; cursor: pointer; font-size: 13px; }
69
69
  .slice-menu button:hover { background: #eef7f1; }
70
70
 
71
- /* 페인팅 플로팅 패널 */
71
+ /* Floating painting panel */
72
72
  .brush-panel { position: absolute; top: 58px; left: 14px; width: 214px; background: rgba(22,27,33,.95);
73
73
  border: 1px solid #2c353d; border-radius: 10px; padding: 10px; z-index: 4; box-shadow: 0 3px 14px rgba(0,0,0,.45); }
74
74
  .bp-title { font-weight: 600; margin-bottom: 6px; color: #cdd6dd; }
@@ -80,12 +80,12 @@
80
80
  .bp-radius input { width: 100%; }
81
81
  .bp-counts { margin-top: 6px; font-size: 11px; }
82
82
 
83
- /* Preview 좌하단 stats 카드 */
83
+ /* Preview stats card, bottom left */
84
84
  .stats-card { position: absolute; left: 14px; bottom: 32px; background: rgba(22,27,33,.93); border: 1px solid #2c353d;
85
85
  border-radius: 10px; padding: 10px 14px; font-size: 13px; line-height: 1.7; z-index: 2; color: #cdd6dd; }
86
86
  .stats-card b { color: #9fe3bd; }
87
87
 
88
- /* S4 우측 사이드바 (라이트) */
88
+ /* S4 right sidebar (light) */
89
89
  .sidebar { flex: 0 0 384px; width: 384px; display: flex; flex-direction: column; background: #eef0f2; color: #24282e;
90
90
  border-left: 1px solid #d5d8dd; min-height: 0; }
91
91
  .sidebar-scroll { flex: 1 1 auto; overflow-y: auto; padding: 10px; display: flex; flex-direction: column; gap: 10px; }
@@ -114,12 +114,12 @@
114
114
  .obj-del { border: 0; background: #fbe9ec; color: #d0435a; border-radius: 5px; cursor: pointer; padding: 0 6px; }
115
115
  .side-card .slice-support { color: #444; font-size: 12px; margin: 5px 0 0; }
116
116
 
117
- /* 프로세스 카드 = 설정 패널 임베드 (라이트, 전폭) */
117
+ /* Process card = embedded settings panel (light, full width) */
118
118
  .process-card { padding: 8px; }
119
- /* 임베드된 SettingsPanel 의 폭·테두리 오버라이드는 이제 패널 자신의 shadow 안 규칙
120
- (:host(.sp-embedded), components/styles.css) — 여기서는 경계 너머라 닿지 않는다. */
119
+ /* Width/border overrides for the embedded SettingsPanel now live in the panel's own shadow rules
120
+ (:host(.sp-embedded), components/styles.css) — they are across the boundary and unreachable from here. */
121
121
 
122
- /* Preview 컨트롤(라이트 카드 내부) 오버라이드 */
122
+ /* Preview control overrides (inside the light card) */
123
123
  .side-card .view-type-row { color: #444; }
124
124
  .side-card .view-type-row select, .side-card .obj-ext { background: #fff; color: #222; border: 1px solid #ccd0d5; }
125
125
  .side-card .slice-layer label { color: #444; font-size: 12px; }
@@ -128,9 +128,11 @@
128
128
  .side-card .layer-ctl button { background: #f1f2f4; color: #333; border: 1px solid #d3d6db; }
129
129
  .side-card .layer-ctl button.on { background: #00ae42; color: #fff; border-color: #00ae42; }
130
130
 
131
- /* ⑤ 하단 고정 버튼 바 */
131
+ /* (5) Fixed bottom button bar */
132
132
  .side-bottom { flex: 0 0 auto; display: flex; gap: 8px; padding: 10px; border-top: 1px solid #dcdfe3; background: #e6e9ec; }
133
133
  .side-bottom .slice-btn { flex: 1.4; margin: 0; }
134
+ .auto-slice { display: flex; align-items: center; gap: 5px; font-size: 12px; color: #444; cursor: pointer; white-space: nowrap; user-select: none; }
135
+ .auto-slice input { margin: 0; }
134
136
  .export-btn { flex: 1; text-align: center; padding: 9px; border-radius: 8px; background: #dfe6ea; color: #1f6feb;
135
137
  border: 1px solid #c7d2da; text-decoration: none; font-weight: 600; cursor: pointer; font-size: 13px; }
136
138
  .export-btn:hover { background: #d3dee4; }
@@ -143,7 +145,7 @@
143
145
  .vp-status { position: absolute; bottom: 8px; left: 12px; right: 12px; z-index: 1;
144
146
  font-size: 11.5px; color: #9fe3bd; font-family: ui-monospace, monospace;
145
147
  text-shadow: 0 1px 2px #000; pointer-events: none; }
146
- /* ---- Slice 상태 표시 (뷰포트 오버레이) ---- */
148
+ /* ---- Slice status display (viewport overlay) ---- */
147
149
  .slice-warn { color: #f0c060; margin: 4px 0; }
148
150
  .slice-btn { width: 100%; margin-top: 6px; padding: 7px; border: 0; border-radius: 7px;
149
151
  background: #00ae42; color: #fff; font-weight: 700; cursor: pointer; }
@@ -155,7 +157,7 @@
155
157
  .slice-layer input[type=range] { width: 100%; }
156
158
  .slice-travel { color: #aebcc6; display: flex; align-items: center; gap: 4px; }
157
159
 
158
- /* 25단계 S6: 뷰 타입 셀렉트 · 이중 슬라이더 · 그라디언트 범례 */
160
+ /* Stage 25 S6: view type select · dual slider · gradient legend */
159
161
  .view-type-row { display: flex; align-items: center; justify-content: space-between; gap: 6px; color: #aebcc6; }
160
162
  .view-type-row select { flex: 1; background: #10141a; color: #eee; border: 1px solid #2c353d; border-radius: 4px; padding: 2px 4px; }
161
163
  .dual-slider { position: relative; height: 22px; }
@@ -176,7 +178,7 @@
176
178
  .role-item i { width: 10px; height: 6px; border-radius: 2px; display: inline-block; }
177
179
  .role-item b { color: #cdd6dd; }
178
180
 
179
- /* 26단계: 빈 씬 안내 + 드래그앤드롭 오버레이 */
181
+ /* Stage 26: empty scene hint + drag-and-drop overlay */
180
182
  .vp-canvas.drag-over { outline: 3px dashed #00ae42; outline-offset: -6px; }
181
183
  .empty-hint { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
182
184
  gap: 8px; pointer-events: none; color: #9fb3c0; text-align: center; }
@@ -190,12 +192,12 @@
190
192
  background: rgba(0,174,66,.14); color: #d7f7e3; font-size: 18px; font-weight: 700; pointer-events: none;
191
193
  border: 3px dashed #00ae42; box-sizing: border-box; }
192
194
 
193
- /* 오브젝트 분리 버튼 */
195
+ /* Split objects button */
194
196
  .obj-split { border: 0; background: transparent; cursor: pointer; padding: 0 3px; line-height: 0; }
195
197
  .obj-split img { width: 13px; height: 13px; opacity: .6; }
196
198
  .obj-split:hover img { opacity: 1; }
197
199
 
198
- /* 우클릭 컨텍스트 메뉴 */
200
+ /* Right-click context menu */
199
201
  .ctx-scrim { position: absolute; inset: 0; z-index: 24; }
200
202
  .ctx-menu { position: absolute; z-index: 25; min-width: 168px; padding: 4px;
201
203
  background: #1b2027; border: 1px solid #2f3944; border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.5); }
@@ -208,7 +210,7 @@
208
210
  .ctx-menu hr { margin: 4px 6px; border: 0; border-top: 1px solid #2f3944; }
209
211
  .ctx-key { color: #7d8b96; font-family: ui-monospace, monospace; font-size: 11px; }
210
212
 
211
- /* 단축키 도움말 오버레이 ('?') */
213
+ /* Keyboard shortcut help overlay ('?') */
212
214
  .help-overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
213
215
  background: rgba(8,12,16,.72); z-index: 20; }
214
216
  .help-card { background: #1b2027; color: #dfe6ee; border: 1px solid #2f3944; border-radius: 10px;
@@ -220,12 +222,12 @@
220
222
  .help-card dt { color: #9fb0c2; font-family: ui-monospace, monospace; white-space: nowrap; }
221
223
  .help-card dd { margin: 0; }
222
224
 
223
- /* 멀티 오브젝트 목록 / 서포트 토글 (3단계) */
225
+ /* Multi-object list / support toggle (stage 3) */
224
226
  .slice-support { display: flex; align-items: center; gap: 5px; margin: 6px 0; color: #cdd6dd; font-size: 12px; }
225
227
 
226
228
  .slice-support .muted { font-size: 10px; }
227
229
 
228
- /* 프린터 카드 — 베드 폭×깊이 인라인 편집 */
230
+ /* Printer card — inline editing of bed width x depth */
229
231
  .sc-bed { display: inline-flex; align-items: center; gap: 4px; font-weight: 700; color: #222; }
230
232
  .sc-bed input { width: 54px; padding: 1px 4px; font-size: 12px; font-weight: 600; text-align: right;
231
233
  border: 1px solid #ccd0d5; border-radius: 5px; background: #fff; color: #222; }