brepjs-viewer 0.1.0

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.
@@ -0,0 +1,1194 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ let react = require("react");
25
+ let three = require("three");
26
+ three = __toESM(three, 1);
27
+ let react_jsx_runtime = require("react/jsx-runtime");
28
+ let _react_three_fiber = require("@react-three/fiber");
29
+ let _react_three_drei = require("@react-three/drei");
30
+ //#region src/geometry.ts
31
+ function buildGeometry(data) {
32
+ const geo = new three.BufferGeometry();
33
+ geo.setAttribute("position", new three.BufferAttribute(data.position, 3));
34
+ geo.setAttribute("normal", new three.BufferAttribute(data.normal, 3));
35
+ geo.setIndex(new three.BufferAttribute(data.index, 1));
36
+ return geo;
37
+ }
38
+ function meshBounds(data) {
39
+ const p = data.position;
40
+ if (p.length < 3) return {
41
+ min: [
42
+ 0,
43
+ 0,
44
+ 0
45
+ ],
46
+ max: [
47
+ 0,
48
+ 0,
49
+ 0
50
+ ]
51
+ };
52
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
53
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
54
+ for (let i = 0; i + 2 < p.length; i += 3) {
55
+ const x = p[i];
56
+ const y = p[i + 1];
57
+ const z = p[i + 2];
58
+ if (x < minX) minX = x;
59
+ if (x > maxX) maxX = x;
60
+ if (y < minY) minY = y;
61
+ if (y > maxY) maxY = y;
62
+ if (z < minZ) minZ = z;
63
+ if (z > maxZ) maxZ = z;
64
+ }
65
+ return {
66
+ min: [
67
+ minX,
68
+ minY,
69
+ minZ
70
+ ],
71
+ max: [
72
+ maxX,
73
+ maxY,
74
+ maxZ
75
+ ]
76
+ };
77
+ }
78
+ function meshSize(data) {
79
+ const { min, max } = meshBounds(data);
80
+ return [
81
+ max[0] - min[0],
82
+ max[1] - min[1],
83
+ max[2] - min[2]
84
+ ];
85
+ }
86
+ function sectionPlane(axis, position, flip = false) {
87
+ const normal = new three.Vector3(axis === "x" ? 1 : 0, axis === "y" ? 1 : 0, axis === "z" ? 1 : 0);
88
+ if (flip) normal.negate();
89
+ const point = new three.Vector3(axis === "x" ? position : 0, axis === "y" ? position : 0, axis === "z" ? position : 0);
90
+ return new three.Plane(normal, -normal.dot(point));
91
+ }
92
+ function findFaceGroupAt(groups, triangleIndex) {
93
+ const off = triangleIndex * 3;
94
+ let lo = 0;
95
+ let hi = groups.length - 1;
96
+ while (lo <= hi) {
97
+ const mid = lo + hi >>> 1;
98
+ const g = groups[mid];
99
+ if (!g) break;
100
+ if (off < g.start) hi = mid - 1;
101
+ else if (off >= g.start + g.count) lo = mid + 1;
102
+ else return g;
103
+ }
104
+ return null;
105
+ }
106
+ //#endregion
107
+ //#region src/Renderer.tsx
108
+ function Renderer({ data, viewMode = "solid", clippingPlanes, onFacePick, onFaceHover, onFaceContextMenu }) {
109
+ const pickable = Boolean(data.faceGroups && data.faceInfos && (onFacePick || onFaceHover || onFaceContextMenu));
110
+ const geometry = (0, react.useMemo)(() => buildGeometry(data), [
111
+ data.position,
112
+ data.normal,
113
+ data.index
114
+ ]);
115
+ (0, react.useEffect)(() => () => geometry.dispose(), [geometry]);
116
+ (0, react.useEffect)(() => () => {
117
+ document.body.style.cursor = "";
118
+ }, []);
119
+ const faceInfoById = (0, react.useMemo)(() => {
120
+ if (!data.faceInfos) return null;
121
+ const m = /* @__PURE__ */ new Map();
122
+ for (const i of data.faceInfos) m.set(i.faceId, i);
123
+ return m;
124
+ }, [data.faceInfos]);
125
+ const resolveFace = (0, react.useCallback)((e) => {
126
+ if (!data.faceGroups || !faceInfoById) return null;
127
+ const t = e.faceIndex;
128
+ if (t === void 0 || t === null) return null;
129
+ const g = findFaceGroupAt(data.faceGroups, t);
130
+ return g ? faceInfoById.get(g.faceId) ?? null : null;
131
+ }, [data.faceGroups, faceInfoById]);
132
+ const onClick = (0, react.useCallback)((e) => {
133
+ const info = resolveFace(e);
134
+ if (!info) return;
135
+ e.stopPropagation();
136
+ onFacePick?.(info, e.shiftKey, {
137
+ x: e.clientX,
138
+ y: e.clientY
139
+ });
140
+ }, [resolveFace, onFacePick]);
141
+ const onCtx = (0, react.useCallback)((e) => {
142
+ const info = resolveFace(e);
143
+ if (!info) return;
144
+ e.stopPropagation();
145
+ e.nativeEvent.preventDefault();
146
+ onFaceContextMenu?.(info, {
147
+ x: e.clientX,
148
+ y: e.clientY
149
+ });
150
+ }, [resolveFace, onFaceContextMenu]);
151
+ const onMove = (0, react.useCallback)((e) => {
152
+ const info = resolveFace(e);
153
+ if (!info) return;
154
+ onFaceHover?.(info, {
155
+ x: e.clientX,
156
+ y: e.clientY
157
+ });
158
+ }, [resolveFace, onFaceHover]);
159
+ const onOver = (0, react.useCallback)(() => {
160
+ document.body.style.cursor = "pointer";
161
+ }, []);
162
+ const onOut = (0, react.useCallback)(() => {
163
+ document.body.style.cursor = "";
164
+ onFaceHover?.(null);
165
+ }, [onFaceHover]);
166
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("mesh", {
167
+ geometry,
168
+ ...pickable ? {
169
+ onClick,
170
+ onContextMenu: onCtx,
171
+ onPointerOver: onOver,
172
+ onPointerOut: onOut,
173
+ onPointerMove: onMove
174
+ } : {},
175
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("meshStandardMaterial", {
176
+ color: data.color ?? "#d4d8dc",
177
+ metalness: 0,
178
+ roughness: .45,
179
+ emissive: data.color ?? "#d4d8dc",
180
+ emissiveIntensity: .08,
181
+ side: viewMode === "solid" ? three.FrontSide : three.DoubleSide,
182
+ polygonOffset: true,
183
+ polygonOffsetFactor: 1,
184
+ polygonOffsetUnits: 1,
185
+ wireframe: viewMode === "wireframe",
186
+ transparent: viewMode === "xray",
187
+ opacity: viewMode === "xray" ? .35 : 1,
188
+ depthWrite: viewMode !== "xray",
189
+ clippingPlanes: clippingPlanes ?? null
190
+ })
191
+ });
192
+ }
193
+ //#endregion
194
+ //#region src/EdgeRenderer.tsx
195
+ function findGroupAt(groups, vertexIndex) {
196
+ let lo = 0;
197
+ let hi = groups.length - 1;
198
+ while (lo <= hi) {
199
+ const mid = lo + hi >>> 1;
200
+ const group = groups[mid];
201
+ if (!group) break;
202
+ if (vertexIndex < group.start) hi = mid - 1;
203
+ else if (vertexIndex >= group.start + group.count) lo = mid + 1;
204
+ else return group;
205
+ }
206
+ return null;
207
+ }
208
+ function EdgeRenderer({ edges, edgeGroups, edgeInfos, clippingPlanes, onEdgePick, onEdgeHover, onEdgeContextMenu }) {
209
+ const pickable = Boolean(edgeGroups && edgeInfos && (onEdgePick || onEdgeHover || onEdgeContextMenu));
210
+ const viewportHeight = (0, _react_three_fiber.useThree)((s) => s.size.height);
211
+ const viewportHeightRef = (0, react.useRef)(viewportHeight);
212
+ viewportHeightRef.current = viewportHeight;
213
+ const lastEdgeId = (0, react.useRef)(null);
214
+ const geometry = (0, react.useMemo)(() => {
215
+ const geo = new three.BufferGeometry();
216
+ geo.setAttribute("position", new three.BufferAttribute(edges, 3));
217
+ return geo;
218
+ }, [edges]);
219
+ (0, react.useEffect)(() => {
220
+ return () => {
221
+ geometry.dispose();
222
+ };
223
+ }, [geometry]);
224
+ const edgeInfoById = (0, react.useMemo)(() => {
225
+ if (!edgeInfos) return null;
226
+ const byId = /* @__PURE__ */ new Map();
227
+ for (const info of edgeInfos) byId.set(info.edgeId, info);
228
+ return byId;
229
+ }, [edgeInfos]);
230
+ const resolveEdge = (0, react.useCallback)((event) => {
231
+ if (!edgeGroups || !edgeInfoById) return null;
232
+ const vertexIndex = event.index;
233
+ if (vertexIndex === void 0) return null;
234
+ const group = findGroupAt(edgeGroups, vertexIndex);
235
+ if (!group) return null;
236
+ return edgeInfoById.get(group.edgeId) ?? null;
237
+ }, [edgeGroups, edgeInfoById]);
238
+ const handleClick = (0, react.useCallback)((event) => {
239
+ const info = resolveEdge(event);
240
+ if (!info) return;
241
+ event.stopPropagation();
242
+ onEdgePick?.(info, event.shiftKey, {
243
+ x: event.clientX,
244
+ y: event.clientY
245
+ });
246
+ }, [resolveEdge, onEdgePick]);
247
+ const handleContextMenu = (0, react.useCallback)((event) => {
248
+ const info = resolveEdge(event);
249
+ if (!info) return;
250
+ event.stopPropagation();
251
+ event.nativeEvent.preventDefault();
252
+ onEdgeContextMenu?.(info, {
253
+ x: event.clientX,
254
+ y: event.clientY
255
+ });
256
+ }, [resolveEdge, onEdgeContextMenu]);
257
+ const handlePointerOver = (0, react.useCallback)(() => {
258
+ document.body.style.cursor = "pointer";
259
+ }, []);
260
+ const handlePointerOut = (0, react.useCallback)(() => {
261
+ document.body.style.cursor = "";
262
+ onEdgeHover?.(null);
263
+ lastEdgeId.current = null;
264
+ }, [onEdgeHover]);
265
+ const handlePointerMove = (0, react.useCallback)((event) => {
266
+ const info = resolveEdge(event);
267
+ if (!info) return;
268
+ event.stopPropagation();
269
+ lastEdgeId.current = info.edgeId;
270
+ onEdgeHover?.(info, {
271
+ x: event.clientX,
272
+ y: event.clientY
273
+ });
274
+ }, [resolveEdge, onEdgeHover]);
275
+ (0, react.useEffect)(() => {
276
+ return () => {
277
+ document.body.style.cursor = "";
278
+ onEdgeHover?.(null);
279
+ };
280
+ }, [onEdgeHover]);
281
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("lineSegments", {
282
+ geometry,
283
+ renderOrder: 1,
284
+ raycast: (0, react.useCallback)(function(raycaster, intersects) {
285
+ const lineParams = raycaster.params.Line;
286
+ if (!lineParams) {
287
+ three.LineSegments.prototype.raycast.call(this, raycaster, intersects);
288
+ return;
289
+ }
290
+ const previousThreshold = lineParams.threshold;
291
+ lineParams.threshold = computeWorldThreshold(this, raycaster, viewportHeightRef.current);
292
+ three.LineSegments.prototype.raycast.call(this, raycaster, intersects);
293
+ lineParams.threshold = previousThreshold;
294
+ }, []),
295
+ ...pickable ? {
296
+ onClick: handleClick,
297
+ onContextMenu: handleContextMenu,
298
+ onPointerOver: handlePointerOver,
299
+ onPointerOut: handlePointerOut,
300
+ onPointerMove: handlePointerMove
301
+ } : {},
302
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("lineBasicMaterial", {
303
+ color: "#000000",
304
+ depthTest: true,
305
+ linewidth: 2,
306
+ clippingPlanes: clippingPlanes ?? null
307
+ })
308
+ });
309
+ }
310
+ var PICK_THRESHOLD_PX = 6;
311
+ var FALLBACK_WORLD_THRESHOLD = .15;
312
+ function computeWorldThreshold(lines, raycaster, viewportHeight) {
313
+ const camera = raycaster.camera;
314
+ if (!camera || viewportHeight <= 0) return FALLBACK_WORLD_THRESHOLD;
315
+ if (camera.isPerspectiveCamera) {
316
+ const persp = camera;
317
+ if (!lines.geometry.boundingSphere) lines.geometry.computeBoundingSphere();
318
+ const sphere = lines.geometry.boundingSphere;
319
+ if (!sphere) return FALLBACK_WORLD_THRESHOLD;
320
+ const center = new three.Vector3().copy(sphere.center).applyMatrix4(lines.matrixWorld);
321
+ const distance = persp.position.distanceTo(center);
322
+ const fovRad = persp.fov * Math.PI / 180;
323
+ return 2 * distance * Math.tan(fovRad / 2) / viewportHeight * PICK_THRESHOLD_PX;
324
+ }
325
+ if (camera.isOrthographicCamera) {
326
+ const ortho = camera;
327
+ return (ortho.top - ortho.bottom) / ortho.zoom / viewportHeight * PICK_THRESHOLD_PX;
328
+ }
329
+ return FALLBACK_WORLD_THRESHOLD;
330
+ }
331
+ //#endregion
332
+ //#region src/SelectionHighlight.tsx
333
+ var FACE_COLOR = "#4ACECC";
334
+ var EDGE_COLOR = "#fbbf24";
335
+ function buildFaceHighlightGeometry(data, faceIds) {
336
+ if (faceIds.length === 0 || !data.faceGroups || !data.index) return null;
337
+ const groupById = new Map(data.faceGroups.map((g) => [g.faceId, g]));
338
+ let total = 0;
339
+ const ranges = [];
340
+ for (const id of faceIds) {
341
+ const g = groupById.get(id);
342
+ if (g) {
343
+ ranges.push({
344
+ start: g.start,
345
+ count: g.count
346
+ });
347
+ total += g.count;
348
+ }
349
+ }
350
+ if (total === 0) return null;
351
+ const newIndex = new Uint32Array(total);
352
+ let off = 0;
353
+ for (const r of ranges) {
354
+ newIndex.set(data.index.subarray(r.start, r.start + r.count), off);
355
+ off += r.count;
356
+ }
357
+ const geo = new three.BufferGeometry();
358
+ geo.setAttribute("position", new three.BufferAttribute(data.position, 3));
359
+ geo.setAttribute("normal", new three.BufferAttribute(data.normal, 3));
360
+ geo.setIndex(new three.BufferAttribute(newIndex, 1));
361
+ return geo;
362
+ }
363
+ function buildEdgeHighlightGeometry(data, edgeIds) {
364
+ if (edgeIds.length === 0 || !data.edgeGroups) return null;
365
+ const groupById = new Map(data.edgeGroups.map((g) => [g.edgeId, g]));
366
+ let totalVerts = 0;
367
+ const ranges = [];
368
+ for (const id of edgeIds) {
369
+ const g = groupById.get(id);
370
+ if (g) {
371
+ ranges.push({
372
+ start: g.start,
373
+ count: g.count
374
+ });
375
+ totalVerts += g.count;
376
+ }
377
+ }
378
+ if (totalVerts === 0) return null;
379
+ const newPos = new Float32Array(totalVerts * 3);
380
+ let off = 0;
381
+ for (const r of ranges) {
382
+ newPos.set(data.edges.subarray(r.start * 3, (r.start + r.count) * 3), off);
383
+ off += r.count * 3;
384
+ }
385
+ const geo = new three.BufferGeometry();
386
+ geo.setAttribute("position", new three.BufferAttribute(newPos, 3));
387
+ return geo;
388
+ }
389
+ function SelectionHighlight({ data, selectedFaceIds = [], selectedEdgeIds = [], hoverFaceId = null, hoverEdgeId = null }) {
390
+ const selectedFaceKey = selectedFaceIds.join(",");
391
+ const selectedEdgeKey = selectedEdgeIds.join(",");
392
+ const faceIds = (0, react.useMemo)(() => selectedFaceIds.filter((id) => data.faceGroups?.some((g) => g.faceId === id)), [data, selectedFaceKey]);
393
+ const edgeIds = (0, react.useMemo)(() => selectedEdgeIds.filter((id) => data.edgeGroups?.some((g) => g.edgeId === id)), [data, selectedEdgeKey]);
394
+ const faceKey = faceIds.join(",");
395
+ const edgeKey = edgeIds.join(",");
396
+ const faceGeometry = (0, react.useMemo)(() => buildFaceHighlightGeometry(data, faceIds), [data, faceKey]);
397
+ const edgeGeometry = (0, react.useMemo)(() => buildEdgeHighlightGeometry(data, edgeIds), [data, edgeKey]);
398
+ const resolvedHoverFaceId = (0, react.useMemo)(() => {
399
+ if (hoverFaceId === null) return null;
400
+ if (!data.faceGroups?.some((g) => g.faceId === hoverFaceId)) return null;
401
+ return hoverFaceId;
402
+ }, [hoverFaceId, data.faceGroups]);
403
+ const resolvedHoverEdgeId = (0, react.useMemo)(() => {
404
+ if (hoverEdgeId === null) return null;
405
+ if (!data.edgeGroups?.some((g) => g.edgeId === hoverEdgeId)) return null;
406
+ return hoverEdgeId;
407
+ }, [hoverEdgeId, data.edgeGroups]);
408
+ const hoverFaceGeometry = (0, react.useMemo)(() => resolvedHoverFaceId !== null ? buildFaceHighlightGeometry(data, [resolvedHoverFaceId]) : null, [data, resolvedHoverFaceId]);
409
+ const hoverEdgeGeometry = (0, react.useMemo)(() => resolvedHoverEdgeId !== null ? buildEdgeHighlightGeometry(data, [resolvedHoverEdgeId]) : null, [data, resolvedHoverEdgeId]);
410
+ const shouldRenderHoverFace = resolvedHoverFaceId !== null && !faceIds.includes(resolvedHoverFaceId);
411
+ const shouldRenderHoverEdge = resolvedHoverEdgeId !== null && !edgeIds.includes(resolvedHoverEdgeId);
412
+ (0, react.useEffect)(() => {
413
+ return () => {
414
+ faceGeometry?.dispose();
415
+ };
416
+ }, [faceGeometry]);
417
+ (0, react.useEffect)(() => {
418
+ return () => {
419
+ edgeGeometry?.dispose();
420
+ };
421
+ }, [edgeGeometry]);
422
+ (0, react.useEffect)(() => {
423
+ return () => {
424
+ hoverFaceGeometry?.dispose();
425
+ };
426
+ }, [hoverFaceGeometry]);
427
+ (0, react.useEffect)(() => {
428
+ return () => {
429
+ hoverEdgeGeometry?.dispose();
430
+ };
431
+ }, [hoverEdgeGeometry]);
432
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
433
+ faceGeometry && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("mesh", {
434
+ geometry: faceGeometry,
435
+ raycast: skipRaycast,
436
+ renderOrder: 2,
437
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("meshStandardMaterial", {
438
+ color: FACE_COLOR,
439
+ emissive: FACE_COLOR,
440
+ emissiveIntensity: .6,
441
+ metalness: 0,
442
+ roughness: .3,
443
+ side: three.DoubleSide,
444
+ transparent: true,
445
+ opacity: .55,
446
+ depthWrite: false,
447
+ polygonOffset: true,
448
+ polygonOffsetFactor: -2,
449
+ polygonOffsetUnits: -2
450
+ })
451
+ }),
452
+ edgeGeometry && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("lineSegments", {
453
+ geometry: edgeGeometry,
454
+ raycast: skipRaycast,
455
+ renderOrder: 3,
456
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("lineBasicMaterial", {
457
+ color: EDGE_COLOR,
458
+ depthTest: false,
459
+ transparent: true
460
+ })
461
+ }),
462
+ hoverFaceGeometry && shouldRenderHoverFace && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("mesh", {
463
+ geometry: hoverFaceGeometry,
464
+ raycast: skipRaycast,
465
+ renderOrder: 1,
466
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("meshStandardMaterial", {
467
+ color: FACE_COLOR,
468
+ emissive: FACE_COLOR,
469
+ emissiveIntensity: .35,
470
+ metalness: 0,
471
+ roughness: .3,
472
+ side: three.DoubleSide,
473
+ transparent: true,
474
+ opacity: .22,
475
+ depthWrite: false,
476
+ polygonOffset: true,
477
+ polygonOffsetFactor: -2,
478
+ polygonOffsetUnits: -2
479
+ })
480
+ }),
481
+ hoverEdgeGeometry && shouldRenderHoverEdge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("lineSegments", {
482
+ geometry: hoverEdgeGeometry,
483
+ raycast: skipRaycast,
484
+ renderOrder: 2,
485
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("lineBasicMaterial", {
486
+ color: EDGE_COLOR,
487
+ depthTest: false,
488
+ transparent: true,
489
+ opacity: .55
490
+ })
491
+ })
492
+ ] });
493
+ }
494
+ function skipRaycast() {}
495
+ //#endregion
496
+ //#region src/GradientBackground.tsx
497
+ var vertexShader$1 = `
498
+ varying vec2 vUv;
499
+ void main() {
500
+ vUv = uv;
501
+ gl_Position = vec4(position.xy, 0.9999, 1.0);
502
+ }
503
+ `;
504
+ var fragmentShader$1 = `
505
+ uniform vec3 colorTop;
506
+ uniform vec3 colorBottom;
507
+ varying vec2 vUv;
508
+ void main() {
509
+ gl_FragColor = vec4(mix(colorBottom, colorTop, vUv.y), 1.0);
510
+ }
511
+ `;
512
+ function GradientBackground({ colorTop = "#2a2a3e", colorBottom = "#2a2a3e" }) {
513
+ const matRef = (0, react.useRef)(null);
514
+ (0, react.useEffect)(() => {
515
+ return () => {
516
+ matRef.current?.dispose();
517
+ };
518
+ }, []);
519
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("mesh", {
520
+ renderOrder: -1,
521
+ frustumCulled: false,
522
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("planeGeometry", { args: [2, 2] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("shaderMaterial", {
523
+ ref: matRef,
524
+ vertexShader: vertexShader$1,
525
+ fragmentShader: fragmentShader$1,
526
+ uniforms: {
527
+ colorTop: { value: new three.Color(colorTop) },
528
+ colorBottom: { value: new three.Color(colorBottom) }
529
+ },
530
+ depthWrite: false,
531
+ depthTest: false
532
+ })]
533
+ });
534
+ }
535
+ //#endregion
536
+ //#region src/InfiniteGrid.tsx
537
+ var vertexShader = `
538
+ varying vec2 vWorldPos;
539
+ void main() {
540
+ vec4 worldPos = modelMatrix * vec4(position, 1.0);
541
+ vWorldPos = worldPos.xz;
542
+ gl_Position = projectionMatrix * viewMatrix * worldPos;
543
+ }
544
+ `;
545
+ var fragmentShader = `
546
+ uniform float cellSize;
547
+ uniform vec3 lineColor;
548
+ uniform float lineOpacity;
549
+ uniform float fadeStart;
550
+ uniform float fadeEnd;
551
+
552
+ varying vec2 vWorldPos;
553
+
554
+ void main() {
555
+ vec2 coord = vWorldPos / cellSize;
556
+ vec2 grid = abs(fract(coord - 0.5) - 0.5);
557
+ vec2 line = fwidth(coord);
558
+ vec2 gridAA = smoothstep(line * 0.5, line * 1.5, grid);
559
+ float gridLine = 1.0 - min(gridAA.x, gridAA.y);
560
+
561
+ float dist = length(vWorldPos);
562
+ float fade = 1.0 - smoothstep(fadeStart, fadeEnd, dist);
563
+
564
+ float alpha = gridLine * lineOpacity * fade;
565
+ if (alpha < 0.001) discard;
566
+
567
+ gl_FragColor = vec4(lineColor, alpha);
568
+ }
569
+ `;
570
+ function InfiniteGrid({ cellSize = 10, lineColor = "#888898", lineOpacity = .1, fadeStart = 50, fadeEnd = 200 }) {
571
+ const matRef = (0, react.useRef)(null);
572
+ const planeSize = fadeEnd * 2.5;
573
+ (0, react.useEffect)(() => {
574
+ return () => {
575
+ matRef.current?.dispose();
576
+ };
577
+ }, []);
578
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("mesh", {
579
+ rotation: [
580
+ -Math.PI / 2,
581
+ 0,
582
+ 0
583
+ ],
584
+ position: [
585
+ 0,
586
+ -.01,
587
+ 0
588
+ ],
589
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("planeGeometry", { args: [planeSize, planeSize] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("shaderMaterial", {
590
+ ref: matRef,
591
+ vertexShader,
592
+ fragmentShader,
593
+ uniforms: {
594
+ cellSize: { value: cellSize },
595
+ lineColor: { value: new three.Color(lineColor) },
596
+ lineOpacity: { value: lineOpacity },
597
+ fadeStart: { value: fadeStart },
598
+ fadeEnd: { value: fadeEnd }
599
+ },
600
+ transparent: true,
601
+ depthWrite: false,
602
+ side: three.DoubleSide,
603
+ polygonOffset: true,
604
+ polygonOffsetFactor: 1,
605
+ polygonOffsetUnits: 1
606
+ })]
607
+ });
608
+ }
609
+ //#endregion
610
+ //#region src/SceneLighting.tsx
611
+ function SceneLighting() {
612
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
613
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("hemisphereLight", { args: [
614
+ "#ffffff",
615
+ "#1a1a2e",
616
+ .65
617
+ ] }),
618
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("directionalLight", {
619
+ position: [
620
+ -50,
621
+ 60,
622
+ 80
623
+ ],
624
+ intensity: .85,
625
+ color: "#fff8f0"
626
+ }),
627
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("directionalLight", {
628
+ position: [
629
+ 40,
630
+ -40,
631
+ 30
632
+ ],
633
+ intensity: .15,
634
+ color: "#e0e8ff"
635
+ })
636
+ ] });
637
+ }
638
+ //#endregion
639
+ //#region src/SceneSetup.tsx
640
+ function SceneSetup({ autoRotate = false, target, gridVisible = true, gridProps, controlsProps, controlsRef, onControlsStart }) {
641
+ const optionalControls = {
642
+ ...controlsRef ? { ref: controlsRef } : {},
643
+ ...target ? { target } : {},
644
+ ...onControlsStart ? { onStart: onControlsStart } : {}
645
+ };
646
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
647
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SceneLighting, {}),
648
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GradientBackground, {}),
649
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_three_drei.OrbitControls, {
650
+ makeDefault: true,
651
+ autoRotate,
652
+ autoRotateSpeed: 1.5,
653
+ ...optionalControls,
654
+ ...controlsProps
655
+ }),
656
+ gridVisible && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfiniteGrid, { ...gridProps })
657
+ ] });
658
+ }
659
+ //#endregion
660
+ //#region src/ViewerCanvas.tsx
661
+ var VIEW_DIR = {
662
+ iso: new three.Vector3(.6, .5, .6),
663
+ front: new three.Vector3(0, .3, 1),
664
+ top: new three.Vector3(0, 1, -.01),
665
+ right: new three.Vector3(1, .3, 0)
666
+ };
667
+ function Framing({ data, view, fitSignal, projection, onFirstFrame }) {
668
+ const camera = (0, _react_three_fiber.useThree)((s) => s.camera);
669
+ const invalidate = (0, _react_three_fiber.useThree)((s) => s.invalidate);
670
+ const fired = (0, react.useRef)(false);
671
+ const onFirstFrameRef = (0, react.useRef)(onFirstFrame);
672
+ onFirstFrameRef.current = onFirstFrame;
673
+ const { center, radius } = (0, react.useMemo)(() => {
674
+ const g = buildGeometry(data);
675
+ g.computeBoundingSphere();
676
+ const s = g.boundingSphere ?? new three.Sphere(new three.Vector3(), 1);
677
+ const result = {
678
+ center: s.center.clone(),
679
+ radius: s.radius || 1
680
+ };
681
+ g.dispose();
682
+ return result;
683
+ }, [data]);
684
+ (0, react.useEffect)(() => {
685
+ const dir = VIEW_DIR[view].clone().normalize();
686
+ camera.position.copy(center).addScaledVector(dir, radius * 3);
687
+ camera.near = radius / 100;
688
+ camera.far = radius * 100;
689
+ const ortho = camera;
690
+ if (ortho.isOrthographicCamera) {
691
+ const viewSize = Math.min(ortho.right - ortho.left, ortho.top - ortho.bottom);
692
+ if (viewSize > 0 && radius > 0) ortho.zoom = viewSize / (radius * 2.4);
693
+ }
694
+ camera.lookAt(center);
695
+ camera.updateProjectionMatrix();
696
+ invalidate();
697
+ if (!fired.current) {
698
+ fired.current = true;
699
+ onFirstFrameRef.current?.();
700
+ }
701
+ }, [
702
+ camera,
703
+ invalidate,
704
+ center,
705
+ radius,
706
+ view,
707
+ fitSignal,
708
+ projection
709
+ ]);
710
+ return null;
711
+ }
712
+ function OrthoCamera() {
713
+ const camera = (0, _react_three_fiber.useThree)((s) => s.camera);
714
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_three_drei.OrthographicCamera, {
715
+ makeDefault: true,
716
+ position: (0, react.useRef)([
717
+ camera.position.x,
718
+ camera.position.y,
719
+ camera.position.z
720
+ ]).current,
721
+ zoom: 20,
722
+ near: .1,
723
+ far: 2e3
724
+ });
725
+ }
726
+ function LocalClipping() {
727
+ const gl = (0, _react_three_fiber.useThree)((s) => s.gl);
728
+ (0, react.useEffect)(() => {
729
+ gl.localClippingEnabled = true;
730
+ return () => {
731
+ gl.localClippingEnabled = false;
732
+ };
733
+ }, [gl]);
734
+ return null;
735
+ }
736
+ function ViewerCanvas({ data, view = "iso", fitSignal, autoRotate = false, gridVisible = true, projection = "perspective", onFirstFrame, children }) {
737
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_react_three_fiber.Canvas, {
738
+ frameloop: autoRotate ? "always" : "demand",
739
+ gl: { preserveDrawingBuffer: true },
740
+ children: [
741
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LocalClipping, {}),
742
+ projection === "orthographic" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OrthoCamera, {}),
743
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SceneSetup, {
744
+ autoRotate,
745
+ gridVisible
746
+ }),
747
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Framing, {
748
+ data,
749
+ view,
750
+ fitSignal,
751
+ projection,
752
+ onFirstFrame
753
+ }),
754
+ children
755
+ ]
756
+ });
757
+ }
758
+ //#endregion
759
+ //#region src/types.ts
760
+ var VIEW_NAMES = [
761
+ "iso",
762
+ "front",
763
+ "top",
764
+ "right"
765
+ ];
766
+ //#endregion
767
+ //#region src/ViewerControls.tsx
768
+ var MODE_LABELS = {
769
+ solid: "Solid",
770
+ wireframe: "Wire",
771
+ xray: "X-ray"
772
+ };
773
+ var VIEW_LABELS = {
774
+ iso: "Iso",
775
+ front: "Front",
776
+ top: "Top",
777
+ right: "Right"
778
+ };
779
+ function ViewerControls({ viewMode, onViewModeChange, showEdges, onToggleEdges, showGrid, onToggleGrid, autoRotate, onToggleAutoRotate, projection, onToggleProjection, activeView, onView, onFit, onScreenshot, className }) {
780
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
781
+ className,
782
+ style: className ? void 0 : containerStyle$3,
783
+ children: [
784
+ (onFit || onScreenshot) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Group, { children: [onFit && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Btn$1, {
785
+ label: "Fit",
786
+ onClick: onFit
787
+ }), onScreenshot && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Btn$1, {
788
+ label: "Snap",
789
+ onClick: onScreenshot
790
+ })] }),
791
+ viewMode && onViewModeChange && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Group, { children: Object.keys(MODE_LABELS).map((mode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Btn$1, {
792
+ label: MODE_LABELS[mode],
793
+ active: viewMode === mode,
794
+ onClick: () => {
795
+ onViewModeChange(mode);
796
+ }
797
+ }, mode)) }),
798
+ (onToggleEdges || onToggleGrid || onToggleAutoRotate || onToggleProjection) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Group, { children: [
799
+ onToggleEdges && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Btn$1, {
800
+ label: "Edges",
801
+ active: showEdges,
802
+ onClick: onToggleEdges
803
+ }),
804
+ onToggleGrid && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Btn$1, {
805
+ label: "Grid",
806
+ active: showGrid,
807
+ onClick: onToggleGrid
808
+ }),
809
+ onToggleAutoRotate && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Btn$1, {
810
+ label: "Spin",
811
+ active: autoRotate,
812
+ onClick: onToggleAutoRotate
813
+ }),
814
+ onToggleProjection && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Btn$1, {
815
+ label: projection === "orthographic" ? "Ortho" : "Persp",
816
+ active: projection === "orthographic",
817
+ onClick: onToggleProjection
818
+ })
819
+ ] }),
820
+ onView && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Group, { children: VIEW_NAMES.map((view) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Btn$1, {
821
+ label: VIEW_LABELS[view],
822
+ active: activeView === view,
823
+ onClick: () => {
824
+ onView(view);
825
+ }
826
+ }, view)) })
827
+ ]
828
+ });
829
+ }
830
+ function Group({ children }) {
831
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
832
+ style: groupStyle,
833
+ children
834
+ });
835
+ }
836
+ function Btn$1({ label, active, onClick }) {
837
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
838
+ type: "button",
839
+ onClick,
840
+ "aria-pressed": active,
841
+ style: {
842
+ ...buttonStyle$1,
843
+ ...active ? activeButtonStyle$1 : null
844
+ },
845
+ children: label
846
+ });
847
+ }
848
+ var containerStyle$3 = {
849
+ position: "absolute",
850
+ top: 12,
851
+ right: 12,
852
+ zIndex: 10,
853
+ display: "flex",
854
+ flexDirection: "column",
855
+ gap: 6,
856
+ pointerEvents: "none",
857
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif"
858
+ };
859
+ var groupStyle = {
860
+ display: "flex",
861
+ flexDirection: "column",
862
+ gap: 4,
863
+ padding: 4,
864
+ borderRadius: 8,
865
+ background: "rgba(26, 29, 33, 0.7)",
866
+ backdropFilter: "blur(6px)",
867
+ border: "1px solid rgba(255, 255, 255, 0.08)"
868
+ };
869
+ var buttonStyle$1 = {
870
+ cursor: "pointer",
871
+ pointerEvents: "auto",
872
+ padding: "4px 10px",
873
+ borderRadius: 5,
874
+ fontSize: 12,
875
+ fontWeight: 500,
876
+ lineHeight: 1.2,
877
+ color: "#9aa3ad",
878
+ background: "rgba(255, 255, 255, 0.04)",
879
+ border: "1px solid transparent",
880
+ transition: "color 120ms, background 120ms"
881
+ };
882
+ var activeButtonStyle$1 = {
883
+ color: "#6ee7d7",
884
+ background: "rgba(45, 212, 191, 0.16)",
885
+ borderColor: "rgba(45, 212, 191, 0.3)"
886
+ };
887
+ //#endregion
888
+ //#region src/ViewerInfoPanel.tsx
889
+ function ViewerInfoPanel({ dims, volume, area, triangles, valid, unit, className }) {
890
+ const u = unit ? ` ${unit}` : "";
891
+ const rows = [];
892
+ if (dims) rows.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row$1, {
893
+ label: "Size",
894
+ value: `${fmt$1(dims[0])} × ${fmt$1(dims[1])} × ${fmt$1(dims[2])}${u}`
895
+ }, "size"));
896
+ if (volume !== void 0) rows.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row$1, {
897
+ label: "Volume",
898
+ value: `${fmt$1(volume)}${unit ? ` ${unit}³` : ""}`
899
+ }, "vol"));
900
+ if (area !== void 0) rows.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row$1, {
901
+ label: "Area",
902
+ value: `${fmt$1(area)}${unit ? ` ${unit}²` : ""}`
903
+ }, "area"));
904
+ if (triangles !== void 0) rows.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row$1, {
905
+ label: "Triangles",
906
+ value: triangles.toLocaleString()
907
+ }, "tris"));
908
+ if (valid !== void 0) rows.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row$1, {
909
+ label: "Validity",
910
+ value: valid ? "valid" : "invalid",
911
+ valueColor: valid ? "#6ee7d7" : "#f0a0a0"
912
+ }, "valid"));
913
+ if (rows.length === 0) return null;
914
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
915
+ className,
916
+ style: className ? void 0 : containerStyle$2,
917
+ children: rows
918
+ });
919
+ }
920
+ function Row$1({ label, value, valueColor }) {
921
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
922
+ style: rowStyle$1,
923
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
924
+ style: labelStyle$1,
925
+ children: label
926
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
927
+ style: {
928
+ ...valueStyle$1,
929
+ ...valueColor ? { color: valueColor } : null
930
+ },
931
+ children: value
932
+ })]
933
+ });
934
+ }
935
+ function fmt$1(n) {
936
+ if (!Number.isFinite(n)) return "—";
937
+ if (Number.isInteger(n)) return n.toLocaleString();
938
+ return Number(n.toFixed(2)).toLocaleString();
939
+ }
940
+ var containerStyle$2 = {
941
+ position: "absolute",
942
+ left: 12,
943
+ bottom: 12,
944
+ zIndex: 10,
945
+ display: "flex",
946
+ flexDirection: "column",
947
+ gap: 2,
948
+ padding: "8px 10px",
949
+ borderRadius: 8,
950
+ minWidth: 150,
951
+ background: "rgba(26, 29, 33, 0.7)",
952
+ backdropFilter: "blur(6px)",
953
+ border: "1px solid rgba(255, 255, 255, 0.08)",
954
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif"
955
+ };
956
+ var rowStyle$1 = {
957
+ display: "flex",
958
+ justifyContent: "space-between",
959
+ gap: 16,
960
+ fontSize: 12,
961
+ lineHeight: 1.5
962
+ };
963
+ var labelStyle$1 = { color: "#7b8591" };
964
+ var valueStyle$1 = {
965
+ color: "#c8cdd3",
966
+ fontVariantNumeric: "tabular-nums"
967
+ };
968
+ //#endregion
969
+ //#region src/ViewerSelectionPanel.tsx
970
+ var SURFACE_LABELS = {
971
+ PLANE: "Planar",
972
+ CYLINDER: "Cylindrical",
973
+ CONE: "Conical",
974
+ SPHERE: "Spherical",
975
+ TORUS: "Toroidal",
976
+ BEZIER_SURFACE: "Bézier",
977
+ BSPLINE_SURFACE: "B-spline",
978
+ REVOLUTION_SURFACE: "Revolved",
979
+ EXTRUSION_SURFACE: "Extruded",
980
+ OFFSET_SURFACE: "Offset",
981
+ OTHER_SURFACE: "Surface"
982
+ };
983
+ function surfaceLabel(t) {
984
+ return SURFACE_LABELS[t] ?? "Surface";
985
+ }
986
+ function fmt(n) {
987
+ if (!Number.isFinite(n)) return "—";
988
+ if (Number.isInteger(n)) return n.toLocaleString();
989
+ return Number(n.toFixed(2)).toLocaleString();
990
+ }
991
+ function ViewerSelectionPanel({ face, onClear, unit, className }) {
992
+ if (!face) return null;
993
+ const [nx, ny, nz] = face.normal;
994
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
995
+ className,
996
+ style: className ? void 0 : containerStyle$1,
997
+ children: [
998
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
999
+ style: headerStyle,
1000
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1001
+ style: titleStyle,
1002
+ children: [surfaceLabel(face.surfaceType), " face"]
1003
+ }), onClear && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1004
+ type: "button",
1005
+ onClick: onClear,
1006
+ "aria-label": "Clear selection",
1007
+ style: closeStyle,
1008
+ children: "✕"
1009
+ })]
1010
+ }),
1011
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
1012
+ label: "Area",
1013
+ value: `${fmt(face.area)}${unit ? ` ${unit}²` : ""}`
1014
+ }),
1015
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
1016
+ label: "Normal",
1017
+ value: `${fmt(nx)}, ${fmt(ny)}, ${fmt(nz)}`
1018
+ })
1019
+ ]
1020
+ });
1021
+ }
1022
+ function Row({ label, value }) {
1023
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1024
+ style: rowStyle,
1025
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1026
+ style: labelStyle,
1027
+ children: label
1028
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1029
+ style: valueStyle,
1030
+ children: value
1031
+ })]
1032
+ });
1033
+ }
1034
+ var containerStyle$1 = {
1035
+ position: "absolute",
1036
+ left: 12,
1037
+ top: 12,
1038
+ zIndex: 10,
1039
+ display: "flex",
1040
+ flexDirection: "column",
1041
+ gap: 2,
1042
+ padding: "8px 10px",
1043
+ borderRadius: 8,
1044
+ minWidth: 170,
1045
+ background: "rgba(26, 29, 33, 0.7)",
1046
+ backdropFilter: "blur(6px)",
1047
+ border: "1px solid rgba(74, 206, 204, 0.3)",
1048
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif"
1049
+ };
1050
+ var headerStyle = {
1051
+ display: "flex",
1052
+ justifyContent: "space-between",
1053
+ alignItems: "center",
1054
+ gap: 12,
1055
+ marginBottom: 2
1056
+ };
1057
+ var titleStyle = {
1058
+ color: "#6ee7d7",
1059
+ fontSize: 12,
1060
+ fontWeight: 600
1061
+ };
1062
+ var closeStyle = {
1063
+ cursor: "pointer",
1064
+ border: "none",
1065
+ background: "transparent",
1066
+ color: "#7b8591",
1067
+ fontSize: 12,
1068
+ lineHeight: 1,
1069
+ padding: 0
1070
+ };
1071
+ var rowStyle = {
1072
+ display: "flex",
1073
+ justifyContent: "space-between",
1074
+ gap: 16,
1075
+ fontSize: 12,
1076
+ lineHeight: 1.5
1077
+ };
1078
+ var labelStyle = { color: "#7b8591" };
1079
+ var valueStyle = {
1080
+ color: "#c8cdd3",
1081
+ fontVariantNumeric: "tabular-nums"
1082
+ };
1083
+ //#endregion
1084
+ //#region src/ViewerSectionControls.tsx
1085
+ var AXES = [
1086
+ "x",
1087
+ "y",
1088
+ "z"
1089
+ ];
1090
+ function ViewerSectionControls({ enabled, onToggle, axis, onAxisChange, position, min, max, onPositionChange, flip, onToggleFlip, className }) {
1091
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1092
+ className,
1093
+ style: className ? void 0 : containerStyle,
1094
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Btn, {
1095
+ label: "Section",
1096
+ active: enabled,
1097
+ onClick: onToggle
1098
+ }), enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1099
+ AXES.map((a) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Btn, {
1100
+ label: a.toUpperCase(),
1101
+ active: axis === a,
1102
+ onClick: () => {
1103
+ onAxisChange(a);
1104
+ }
1105
+ }, a)),
1106
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1107
+ type: "range",
1108
+ min,
1109
+ max,
1110
+ step: (max - min) / 200 || .01,
1111
+ value: position,
1112
+ disabled: min === max,
1113
+ onChange: (e) => {
1114
+ onPositionChange(Number(e.target.value));
1115
+ },
1116
+ "aria-label": "Section position",
1117
+ style: {
1118
+ ...sliderStyle,
1119
+ ...min === max ? { opacity: .4 } : null
1120
+ }
1121
+ }),
1122
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Btn, {
1123
+ label: "Flip",
1124
+ active: flip,
1125
+ onClick: onToggleFlip
1126
+ })
1127
+ ] })]
1128
+ });
1129
+ }
1130
+ function Btn({ label, active, onClick }) {
1131
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1132
+ type: "button",
1133
+ onClick,
1134
+ "aria-pressed": active,
1135
+ style: {
1136
+ ...buttonStyle,
1137
+ ...active ? activeButtonStyle : null
1138
+ },
1139
+ children: label
1140
+ });
1141
+ }
1142
+ var containerStyle = {
1143
+ position: "absolute",
1144
+ bottom: 12,
1145
+ left: "50%",
1146
+ transform: "translateX(-50%)",
1147
+ zIndex: 10,
1148
+ display: "flex",
1149
+ alignItems: "center",
1150
+ gap: 4,
1151
+ padding: 4,
1152
+ borderRadius: 8,
1153
+ background: "rgba(26, 29, 33, 0.7)",
1154
+ backdropFilter: "blur(6px)",
1155
+ border: "1px solid rgba(255, 255, 255, 0.08)",
1156
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif"
1157
+ };
1158
+ var buttonStyle = {
1159
+ cursor: "pointer",
1160
+ padding: "4px 10px",
1161
+ borderRadius: 5,
1162
+ fontSize: 12,
1163
+ fontWeight: 500,
1164
+ lineHeight: 1.2,
1165
+ color: "#9aa3ad",
1166
+ background: "rgba(255, 255, 255, 0.04)",
1167
+ border: "1px solid transparent"
1168
+ };
1169
+ var activeButtonStyle = {
1170
+ color: "#6ee7d7",
1171
+ background: "rgba(45, 212, 191, 0.16)",
1172
+ borderColor: "rgba(45, 212, 191, 0.3)"
1173
+ };
1174
+ var sliderStyle = {
1175
+ width: 160,
1176
+ accentColor: "#4ACECC",
1177
+ margin: "0 4px"
1178
+ };
1179
+ //#endregion
1180
+ exports.EdgeRenderer = EdgeRenderer;
1181
+ exports.Renderer = Renderer;
1182
+ exports.SceneSetup = SceneSetup;
1183
+ exports.SelectionHighlight = SelectionHighlight;
1184
+ exports.VIEW_NAMES = VIEW_NAMES;
1185
+ exports.ViewerCanvas = ViewerCanvas;
1186
+ exports.ViewerControls = ViewerControls;
1187
+ exports.ViewerInfoPanel = ViewerInfoPanel;
1188
+ exports.ViewerSectionControls = ViewerSectionControls;
1189
+ exports.ViewerSelectionPanel = ViewerSelectionPanel;
1190
+ exports.buildGeometry = buildGeometry;
1191
+ exports.findFaceGroupAt = findFaceGroupAt;
1192
+ exports.meshBounds = meshBounds;
1193
+ exports.meshSize = meshSize;
1194
+ exports.sectionPlane = sectionPlane;